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
νμ νλ
Entityμ κ³ μ μλ³μμ
λλ€. PascalCaseλ‘ μμ±ν©λλ€.
- Entity ν΄λμ€λͺ
, νμ
λͺ
μ μ¬μ©λ©λλ€ - μ: UserModel, User, UserBaseSchema
λ°μ΄ν°λ² μ΄μ€ ν
μ΄λΈ μ΄λ¦μ
λλ€. snake_caseλ‘ μμ±ν©λλ€.
ν
μ΄λΈλͺ
μ μλ΅νλ©΄ Entity IDλ₯Ό κΈ°λ°μΌλ‘ μλ μμ±λ©λλ€: - User β users - BlogPost β
blog_posts
Entityμ νκΈ λλ νμμ© μ΄λ¦μ
λλ€.
Entityμ μμ±(컬λΌ) λ°°μ΄μ
λλ€. κ° μμ±μ νμ
κ³Ό μ΅μ
μ μ μν©λλ€.
{
"props": [
{ "name": "id", "type": "integer", "desc": "ID" },
{ "name": "email", "type": "string", "length": 255, "desc": "μ΄λ©μΌ" },
{ "name": "created_at", "type": "date", "dbDefault": "CURRENT_TIMESTAMP" }
]
}
κΈ°λ³Έ μμ± μ΅μ
:
| μ΅μ
| νμ
| μ€λͺ
| κΈ°λ³Έκ° |
|---|
name | string | μμ±λͺ
(snake_case) | νμ |
type | string | λ°μ΄ν° νμ
| νμ |
desc | string | μ€λͺ
| - |
nullable | boolean | NULL νμ© μ¬λΆ | false |
dbDefault | string | DB κΈ°λ³Έκ° | - |
λ μμ보기 - 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": {
"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 μμ
{
"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 ν¬ν¨ μμ
{
"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 μ μνκΈ°
-
Sonamu UI μ μ
# API μλ² μ€ν ν
http://localhost:34900/sonamu-ui
-
Entity μμ±
- βEntitiesβ ν ν΄λ¦
- βCreate Entityβ λ²νΌ ν΄λ¦
- Entity ID, ν
μ΄λΈλͺ
, Title μ
λ ₯
-
μμ±(Props) μΆκ°
- βAdd Propertyβ λ²νΌμΌλ‘ μ μμ± μΆκ°
- νμ
μ ν λ° μ΅μ
μ€μ
- λλκ·Έ μ€ λλ‘μΌλ‘ μμ λ³κ²½
-
Subset μ μ
- βSubsetsβ νμμ subset ν€ μΆκ°
- 체ν¬λ°μ€λ‘ ν¬ν¨ν νλ μ ν
- Relation νλλ νΈλ¦¬ ννλ‘ νΌμ³μ μ ν
-
μ μ₯ λ° μμ±
- βSaveβ λ²νΌ ν΄λ¦
- Entity νμΌ μλ μμ±
- Migration μλ μμ±
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 νμ
μ μνκ³ νμ©νλ λ°©λ² λ°°μ°κΈ°