๋ฉ”์ธ ์ฝ˜ํ…์ธ ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
Sonamu๋Š” ์—”ํ‹ฐํ‹ฐ ์ค‘์‹ฌ์˜ ํ’€์Šคํƒ ๊ฐœ๋ฐœ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋Š” TypeScript ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค. ์ด ๊ฐ€์ด๋“œ์—์„œ๋Š” Sonamu์˜ ์ „์ฒด ์•„ํ‚คํ…์ฒ˜์™€ ํ•ต์‹ฌ ์ž‘๋™ ์›๋ฆฌ๋ฅผ ์•Œ์•„๋ด…๋‹ˆ๋‹ค.

Sonamu์˜ ์ฒ ํ•™

Sonamu๋Š” ๋‹ค์Œ ์›์น™์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค:
1

1. Entity First (์—”ํ‹ฐํ‹ฐ ์šฐ์„ )

๋ชจ๋“  ๊ฐœ๋ฐœ์€ ์—”ํ‹ฐํ‹ฐ ์ •์˜์—์„œ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ ๋จผ์ € ์ •์˜ํ•˜๋ฉด, ๋‚˜๋จธ์ง€๋Š” ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.
2

2. Type Safety (ํƒ€์ž… ์•ˆ์ „์„ฑ)

๋ฐฑ์—”๋“œ๋ถ€ํ„ฐ ํ”„๋ก ํŠธ์—”๋“œ๊นŒ์ง€ ์™„์ „ํ•œ ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. ์ปดํŒŒ์ผ ํƒ€์ž„์— ์˜ค๋ฅ˜๋ฅผ ๋ฐœ๊ฒฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
3

3. Code Generation (์ฝ”๋“œ ์ž๋™ ์ƒ์„ฑ)

๋ฐ˜๋ณต์ ์ธ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ ์ฝ”๋“œ๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ์ž๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์—๋งŒ ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
4

4. Developer Experience (๊ฐœ๋ฐœ์ž ๊ฒฝํ—˜)

HMR, ํƒ€์ž… ์ถ”๋ก , Sonamu UI ๋“ฑ์œผ๋กœ ๋›ฐ์–ด๋‚œ ๊ฐœ๋ฐœ ๊ฒฝํ—˜์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

์ „์ฒด ์•„ํ‚คํ…์ฒ˜

Sonamu๋Š” 6๊ฐœ์˜ ๋ ˆ์ด์–ด๊ฐ€ ์ž๋™์œผ๋กœ ์—ฐ๊ฒฐ๋˜์–ด ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค:

ํ•ต์‹ฌ ๊ตฌ์„ฑ ์š”์†Œ

1. Entity (์—”ํ‹ฐํ‹ฐ)

์—”ํ‹ฐํ‹ฐ๋Š” Sonamu์˜ ๋ชจ๋“  ๊ฒƒ์˜ ์‹œ์ž‘์ ์ž…๋‹ˆ๋‹ค.
user.entity.json
{
  "entityId": "User",
  "tableName": "users",
  "title": "์‚ฌ์šฉ์ž",
  "properties": [
    {
      "name": "id",
      "type": "int",
      "isPrimary": true
    },
    {
      "name": "email",
      "type": "varchar"
    },
    {
      "name": "name",
      "type": "varchar"
    }
  ],
  "subsets": {
    "A": ["id", "email", "name", "created_at"],
    "C": ["id", "email", "name"]
  }
}
์—”ํ‹ฐํ‹ฐ ์ •์˜์˜ ์—ญํ• 
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ์˜ ์†Œ์Šค
  • TypeScript ํƒ€์ž… ์ƒ์„ฑ์˜ ๊ธฐ๋ฐ˜
  • API ์ธํ„ฐํŽ˜์ด์Šค์˜ ์ •์˜
  • ํ”„๋ก ํŠธ์—”๋“œ ํƒ€์ž…์˜ ๊ธฐ์ค€

2. Type System (ํƒ€์ž… ์‹œ์Šคํ…œ)

์—”ํ‹ฐํ‹ฐ ์ •์˜๋กœ๋ถ€ํ„ฐ 3๊ฐ€์ง€ ํƒ€์ž… ํŒŒ์ผ์ด ์ž๋™ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค:
// ์ž๋™ ์ƒ์„ฑ + ํ™•์žฅ ๊ฐ€๋Šฅ
import { z } from "zod";
import { UserBaseSchema } from "../sonamu.generated";

// Base ํƒ€์ž… (์ž๋™ ์ƒ์„ฑ)
export type User = z.infer<typeof UserBaseSchema>;

// ์ปค์Šคํ…€ ํƒ€์ž… (์ง์ ‘ ์ž‘์„ฑ ๊ฐ€๋Šฅ)
export const UserSaveParams = UserBaseSchema.partial({
  id: true,
  created_at: true,
});
export type UserSaveParams = z.infer<typeof UserSaveParams>;
ํƒ€์ž… ํŒŒ์ผ 3์ข… ์„ธํŠธ
  1. {entity}.types.ts - ์—”ํ‹ฐํ‹ฐ๋ณ„ ํƒ€์ž… (ํ™•์žฅ ๊ฐ€๋Šฅ)
  2. sonamu.generated.ts - ์ „์ฒด Base ์Šคํ‚ค๋งˆ (์ž๋™ ์ƒ์„ฑ)
  3. sonamu.generated.sso.ts - Subset ์ฟผ๋ฆฌ (์ž๋™ ์ƒ์„ฑ)

3. Model (๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง)

Model์€ ์—”ํ‹ฐํ‹ฐ์˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.
user.model.ts
import { api, BaseModelClass } from "sonamu";
import type { UserSubsetKey, UserSubsetMapping } from "../sonamu.generated";
import { userSubsetQueries } from "../sonamu.generated.sso";

class UserModelClass extends BaseModelClass<
  UserSubsetKey,
  UserSubsetMapping,
  typeof userSubsetQueries
> {
  constructor() {
    super("User", userSubsetQueries);
  }

  // CRUD API
  @api({ httpMethod: "GET", clients: ["axios", "tanstack-query"] })
  async findById<T extends UserSubsetKey>(
    subset: T,
    id: number,
  ): Promise<UserSubsetMapping[T]> {
    const user = await this.db().where("id", id).first();
    return user;
  }

  // ์ปค์Šคํ…€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง
  @api({ httpMethod: "POST", clients: ["axios"] })
  async changePassword(userId: number, newPassword: string): Promise<void> {
    await this.db()
      .where("id", userId)
      .update({ password: await this.hashPassword(newPassword) });
  }

  private async hashPassword(password: string): Promise<string> {
    // ๋น„๋ฐ€๋ฒˆํ˜ธ ํ•ด์‹ฑ ๋กœ์ง
    return password;
  }
}

export const UserModel = new UserModelClass();
Model์˜ ํŠน์ง•
  • @api ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋กœ ์ž๋™ REST API ์ƒ์„ฑ
  • BaseModelClass ์ƒ์†์œผ๋กœ ๊ธฐ๋ณธ CRUD ๋ฉ”์„œ๋“œ ์ œ๊ณต
  • Puri ์ฟผ๋ฆฌ ๋นŒ๋”๋กœ ํƒ€์ž… ์•ˆ์ „ํ•œ DB ์ฟผ๋ฆฌ
  • ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์—๋งŒ ์ง‘์ค‘ ๊ฐ€๋Šฅ

4. Syncer (๋™๊ธฐํ™” ์‹œ์Šคํ…œ)

Syncer๋Š” Sonamu์˜ ํ•ต์‹ฌ ์—”์ง„์ž…๋‹ˆ๋‹ค. ํŒŒ์ผ ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ํ•˜๊ณ  ์ž๋™์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“ธ ํ•„์š”: Syncer ์ž‘๋™ ํ๋ฆ„๋„ (ํŒŒ์ผ ๋ณ€๊ฒฝ ๊ฐ์ง€ โ†’ ์ฝ”๋“œ ์ƒ์„ฑ โ†’ ๋™๊ธฐํ™”)

// Syncer์˜ ์ฃผ์š” ์—ญํ• 

1. ํŒŒ์ผ ๋ณ€๊ฒฝ ๊ฐ์ง€ (Checksum ๊ธฐ๋ฐ˜)
   - entity.json ๋ณ€๊ฒฝ โ†’ Types ์žฌ์ƒ์„ฑ
   - model.ts ๋ณ€๊ฒฝ โ†’ Service ์žฌ์ƒ์„ฑ
   - types.ts ๋ณ€๊ฒฝ โ†’ Web์œผ๋กœ ๋ณต์‚ฌ

2. ์ฝ”๋“œ ์ž๋™ ์ƒ์„ฑ
   - sonamu.generated.ts
   - sonamu.generated.sso.ts
   - {Entity}Service.ts (ํ”„๋ก ํŠธ์—”๋“œ)

3. ํŒŒ์ผ ๋™๊ธฐํ™”
   - api/src/application โ†’ web/src/services
   - ํƒ€์ž… ํŒŒ์ผ ๋ณต์‚ฌ
   - sonamu.shared.ts ๋ฐฐํฌ

4. HMR ํŠธ๋ฆฌ๊ฑฐ
   - ๋ณ€๊ฒฝ๋œ ๋ชจ๋“ˆ ๋ฌดํšจํ™”
   - API ์„œ๋ฒ„ ์ž๋™ ์žฌ์‹œ์ž‘
Syncer ๋™์ž‘ ์‹œ์ 
  • ์ž๋™: pnpm dev ์‹คํ–‰ ์ค‘ ํŒŒ์ผ ๋ณ€๊ฒฝ ์‹œ (HMR)
  • ์ˆ˜๋™: pnpm sync ๋ช…๋ น์–ด ์‹คํ–‰ ์‹œ

5. API Layer (REST API)

Model์˜ @api ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๊ฐ€ ์ž๋™์œผ๋กœ REST API๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
// Model ๋ฉ”์„œ๋“œ
@api({ httpMethod: "GET", clients: ["axios"] })
async findById(subset: UserSubsetKey, id: number): Promise<User> {
  // ...
}

// โ†“ ์ž๋™ ๋ณ€ํ™˜

// REST API ์—”๋“œํฌ์ธํŠธ
GET /api/users/:id?subset=A

// Request
curl http://localhost:1028/api/users/1?subset=A

// Response
{
  "id": 1,
  "email": "[email protected]",
  "name": "ํ™๊ธธ๋™",
  "created_at": "2025-01-06T12:00:00Z"
}
์ž๋™ ์ƒ์„ฑ๋˜๋Š” ๊ฒƒ๋“ค
  • โœ… REST API ๋ผ์šฐํŠธ
  • โœ… ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฒ€์ฆ (Zod)
  • โœ… ์‘๋‹ต ํƒ€์ž…
  • โœ… ์—๋Ÿฌ ํ•ธ๋“ค๋ง
  • โœ… API ๋ฌธ์„œ (sonamu.generated.http)

6. Frontend Service (ํ”„๋ก ํŠธ์—”๋“œ ํ†ตํ•ฉ)

Model์˜ API๋Š” ์ž๋™์œผ๋กœ ํ”„๋ก ํŠธ์—”๋“œ Service๋กœ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.
web/src/services/UserService.ts (์ž๋™ ์ƒ์„ฑ)
export class UserService {
  static async findById(subset: UserSubsetKey, id: number): Promise<User> {
    const res = await axios.get(`/api/users/${id}`, {
      params: { subset },
    });
    return res.data;
  }

  static async changePassword(
    userId: number,
    newPassword: string,
  ): Promise<void> {
    await axios.post("/api/users/changePassword", {
      userId,
      newPassword,
    });
  }
}
ํƒ€์ž… ์•ˆ์ „์„ฑ ๋ฐฑ์—”๋“œ์˜ ํƒ€์ž…์ด ํ”„๋ก ํŠธ์—”๋“œ์— ๊ทธ๋Œ€๋กœ ๋™๊ธฐํ™”๋˜์–ด, ํƒ€์ž… ๋ถˆ์ผ์น˜ ์˜ค๋ฅ˜๋ฅผ ์ปดํŒŒ์ผ ํƒ€์ž„์— ๋ฐœ๊ฒฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

๊ฐœ๋ฐœ ํ”Œ๋กœ์šฐ

์‹ค์ œ ๊ฐœ๋ฐœ ์‹œ Sonamu๊ฐ€ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ์‚ดํŽด๋ด…์‹œ๋‹ค:
1

1. ์—”ํ‹ฐํ‹ฐ ์ •์˜ (Sonamu UI)

{
  "entityId": "Post",
  "properties": [
    { "name": "id", "type": "int", "isPrimary": true },
    { "name": "title", "type": "varchar" }
  ]
}
์ €์žฅ ์‹œ ์ž๋™ ์ƒ์„ฑ:
  • post.types.ts
  • sonamu.generated.ts ์—…๋ฐ์ดํŠธ
2

2. ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ (Migration ํƒญ)

CREATE TABLE posts (
  id SERIAL PRIMARY KEY,
  title VARCHAR(255) NOT NULL
);
์‹คํ–‰ ์‹œ:
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ํ…Œ์ด๋ธ” ์ƒ์„ฑ
3

3. Model ์ž‘์„ฑ (์ง์ ‘ ๋˜๋Š” ์Šค์บํด๋”ฉ)

@api({ httpMethod: "GET" })
async findById(id: number): Promise<Post> {
  return await this.db().where("id", id).first();
}
์ €์žฅ ์‹œ ์ž๋™ ์ƒ์„ฑ:
  • REST API: GET /api/posts/:id
  • PostService.ts (ํ”„๋ก ํŠธ์—”๋“œ)
  • sonamu.generated.http ์—…๋ฐ์ดํŠธ
4

4. ํ”„๋ก ํŠธ์—”๋“œ ์‚ฌ์šฉ

// ํƒ€์ž… ์•ˆ์ „ํ•˜๊ฒŒ API ํ˜ธ์ถœ
const post = await PostService.findById(1);
console.log(post.title); // โœ… ํƒ€์ž… ์ฒดํฌ
console.log(post.invalid); // โŒ ์ปดํŒŒ์ผ ์—๋Ÿฌ!
ํƒ€์ž… ์•ˆ์ „์„ฑ:
  • ๋ฐฑ์—”๋“œ ํƒ€์ž… ๋ณ€๊ฒฝ ์‹œ ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ์ฆ‰์‹œ ์˜ค๋ฅ˜ ๊ฐ์ง€

๐ŸŽฌ ํ•„์š”: ์œ„ 4๋‹จ๊ณ„ ํ”Œ๋กœ์šฐ๋ฅผ ์‹ค์ œ๋กœ ๋ณด์—ฌ์ฃผ๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋˜๋Š” ์˜์ƒ

HMR (Hot Module Replacement)

Sonamu๋Š” ๊ฐ•๋ ฅํ•œ HMR ์‹œ์Šคํ…œ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

HMR ์ž‘๋™ ๋ฐฉ์‹

// 1. ํŒŒ์ผ ๋ณ€๊ฒฝ ๊ฐ์ง€
user.model.ts ์ˆ˜์ • โ†’ Watcher ๊ฐ์ง€

// 2. ๋ชจ๋“ˆ ๋ฌดํšจํ™”
hmr-hook์ด ์˜์กด์„ฑ ๊ทธ๋ž˜ํ”„ ๋ถ„์„
user.model.ts์™€ ์˜์กด ๋ชจ๋“ˆ๋“ค invalidate

// 3. ์žฌ์ƒ์„ฑ (Syncer)
- UserService.ts ์žฌ์ƒ์„ฑ
- API ๋ผ์šฐํŠธ ์žฌ๋“ฑ๋ก

// 4. ์„œ๋ฒ„ ์žฌ์‹œ์ž‘
graceful shutdown โ†’ reload
HMR์˜ ์ด์ 
  • โœ… ๋น ๋ฅธ ํ”ผ๋“œ๋ฐฑ - ์ฝ”๋“œ ๋ณ€๊ฒฝ ํ›„ 1-2์ดˆ ๋‚ด ๋ฐ˜์˜
  • โœ… ์ƒํƒœ ์œ ์ง€ - ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ๋“ฑ ์œ ์ง€
  • โœ… ์ž๋™ ๋™๊ธฐํ™” - ํ”„๋ก ํŠธ์—”๋“œ Service ์ž๋™ ์—…๋ฐ์ดํŠธ

๐Ÿ“ธ ํ•„์š”: HMR ๋™์ž‘ํ•˜๋Š” ํ„ฐ๋ฏธ๋„ ๋กœ๊ทธ (ํŒŒ์ผ ๋ณ€๊ฒฝ โ†’ invalidate โ†’ ์žฌ์‹œ์ž‘)

์ž๋™ ์ƒ์„ฑ ๋ฉ”์ปค๋‹ˆ์ฆ˜

Sonamu๊ฐ€ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ๋“ค์„ ์ •๋ฆฌํ•ด๋ด…์‹œ๋‹ค:

์—”ํ‹ฐํ‹ฐ ์ •์˜ ๋ณ€๊ฒฝ ์‹œ

user.entity.json ์ˆ˜์ •
โ†“
์ž๋™ ์ƒ์„ฑ:
โœ… user.types.ts (ํƒ€์ž… ์ •์˜)
โœ… sonamu.generated.ts (Base ์Šคํ‚ค๋งˆ)
โœ… sonamu.generated.sso.ts (Subset ์ฟผ๋ฆฌ)
โœ… migration SQL (ํ…Œ์ด๋ธ” ๋ณ€๊ฒฝ)

Model ํŒŒ์ผ ๋ณ€๊ฒฝ ์‹œ

user.model.ts ์ˆ˜์ •
โ†“
์ž๋™ ์ƒ์„ฑ:
โœ… UserService.ts (ํ”„๋ก ํŠธ์—”๋“œ)
โœ… services.generated.ts (Service ํ†ตํ•ฉ)
โœ… sonamu.generated.http (API ๋ฌธ์„œ)
โœ… REST API ๋ผ์šฐํŠธ ์žฌ๋“ฑ๋ก

Types ํŒŒ์ผ ๋ณ€๊ฒฝ ์‹œ

user.types.ts ์ˆ˜์ •
โ†“
์ž๋™ ๋™๊ธฐํ™”:
โœ… web/src/services/user.types.ts (๋ณต์‚ฌ)
โœ… ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ์ฆ‰์‹œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
์ ˆ๋Œ€ ์ˆ˜์ •ํ•˜๋ฉด ์•ˆ ๋˜๋Š” ํŒŒ์ผ
  • sonamu.generated.ts - ๋‹ค์Œ sync ์‹œ ๋ฎ์–ด์”Œ์›Œ์ง
  • {Entity}Service.ts - ๋‹ค์Œ sync ์‹œ ๋ฎ์–ด์”Œ์›Œ์ง
  • sonamu.generated.sso.ts - ๋‹ค์Œ sync ์‹œ ๋ฎ์–ด์”Œ์›Œ์ง
์ด ํŒŒ์ผ๋“ค์€ ํ•ญ์ƒ ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜๋ฏ€๋กœ ์ง์ ‘ ์ˆ˜์ •ํ•˜์ง€ ๋งˆ์„ธ์š”!

ํƒ€์ž… ์•ˆ์ „์„ฑ์˜ ํ๋ฆ„

Sonamu์˜ End-to-End ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ์‹œ๊ฐํ™”ํ•˜๋ฉด:
// 1. Entity ์ •์˜
{
  "name": "email",
  "type": "varchar"
}

โ†“

// 2. ํƒ€์ž… ์ƒ์„ฑ (์ž๋™)
type User = {
  email: string;
}

โ†“

// 3. Model ๋ฉ”์„œ๋“œ (ํƒ€์ž… ์•ˆ์ „)
async findByEmail(email: string): Promise<User> {
  return await this.db().where("email", email).first();
}

โ†“

// 4. REST API (์ž๋™ ์ƒ์„ฑ, ํƒ€์ž… ์•ˆ์ „)
GET /api/users/email/:email
Response: User

โ†“

// 5. Frontend Service (์ž๋™ ์ƒ์„ฑ, ํƒ€์ž… ์•ˆ์ „)
static async findByEmail(email: string): Promise<User> {
  const res = await axios.get(`/api/users/email/${email}`);
  return res.data; // ํƒ€์ž…: User
}

โ†“

// 6. React Component (ํƒ€์ž… ์•ˆ์ „)
const user = await UserService.findByEmail("[email protected]");
console.log(user.email); // โœ… ํƒ€์ž… ์ฒดํฌ
console.log(user.invalid); // โŒ ์ปดํŒŒ์ผ ์—๋Ÿฌ!
ํƒ€์ž… ์•ˆ์ „์„ฑ ๋ณด์žฅ
  1. ์—”ํ‹ฐํ‹ฐ ์ •์˜ โ†’ ํƒ€์ž… ์ƒ์„ฑ
  2. Model โ†’ API ํƒ€์ž… ์ถ”๋ก 
  3. API โ†’ Service ํƒ€์ž… ๋™๊ธฐํ™”
  4. Service โ†’ UI ํƒ€์ž… ์ฒดํฌ
์–ด๋А ๋‹จ๊ณ„์—์„œ๋“  ํƒ€์ž…์ด ๋ณ€๊ฒฝ๋˜๋ฉด, ์ „์ฒด ์ฒด์ธ์— ๋ฐ˜์˜๋˜์–ด ์ปดํŒŒ์ผ ํƒ€์ž„์— ์˜ค๋ฅ˜๋ฅผ ๋ฐœ๊ฒฌํ•ฉ๋‹ˆ๋‹ค!

Sonamu์˜ ์ฃผ์š” ์žฅ์ 

1. ๊ฐœ๋ฐœ ์†๋„ ํ–ฅ์ƒ

์ „ํ†ต์ ์ธ ๋ฐฉ๋ฒ•:
1. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ” ์„ค๊ณ„
2. Migration ์ž‘์„ฑ
3. ๋ฐฑ์—”๋“œ ํƒ€์ž… ์ •์˜
4. API ์ปจํŠธ๋กค๋Ÿฌ ์ž‘์„ฑ
5. API ๋ผ์šฐํŠธ ๋“ฑ๋ก
6. ํ”„๋ก ํŠธ์—”๋“œ ํƒ€์ž… ์ •์˜
7. API ํด๋ผ์ด์–ธํŠธ ์ž‘์„ฑ
โฑ๏ธ ์ด ์†Œ์š” ์‹œ๊ฐ„: 2-3์‹œ๊ฐ„

Sonamu ๋ฐฉ๋ฒ•:
1. ์—”ํ‹ฐํ‹ฐ ์ •์˜ (Sonamu UI)
2. ์Šค์บํด๋”ฉ (Model ์ž๋™ ์ƒ์„ฑ)
โฑ๏ธ ์ด ์†Œ์š” ์‹œ๊ฐ„: 10-15๋ถ„

โœ… ์•ฝ 90% ์‹œ๊ฐ„ ๋‹จ์ถ•!

2. ํƒ€์ž… ์•ˆ์ „์„ฑ

// โŒ ์ „ํ†ต์ ์ธ ๋ฐฉ๋ฒ•: ๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ
const user = await fetch("/api/users/1").then(r => r.json());
console.log(user.eamil); // ์˜คํƒ€! ๋Ÿฐํƒ€์ž„์—๋งŒ ๋ฐœ๊ฒฌ โš ๏ธ

// โœ… Sonamu: ์ปดํŒŒ์ผ ์—๋Ÿฌ
const user = await UserService.findById("A", 1);
console.log(user.eamil); // ์ปดํŒŒ์ผ ์—๋Ÿฌ! ์ฆ‰์‹œ ๋ฐœ๊ฒฌ โœ…

3. ์œ ์ง€๋ณด์ˆ˜์„ฑ

// ์—”ํ‹ฐํ‹ฐ ๋ณ€๊ฒฝ: email โ†’ username
// โŒ ์ „ํ†ต์ ์ธ ๋ฐฉ๋ฒ•:
// 1. DB Migration ์ž‘์„ฑ
// 2. ๋ฐฑ์—”๋“œ ํƒ€์ž… ์ˆ˜์ •
// 3. API ์ˆ˜์ •
// 4. ํ”„๋ก ํŠธ์—”๋“œ ํƒ€์ž… ์ˆ˜์ •
// 5. API ํ˜ธ์ถœ๋ถ€ ์ „๋ถ€ ์ˆ˜์ •
// โš ๏ธ ๋ˆ„๋ฝ๋œ ๊ณณ์€ ๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ!

// โœ… Sonamu:
// 1. entity.json์—์„œ ํ•„๋“œ๋ช… ๋ณ€๊ฒฝ
// 2. Migration ์ž๋™ ์ƒ์„ฑ
// 3. ๋ชจ๋“  ํƒ€์ž… ์ž๋™ ์—…๋ฐ์ดํŠธ
// 4. ์ปดํŒŒ์ผ ์—๋Ÿฌ๋กœ ์ˆ˜์ • ํ•„์š”ํ•œ ๊ณณ ์ฆ‰์‹œ ํ‘œ์‹œ
// โœ… ๋น ๋œจ๋ฆฐ ๊ณณ ์—†์ด ์•ˆ์ „ํ•˜๊ฒŒ ๋ณ€๊ฒฝ!

4. ์ผ๊ด€์„ฑ

// โœ… Sonamu๋Š” ์ฝ”๋“œ ์Šคํƒ€์ผ์ด ์ž๋™์œผ๋กœ ์ผ๊ด€๋ฉ๋‹ˆ๋‹ค

// ๋ชจ๋“  Service๊ฐ€ ๋™์ผํ•œ ํŒจํ„ด
UserService.findById(...)
PostService.findById(...)
CommentService.findById(...)

// ๋ชจ๋“  ํƒ€์ž…์ด ๋™์ผํ•œ ๊ตฌ์กฐ
type User = z.infer<typeof UserBaseSchema>;
type Post = z.infer<typeof PostBaseSchema>;
type Comment = z.infer<typeof CommentBaseSchema>;

์ œ์•ฝ์‚ฌํ•ญ๊ณผ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„

Sonamu์˜ ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ์€ ์ผ๋ถ€ ์ œ์•ฝ์‚ฌํ•ญ์„ ๋™๋ฐ˜ํ•ฉ๋‹ˆ๋‹ค:
์•Œ์•„๋‘์–ด์•ผ ํ•  ์ œ์•ฝ์‚ฌํ•ญ
  1. ํ•™์Šต ๊ณก์„ : Sonamu์˜ ๊ฐœ๋…๊ณผ ๊ทœ์น™์„ ์ดํ•ดํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
  2. ์ž๋™ ์ƒ์„ฑ ํŒŒ์ผ ์ˆ˜์ • ๋ถˆ๊ฐ€: ์ˆ˜๋™ ์ˆ˜์ • ์‹œ ๋ฎ์–ด์”Œ์›Œ์ง‘๋‹ˆ๋‹ค
  3. ์—”ํ‹ฐํ‹ฐ ์ค‘์‹ฌ ์„ค๊ณ„ ํ•„์ˆ˜: Sonamu ๋ฐฉ์‹์„ ๋”ฐ๋ผ์•ผ ํ•ฉ๋‹ˆ๋‹ค
  4. ๋ณต์žกํ•œ ์ฟผ๋ฆฌ: ๋งค์šฐ ๋ณต์žกํ•œ ๊ฒฝ์šฐ Raw SQL ์‚ฌ์šฉ ํ•„์š”
ํ•˜์ง€๋งŒ ์žฅ์ ์ด ํ›จ์”ฌ ํฝ๋‹ˆ๋‹คโœ… ๊ฐœ๋ฐœ ์†๋„ ํ–ฅ์ƒ (90% ์‹œ๊ฐ„ ๋‹จ์ถ•) โœ… ํƒ€์ž… ์•ˆ์ „์„ฑ ๋ณด์žฅ โœ… ์œ ์ง€๋ณด์ˆ˜์„ฑ ํ–ฅ์ƒ โœ… ์ฝ”๋“œ ์ผ๊ด€์„ฑ โœ… ํŒ€ ์ƒ์‚ฐ์„ฑ ํ–ฅ์ƒ

๋‹ค์Œ ๋‹จ๊ณ„

Sonamu์˜ ์ž‘๋™ ์›๋ฆฌ๋ฅผ ์ดํ•ดํ–ˆ๋‹ค๋ฉด, ์ด์ œ ๊ฐ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ์ž์„ธํžˆ ์•Œ์•„๋ณด์„ธ์š”: