Skip to content

Commands

Commands are input definitions — imperative intents. A command declares its properties, its authorization, its validation rules, and what events it produces.

command <Name>
<property> <Type>[?]
...
[authorize <PolicyName> [<PolicyName>]*]
[validate
<rule> message "<message>"
...]
[validate csharp
```
<C# yielding ValidationError>
```]
[produces ...] ← declarative — repeatable
[handler ← imperative fallback — instead of produces
file <Path>
| csharp
```
<C# returning the events to append>
```]

Declarative validation covers the common cases without code:

RuleExample
not emptyname not empty
max <n>reason max 500
min <n>quantity min 1
> <value>quantity > 0
>= <value>discountPct >= 0
< <value>dateOfBirth < today
length == <n>currency length == 3
matches <regex>email matches email
matches "<pattern>"invoiceNumber matches "^INV-[0-9]{6}$"
all > <value> (on collection)lines.quantity all > 0

Every rule carries a message shown when it fails:

validate
invoiceNumber not empty message "Invoice number is required"
invoiceNumber matches "^INV-[0-9]{6}$" message "Invoice number must match INV-000000"
dueDate > today message "Due date must be in the future"

Cross-field or complex rules drop into C#:

validate csharp
```
var total = Lines.Sum(l => l.Quantity * l.UnitPrice);
if (total > 1_000_000 && PaymentTerms == PaymentTerms.Immediate)
yield ValidationError("Invoices over 1,000,000 cannot require immediate payment");
```

Both validate and validate csharp can coexist on the same command.

authorize <PolicyName> [<PolicyName>]*

Multiple policies must all pass. Continuation lines list additional policies; or makes policies alternatives:

authorize CanManageInvoice
IsAdultCustomer
authorize IsAccountant
or IsCustomerSelf

Policies are declared at the top of the file — see Policies.

Declares what events a command emits. Supports single, multiple, and conditional forms. For a fully imperative implementation, use a handler instead.

produces InvoiceRegistered
invoiceId = invoiceId // from command property
registeredAt = $context.occurred // from event context
registeredBy = $context.identity.id // caller identity
source = $env.SERVICE_NAME // environment variable
status = "draft" // string constant
lineCount = 0 // numeric constant
SourceSyntaxDescription
Command property= <propertyName>Direct copy from command
Event context= $context.occurredTimestamp of the event
Caller identity= $context.identity.idSubject from auth token
Environment= $env.<VAR_NAME>Environment variable
String constant= "value"Literal string
Numeric constant= 0Literal number
Expression= lines.sum(l => l.quantity * l.unitPrice)Computed value

Repeat produces for each event; all are emitted:

produces InvoiceLineItemAdded
invoiceId = invoiceId
addedAt = $context.occurred
produces InvoiceRunningTotalUpdated
invoiceId = invoiceId
adjustment = lines.sum(l => l.quantity * l.unitPrice * (1 - l.discountPct / 100))

produces when <condition> emits the indented event only when the condition holds. Conditions compare command properties, constants, and environment variables with ==, !=, >, >=, <, <=, combined with and/or:

produces when isProForma == true
ProFormaInvoiceIssued
invoiceId = invoiceId
issuedAt = $context.occurred
produces when paymentTerms == "net30" or paymentTerms == "net60"
DeferredPaymentInvoiceRegistered
invoiceId = invoiceId
paymentTerms = paymentTerms
produces when $env.WELCOME_EMAILS_ENABLED == "true"
CustomerWelcomeEmailRequested
customerId = customerId

Multiple produces when blocks form mutually exclusive or overlapping branches — each condition is evaluated independently.

Declares a fully imperative implementation of the command, in C#, as either an inline block or a reference to an external file. Use it when the declarative produces forms cannot express the logic — batch processing, imperative branching, or anything that needs more than property mappings and conditions.

Delegating to a file:

handler
file Commands/ProcessInvoiceBatchHandler.cs

Inline C#:

handler
csharp
```
var events = new List<object>();
events.Add(new InvoiceBatchProcessingStarted(
BatchId: BatchId,
StartedAt: DateTimeOffset.UtcNow
));
foreach (var invoiceId in InvoiceIds)
events.Add(new InvoiceSent(invoiceId, DateTimeOffset.UtcNow, context.Identity.Id, null));
return events;
```

A command uses either produces blocks or a handler — not both. Keep handler logic small; anything substantial belongs in a file reference where it can be tested on its own.