Skip to content

Command

Syntax

command <Name> {
<fieldName>: <TypeName>
...
validate <expression>
else "<error message>"
...
}

Commands carry intent. Each command declares typed fields and optional validation constraints.

Fields

Fields use name: TypeName syntax. The type references a type declaration within the same context.

Validation

validate constraints define value-level rules checked at command construction time. The grammar accepts validate syntax today — the parser builds AST nodes for each constraint.

The code generator produces Smart Constructors from validate constraints: a private constructor paired with a create() factory returning Result<Command, ValidationError[]>. Invalid commands cannot be instantiated directly.

Multiple validate clauses are allowed. All are checked, and all failures are collected (unlike require guards, which short-circuit).

A command with no validate clauses has a public constructor.

Field Bindings

When emitting events from a decide clause, field bindings pass command data to event fields:

decide(Register, Unregistered) -> [Registered { userId: userId, email: email }]

Shorthand (field name matches command field):

decide(Register, Unregistered) -> [Registered { userId, email, name }]

When the binding name matches a field on the event, the value is inferred from the command’s same-named field.

Example

add-item.ddd
context Ordering {
type CartId = String
type ProductId = String
type Quantity = Integer
command AddItem {
cartId: CartId
productId: ProductId
quantity: Quantity
validate quantity > 0
else "Quantity must be positive"
validate quantity <= 99
else "Maximum 99 items per product"
}
event ItemAdded {
cartId: CartId
productId: ProductId
quantity: Quantity
}
decider Cart {
commands: AddItem
events: ItemAdded
state: Empty | HasItems
initial: Empty
terminal: HasItems
decide(AddItem, Empty) -> [ItemAdded]
evolve(Empty, ItemAdded) -> HasItems
}
}