Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

216 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

mdd-booster

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 ๋Ÿฐํƒ€์ž„)

์‚ฌ์šฉ๋ฒ•

mdd.json (์†Œ๋น„ ํ”„๋กœ์ ํŠธ์— ๋ฐฐ์น˜)

{
  "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 โ€” ์•„๋ž˜).

์™ธ๋ž˜ ํ‚ค ์ธ๋ฑ์Šค (emitForeignKeyIndexes)

์–ด๋А ์—”์ง„๋„ ์™ธ๋ž˜ ํ‚ค๋ฅผ ์ž๋™ ์ธ๋ฑ์‹ฑํ•˜์ง€ ์•Š๋Š”๋‹ค. 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)์ด๊ณ , ๊ณ ์น˜๋ ค๋ฉด ํŒ์ •๊ธฐ๊ฐ€ ๋ฐฉ์–ธ์„ ์•Œ์•„์•ผ ํ•˜๋ฏ€๋กœ ์œ ์ง€ํ–ˆ๋‹ค.

ํƒ€๊นƒ๋ณ„ ์—”ํ‹ฐํ‹ฐ ๋ถ€๋ถ„์ง‘ํ•ฉ (includeEntities / excludeEntities)

ํ•œ ๋ชจ๋ธ ์ •๋ณธ์„ ์—ฌ๋Ÿฌ ์„œ๋ฒ„๊ฐ€ ๊ณต์œ ํ•  ๋•Œ, ๊ฐ ์„œ๋ฒ„๊ฐ€ ๋…ธ์ถœํ•  ์—”ํ‹ฐํ‹ฐ๋ฅผ ํƒ€๊นƒ๋ณ„๋กœ ์ขํžŒ๋‹ค. ํ‘œ๋ฉด ํƒ€๊นƒ(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 ์ด ์˜ํ–ฅํ•˜๋Š” ์‚ฐ์ถœ๋ฌผ

@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 ๋งคํ•‘์€ ๋…ธ์ถœ๊ณผ ๋ฌด๊ด€ํ•˜๋‹ค

PostgreSQL ๋ฐฉ์–ธ (dialect: "postgres")

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 ์ด ์žˆ๋Š” ๋””๋ ‰ํ„ฐ๋ฆฌ โ€” ์ƒ๋žตํ•˜๋ฉด ํ˜„์žฌ ๋””๋ ‰ํ„ฐ๋ฆฌ

M3L ๊ธฐ๋Šฅ ์ง€์› (ํ˜„์žฌ)

๊ธฐ๋Šฅ ์ง€์›
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# ๊ฒ€์ฆ ์–ดํŠธ๋ฆฌ๋ทฐํŠธ + ์ดˆ๊ธฐํ™”์ž (์•„๋ž˜) โœ…

์„ ์–ธ๋œ ์ œ์•ฝ โ†’ C# ์—”ํ‹ฐํ‹ฐ (Model ํƒ€๊นƒ)

๋ชจ๋ธ์ด ์„ ์–ธํ•œ ์ œ์•ฝ์€ 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() ๊ฐ™์€ ์„œ๋ฒ„ ์ธก ๊ธฐ๋ณธ๊ฐ’์ด ์ฝ”๋“œ๋กœ ์ƒˆ์ง€ ์•Š๋Š”๋‹ค. ๋„ ํ—ˆ์šฉ ํ•„๋“œ๋Š” ์„ ์–ธ๋œ ๊ธฐ๋ณธ๊ฐ’์ด ์žˆ์–ด๋„ ์ดˆ๊ธฐํ™”์ž๋ฅผ ๋ฐ›์ง€ ์•Š๋Š”๋‹ค โ€” ์„ ํƒ ์†์„ฑ์„ ์ฑ„์šฐ๋ฉด "๋ฏธ์„ค์ •"์˜ ์˜๋ฏธ๊ฐ€ ๋ฐ”๋€Œ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

โš ๏ธ ์†Œ๋น„ ํ”„๋กœ์ ํŠธ ๊ณ„์•ฝ (TypeScript ํƒ€๊นƒ)

์ƒ์„ฑ๋œ *Form_gen.tsx๋Š” ์†Œ๋น„ ํ”„๋กœ์ ํŠธ๊ฐ€ ์ œ๊ณตํ•ด์•ผ ํ•˜๋Š” ๋ชจ๋“ˆ์„ importํ•œ๋‹ค. ์ƒ์„ฑ๋˜๋Š” ๋ชจ๋“  import๋Š” ์†Œ๋น„์ž์— ๋Œ€ํ•œ ์š”๊ตฌ์กฐ๊ฑด์ด๋‹ค โ€” ์•„๋ž˜๋ฅผ ๊ฐ–์ถ”์ง€ ์•Š์œผ๋ฉด ์ƒ์„ฑ ์ฝ”๋“œ๊ฐ€ ์†Œ๋น„์•ฑ ๋นŒ๋“œ์—์„œ ์ปดํŒŒ์ผ๋˜์ง€ ์•Š๋Š”๋‹ค. (mdd-booster ์ž์ฒด ํ…Œ์ŠคํŠธ๋Š” ์ƒ์„ฑ๋œ TS๋ฅผ ์ปดํŒŒ์ผํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ, ์ด ๊ณ„์•ฝ์˜ ์œ„๋ฐ˜์€ ์†Œ๋น„์•ฑ ๋นŒ๋“œ์—์„œ๋งŒ ๋“œ๋Ÿฌ๋‚œ๋‹ค.)

../components/ui โ€” ๋ฐฐ๋Ÿด์ด exportํ•ด์•ผ ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ

์ปดํฌ๋„ŒํŠธ ์–ธ์ œ 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๋กœ ๋นŒ๋“œ๊ฐ€ ๊นจ์ง„๋‹ค.

../lib/select-options โ€” ํ—ฌํผ

export function enumToOptions(labels: Record<string, string>): /* USelect์˜ options ํƒ€์ž… */

์ƒ์„ฑ๊ธฐ๊ฐ€ ์š”๊ตฌํ•˜๋Š” ๊ฒƒ์€ ์ธ์ž ํ•˜๋‚˜์™€ ๊ทธ ๊ฒฐ๊ณผ๊ฐ€ USelect์˜ options์— ๊ทธ๋Œ€๋กœ ๋“ค์–ด๊ฐ„๋‹ค๋Š” ๊ฒƒ๋ฟ์ด๋‹ค (๋ฐ˜ํ™˜ ํƒ€์ž…์€ ์†Œ๋น„์ž๊ฐ€ ์ •ํ•œ๋‹ค). ๊ฐ’์„ ์ขํž ๋•Œ๋„ ์ธ์ž๋ฅผ ๋Š˜๋ฆฌ์ง€ ์•Š๊ณ  ์ขํ˜€์ง„ ๋ผ๋ฒจ๋งต์„ ๋”ฐ๋กœ ์ƒ์„ฑํ•ด ๋„˜๊ธฐ๋ฏ€๋กœ(์•„๋ž˜ @system ์ ˆ), ์ด ์‹œ๊ทธ๋‹ˆ์ฒ˜๋Š” ์•ˆ์ •์ ์ด๋‹ค.

@iyulab/enterprise โ€” ๋ ˆ์ด์•„์›ƒ

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 ์ง๋ ฌํ™” ์ปจํŠธ๋กค์ด ๋ฐ›๋Š” ํ˜•์‹ ์™•๋ณต
date DateOnly "2026-07-28" type="date" = YYYY-MM-DD โœ…
timestampยทdatetime DateTimeOffset "2026-07-28T14:30:00+09:00" datetime-local = YYYY-MM-DDTHH:mm[:ss] โ€” ์˜คํ”„์…‹ ๋ถˆ๊ฐ€ โŒ
time TimeOnly "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๋Š” ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค.)

์ƒ์„ฑ๊ธฐ ํ•ด์„ attribute (TypeScript ํƒ€๊นƒ)

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 ๊ฐ’ ์ƒ์„ฑ ํผ ์„ ํƒ์ง€์—์„œ ์ œ์™ธ (์•„๋ž˜)

enum ๊ฐ’์˜ @system โ€” ํ‘œ์‹œ ๋ผ๋ฒจ๊ณผ ์ž…๋ ฅ ์„ ํƒ์ง€์˜ ๋ถ„๋ฆฌ

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.Native 0.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 ์ ‘๋ฏธ ํด๋”๋Š” ์žฌ์ƒ์„ฑ ์‹œ ์™„์ „ํžˆ ๋ฎ์–ด์จ์ง€๋ฏ€๋กœ ์ ˆ๋Œ€ ์ˆ˜๋™ ํŽธ์ง‘ํ•˜์ง€ ๋ง ๊ฒƒ.

์˜๋ฏธ ๋ถ„์„ (SemanticAnalyzer)

๋นŒ๋“œ ์ „์— 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)

About

Model-driven code generator: one Markdown-based M3L data model becomes SQL schema files, EF Core entities and DbContext, and OData/GraphQL registration code.

Topics

Resources

Security policy

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages