A Reaction (sometimes called a policy) listens to internal, error, or temporal events; evaluates a guard expression; and issues a command in response. Reactions are asynchronous and cross-aggregate by nature — they are how aggregates coordinate behavior between themselves without sharing a transaction. Where an invariant is a synchronous predicate guarded inside one aggregate, a reaction is the metamodel’s “when X happens, do Y” mechanism that crosses aggregate boundaries cleanly.

Anatomy of a reaction

A reaction has four parts:

  • Name — anchored in the ubiquitous language (e.g. notifyOnStockout, reArmDriverDeadline).
  • Trigger — one or more event references: an internal event raised from a state transition, an error event from a failed operation, or a temporal event anchored to a time reference.
  • Guard — a boolean expression that must hold for the reaction to fire. Optional; absent means “always fire on the trigger.”
  • Effect — exactly one command issued when the guard holds. The command targets its own aggregate, where it goes through the normal operation path: preconditions, invariants, postconditions, emitted events.

Reactions never mutate state directly. They observe and decide; the command they issue is what changes state, in its own transaction, with all of that aggregate’s rules applied.

What can trigger a reaction

A reaction’s trigger is always an event. The metamodel admits three event flavors as triggers, each with a distinct cause:

  • Internal event — raised synchronously from an entity state transition inside the same bounded context. Most reactions fire on internal events; this is the default coordination path between aggregates.
  • Error event — raised by an operation that failed. Reactions on error events express recovery and compensation policies — for example, automatically retrying a downstream call or rolling back a related state.
  • Temporal event — fires when a time condition is met (relative offset, absolute moment, or recurring schedule). Reactions on temporal events express deadlines, time-based escalations, and the recomputation policies that re-arm relative temporal events when their anchor changes.

External events from upstream contexts do not trigger reactions directly. They trigger commands at the bounded-context boundary; those commands run their operations, which raise internal events, which then trigger local reactions. Keeping that indirection explicit keeps the local model in charge of its own coordination.

Examples

reaction notifyOnStockout {
  satisfies [REQ-ORD-004]

  triggered by: InventoryDepleted
  guard: inventory.product in activePromotions
  effect: command CancelPromotion(inventory.product)
}

reaction reArmDriverDeadline {
  satisfies [REQ-RIDE-021]

  triggered by: RideETAUpdated         // recomputation policy
  effect: command RescheduleDriverDeadline(ride.id, ride.estimatedDriverArrivalTime)
}

reaction retryPaymentOnTransientError {
  satisfies [REQ-PAY-018]

  triggered by: PaymentGatewayTimeout  // error event
  guard: attempt.count < 3
  effect: command RetryPayment(payment.id, attempt.count + 1)
}

The first reaction coordinates two aggregates (Inventory and Promotion) when one observes a fact the other cares about. The second is the recomputation policy referenced by temporal events: whenever the ETA changes, the DriverArrivalDeadlineElapsed temporal event must be re-armed against the new anchor. The third illustrates an error-event reaction expressing a bounded retry policy.

Differentiators with related concepts

A reaction looks superficially like other coordination constructs. The distinctions are sharp:

  • From an invariant. An invariant is a predicate over state, checked synchronously within one aggregate’s transaction. A reaction is a reactive rule, triggered asynchronously by an event after a transaction has committed.
  • From a precondition. A precondition gates the operation it belongs to — it is inside the operation’s contract. A reaction reacts to the outcome of an operation it does not own — it is downstream of the event that operation emitted.
  • From a reconciliation. A reconciliation is a coordinated mechanism that maintains exactly one agreement; it has a defined trigger, detection strategy, compensation, and escalation chain. A reaction is a standalone reactive rule with independent domain meaning. A set of cooperating reactions that exists only to maintain a single cross-aggregate truth is better named explicitly as an agreement plus its reconciliation — leaving the coordination implicit hides intent.