Reading the DSL: one line, every layer
The whole grammar is six habits, so a developer writes it by hand and a reviewer reads it at a glance. New features keep these shapes - there is no new grammar per feature.
| Principle | Example | |
|---|---|---|
| 1 | One line is one field - it decides storage, validation, search and exposure | name: string(30)! @search(like) |
| 2 | Common things are symbols beside the type | (30) length, ! required, (10.2) precision |
| 3 | Entity-level rules go on the one meta: line | meta: entity @root @editable @expose(20) @access(read=public) |
| 4 | Defaults - write only what differs | @expose(20) alone gives create, read, search, paging and REST |
| 5 | YAML sections are the concepts | entity, dto, enumeration, states, metrics, port |
| 6 | The description follows -- - the model documents itself | -- unit of sale (Large, Male Puppy ...) |
A line, taken apart
unitCost: money(10.2)! @min(0.01, 'price must be positive') @terms(price, 가격)
| Piece | Means |
|---|---|
unitCost | the name |
money(10.2) | the type and its precision (10 digits, 2 after the point) |
! | required |
@min(0.01, 'price must be positive') | a rule and its message |
@terms(price, 가격) | what people call this value (synonyms BI and AI tools look for) |
Business rules keep the same shapes
| Feature | Shape | Example (pet store) |
|---|---|---|
| A lifecycle step | command: FROM -> TO | ship: PLACED -> SHIPPED |
| States a row may be deleted in | one more line in states: | delete: CANCELLED |
| A business measure | name: expression @terms -- description | revenue: sum(PurchaseOrder.total) where status != CANCELLED |
| Access and ownership | marks on the meta: line | @owner(customerId) @access(read=owner, create=user, cancel=owner) |
| Another context's type or event | context:Name | catalog:Product, on: [shop:OrderPlaced] |
When the DSL is wrong the generator says, in one sentence, what is wrong and how to fix it - for
example states.status names SHIPED, which OrderStatus does not have (it has PLACED, SHIPPED, CANCELLED). A person or an AI reads that and fixes it right away.
The top-level sections
| Section | What | Reference |
|---|---|---|
entity: | entities and value objects; inside an entity fields:, states:, commands: | Entities |
dto: | DTOs of your own | DTOs |
enumeration: | enumerations with codes and labels | Enumerations |
repository: | query methods added to a repository | Repositories |
port: | calls to other systems (HTTP, SQL, gRPC) | Ports |
domainService:, processManager: | operations of no single aggregate; reactions to events (DDD bundles) | Derived values and events |
metrics: | business measures for the Ossie export | Metrics |
Files: src/main/resources/blueprint/<domain>/model/*.yml - split them by kind (entity.yml,
enum.yml, dto.yml, ...) as you like; settings go in blueprint/<domain>/env.yml.
A description with : in it (-- ordered by: the member) breaks the file, and # cuts the rest
of the line off as a comment. Write -- ordered by the member, or quote the whole line. A file that
does not parse is reported as * LOAD ERROR: <file> and, on the rule engine, stops the build.