Skip to content

Actions, events, and rules

Behavior is modeled as intent, outcome, and constraint. Commands request change, mutations describe its effect, events record what happened, and rules determine when it is allowed.

The product-facing action vocabulary has three kinds:

  • mutates: a user or system requests a state change;
  • reads: an actor asks a question;
  • triggers: behavior starts from an event or timer rather than a person.

A mutating action materializes as a command with typed parameters, allowed actors, row scope, mutations, emitted events, and possible domain errors.

Prefer product language:

ConfirmAppointment
CancelAppointment
RecordPaymentAuthorization

UpdateAppointment hides intent and makes permissions, rules, and tests harder to express.

Mutations connect command inputs or declared values to real object fields. They may set a parameter value, use a constant, copy existing state, compute a result, stamp the current time, generate an identifier, or modify a collection.

Every mutation target must resolve. A required creation field must have an explicit source. The generator must not guess a mapping from similarly named parameters when the result would be ambiguous.

An event is a past-tense domain fact emitted after a successful change:

  • AppointmentConfirmed
  • PaymentAuthorizationFailed
  • MemberInvited

Events carry enough data for observers and reactions to act without reading hidden request state. They can notify actors, trigger cross-object reactions, or feed audit and integration behavior.

A reaction connects an event to another command. It can map event fields to command parameters and, for a cascade, declare how affected rows are matched.

A timer triggers a command from a schedule or time condition. Model the business meaning—such as expiring a reservation—rather than only the scheduler expression.

Choose synchronous dispatch only when the initiating request needs the result and can tolerate the dependency. Async dispatch needs idempotency, retry policy, observability, and a defined failure outcome.

Rules express constraints, calculations, and preconditions. Bind them to the relevant action or lifecycle transition.

CancelAppointment is rejected when startsAt < now + 24 hours.
ConfirmAppointment is rejected when the slot is already occupied.

A rule should point to a typed domain error with product-facing language. “Validation failed” is not enough for a user or an operator.

Use a state machine when an object’s status has a closed lifecycle. Declare states, allowed transitions, triggering actions, and emitted events. This lets backend validation, UI action availability, and executable scenarios derive from one source.

Do not rely on the frontend to hide invalid actions. The generated backend must enforce the rule at the authoritative write boundary.

Changes inside one aggregate should complete atomically. Cross-aggregate reactions and external calls require an explicit consistency strategy; do not imply a distributed transaction that the runtime cannot provide.

Next, model the read side with Queries and computed values.