The idea: one business transaction, two ways to drive it
An order touches four services, each with its own database. Order saves the order, Payment charges 100, Inventory reserves one item and Shipping books a courier. No single transaction covers all four, so the order is a saga: a chain of local transactions, each undone by a compensating transaction if a later one fails. See Saga vs Two-Phase Commit for why a saga and not 2PC.
A saga can be driven in two ways. The page runs the same order with the same failure through both at once, on one clock where every hop takes one tick. On the left is orchestration. On the right is choreography. Under each side, a table shows where the flow is written down.
Orchestration: commands, replies, one saga definition
An orchestrator (here the Order Saga, often a class inside the Order service or a workflow engine such as Temporal or Camunda) holds the saga definition: the list of steps, each with its command and its compensation. It sends one command, waits for the reply, logs it, and sends the next one. Participants do not know about each other. They only answer commands.
The whole flow, including the failure path, is in one table. Reading the saga log tells you exactly where any order is. The price is a central component that every new step has to go through. It can also grow into a "god service" that holds business logic that belongs to the participants.
Choreography: events, subscriptions, no one in charge
With choreography there is no coordinator. Each service listens to some events and publishes its own: OrderCreated → Payment charges → PaymentCharged → Inventory reserves → StockReserved → Shipping books → CourierBooked → Order confirms. The broker (Kafka, RabbitMQ, a cloud event bus) only stores and delivers events.
Services stay loosely coupled: Payment doesn't know who reacts to PaymentCharged. The flow, though, is not written anywhere. It exists only as the sum of the subscription lists in the "who reacts to what" table, spread over every service's code. To answer "what happens after a payment?" you have to read all of them.
| orchestration | choreography | |
|---|---|---|
| messages | commands and replies | events |
| who knows the flow | the orchestrator (one table) | nobody; it is spread over the subscriptions |
| where an order is | in the saga log | must be pieced together from every service |
| coupling | orchestrator → each participant | each service → the events it listens to |
| messages per happy order (page) | 8 | 4 events |
Failures and compensation on both sides
When Inventory is out of stock, both sides end with a refund and a cancelled order at the same tick (Demo 2: Inventory out of stock). On the left the orchestrator reads StockFailed, looks up the compensations of the steps already done and sends RefundPayment, then RejectOrder. On the right the same result needs two more subscriptions: Payment listens to StockFailed and refunds, and Order listens to PaymentRefunded and cancels. When Payment declines, there is nothing to undo (Demo 3).
A service is down: a timeout vs a stalled order
Shipping is down for the whole run (Demo 4: Shipping is down). The orchestrator is waiting for a reply, so its silence is noticed. After 3 ticks it retries, after 3 more it gives up, releases the stock, refunds the payment and rejects the order. The order ends in a known state, and the saga log says why.
In choreography the broker holds StockReserved until Shipping comes back. No service is waiting for anything, so nothing happens: the order stays PENDING, with the customer charged and the item reserved, maybe for hours. Holding the event is correct (no event is lost), but nobody is accountable for the order. Teams add a timeout watcher (a job that looks for orders stuck in PENDING), distributed tracing and dashboards. Some add a small orchestrator just for the deadline.
Changing the flow: coupling and cycles
The business wants a fraud check before payment (Demo 5: add a fraud check before payment). Orchestration changes the saga definition and adds the Fraud service: 2 places. Choreography adds Fraud (it listens to OrderCreated), changes Payment (it must now wait for FraudCleared instead of OrderCreated) and changes Order (it must cancel on FraudRejected): 3 services, often owned by 3 teams, deployed in the right order. Adding a step that only reacts (send an e-mail on CourierBooked) is where choreography shines: one new subscriber, no change anywhere else.
With many services, event chains can also form cycles. A service reacts to an event that, a few hops later, causes the event it started from. Nothing in the code shows the loop. It only shows up in production, as a storm of events.
Reliable messaging underneath
Both sides assume a service never commits its local change without also sending its reply or event, and never sends one without the change. That needs the transactional outbox. Delivery is then at-least-once, so every handler must be idempotent: an inbox table, or a key per saga and step. The page draws each hop once and leaves this out. See also How Kafka Moves a Message for the broker side.
When to use which
| prefer | when |
|---|---|
| orchestration | many steps, a strict order, failure paths that matter, deadlines, someone has to answer "where is order 1?" |
| choreography | few steps, independent reactions (notify, update a read model, award points), teams that must not depend on a central service |
| both | common in practice: each bounded context orchestrates its own saga, and contexts talk through events |
What the page leaves out
The outbox and the idempotent handlers that every hop needs; retries inside choreography; the orchestrator's own crash and recovery from its log (see Saga vs Two-Phase Commit); parallel steps; several orders competing for the same stock; semantic locks (a PENDING state that other sagas respect); and workflow engines' durable timers and versioned definitions.