M3L โ SQL/C#/API ์ฝ๋ ์์ฑ๊ธฐ. ๋จ์ผ tables.m3l.md ์์ค๋ก SSDT ์คํค๋ง, EF Core ์ํฐํฐ + DbContext, OData/GraphQL ๋ฑ๋ก ์ฝ๋๋ฅผ ์ผ๊ด ์์ฑํ๋ค.
์ํ: 0.x โ ํ๋ฉด์ด ์์ง ์ ์ฐฉ ์ค์ด๋ฉฐ, ๋ง์ด๋ ๋ฒ์ ์ด ์๋ชป๋ ๋์์ ๊ณ ์น ์ ์๋ค.
dotnet tool install -g mdd๋ก ์ค์นํ๋ค(NuGet). ๋ฆด๋ฆฌ์ค๋ณ ๋ณ๊ฒฝ์ CHANGELOG ๋ฅผ ๋ณผ ๊ฒ. ์ด์ MDD-Booster์ ์ญ ๋๊ตฌ๋ ์ด ์ ์ฅ์์ ๊ณผ๊ฑฐ ๋ฒ์ ์ด๋ฉฐ, ํ์ฌ ์ ๋ ฅ ํ์ยท์ค์ ๊ณผ ํธํ๋์ง ์๋๋ค.
| ํ์ | ์ถ๋ ฅ | ์๋น์ |
|---|---|---|
| Sql | tsql(๊ธฐ๋ณธ): dbo/Tables_gen/{Entity}.sql, dbo/Views_gen/{Entity}_{full,ext}.sql, .sqlproj ItemGroup ํจ์น ยท postgres: tables_gen/{table}.sql (snake_case, ์๋ ๋ฐฉ์ธ ์ ) |
SSDT ํ๋ก์ ํธ ยท Schemorph |
| Model | Entity_gen/{I,}{Entity}{,Ext}.cs, Enum_gen/{Enum}.cs, DbContext_gen/{Name}.cs (with auto-ToView ๋งคํ) |
C# classlib (EF Core + Iyu.Core) |
| Api | Api_gen/ApiRegistration_gen.cs (OData + GraphQL ์ํฐํฐ ํ์ด ๋ฑ๋ก) |
ASP.NET Core ์๋ฒ (iyu-framework-v5 ๋ฐํ์) |
{
"sources": ["./tables.m3l.md"],
"targets": [
{ "type": "Sql", "projectPath": "../src/MyApp.Database", "schema": "dbo" },
{ "type": "Model", "projectPath": "../src/MyApp.Entities", "namespace": "MyApp.Entities", "dbContextName": "MyAppDbContext" },
{ "type": "Api", "projectPath": "../src/MyApp.Server", "namespace": "MyApp.Server" }
]
}Sql ํ๊น ์ ํ ๋
ธ๋ธ(๋ชจ๋ ์๋ต ๊ฐ๋ฅ): emitSqlProj(๊ธฐ๋ณธ true โ SSDT .sqlproj ํจ์น),
emitRefreshScript(๊ธฐ๋ณธ true โ post-deployment sp_refreshview ์คํฌ๋ฆฝํธ),
emitEnumCheckConstraints(๊ธฐ๋ณธ false โ enum ์ปฌ๋ผ table-level CK_{Table}_{Column} CHECK.
SSDT dacpac์ CHECK diff๊ฐ ๋ถ์์ ํ๋ฏ๋ก ์ ์ธํ ๋๊ตฌ(Schemorph) ์๋น์์ฉ opt-in),
emitForeignKeyIndexes(๊ธฐ๋ณธ false โ ์๋).
์ด๋ ์์ง๋ ์ธ๋ ํค๋ฅผ ์๋ ์ธ๋ฑ์ฑํ์ง ์๋๋ค. FK ๋ก ํ๋ ์กฐ์ธ๊ณผ, ์ญ์ ์ ์ฐธ์กฐ ํ์ ํ์ธํ๋
๊ฒ์ฌ๊ฐ ์์ ํ
์ด๋ธ์ ํ๋๋ค. ์ผ๋ฉด ๋ชจ๋ธ์ด ๋ฎ์ง ์์ FK ์ปฌ๋ผ๋ง๋ค ์ธ๋ฑ์ค๋ฅผ ๋ง๋ ๋ค โ
T-SQL IX_{Model}_{Column} ยท PostgreSQL ix_{table}_{column}. ๋์ ํ์ ์ ๋ฐฉ์ธ๊ณผ ๋ฌด๊ดํ๊ฒ
ํ ๊ณณ์์ ๋ด๋ฆฐ๋ค.
| ์ํฉ | ์๋ ์ธ๋ฑ์ค |
|---|---|
@reference ๋ง ๋ถ์ ์ปฌ๋ผ |
โ ์์ฑ |
@pk ยท @unique ยท @index ๊ฐ ์ด๋ฏธ ๋ถ์ |
โ ์ ์ฝยท์ ์ธ์ด ์ธ๋ฑ์ค๋ฅผ ์์ ํ๋ค |
| ๋ณตํฉ ์ธ๋ฑ์ค/์ ๋ํฌ์ ์ ๋ ์ปฌ๋ผ | โ (a, b) ์ธ๋ฑ์ค๊ฐ a ์กฐํ๋ฅผ ์ฒ๋ฆฌํ๋ค |
| ๋ณตํฉ ์ธ๋ฑ์ค/์ ๋ํฌ์ ๋์งธ ์ดํ ์ปฌ๋ผ | โ ๊ทธ ์ธ๋ฑ์ค๋ก๋ ์กฐํ๋์ง ์๋๋ค |
๊ธฐ๋ณธ๊ฐ์ false๋ค. ์ผ๋ ๊ฒ์ ์ฝ๊ธฐ ์ด๋๊ณผ ์ฐ๊ธฐยท์ ์ฅ ๋น์ฉ์ ๊ตํ์ด๋ฉฐ, ๊ธฐ์กด ์คํค๋ง์ ์กฐ์ฉํ
์ ์ฉํ ํ๋จ์ด ์๋๋ค. ๋ ์ํ์ ์ฐ์ถ๋ฌผ์ ์ด์ ๊ณผ ๋์ผํ๋ค.
์๋ ค์ง ์์ธ: ๋ ํ์ฉ ์ ๋ํฌ ์ปฌ๋ผ์ T-SQL ์์ filtered unique index ๊ฐ ๋์ด ์ผ๋ฐ ์กฐ์ธ์๋ ์ฐ์ด์ง ์์ง๋ง(PG ์ plain unique index ์ ๋ค๋ฅด๋ค), ํ์ ๊ธฐ๋ ์์ชฝ ๋ชจ๋ ๋ฎ์ธ ๊ฒ์ผ๋ก ๋ณธ๋ค. ์ข์ ํํ(๋ ํ์ฉ ์ ๋ํฌ FK = ์ ํ์ 1:1)์ด๊ณ , ๊ณ ์น๋ ค๋ฉด ํ์ ๊ธฐ๊ฐ ๋ฐฉ์ธ์ ์์์ผ ํ๋ฏ๋ก ์ ์งํ๋ค.
ํ ๋ชจ๋ธ ์ ๋ณธ์ ์ฌ๋ฌ ์๋ฒ๊ฐ ๊ณต์ ํ ๋, ๊ฐ ์๋ฒ๊ฐ ๋ ธ์ถํ ์ํฐํฐ๋ฅผ ํ๊น๋ณ๋ก ์ขํ๋ค. ํ๋ฉด ํ๊น(ApiยทTypeScript) ์ ์ฉ์ด๋ฉฐ ๋ ๋ค ์๋ตํ๋ฉด ์ ๋(ํํ)์ด๋ค.
{ "type": "Api", "projectPath": "../src/MyApp.OpsServer", "namespace": "MyApp.OpsServer",
"includeEntities": ["ProductionWork", "QRScanLog"] }| ๊ท์น | ๋์ |
|---|---|
| ๋ ๋ค ์๋ต / ๋น ๋ชฉ๋ก | ์ ๋ (์์ ํ์ํธํ) |
includeEntities |
ํ์ดํธ๋ฆฌ์คํธ |
excludeEntities |
๋ธ๋๋ฆฌ์คํธ |
| ๋ ๋ค ์ง์ | ๋น๋ ์ค๋ฅ |
| ์๋ ์ํฐํฐ๋ช | ๋น๋ ์ค๋ฅ + ๊ฐ์ฅ ๊ฐ๊น์ด ์ด๋ฆ ์ ์ |
includeEntities ์ @internal ์ํฐํฐ |
๋น๋ ์ค๋ฅ (๋ฐ์ดํฐ API ๋ ธ์ถ ๋์์ด ์๋๋ค) |
SqlยทModel ํ๊น์ ์ง์ |
๋น๋ ์ค๋ฅ โ ๋ถ๋ถ์งํฉ์ FK/์์ ๋ฌด๊ฒฐ์ฑ์ ๊นฌ๋ค |
ํํฐ๊ฐ ๊ฑธ๋ฆฐ ํ๊น์ ๋น๋๋ง๋ค ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ์ถ๋ ฅํ๋ค(ํฌํจ N๊ฐ / ์ ์ธ M๊ฐ โ ์ด๋ฆโฆ).
Api ํ๊น๋ง ์ขํ๋ฉด ๊ฒฝ๊ณ ๊ฐ ๋ฌ๋ค. entity_names_gen.ts ์ EntitySetName ์ OData entity set
์ด๋ฆ์ ๋ฏธ๋ฌ์ด๋ฏ๋ก, Api ํ๋ฉด์ ์ขํ๋๋ฐ TypeScript ๋ฅผ ๊ทธ๋๋ก ๋๋ฉด ์ด๋ค ์๋ฒ๋ ๋ฑ๋กํ์ง ์๋
์ด๋ฆ์ ๊ณ์ ๊ด๊ณ ํ๋ค. ๋น๋๊ฐ ๊ทธ ์ด๋ฆ๋ค์ ์ด๊ฑฐํด ๊ฒฝ๊ณ ํ๋ฉฐ, ๊ฐ์ ํํฐ๋ฅผ TypeScript ํ๊น์๋
์ง์ ํ๋ฉด ์ฌ๋ผ์ง๋ค. ํ์ ์ ์ Api ํ๊น์ ํฉ์งํฉ ๊ธฐ์ค์ด๋ฏ๋ก, ๊ณต์ UI ํ๋๊ฐ ์ฌ๋ฌ ์๋ฒ๋ฅผ
๋ด๋นํ๋ ๊ตฌ์ฑ(๊ฐ ์๋ฒ๊ฐ ์๋ก ๋ค๋ฅธ ๋ถ๋ถ์งํฉ)์ ๊ฒฝ๊ณ ๋์์ด ์๋๋ค. Api ํ๊น์ด ์๋ ์ค์
(์๋ฒ๋ฅผ ๋ค๋ฅธ mdd.json ์์ ์์ฑ)์ ํ์ ๊ทผ๊ฑฐ๊ฐ ์์ด ๊ฒ์ฌํ์ง ์๋๋ค.
โ ๏ธ includeEntities๋ drift ํ๋ค. ์ ๋ณธ์ ์ ์ํฐํฐ๊ฐ ์ถ๊ฐ๋ผ๋ ์ด ํ๊น์๋ ์กฐ์ฉํ ๋ํ๋์ง ์๋๋ค. ์ ๊ท ์ํฐํฐ๊ฐ ๊ธฐ๋ณธ ๋ ธ์ถ๋๊ธฐ๋ฅผ ์ํ๋ฉดexcludeEntities๋ฅผ ์ธ ๊ฒ. (์ปค๋ฒ๋ฆฌ์ง ์ถ๋ ฅ์ด ๋ฌด์์ด ๋น ์ก๋์ง๋ ๋งค ๋น๋์์ ๋ณด์ฌ์ค๋ค.)
๊ฐ์ ์ข ๋ฅ์ ํ๊น์ ์ฌ๋ฌ ๊ฐ ๋ ์ ์๋ค(ํ ์ ๋ณธ โ ์ฌ๋ฌ ์๋ฒ). ๋ค๋ง ์กฐ์ฉํ ์ค์ถ๋ ฅ์ด ๋๋ ๋ ๊ฒฝ์ฐ๋ ์ค๋ฅ๋ค.
| ์ํฉ | ๋์ |
|---|---|
| ๊ฐ์ ์ข ๋ฅยท๋ค๋ฅธ ๊ฒฝ๋ก ํ๊น 2๊ฐ | ์ ์ โ ๋ณต์ ์๋ฒ ์๋๋ฆฌ์ค |
| ๊ฐ์ ์ข ๋ฅยท๊ฐ์ ๊ฒฝ๋ก ํ๊น 2๊ฐ | ๋น๋ ์ค๋ฅ (๋์ค ๊ฒ์ด ์์ ๊ฒ์ ์ฐ์ถ๋ฌผ์ ๋ฎ์ด์ด๋ค. ๊ฒฝ๋ก๋ ์ ๊ทํํด ๋น๊ต) |
Model ํ๊น 1๊ฐ + Api ํ๊น |
์ ์ โ Api ๊ฐ ๊ทธ namespace ๋ฅผ ์ถ๋ก |
Model ํ๊น 2๊ฐ ์ด์ + entitiesNamespace ๋ฏธ์ง์ Api ํ๊น |
๋น๋ ์ค๋ฅ โ ์ด๋ ๊ฒ์ ์ฐธ์กฐํ ์ง ์ถ๋ก ํ์ง ์๋๋ค. ํด๋น Api ํ๊น์ entitiesNamespace ๋ฅผ ๋ช
์ |
{ "type": "Api", "projectPath": "../src/MyApp.OpsServer", "namespace": "MyApp.OpsServer",
"entitiesNamespace": "MyApp.Entities", "includeEntities": ["ProductionWork"] }| ํ๋ฉด | ๊ท์น | ์ (QRScanLog) |
|---|---|---|
| OData entity set | PascalCase ๋ณต์ (DbSet ์ด๋ฆ๊ณผ ์ผ์น) | QRScanLogs |
| GraphQL query / mutation prefix | camelCase (์ ํ ์ฝ์ด๋ฅผ ๋ฌถ์ด ์๋ฌธ์ํ โ .NET JsonNamingPolicy.CamelCase ์ ๋์ผ) |
qrScanLogs / qrScanLog |
| TypeScript ํผ ํฌํผ | camelCase | qrScanLogFromEntity |
| PostgreSQL ์๋ณ์ | snake_case (๊ฐ์ ๋จ์ด๋ถ๋ฆฌ ๊ท์น) | qr_scan_log |
ํํ ์ฝ์ด๋ ๋ณด์กดํ๋ค: OrderQR โ orderQR.
@internal ๋ชจ๋ธ์ ๋ฐ์ดํฐ API ํ๋ฉด์์๋ง ์ ์ธ๋๋ค โ ํ
์ด๋ธยท๋ทฐยทC# ์ํฐํฐยทEF DbSet ์ ๊ทธ๋๋ก ์์ฑ๋๋ค.
| ์ฐ์ถ๋ฌผ | @internal ์กด์ค |
์ด์ |
|---|---|---|
ApiRegistration_gen.cs ยท Controllers_gen.cs |
โ ์ ์ธ | ๋ฒ์ฉ CRUDยท์ํฌ๋ฆฟ ์ปฌ๋ผ์ ๋ฐ์ดํฐ API์ ๋ ธ์ถํ์ง ์๋๋ค |
entity_names_gen.ts (EntitySetName) |
โ ์ ์ธ | OData entity set ์ด๋ฆ์ ๋ฏธ๋ฌ๋ค. ๋จ๊ฒจ ๋๋ฉด ์กด์ฌํ์ง ์๋ ๊ฒฝ๋ก๋ฅผ ํ์ ์ธ์ดํํ๊ฒ ๊ด๊ณ ํ๋ค |
entities_gen.ts ยท field_schema_gen.ts ยท *Form_gen.tsx |
โ ์ ์ง | ํ์ ์ ๋ฐ์ดํฐ API ์ ์ฉ์ด ์๋๋ค โ ์ ์ฉ ์๋ํฌ์ธํธ๋ก ๊ด๋ฆฌ๋๋ ์ํฐํฐ์๋ ์ ์ฉํ๋ค |
| Sql ยท Model ํ๊น ์ ์ฒด | โ ์ ์ง | ์คํ ๋ฆฌ์งยทEF ๋งคํ์ ๋ ธ์ถ๊ณผ ๋ฌด๊ดํ๋ค |
SqlยทModel ํ๊น์ dialect ๋
ธ๋ธ๋ฅผ ๋ฐ๋๋ค โ ๊ธฐ๋ณธ "tsql"(์ ํํ ๋์), "postgres"๋
PG์ฉ ์ฐ์ถ๋ฌผ์ ๋ธ๋ค. ๋ ํ๊น์ ๊ฐ์ dialect๋ฅผ ์ง์ ํ ๊ฒ (๋ถ์ผ์น ์ ๋น๋ ๊ฒฝ๊ณ โ
DDL๊ณผ EF ๋งคํ์ด ์๋ก ๋ค๋ฅธ ๋ค์ด๋ฐ์ ์ ์ ํ๊ฒ ๋๋ค).
{ "type": "Sql", "dialect": "postgres", "projectPath": "../db", "emitEnumCheckConstraints": true },
{ "type": "Model", "dialect": "postgres", "projectPath": "../src/MyApp.Entities", "namespace": "โฆ", "dbContextName": "โฆ" }Sql ํ๊น (postgres) โ {projectPath}/tables_gen/{table}.sql (Schemorph desired-state
๊ด๋ก: ํ
์ด๋ธ๋น ํ ํ์ผ. schema ๊ธฐ๋ณธ public):
- ์๋ณ์ = ๋น์ธ์ฉ snake_case: ํ
์ด๋ธ๋ช
์ ๋ชจ๋ธ๋ช
Pascalโsnake ๊ฒฐ์ ์ ๋ณํ
(
WorkOrderโwork_order,FMSCodeโfms_code,Iso14224Classโiso14224_class), ์ปฌ๋ผ๋ช ์ M3L ํ๋๋ช ๊ทธ๋๋ก(์ด๋ฏธ snake). ์ค๋ฅ ๊ฒ์ดํธ 4์ข โ ํจํด^[a-z][a-z0-9_]*$ยท 63๋ฐ์ดํธ(NAMEDATALEN, ์ ์ฝ๋ช ํฌํจ โ PG์ ๋ฌด์ ์ ๋จ ์ฐจ๋จ) ยท fold ์ถฉ๋ ยท PG ์์ฝ์ด(Order,User,Group๋ฑ์ ๋ชจ๋ธ๋ช ์ผ๋ก ์ธ ์ ์๋ค โ ์ธ์ฉ ์๋ณ์๋ ์์ฑํ์ง ์์ผ๋ฏ๋ก ์ค๋ฅ). ์๋ฐ์ ์ ๋ถ ๋ชจ์ ํ ๋ฒ์ ๋ณด๊ณ . - ์ ์ฝ์ ์ด๋ฆ ์๋ ์ ์ฝ์ผ๋ก:
pk_{t}ยทfk_{t}_{col}ยทuq_{t}_{cols}ยทck_{t}_{col}. FK๋ ๋์ ๋ชจ๋ธ์ PK ๋ฌผ๋ฆฌ๋ช ์ ์ฐธ์กฐํ๋ฏ๋ก ๊ณต์ PK ํ์ฅ ํ ์ด๋ธ ์ฌ์ฐธ์กฐ๊ฐ ์ฑ๋ฆฝ (REFERENCES facility_profile (facility_id)). - ์ธ๋ฑ์ค:
@index/### Indexes์ ์ธ์CREATE TABLE๋ค์ ๋ ๋ฆฝ ๋ฌธ (CREATE INDEX ix_{table}_{cols} ON {schema}.{table} (...))์ผ๋ก ๋ฐฉ์ถํ๋ค. PKยทUNIQUE ์ ์ฝ์ด ์์ ํ๋ ์ธ๋ฑ์ค๋ ์ค๋ณต ๋ฐฉ์ถํ์ง ์๊ณ ,CONCURRENTLY๋ ์ฐ์ง ์๋๋ค (์ ์ฉ์ด ๋จ์ผ ํธ๋์ญ์ ์ด๋ผ concurrent ๋น๋๊ฐ ์ฐธ์ฌํ ์ ์๋ค). ์ธ๋ฑ์ค๋ช ๋ ์ ์ฝ๋ช ๊ณผ ๊ฐ์ 63๋ฐ์ดํธ ๊ฒ์ดํธ๋ฅผ ํต๊ณผํด์ผ ํ๋ค โ ๋ค์ค ์ปฌ๋ผ ์ธ๋ฑ์ค๋ช ์ด ์ ์ฝ๋ช ๋ณด๋ค ์ฝ๊ฒ ๋๊ธด๋ค. - ๋ทฐ๋ ์์ง ๋ฐฉ์ถํ์ง ์๋๋ค (derived ํ๋์
_full/_ud) โ ๋ฌด์ ํ๋ฝ ๋์ stderr ๊ฒฝ๊ณ ๋ก ํ๋ฉดํํ๋ค. - ๋ ํ์ฉ
@unique๋ PG๊ฐ NULL์ distinct๋ก ์ทจ๊ธํ๋ฏ๋ก filtered index ์์ด UNIQUE ์ ์ฝ ํ๋๋ก ์ ํํ๋ค (T-SQL ๊ฒฝ๋ก๋ filtered unique index๊ฐ ํ์ํ๋ค). emitSqlProj/emitRefreshScript๋ SSDT ๊ฐ๋ โ postgres์ ํจ๊ป ๋ช ์ํ๋ฉด ์ค๋ฅ.- ํ์
๋งคํ ์ฃผ์์ :
timestamp/datetimeโtimestamptzยทstringโtext(๊ธธ์ด ์ง์ ์varchar(n)) ยทjsonโjsonbยทbyteโsmallint(PG์ 1๋ฐ์ดํธ ์ ์ ์์ โ ์น๊ฒฉ) ยทbinary(n)โbytea(๊ธธ์ด ์ํ ์์ค โ bytea์ ๊ธธ์ด ๊ฐ๋ ์์).
Model ํ๊น (postgres) โ DbContext์ ๋ช
์ ๋งคํ์ ๊ตฝ๋๋ค(๋ฐํ์ ๋ค์ด๋ฐ ์ปจ๋ฒค์
์ถ๋ก
์์): ์ํฐํฐ๋ณ ToTable("snake") + ์ ์ฅ ํ๋ ์ ์ฒด HasColumnName("ํ๋๋ช
"),
๊ณต์ PK๋ ์์ Id๋ฅผ PK ๋ฌผ๋ฆฌ๋ช
์ผ๋ก, json ํ๋๋ HasColumnType("jsonb").
Ext ์ฝ๊ธฐ ๋ชจ๋ธ์ ๋ทฐ backing์ด ์์ผ๋ฉด ๊ฐ์ ํ
์ด๋ธ์ ์ฝ๊ณ , ๋ทฐ backing์ด ํ์ํ ๋ชจ๋ธ
(derived ํ๋ยทsoft-delete)์ ๊ฒฝ๊ณ โ PG ๋ฐฉ์ธ์ ๋ทฐ๋ฅผ ๋ฐฉ์ถํ์ง ์์ผ๋ฏ๋ก ํด๋น ๋ทฐ๋ฅผ
์ง์ ๋ง๋ค๊ธฐ ์ ๊น์ง Ext ์ง์๋ ์คํจํ๋ค.
dotnet run --project src/MddBooster.Cli -- build path/to/mdd๋๋ ์ค์น๋ CLI:
mdd build ./mdd # mdd.json ์ด ์๋ ๋๋ ํฐ๋ฆฌ โ ์๋ตํ๋ฉด ํ์ฌ ๋๋ ํฐ๋ฆฌ| ๊ธฐ๋ฅ | ์ง์ |
|---|---|
| Primitive ํ์ 18์ข (identifier/string/decimal/phone/email/...) | โ |
Enum (C# enum + [EnumMember]; SQL CHECK๋ emitEnumCheckConstraints opt-in) |
โ |
phone/email/url โ ๊ฒ์ฆ ๋ฌธ์์ด (plain string, NVARCHAR(30/200/500)). ๊ฐ๊ฐ์ฒด ๋งคํ ์๋ โ ODataConventionModelBuilder๊ฐ ๊ฐ๊ฐ์ฒด struct๋ฅผ EDM ๋ณตํฉ ํ์
์ผ๋ก ๋ฑ๋กํ์ง ๋ชปํด ์ง๋ ฌํ๊ฐ ๊นจ์ง๊ธฐ ๋๋ฌธ. ๋ฐ์ดํฐ ๊ณ์ธต(Iyu.Data)์ ๊ฐ๊ฐ์ฒด ValueConverter๊ฐ ์์ผ๋ฏ๋ก ๋งํ ์ง์ ์ OData ์ง๋ ฌํ ๊ณ์ธต ํ์ |
โ |
@reference(Target) โ SQL FK + C# [Reference] ์์ฑ |
โ |
@unique (๋จ์ผ ์ปฌ๋ผ) |
โ |
@lookup(fk.col) โ _full ๋ทฐ LEFT JOIN + [Lookup] ์์ฑ |
โ |
@rollup(Target.fk, aggregate) โ _ext ๋ทฐ ์๋ธ์ฟผ๋ฆฌ + [Rollup] |
โ |
@computed("expr") โ _ext ๋ทฐ ํํ์ ์ปฌ๋ผ + [Computed] |
โ |
@indexed + rollup โ WITH SCHEMABINDING |
โ |
@unique(col1, col2) ๋ณตํฉ |
โ
โ ๋ ํ์ฉ ์ปฌ๋ผ์ด ์์ด๋ฉด filtered unique index (WHERE โฆ IS NOT NULL)๋ก ๋ฐฉ์ถํ๋ค. ๊ทธ๋ฌ์ง ์์ผ๋ฉด ๋ ๋ฒ์งธ ์ ์ฒด-NULL ํ์ด ๊ฑฐ๋ถ๋๋ค |
### Indexes ์น์
(- @unique(...) / - @index(...)) |
โ |
@inherits(FQN) โ C# ๋ฒ ์ด์คํด๋์ค ์ค๋ฒ๋ผ์ด๋ (๋๋ฉ์ธ ์ค๋ฆฝ, verbatim) |
โ |
@implements(FQN, ...) โ C# ์ธํฐํ์ด์ค append (๋๋ฉ์ธ ์ค๋ฆฝ, verbatim) |
โ |
enum ๊ฐ์ @system โ ์์ฑ ํผ ์ ํ์ง์์ ์ ์ธ (์๋) |
โ |
๋ ํ์ฉ ์ฌ๋ถ ยท string(n) ยท = <value> โ C# ๊ฒ์ฆ ์ดํธ๋ฆฌ๋ทฐํธ + ์ด๊ธฐํ์ (์๋) |
โ |
๋ชจ๋ธ์ด ์ ์ธํ ์ ์ฝ์ SQL ์ปฌ๋ผ๋ฟ ์๋๋ผ ์์ฑ ์ํฐํฐ์๋ ๋ฐฉ์ถ๋๋ค. API ํ๋ฉด์ด ๋ชจ๋ธ์ด ๊ธ์งํ ๊ฐ์ ๋ฐ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๊น์ง ๋ด๋ ค๋ณด๋ด์ง ์๋๋ก ํ๊ธฐ ์ํ ๊ฒ์ด๋ค.
| ์ ์ธ | ๋ฐฉ์ถ | ๋น๊ณ |
|---|---|---|
| ๋ ํ์ฉํ์ง ์๋ ์ฐธ์กฐํ ํ๋ | [Required] |
string/text/json/phone/email/url/binary. ๊ฐํ(์ซ์ยท์๊ฐยทGuidยทenum)์ CLR์ด ์ด๋ฏธ ๋์ ํ์ฉํ์ง ์์ผ๋ฏ๋ก ์ ์ธ |
string(n) |
[StringLength(n)] |
๋ ํ์ฉ ์ฌ๋ถ์ ๋ฌด๊ด โ string(50)? ๋ ์ํ 50์ ๊ฐ๋๋ค |
= <value> |
์์ฑ ์ด๊ธฐํ์ | = true; ยท = 3; ยท = "NEW"; ยท = 0.5m; ยท = Status.Draft; |
๊ธฐ์ค์ @not_null ์ ์ ์๋์ง๊ฐ ์๋๋ผ ํ๋๊ฐ ์ค์ ๋ก ๋์ ํ์ฉํ๋์ง๋ค. - name: string(50)
์ฒ๋ผ ์์ฑ ์์ด ์ ์ธํ ํ๋๋ ์ปฌ๋ผ์ด NOT NULL ์ด๋ฏ๋ก ๋์ผํ๊ฒ ๋ฐฉ์ถ๋๋ค.
[Required] ๋ NOT NULL ๋ณด๋ค ์ข๋ค. SQL NOT NULL ์ปฌ๋ผ์ ๋น ๋ฌธ์์ด์ ํ์ฉํ์ง๋ง
[Required] ๋ ๊ฑฐ๋ถํ๊ณ , ํ์ ์ ์ trim ํ๋ฏ๋ก ๊ณต๋ฐฑ๋ง ์๋ ๊ฐ๋ ๊ฑฐ๋ถํ๋ค. ๋ ํ์ฉํ์ง ์๋
๋ฌธ์์ด ํ๋๋ API ํ๋ฉด์์ "๊ฐ์ด ์์ด์ผ ํ๋ค"๋ฅผ ๋ปํ๊ฒ ๋๋ค.
ํ์ ํ๋(@lookup/@rollup/@computed)์๋ [Required] ๊ฐ ๋ถ์ง ์๋๋ค โ ๋ทฐ๊ฐ ์ฑ์ฐ๋ ๊ฐ์ด๋ผ
ํธ์ถ์๊ฐ ๋ณด๋ผ ์ ์๊ณ , ๋ถ์ด๋ฉด ์์ฑ ์์ฒญ์ด ์ ๋ถ ๊ฑฐ๋ถ๋๋ค. [StringLength] ์ญ์ ์ ์ฅ ํ๋์๋ง ๋ถ๋๋ค.
๊ธฐ๋ณธ๊ฐ์ C# ๋ฆฌํฐ๋ด๋ก ํํํ ์ ์๋ ํ์
์๋ง ๋ฐฉ์ถ๋๋ค. ์๊ฐ ํ์
ยท identifier ยท binary ๋
๊ธฐ๋ณธ๊ฐ ํ๊ธฐ๊ฐ ๋ฌด์์ด๋ ์ด๊ธฐํ์๋ฅผ ๋ง๋ค์ง ์์ผ๋ฏ๋ก = now() ๊ฐ์ ์๋ฒ ์ธก ๊ธฐ๋ณธ๊ฐ์ด ์ฝ๋๋ก ์์ง ์๋๋ค.
๋ ํ์ฉ ํ๋๋ ์ ์ธ๋ ๊ธฐ๋ณธ๊ฐ์ด ์์ด๋ ์ด๊ธฐํ์๋ฅผ ๋ฐ์ง ์๋๋ค โ ์ ํ ์์ฑ์ ์ฑ์ฐ๋ฉด "๋ฏธ์ค์ "์
์๋ฏธ๊ฐ ๋ฐ๋๊ธฐ ๋๋ฌธ์ด๋ค.
์์ฑ๋ *Form_gen.tsx๋ ์๋น ํ๋ก์ ํธ๊ฐ ์ ๊ณตํด์ผ ํ๋ ๋ชจ๋์ importํ๋ค.
์์ฑ๋๋ ๋ชจ๋ import๋ ์๋น์์ ๋ํ ์๊ตฌ์กฐ๊ฑด์ด๋ค โ ์๋๋ฅผ ๊ฐ์ถ์ง ์์ผ๋ฉด ์์ฑ ์ฝ๋๊ฐ
์๋น์ฑ ๋น๋์์ ์ปดํ์ผ๋์ง ์๋๋ค. (mdd-booster ์์ฒด ํ
์คํธ๋ ์์ฑ๋ TS๋ฅผ ์ปดํ์ผํ์ง ์์ผ๋ฏ๋ก,
์ด ๊ณ์ฝ์ ์๋ฐ์ ์๋น์ฑ ๋น๋์์๋ง ๋๋ฌ๋๋ค.)
| ์ปดํฌ๋ํธ | ์ธ์ import๋๋ | ๋ฐ๋ ํ๋กญ |
|---|---|---|
UInput |
date ยท ์ซ์ ยท ๋ฌธ์์ด ํ๋ | label required? description? type?("date"/"number") step?: number maxlength?: number value: string onChange: (v: string) => void |
UTextarea |
text ํ๋ |
label required? description? minRows: number value: string onChange: (v: string) => void |
USelect |
enum ํ๋ | label required? description? placeholder? value: string options onChange: (v: string) => void |
UCheckbox |
boolean ํ๋ | label description? checked: boolean onChange: (v: boolean) => void |
required/description์ ๋ชจ๋ธ์ด ๊ทธ๋ ๊ฒ ๋งํ ๋๋ง ๋ฐฉ์ถ๋๋ค (@not_nullโrequired,@help("...")โdescription). ์ฆ ๋ค ์ปดํฌ๋ํธ ๋ชจ๋description์ ๋ฐ์ ์ ์์ด์ผ ํ๋ค โ ํ๋๋ผ๋ ๋น ์ง๋ฉด ๊ทธ ํ์ ์ ํ๋์@help๋ฅผ ๋ถ์ด๋ ์๊ฐ ๋น๋๊ฐ ๊นจ์ง๋ค.value/onChange๋ controlled ํจํด์ ์ ์ ํ๋ค(๋น ์ํ sentinel์'').stepยทmaxlength๋ 0.8.0์์ ์ถ๊ฐ๋ ์๊ตฌ์กฐ๊ฑด์ด๋ค โ 0.7.0 ์ดํ์์ ๋ง๋ ๋ํผ๋ ๊ฐฑ์ ํด์ผ ํ๋ค(CHANGELOG 0.8.0 ์ฐธ์กฐ).stepยทmaxlength๋number์ด์ง ๋ฌธ์์ด์ด ์๋๋ค โ ์์ฑ๋ฌผ์step={0.0001}ยทmaxlength={50}(์ค๊ดํธ ์ซ์ ๋ฆฌํฐ๋ด)์ ๋ฐฉ์ถํ๋ค. ๋ํผ๊ฐ ์ด๋ฅผ ๊ทธ๋๋ก ์๋ input์ ์ ๋ฌํด์ผ ํ๋ค.โ ๏ธ maxlength๋ ์๋ฌธ์๋ค (React DOM์maxLength๊ฐ ์๋๋ผu-input์ ํ๋ฉด ํ๊ธฐ). ๊ทธ๋ฆฌ๊ณstring(n)์ ๊ฑฐ์ ๋ชจ๋ ๋ชจ๋ธ์ ์์ผ๋ฏ๋กstep๋ณด๋ค ์ํฅ ๋ฒ์๊ฐ ํจ์ฌ ๋๋ค โdecimal์ ์ ์ฐ๋ ์๋น์๋ ์ด ํ๋กญ์ ๊ฑฐ์ ํ์คํ ํ์ํ๋ค. ๋ํผ์ ์์ผ๋ฉด TS2322๋ก ๋น๋๊ฐ ๊นจ์ง๋ค.
export function enumToOptions(labels: Record<string, string>): /* USelect์ options ํ์
*/์์ฑ๊ธฐ๊ฐ ์๊ตฌํ๋ ๊ฒ์ ์ธ์ ํ๋์ ๊ทธ ๊ฒฐ๊ณผ๊ฐ USelect์ options์ ๊ทธ๋๋ก ๋ค์ด๊ฐ๋ค๋ ๊ฒ๋ฟ์ด๋ค
(๋ฐํ ํ์
์ ์๋น์๊ฐ ์ ํ๋ค). ๊ฐ์ ์ขํ ๋๋ ์ธ์๋ฅผ ๋๋ฆฌ์ง ์๊ณ ์ขํ์ง ๋ผ๋ฒจ๋งต์ ๋ฐ๋ก ์์ฑํด
๋๊ธฐ๋ฏ๋ก(์๋ @system ์ ), ์ด ์๊ทธ๋์ฒ๋ ์์ ์ ์ด๋ค.
FormSection(title) ยท FormRow(full?) ๋ฅผ exportํด์ผ ํ๋ค.
../types/entities_gen ยท ../types/enums_gen ยท ../types/enum_labels_gen โ ์ ๋ถ ์์ฑ๋ฌผ์ด๋ค.
| m3l ํ์ | ์ปจํธ๋กค | ๋น๊ณ |
|---|---|---|
text |
UTextarea (minRows={3}) |
๊ธธ์ด ๋ฌด์ ํ = ์ฌ๋ฌ ์ค ์๋. SQL ํ๊น๋ NVARCHAR(MAX)๋ก ๋ฐฉ์ถํ๋ฉฐ, ํผ์์ ์ ํญ ๋ฐฐ์น๋๋ค |
boolean |
UCheckbox |
|
| enum ํ์ ๋ช | USelect |
|
date |
UInput type="date" |
DateOnly โ "2026-07-28", ์ปจํธ๋กค์ด ๋ฐ๋ ํ์๊ณผ ์ผ์น |
timestamp ยท datetime ยท time |
UInput (์์ ํ
์คํธ, ์๋์ ) |
๋ค์ดํฐ๋ธ ํผ์ปค๊ฐ ๊ฐ์ ํ๊ดดํ๋ค โ ์๋ |
decimal(p,s) |
UInput type="number" step={10^-s} |
์ค์ผ์ผ์์ ์ ๋. decimal(18,4)โstep={0.0001}. ํ๋ผ๋ฏธํฐ ์๋ decimal์ SQL ๊ธฐ๋ณธ๊ฐ DECIMAL(18,2)์ ๋ง์ถฐ step={0.01} |
์ ์ ํ์
(integer/long/short/byte), decimal(p,0) |
UInput type="number" |
step ๋ฏธ๋ฐฉ์ถ โ HTML ๊ธฐ๋ณธ๊ฐ 1์ด ์ ํํ ๋ง๋ค |
float / double |
UInput type="number" |
์๋ ค์ง ํ๊ณ: step ๋ฏธ๋ฐฉ์ถ์ด๋ผ ์์ ์
๋ ฅ์ด ๋งํ๋ค. ์ ๋ต์ธ step="any"๋ฅผ step?: number ๊ณ์ฝ์ด ๋ด์ง ๋ชปํ๋ค(์๋) |
string(n) |
UInput maxlength={n} |
SQL NVARCHAR(n)์ ์ํ์ UI๋ก ์๋น๊ธด๋ค. ์ ์ฅ๋ ๊ฐ์ ์ด๋ฏธ n ์ด๋ด์ด๋ฏ๋ก ๊ธฐ์กด ๊ฐ์ ๋ฌดํจํํ์ง ์๋๋ค |
string(๋ฌดํ๋ผ๋ฏธํฐ) ยท phoneยทemailยทurl |
UInput |
๋ฌดํ๋ผ๋ฏธํฐ string์ NVARCHAR(MAX)๋ผ ์ํ์ด ์๋ค. phone/email/url์ ์ํ(30/200/500)์ ๋ชจ๋ธ์ด ์๋๋ผ ์์ฑ๊ธฐ ๊ด๋ก๋ผ UI๋ก ๋ฐฉ์ถํ์ง ์๋๋ค |
@reference FK ยท @slot |
์ฌ๋กฏ ์๋ฆฌํ์์ | ํธ์ถ๋ถ๊ฐ ๋ด์ฉ์ ์ฃผ์ |
step์ ์ ํ ์ต์ ์ด ์๋๋ค.<input type="number">์step๊ธฐ๋ณธ๊ฐ์ 1์ด๋ผ, ์์ผ๋ฉด ๋ธ๋ผ์ฐ์ ๊ฐ ์์๋ฅผ ๊ฑฐ๋ถํ๊ณ ("Value must be a multiple of 1") submit์ด ์ฑ์ ์๋ฌด ์ ํธ ์์ด ๋งํ๋ค โ ์ค๋ฅ ์์ด ์๋ฌด ์ผ๋ ์ ์ผ์ด๋๋ ๊ฒ์ฒ๋ผ ๋ณด์ธ๋ค. ์ค์ผ์ผ์ ๋ชจ๋ธ์ด ์ด๋ฏธ ๊ฐ๊ณ ์๊ณ (SQL ํ๊น์DECIMAL(p,s), Model ํ๊น์[Column(TypeName)]์ด ๊ฐ์ ๊ฐ์ ์ด๋ค) ํผ๋ ๊ทธ๊ฒ์ ๋ฐ๋ฅธ๋ค.
float/double์ ์์ง ์ด ํํ์ ๋ชป ๋ฐ๋๋ค. ์ค์ผ์ผ ๊ฐ๋ ์ด ์์ดstep="any"๊ฐ ์ ์ผํ ์ ๋ต์ธ๋ฐ, ๊ณ์ฝ์step์ดnumber๋ผ"any"๋ฅผ ์ค์ ์ ์๋ค. ์์๊ฐ ํ์ํ ํ๋๋float/double๋์decimal(p,s)๋ก ๋ชจ๋ธ๋งํ ๊ฒ โ ์ ๋ฐ๋๊ฐ ๋ช ์๋๋ฏ๋ก SQLยทEFยทํผ์ด ๋ชจ๋ ๊ฐ์ ์ฝ์์ ํ๊ฒ ๋๋ค.
timestamp/datetime/time์ด ์์ ํ ์คํธ์ธ ๊ฒ์ ๋ฏธ๊ตฌํ์ด ์๋๋ผ ๊ฒฐ์ ์ด๋ค. ํผ์ปค๋ API๊ฐ ๋๋ ค์ฃผ๋ ๊ฐ์ ์ปจํธ๋กค์ด ๋ฐ์๋ค์ผ ๋๋ง ๋์์ด ๋๋ค. ์ค์ธก ๊ฒฐ๊ณผ:
m3l CLR JSON ์ง๋ ฌํ ์ปจํธ๋กค์ด ๋ฐ๋ ํ์ ์๋ณต dateDateOnly"2026-07-28"type="date"=YYYY-MM-DDโ timestampยทdatetimeDateTimeOffset"2026-07-28T14:30:00+09:00"datetime-local=YYYY-MM-DDTHH:mm[:ss]โ ์คํ์ ๋ถ๊ฐโ timeTimeOnly"14:30:45"ยท"14:30:45.1230000"type="time"โ ๊ธฐ๋ณธstep=60(์ด ๊ฑฐ๋ถ), ์์ ์ด 3์๋ฆฌ ํ๊ณโ SQL ํ์ ์ด
DATETIMEOFFSETยทTIME(7)์ด๋ฏ๋ก ์คํ์ ๊ณผ ์์ ์ด๋ ์ค์ ๋ฐ์ดํฐ๋ค. ํผ์ปค๋ฅผ ๋ถ์ด๋ฉด ๋ธ๋ผ์ฐ์ ๊ฐ ๊ทธ ๊ฐ์ ๊ฑฐ๋ถํด ์ปจํธ๋กค์ด ๋น ์นธ์ผ๋ก ๋ ๋๋๊ณ , ๊ทธ๋๋ก ์ ์ฅํ๋ฉด ๊ธฐ์กด ๊ฐ์ด ์ง์์ง๋ค โ ๊ฐ์ ๋ณด์ฌ์ฃผ๊ธฐ๋ผ๋ ํ๋ ์์ ํ ์คํธ๋ณด๋ค ๋์๋ค.type="time" step={1}์ ์ด ๋ฌธ์ ๋ง ํ๊ณ ์์ ์ด๋ ์กฐ์ฉํ ๋ฒ๋ ค์ ์ด๋ค ๊ฐ์ ๋๊ณ ์ด๋ค ๊ฐ์ ์ฌ๋ผ์ง๋ ๋ ๋์ ์ํ๋ฅผ ๋ง๋ ๋ค.์ซ์
step๊ณผ ๋ฐฉํฅ์ด ๋ฐ๋๋ผ๋ ์ ์ ์ฃผ์: ๊ฑฐ๊ธฐ์๋ ๋ชจ๋ธ ์ ๋ณด๋ฅผ ์ปจํธ๋กค๋ก ์ฎ๊ธฐ๋ฉด ๋งํ๋ ์ ๋ ฅ์ด ๊ฐ๋ฅํด์ง์ง๋ง, ์ฌ๊ธฐ์๋ ์ปจํธ๋กค์ด ๋ชจ๋ธ ์ ๋ณด๋ฅผ ๋ด์ง ๋ชปํด ์ฎ๊ธฐ๋ฉด ๋ฐ์ดํฐ๊ฐ ์ฌ๋ผ์ง๋ค. ์ฆ์("๋ชจ๋ธ์ ํ์ ์ ์๋๋ฐ ํผ์ด ์ ์ด๋ค")์ด ๊ฐ์ ๋ณด์ฌ๋ ์ฒ๋ฐฉ์ด ๋ฐ๋๋ค. ํผ์ปค๋ฅผ ์ฐ๋ ค๋ฉด ์คํ์ ์ธ์ง ๋ณํ ๊ณ์ธต์ด ์๋น์ ๊ณ์ฝ์ ์ถ๊ฐ๋ผ์ผ ํ๋ค โ ์ฌ๋์ด ๊ฒฐ์ ํ ์ฌ์์ด๋ค.
minRows๋ ์ ํ ์ต์ ์ด ์๋๋ค.<u-textarea>๋ ์๋ ๋์ด ์กฐ์ ์ด๋ผ 1์ค์์ ์์ํ๋ฏ๋ก, ์์ผ๋ฉด ๋จ์ผํ ์ ๋ ฅ๊ณผ ์ก์์ผ๋ก ๊ตฌ๋ถ๋์ง ์๋๋ค. (์์ฑ๋ช ์minRows์ด๋ฉฐrows๋ ์กด์ฌํ์ง ์๋๋ค.)
M3L ํ์๋ attribute๋ฅผ ์๋ฏธ ์์ด ๊ธฐ๋ก๋ง ํ๋ค. ์๋๋ mdd-booster์ TypeScript ํ๊น์ด
๊ทธ ๊ธฐ๋ก์ ๋ถ์ฌํ๋ ์๋ฏธ๋ค. M3L ํ์ค attribute(@pkยท@reference ๋ฑ)์ ๋ฌ๋ฆฌ ์ด ๋ชฉ๋ก์
mdd-booster ๊ณ ์ ์ด๋ฉฐ, ๋ค๋ฅธ ์์ฑ๊ธฐ๋ ์ด๋ค์ ๋ฌด์ํ๋ค.
| attribute | ๋์ | ์์ฑ ๊ฒฐ๊ณผ |
|---|---|---|
@group("์ด๋ฆ") |
ํ๋ | ํผ์ <FormSection title="์ด๋ฆ">์ผ๋ก ๋ฌถ๋๋ค. ์์ผ๋ฉด "๊ธฐํ" ์น์
|
@help("์ค๋ช
") |
ํ๋ | ์ปจํธ๋กค์ description="์ค๋ช
" โ ๋ผ๋ฒจ์ ์งง๊ฒ ๋๊ณ ์์ยท๋ถ์ฐ์ ์๋๋ก ๋ถ๋ฆฌ |
@slot |
ํ๋ | ์ธ๋ผ์ธ ์ปจํธ๋กค ๋์ ์ฌ๋กฏ ์๋ฆฌํ์์๋ก ๋ ๋ (ํธ์ถ๋ถ๊ฐ ๋ด์ฉ์ ์ฃผ์
). @reference FK ํ๋๋ ์๋์ผ๋ก ์ฌ๋กฏ |
@display_labels(๋ค๋ฅธEnum) |
enum ํ๋ | ํ์ ํ ์คํธ๋ง ๋ค๋ฅธ enum์ ๋ผ๋ฒจ๋งต์ผ๋ก ๊ต์ฒด. ์ ์ฅ/์บ์คํธ ํ์ ์ ๊ทธ๋๋ก |
@system |
enum ๊ฐ | ์์ฑ ํผ ์ ํ์ง์์ ์ ์ธ (์๋) |
M3L์ enum ๊ฐ์ ๋ถ์ attribute๋ฅผ ์๋ฏธ ์์ด ๊ธฐ๋ก๋ง ํ๋ค. ๊ทธ ์๋ฏธ๋ฅผ ์ ํ๋ ๊ฒ์ ์์ฑ๊ธฐ์ ๋ชซ์ด๋ฉฐ,
@system์ "์์คํ
์ด ์ฐ๋ ๊ฐ, ์ฌ๋์ด ๊ณ ๋ฅด๋ ๊ฐ์ด ์๋๋ค" ๋ก ํด์๋๋ค.
## PaymentMethod ::enum
- cash: "ํ๊ธ"
- card: "์นด๋"
- legacy_carryover: "๋ ๊ฑฐ์ ์ด๊ด ์ ๋ฆฌ" @system์์ฑ ๊ฒฐ๊ณผ:
// enum_labels_gen.ts โ ํ์ ๋ผ๋ฒจ์ ์ ์ฒด ์ ์ง (๊ธฐ์กด ํ์ด ๊ณ์ ๋ ๋๋ผ์ผ ํ๋ฏ๋ก)
export const PaymentMethodLabels: Record<PaymentMethod, string> = {
Cash: 'ํ๊ธ', Card: '์นด๋', LegacyCarryover: '๋ ๊ฑฐ์ ์ด๊ด ์ ๋ฆฌ',
} as const
/** Input choices โ excludes values marked @system in the model. */
export const PaymentMethodSelectableLabels: Record<Exclude<PaymentMethod, 'LegacyCarryover'>, string> = {
Cash: 'ํ๊ธ', Card: '์นด๋',
} as const// {Entity}Form_gen.tsx โ ํผ์ ์ขํ์ง ๋งต์ ์ด๋ค
options={enumToOptions(PaymentMethodSelectableLabels)}@system์ ์ ์ฅ์ด ์๋๋ผ ์์ฑ(authoring)์ ์ ํํ๋ค. ๊ฐ์ SQL CHECK ์ ์ฝ(opt-in ์),
C# enum, ํ์ ๋ผ๋ฒจ ๋งต์ ๊ทธ๋๋ก ๋จ๋๋ค โ ์๋ฒยท๋ง์ด๊ทธ๋ ์ด์
์ด ์ฐ๋ ์ ํจํ ์ ์ฅ๊ฐ์ด๊ธฐ ๋๋ฌธ์ด๋ค.
๋น ์ง๋ ๊ฒ์ ์์ฑ ํผ์ ์ ํ์ง๋ฟ์ด๋ค.
์๋น์ฑ์ enumToOptions ํฌํผ ์๊ทธ๋์ฒ๋ ๋ฐ๋์ง ์๋๋ค. ์ขํ์ ๋ณ๋ ๋งต์ผ๋ก ํํ๋๋ฏ๋ก
ํฌํผ๋ ์ฌ์ ํ Record<string, string> ํ๋๋ง ๋ฐ์ผ๋ฉด ๋๋ค.
์๊ตฌ ๋ฒ์ :
M3L.Native0.6.0 ์ด์ (enum ๊ฐ attribute ํ์ฑ ์ง์).
consumer-repo/
โโโ mdd/
โ โโโ mdd.json
โ โโโ tables.m3l.md
โโโ src/
โโโ MyApp.Database/
โ โโโ MyApp.Database.sqlproj (patched)
โ โโโ dbo/
โ โโโ Tables_gen/ โ ๋งค๋ฒ ์ฌ์์ฑ
โ โ โโโ User.sql
โ โ โโโ Order.sql
โ โโโ Views_gen/ โ ๋งค๋ฒ ์ฌ์์ฑ (derived ํ๋ ์๋ ๋ชจ๋ธ๋ง)
โ โโโ Order_full.sql
โ โโโ Order_ext.sql
โโโ MyApp.Entities/
โ โโโ Entity_gen/ โ ๋งค๋ฒ ์ฌ์์ฑ
โ โโโ Enum_gen/
โ โโโ DbContext_gen/
โโโ MyApp.Server/
โโโ Api_gen/
โโโ ApiRegistration_gen.cs
์์ฑ๋ partial ํด๋์ค๋ ๊ฐ์ ๋ค์์คํ์ด์ค์ ์๋ ํ์ฅ์ ์ง์ํ๋ค. _gen ์ ๋ฏธ ํด๋๋ ์ฌ์์ฑ ์ ์์ ํ ๋ฎ์ด์จ์ง๋ฏ๋ก ์ ๋ ์๋ ํธ์งํ์ง ๋ง ๊ฒ.
๋น๋ ์ ์ cross-entity ๋ฌด๊ฒฐ์ฑ ๊ฒ์ฌ:
| ์ฝ๋ | ์๋ฏธ |
|---|---|
| MDD001 | ํ๋ ํ์ ์ด primitive/enum/model ์ด๋ ๊ฒ๋ ์๋ |
| MDD002 | @reference(X) ๋์ ์ํฐํฐ ์์ |
| MDD003 | @lookup ๊ฒฝ๋ก๊ฐ fk.col ํํ๊ฐ ์๋ |
| MDD004 | @lookup(fk.col)์ fk๊ฐ ๋์ผ ๋ชจ๋ธ์ ์์ |
| MDD005 | ํด๋น fk์ @reference ์์ |
| MDD006 | lookup target ์ํฐํฐ์ col ํ๋ ์์ |
| MDD007-9 | @rollup ๋์ ๊ฒ์ฆ |
์๋ฌ ๋ฐ์ ์ exitcode 3์ผ๋ก ์ข ๋ฃ.
src/
โโโ MddBooster.Core/ AST ๋ก๋ฉ, Semantic ๋ถ์, ๊ณต์ฉ naming/primitives
โโโ MddBooster.Generators.Sql/ TableRenderer, ViewPlanner, Full/ExtViewRenderer, SqlProjPatcher
โโโ MddBooster.Generators.Model/ CSharpTypeMapper, EnumRenderer, EntityPairRenderer, DbContextRenderer
โโโ MddBooster.Generators.Api/ ApiRegistrationRenderer (OData + GraphQL)
โโโ MddBooster.Cli/ BuildCommand (mdd.json ์๋น)
โโโ MddBooster.Tests/ 467 xUnit tests (Roslyn ๊ตฌ๋ฌธ/์๋ฏธ ๊ฒ์ฆ ํฌํจ)
dotnet test MddBooster.slnx --nologoํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง:
-
Renderer ๋จ์ (E2E Roslyn ๊ตฌ๋ฌธ ๊ฒ์ฆ)
-
Semantic analyzer cross-entity ๊ฒ์ฆ
-
CLI 3-ํ๊น ํตํฉ E2E (์์ ๋๋ ํฐ๋ฆฌ์์ ์ ์ฒด ํ์ดํ๋ผ์ธ ์คํ)
-
acceptance ๊ฒ์ดํธ โ ๊ธฐ๋ฅ ํ๋ ฌ์ ํ ๋ฒ์ ๊ต์ฐจํ๋ ๋๊ท๋ชจ ๋ชจ๋ธ(24 ์ํฐํฐ + 15 enum)๋ก 4-ํ๊น ๋น๋๋ฅผ ๋๋ฆฌ๊ณ , ์ฐ์ถ๋ฌผ์ ๋ชจ๋ธ์ด ์ ์ธํ ๊ฒ๊ณผ ์ด๋ฆ ๋จ์๋ก ๋์กฐํ๋ค. ๊ธฐ๋๊ฐ์ ํ์ฑํ ๋ชจ๋ธ์์ ๋์ถ๋๋ฏ๋ก ์ํฐํฐ๋ฅผ ๋๋ ค๋ ํ ์คํธ์ ์ซ์๋ฅผ ๊ณ ์น ํ์๊ฐ ์๊ณ , ์ฐ์ถ๋ฌผ์ด ๋น ์ง ์ ์ธ์ ๊ฐ์ ์ฐจ์ด๊ฐ ์๋๋ผ ์ด๋ฆ์ผ๋ก ๋ณด๊ณ ๋๋ค.
๋ชจ๋ธ์ ๋ฆฌํฌ์ ์ฒดํฌ์ธ๋ผ ์์ด CIยท์ ๊ท ํด๋ก ์์๋ ๊ทธ๋๋ก ๋๋ค.
MDDBOOSTER_ACCEPTANCE_MODEL์ ๋ค๋ฅธ.m3l.md๊ฒฝ๋ก๋ฅผ ๋ฃ์ผ๋ฉด ๊ฐ์ ๊ฒ์ดํธ๋ฅผ ๊ทธ ๋ชจ๋ธ๋ก ๋๋ฆฐ๋ค(๋ฆด๋ฆฌ์ค ์ ๋๊ท๋ชจ ๋ชจ๋ธ ์ ๊ฒ์ฉ). ๊ฒฝ๋ก๊ฐ ์๋ชป๋๋ฉด ๊ฑด๋๋ฐ์ง ์๊ณ ์คํจํ๋ค.
- m3l โ Rust ๊ธฐ๋ฐ M3L ํ์ (NuGet
M3L.Native) - iyu-framework-v5 โ ๋ฐํ์ (EF Core + OData + HotChocolate GraphQL)