Saga: choreography vs orchestration
Two topologies to execute a long-running multi-service saga. Choreography distributes control through the event network; orchestration concentrates it in an explicit coordinator. The choice drives 80% of an EDI flow's observability.
Problem
A typical B2B order crosses 5-10 systems before being invoiced: EDI hub ingests ORDERS, ERP reserves stock, WMS schedules shipment, carrier receives ASN, invoicing generates INVOIC, accounting reconciles REMADV. These steps do not fit in an XA transaction (see 2PC): total duration spans hours or days, resources are heterogeneous (Oracle, REST APIs, SAP, external partners), each participant has its own failure cycle. Which execution topology to pick so that, if step 7 fails, steps 1-6 can compensate?
Forces
- Flow visibility: ops must answer "where is order 12345?" in seconds.
- Coupling: choreography avoids a single knowledge point but increases temporal coupling through events; orchestration centralises but creates a hard dependency on the coordinator.
- Evolvability: adding a step must be local (change a service) rather than invasive (modify the BPMN coordinator).
- Fault tolerance: each step must resume from the failure point without replaying already done steps.
- Compensation: each step must know how to undo its business effect (Garcia-Molina called these compensating transactions).
Solution
Choreography: each service consumes events and publishes others,
without a central coordinator. The EDI hub publishes OrderReceived,
the ERP reacts and publishes StockReserved, the WMS reacts and
publishes ShipmentScheduled, etc. Compensations are triggered by
their own events (OrderCancelled, StockReleased). Easy
to start, becomes chaotic beyond 6-7 services.
Orchestration: a dedicated service (orchestrator) knows the full workflow, calls each service through commands and receives replies, decides next step or compensation. Modern implementations: Temporal, Camunda Zeebe, AWS Step Functions, Azure Durable Functions. Workflow state is persisted and survives crashes.
Structure
CHOREOGRAPHY (decentralised pub/sub)
EDI Hub ──OrderReceived──► [Kafka topic]
│
├──► ERP (reserves) ──StockReserved──► [Kafka]
│ │
├──► CRM (notif) │
│ │
└──► WMS (schedules) ◄───────────────────┘
│
└──ShipmentScheduled──► ...
ORCHESTRATION (centralised)
┌──── Orchestrator ────┐
│ state: state-machine │
│ persistence: DB │
└──────────┬────────────┘
│
┌────────────────┼─────────────────┐
▼ ▼ ▼
call ERP call WMS call Carrier
reserve() schedule() book()
│ │ │
└─────► reply ◄──┴─► reply ◄───────┘
│
Next step? Compensate? EDI implementation
On a typical EDI flow, pick by complexity. Short saga (≤4 steps), stable participants, siloed teams: choreography via Kafka + outbox pattern (see Transactional Outbox). Orchestration code with Temporal:
// Orchestration with Temporal (TypeScript SDK)
export async function orderToInvoiceSaga(orderId: string): Promise<void> {
let stockReserved = false;
let shipmentBooked = false;
try {
await activities.reserveStock(orderId);
stockReserved = true;
await activities.bookShipment(orderId);
shipmentBooked = true;
// Wait up to 7 days for shipment confirmation
const shipped = await wf.condition(
() => shipmentSignaled === true,
'7d'
);
if (!shipped) throw new ShipmentTimeoutError(orderId);
await activities.issueInvoice(orderId);
await activities.sendInvoice(orderId);
} catch (err) {
// Compensations in reverse order
if (shipmentBooked) await activities.cancelShipment(orderId);
if (stockReserved) await activities.releaseStock(orderId);
await activities.notifyCustomer(orderId, 'CANCELLED');
throw err;
}
} Long saga (≥6 steps), business-critical workflow, strong auditability requirements: explicit orchestration. Pro: the workflow reads like sequential code, state is inspectable in a console (Temporal UI, Camunda Cockpit), compensations are visible. Con: strong dependency on the engine (lock-in).
Anti-patterns
- Mixing choreography and orchestration in the same bounded context — you lose choreography's visibility AND orchestration's simplicity.
- Hand-rolling an orchestrator in cron+jobs without state persistence — reinventing Temporal badly.
- No timeout per step in the orchestrator — a blocked step blocks the saga indefinitely.
- Non-idempotent compensations — partial crash during compensation creates permanent data corruption.
- "Distributed monolith" saga in choreography where each service listens to all events to decide — coupling is worse than an explicit orchestrator.
Related patterns
- Saga Choreography — the choreographic variant in detail.
- Saga Orchestration — the orchestrated variant in detail.
- Compensating Transactions — the common technical brick for both.
- Process Manager — the reference EIP pattern for orchestration.
- 2PC — the alternative you want to avoid in EDI.
Sources
- Garcia-Molina H., Salem K. — Sagas, ACM SIGMOD Conference 1987. The founding paper. cs.cornell.edu
- Richardson C. — Microservices Patterns, Manning 2018, ch. 4 ("Managing transactions with sagas"). The reference choreography/orchestration discussion.
- Newman S. — Building Microservices, 2nd ed. O'Reilly 2021, ch. 6 ("Workflow").
- Temporal Documentation — Workflows and Sagas. docs.temporal.io
- Camunda — Saga Pattern Implementation with Zeebe. camunda.com