Commands

A command is a write: it mutates exactly one aggregate. Validate the caller's inputs, apply the change, persist the row — that is the whole shape of a command. It names the aggregate it acts on and the operation it performs (create, update, or delete), and everything else on it either shapes the inputs or constrains the write.

Here is the simplest useful command — a public create that adds a product to the Chowk catalog:

sub-domains/catalog/commands/product.vishwakarma
command CreateProduct {
  aggregate  Product
  operation  create
  visibility public
  doc        "Add a product to the catalog (born DRAFT)."
  systems    [CatalogSystem]

  set status = expr "'DRAFT'"

  sku   string required specs [Sku]
  name  string required
  price Money  required
}

The fields at the bottom (sku, name, price) are the command's input — the wire message a client sends. required and specs [Sku] validate that input before any row is written, so a malformed SKU never reaches the database.

set — server-owned fields

Notice that status is not an input field. It is filled by a set clause:

set status = expr "'DRAFT'"

A set writes a field from a server-side expression instead of from the client. Here it guarantees every product is born DRAFT — the client cannot choose the initial status, because it never supplies it. set is how you keep control of state the caller has no business setting: initial lifecycle status, derived totals, server timestamps.

An expression can read the command's own inputs, too. Chowk's ReserveStock moves a count from available to reserved using the incoming quantity:

sub-domains/inventory/commands/inventory.vishwakarma
command ReserveStock {
  aggregate  StockLevel
  operation  update
  visibility private

  set available = expr "available - req.quantity"
  set reserved  = expr "reserved + req.quantity"

  id       uuid  required
  quantity int32 required
}

guard — a per-row precondition

A command can refuse to run based on the current state of the row it is about to change. That is a guard: a boolean checked against this (the row) before the write lands. Chowk's MarkOrderPaid only pays an order that is still pending:

sub-domains/ordering/commands/order.vishwakarma
command MarkOrderPaid {
  aggregate  Order
  operation  update
  visibility public
  doc        "Mark an order paid (PENDING -> PAID). Guarded, so a repeat is a no-op."

  guard "status == 'PENDING'"
  set   status = expr "'PAID'"

  id uuid required
}

The guard "status == 'PENDING'" makes the operation idempotent by construction: pay a pending order and it moves to PAID; send the same request again and the guard fails, so the second attempt is a safe no-op rather than a double-charge. A guard is the write-side equivalent of a state-machine transition rule — it encodes "this change is only valid from these states."

guard versus policy. A guard asks "is this write allowed given the row's state?" (evaluated over this). A policy asks "is this caller allowed to run this operation at all?" (evaluated over caller.*). They are different questions and belong in different clauses — see Policies.

emits — announcing a fact

A command can announce a domain event when it succeeds. PlaceOrder declares emits OrderPlaced, so the moment an order is opened, that fact is published for anything that cares to react:

sub-domains/ordering/commands/order.vishwakarma
command PlaceOrder {
  aggregate  Order
  operation  create
  visibility public
  doc        "Open an order (born PENDING) and announce it."

  emits OrderPlaced

  set status = expr "'PENDING'"

  customer_id      uuid    required
  total            Money   required
  shipping_address Address required
}

The command does not know or care who listens — it just states that an order was placed. A subscription in another sub-domain picks it up. (Not every event needs an explicit emits: a lifecycle event with trigger on_update fires automatically on the matching state change. See the Domain layer's Domain events page.)

visibility — public and private commands

Every command declares its visibility:

  • public — exposed as an HTTP endpoint a client can call. PlaceOrder, MarkOrderPaid, and CreateProduct are all public.
  • private — reachable only from inside the system: dispatched by a subscription or invoked as a saga step, never by an outside client. Chowk's StartFulfillment is private because it is driven by the OrderPlaced subscription, and ReserveStock is private because the checkout saga drives it.

Marking an internal command private keeps your public API honest: the surface a client sees is exactly the set of operations they are meant to invoke, and orchestration-only commands stay out of it.

Don't declare caller-sourced fields

One input rule is easy to get wrong: never declare a field the caller is already identified by. The tenant, the acting user, the session — all of these are resolved from the authenticated request at the edge and are in scope as caller.* for guards and policies. Declaring tenant_id or caller_user_id as a command input would create a wire contract saying "the client must send this" — but the client must not; the server sources it independently. Model only the fields that are genuinely the caller's to provide.