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 findAll(): Promise<UserSubsetA[]> {
return this.puri()
.select<UserSubsetA>("A")
.many();
}
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 ํ์
์ ์ํ๊ณ ํ์ฉํ๋ ๋ฐฉ๋ฒ ๋ฐฐ์ฐ๊ธฐ