DDDesigner

Long-running business processes

Sagas in DDD: coordinate work across Aggregates

A Saga coordinates a business process that spans multiple Aggregates or Bounded Contexts and cannot be completed in one transaction. It records progress, sends commands, waits for events, and handles failure. A Saga is a business-process model, not merely a technical queue consumer.

When a Saga is appropriate

Use a Saga when the process has multiple durable steps, crosses consistency boundaries, or must wait for external outcomes. A simple event reaction with no process state is usually a Policy instead.

  • Several commands must happen in sequence
  • The process waits for asynchronous events
  • Timeout or retry behavior matters
  • Completed work may need compensation

Orchestration and choreography

An orchestrated Saga explicitly chooses the next command and keeps process state. Choreography distributes reactions among participants. Orchestration is easier to visualize for complex flows; choreography can reduce central control for simple, stable interactions.

Compensation is a business action

A compensation is not a database rollback. RefundPayment or ReleaseStock is a new business operation with its own rules, possible failures, and audit trail.

Saga, Policy, or simple event handler?

A Policy is usually a stateless reaction: when PaymentReceived occurs, send an email. A Saga is appropriate when the system must remember where a process is, wait for a later event, enforce a timeout, retry a step, or choose the next action based on several outcomes.

Use a Saga when the process has a business lifecycle of its own. If stakeholders ask whether an order fulfillment process is waiting, blocked, compensated, or complete, that process deserves an explicit model and state.

State, correlation, and idempotency

A Saga needs a correlation identifier that ties commands and events to one business-process instance. It should record completed steps, pending work, deadlines, and relevant decisions. This state must survive restarts and should be observable when an instance is waiting or has failed.

Messages can be duplicated or delivered out of order. Saga handlers therefore need idempotency rules: repeated confirmation must not perform a second capture, and an old event must not move a completed process backwards. Correlation should use stable business identifiers rather than an in-memory object reference.

Orchestration and choreography in practice

In orchestration, a coordinator explicitly sends the next command and keeps the process state. This is often easier to explain, test, monitor, and change when there are many branches. In choreography, each participant reacts to events and emits its own events without a central coordinator. It can work well for a small number of stable reactions, but the full process may become difficult to discover.

Choose based on the complexity and ownership of the process, not on a slogan. If the business needs one place to see deadlines, compensation, and completion criteria, orchestration is usually the clearer model.

Failures, retries, and compensation

A retry is suitable for a transient technical failure, such as a temporary network timeout. A compensation is a new business action that reverses or offsets an already accepted fact, such as RefundPayment or ReleaseReservedStock. It has its own authorization, invariants, failure modes, and audit trail.

Not every failure can be compensated. Some actions are irreversible, and some require human review. Model those states explicitly instead of hiding them behind a generic “rollback”. A Saga can emit a process-failed event while preserving the facts that already happened.

Saga review checklist

A Saga is ready for implementation when its starting event, state transitions, commands, expected events, timeouts, and failure paths are explicit.

  • What business process does the Saga own?
  • What starts and completes it?
  • How is each instance correlated?
  • Which steps are retryable?
  • Which failures require compensation or a person?
  • How are duplicate and late messages handled?
  • Can operators see a stuck instance?

DDDesigner

Example: order fulfillment across contexts

After OrderPlaced, the Fulfillment Saga asks Inventory to reserve stock and Payments to capture funds. It stores the order and process identifiers, waits for both outcomes, and applies idempotency when messages are repeated. If payment fails after reservation, it sends ReleaseStock and records compensation. If a provider remains unavailable beyond the deadline, the Saga moves to a manual-review state rather than pretending that the transaction rolled back. When inventory, payment, and shipping succeed, it sends ScheduleDelivery and publishes FulfillmentCompleted.

Start free