메인 μ½˜ν…μΈ λ‘œ κ±΄λ„ˆλ›°κΈ°
EntityλŠ” Sonamu ν”„λ‘œμ νŠΈμ˜ 핡심 ꡬ성 μš”μ†Œλ‘œ, λ°μ΄ν„°λ² μ΄μŠ€ ν…Œμ΄λΈ”κ³Ό TypeScript νƒ€μž…μ„ ν•¨κ»˜ μ •μ˜ν•©λ‹ˆλ‹€.

Entityλž€?

EntityλŠ” λ‹€μŒμ„ ν¬ν•¨ν•˜λŠ” 데이터 λͺ¨λΈ μ •μ˜μž…λ‹ˆλ‹€:
  • λ°μ΄ν„°λ² μ΄μŠ€ μŠ€ν‚€λ§ˆ - ν…Œμ΄λΈ” ꡬ쑰, 컬럼, 인덱슀
  • TypeScript νƒ€μž… - νƒ€μž… μ•ˆμ „μ„±μ„ μœ„ν•œ νƒ€μž… μ •μ˜
  • 관계(Relations) - λ‹€λ₯Έ Entityμ™€μ˜ μ—°κ²°
  • Subset - API 응닡을 μœ„ν•œ ν•„λ“œ μ‘°ν•©
  • Enum - μ—΄κ±°ν˜• νƒ€μž…κ³Ό λ ˆμ΄λΈ”

Entity μ •μ˜ 방법

EntityλŠ” Sonamu UI(http://localhost:34900/sonamu-ui)μ—μ„œ μ‹œκ°μ μœΌλ‘œ μ •μ˜ν•˜λŠ” 것을 ꢌμž₯ν•©λ‹ˆλ‹€. Sonamu UIλŠ” μžλ™ μ™„μ„±, μœ νš¨μ„± 검사, μ‹€μ‹œκ°„ 미리보기 κΈ°λŠ₯을 μ œκ³΅ν•©λ‹ˆλ‹€.

entity.json 파일 ꡬ쑰

EntityλŠ” entity.json 파일둜 μ €μž₯되며, λ‹€μŒκ³Ό 같은 ꡬ쑰λ₯Ό κ°€μ§‘λ‹ˆλ‹€:
{
  "id": "User",
  "table": "users",
  "title": "μ‚¬μš©μž",
  "props": [...],
  "indexes": [...],
  "subsets": {...},
  "enums": {...}
}
파일 μœ„μΉ˜: api/src/application/{entity}/{entity}.entity.json

ν•„μˆ˜ ν•„λ“œ

id

Entity의 고유 μ‹λ³„μžμž…λ‹ˆλ‹€. PascalCase둜 μž‘μ„±ν•©λ‹ˆλ‹€.
{
  "id": "User"
}
- Entity 클래슀λͺ…, νƒ€μž…λͺ…에 μ‚¬μš©λ©λ‹ˆλ‹€ - 예: UserModel, User, UserBaseSchema

table

λ°μ΄ν„°λ² μ΄μŠ€ ν…Œμ΄λΈ” μ΄λ¦„μž…λ‹ˆλ‹€. snake_case둜 μž‘μ„±ν•©λ‹ˆλ‹€.
{
  "table": "users"
}
ν…Œμ΄λΈ”λͺ…을 μƒλž΅ν•˜λ©΄ Entity IDλ₯Ό 기반으둜 μžλ™ μƒμ„±λ©λ‹ˆλ‹€: - User β†’ users - BlogPost β†’ blog_posts

title

Entity의 ν•œκΈ€ λ˜λŠ” ν‘œμ‹œμš© μ΄λ¦„μž…λ‹ˆλ‹€.
{
  "title": "μ‚¬μš©μž"
}

props

Entity의 속성(컬럼) λ°°μ—΄μž…λ‹ˆλ‹€. 각 속성은 νƒ€μž…κ³Ό μ˜΅μ…˜μ„ μ •μ˜ν•©λ‹ˆλ‹€.
{
  "props": [
    { "name": "id", "type": "integer", "desc": "ID" },
    { "name": "email", "type": "string", "length": 255, "desc": "이메일" },
    { "name": "created_at", "type": "date", "dbDefault": "CURRENT_TIMESTAMP" }
  ]
}
κΈ°λ³Έ 속성 μ˜΅μ…˜:
μ˜΅μ…˜νƒ€μž…μ„€λͺ…κΈ°λ³Έκ°’
namestring속성λͺ… (snake_case)ν•„μˆ˜
typestring데이터 νƒ€μž…ν•„μˆ˜
descstringμ„€λͺ…-
nullablebooleanNULL ν—ˆμš© μ—¬λΆ€false
dbDefaultstringDB κΈ°λ³Έκ°’-
더 μ•Œμ•„λ³΄κΈ° - Field Types - μ‚¬μš© κ°€λŠ₯ν•œ λͺ¨λ“  데이터 νƒ€μž… - Relations - Entity κ°„ 관계 μ •μ˜

선택 ν•„λ“œ

parentId

λ‹€λ₯Έ Entityλ₯Ό 상속할 λ•Œ μ‚¬μš©ν•©λ‹ˆλ‹€.
{
  "id": "Admin",
  "parentId": "User",
  "title": "κ΄€λ¦¬μž"
}
parentIdλ₯Ό μ‚¬μš©ν•˜λ©΄ λΆ€λͺ¨ Entity의 λͺ¨λ“  propsλ₯Ό μƒμ†λ°›μŠ΅λ‹ˆλ‹€. μ‹ μ€‘ν•˜κ²Œ μ‚¬μš©ν•˜μ„Έμš”.

indexes

λ°μ΄ν„°λ² μ΄μŠ€ 인덱슀λ₯Ό μ •μ˜ν•©λ‹ˆλ‹€.
{
  "indexes": [
    {
      "type": "unique",
      "name": "users_email_unique",
      "columns": [{ "name": "email" }]
    },
    {
      "type": "index",
      "name": "users_created_at_idx",
      "columns": [{ "name": "created_at" }]
    }
  ]
}
인덱슀 νƒ€μž…:
  • index - 일반 인덱슀 (검색 μ„±λŠ₯ ν–₯상)
  • unique - μœ λ‹ˆν¬ 인덱슀 (쀑볡 λ°©μ§€)
  • hnsw - Vector κ²€μƒ‰μš© HNSW 인덱슀
  • ivfflat - Vector κ²€μƒ‰μš© IVFFlat 인덱슀

subsets

API μ‘λ‹΅μ—μ„œ μ‚¬μš©ν•  ν•„λ“œ 쑰합을 μ •μ˜ν•©λ‹ˆλ‹€.
{
  "subsets": {
    "A": ["id", "email", "username", "created_at"],
    "P": ["id", "email", "username", "role"],
    "SS": ["id", "email"]
  }
}
Subset ν™œμš©:
async findMany<T extends UserSubsetKey>(
  subset: T,
  params: UserListParams,
): Promise<ListResult<UserSubsetMapping[T]>> {
  const { qb } = this.getSubsetQueries(subset);
  return this.executeSubsetQuery({ subset, qb, params });
}
Relation ν•„λ“œ 포함:
{
  "subsets": {
    "P": ["id", "username", "employee.id", "employee.department.name"]
  }
}
Subset의 . ν‘œκΈ°λ²•μœΌλ‘œ μ—°κ΄€ Entity의 ν•„λ“œλ₯Ό 포함할 수 μžˆμŠ΅λ‹ˆλ‹€. Sonamuκ°€ μžλ™μœΌλ‘œ JOIN을 μƒμ„±ν•©λ‹ˆλ‹€.

enums

μ—΄κ±°ν˜• νƒ€μž…κ³Ό λ ˆμ΄λΈ”μ„ μ •μ˜ν•©λ‹ˆλ‹€.
{
  "enums": {
    "UserRole": {
      "normal": "일반",
      "admin": "κ΄€λ¦¬μž"
    },
    "UserOrderBy": {
      "id-desc": "ID μ΅œμ‹ μˆœ",
      "created_at-desc": "등둝일 μ΅œμ‹ μˆœ"
    },
    "UserSearchField": {
      "email": "이메일",
      "username": "이름"
    }
  }
}
Enum ν™œμš©:
// μžλ™ μƒμ„±λœ Zod μŠ€ν‚€λ§ˆμ™€ TypeScript νƒ€μž…
import { UserRole } from "./user.types";

// "normal" | "admin"
type Role = z.infer<typeof UserRole>;

// APIμ—μ„œ μ‚¬μš©
async updateRole(userId: number, role: Role) {
  // ...
}

μ‹€μ œ 예제

기본 Entity 예제

user.entity.json
{
  "id": "User",
  "table": "users",
  "title": "μ‚¬μš©μž",
  "props": [
    { "name": "id", "type": "integer", "desc": "ID" },
    { "name": "created_at", "type": "date", "desc": "λ“±λ‘μΌμ‹œ", "dbDefault": "CURRENT_TIMESTAMP" },
    { "name": "email", "type": "string", "length": 255, "desc": "이메일" },
    { "name": "username", "type": "string", "length": 255, "desc": "이름" },
    { "name": "password", "type": "string", "length": 255, "desc": "λΉ„λ°€λ²ˆν˜Έ" },
    { "name": "birth_date", "type": "date", "nullable": true, "desc": "생일" },
    { "name": "role", "type": "enum", "id": "UserRole", "desc": "μ—­ν• " },
    { "name": "is_verified", "type": "boolean", "desc": "인증 μ—¬λΆ€", "dbDefault": "false" },
    { "name": "deleted_at", "type": "date", "nullable": true, "desc": "μ‚­μ œμΌμ‹œ" }
  ],
  "indexes": [{ "type": "unique", "name": "users_email_unique", "columns": [{ "name": "email" }] }],
  "subsets": {
    "A": ["id", "email", "username", "role", "created_at"],
    "P": ["id", "email", "username", "role"]
  },
  "enums": {
    "UserRole": { "normal": "일반", "admin": "κ΄€λ¦¬μž" },
    "UserOrderBy": { "id-desc": "ID μ΅œμ‹ μˆœ" }
  }
}

Relation 포함 예제

employee.entity.json
{
  "id": "Employee",
  "table": "employees",
  "title": "직원",
  "props": [
    { "name": "id", "type": "integer", "desc": "ID" },
    { "name": "created_at", "type": "date", "desc": "λ“±λ‘μΌμ‹œ", "dbDefault": "CURRENT_TIMESTAMP" },
    {
      "type": "relation",
      "name": "user",
      "with": "User",
      "desc": "μ‚¬μš©μž",
      "relationType": "OneToOne",
      "hasJoinColumn": true,
      "onDelete": "CASCADE"
    },
    {
      "type": "relation",
      "name": "department",
      "with": "Department",
      "nullable": true,
      "desc": "λΆ€μ„œ",
      "relationType": "BelongsToOne",
      "onDelete": "SET NULL"
    },
    { "name": "employee_number", "type": "string", "length": 32, "desc": "μ‚¬λ²ˆ" },
    {
      "name": "salary",
      "type": "numeric",
      "precision": 10,
      "scale": 2,
      "nullable": true,
      "desc": "κΈ‰μ—¬"
    },
    { "name": "hire_date", "type": "date", "nullable": true, "desc": "μž…μ‚¬μΌ" }
  ],
  "indexes": [
    {
      "type": "unique",
      "name": "employees_user_id_unique",
      "columns": [{ "name": "user_id" }]
    }
  ],
  "subsets": {
    "A": [
      "id",
      "created_at",
      "user.username",
      "department.name",
      "employee_number",
      "salary",
      "hire_date"
    ]
  },
  "enums": {
    "EmployeeOrderBy": { "id-desc": "ID μ΅œμ‹ μˆœ" }
  }
}

Sonamu UIμ—μ„œ Entity μ •μ˜ν•˜κΈ°

  1. Sonamu UI 접속
    # API μ„œλ²„ μ‹€ν–‰ ν›„
    http://localhost:34900/sonamu-ui
    
  2. Entity 생성
    • β€œEntities” νƒ­ 클릭
    • β€œCreate Entity” λ²„νŠΌ 클릭
    • Entity ID, ν…Œμ΄λΈ”λͺ…, Title μž…λ ₯
  3. 속성(Props) μΆ”κ°€
    • β€œAdd Property” λ²„νŠΌμœΌλ‘œ μƒˆ 속성 μΆ”κ°€
    • νƒ€μž… 선택 및 μ˜΅μ…˜ μ„€μ •
    • λ“œλž˜κ·Έ μ•€ λ“œλ‘­μœΌλ‘œ μˆœμ„œ λ³€κ²½
  4. Subset μ •μ˜
    • β€œSubsets” νƒ­μ—μ„œ subset ν‚€ μΆ”κ°€
    • μ²΄ν¬λ°•μŠ€λ‘œ 포함할 ν•„λ“œ 선택
    • Relation ν•„λ“œλŠ” 트리 ν˜•νƒœλ‘œ νŽΌμ³μ„œ 선택
  5. μ €μž₯ 및 생성
    • β€œSave” λ²„νŠΌ 클릭
    • Entity 파일 μžλ™ 생성
    • Migration μžλ™ 생성
더 μ•Œμ•„λ³΄κΈ° - Sonamu UI μ‚¬μš©ν•˜κΈ° - UI 상세 κ°€μ΄λ“œ

Entity μ •μ˜ ν›„ μžλ™ μƒμ„±λ˜λŠ” 것듀

Entityλ₯Ό μ •μ˜ν•˜κ³  μ €μž₯ν•˜λ©΄ Sonamuκ°€ μžλ™μœΌλ‘œ λ‹€μŒμ„ μƒμ„±ν•©λ‹ˆλ‹€:

TypeScript νƒ€μž…

{entity}.types.ts νŒŒμΌμ— νƒ€μž…κ³Ό Zod μŠ€ν‚€λ§ˆ 생성

λ°μ΄ν„°λ² μ΄μŠ€ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜

ν…Œμ΄λΈ” 생성 SQL이 ν¬ν•¨λœ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 파일 생성

Base μŠ€ν‚€λ§ˆ

sonamu.generated.ts에 Base μŠ€ν‚€λ§ˆμ™€ Enum μΆ”κ°€

Model Scaffold

{entity}.model.ts ν…œν”Œλ¦Ώ 생성 (선택 μ‹œ)

μ£Όμ˜μ‚¬ν•­

Entity μ •μ˜ μˆ˜μ • μ‹œ 주의 - 이미 배포된 ν…Œμ΄λΈ”μ˜ μ»¬λŸΌμ„ μ‚­μ œν•˜κ±°λ‚˜ νƒ€μž…μ„ λ³€κ²½ν•  λ•ŒλŠ” μ‹ μ€‘ν•˜κ²Œ μ§„ν–‰ν•˜μ„Έμš” - λ§ˆμ΄κ·Έλ ˆμ΄μ…˜μ„ 톡해 λ‹¨κ³„μ μœΌλ‘œ λ³€κ²½ν•˜λŠ” 것을 ꢌμž₯ν•©λ‹ˆλ‹€ - Relation을 λ³€κ²½ν•  λ•ŒλŠ” μ°Έμ‘° 무결성을 κ³ λ €ν•˜μ„Έμš”
Entity 섀계 팁 - IDλŠ” 항상 integer νƒ€μž…μœΌλ‘œ μ‹œμž‘ν•˜μ„Έμš” - created_at, updated_at ν•„λ“œλ₯Ό ν¬ν•¨ν•˜μ„Έμš” - Unique μ œμ•½μ΄ ν•„μš”ν•œ ν•„λ“œλŠ” 인덱슀둜 λͺ…μ‹œν•˜μ„Έμš” - 자주 κ²€μƒ‰λ˜λŠ” ν•„λ“œμ—λŠ” 인덱슀λ₯Ό μΆ”κ°€ν•˜μ„Έμš”

λ‹€μŒ 단계

Entity μ •μ˜λ₯Ό λ§ˆμ³€λ‹€λ©΄, λ‹€μŒ 주제λ₯Ό ν•™μŠ΅ν•˜μ„Έμš”:

Sonamu UI μ‚¬μš©ν•˜κΈ°

Sonamu UI의 λͺ¨λ“  κΈ°λŠ₯κ³Ό 단좕킀 읡히기

Field Types

μ‚¬μš© κ°€λŠ₯ν•œ λͺ¨λ“  ν•„λ“œ νƒ€μž…κ³Ό μ˜΅μ…˜ μ•Œμ•„λ³΄κΈ°

Relations

Entity κ°„ 관계 μ •μ˜ν•˜λŠ” 방법 ν•™μŠ΅ν•˜κΈ°

Enums

Enum νƒ€μž… μ •μ˜ν•˜κ³  ν™œμš©ν•˜λŠ” 방법 배우기