Guide

Once you have a saga, someone has to drive it. There are two ways to do that. In orchestration, a central coordinator tells each service what to do and tracks the saga's state. In choreography, there is no coordinator. Each service listens for events and publishes its own.

Concept

An orchestrator is a state machine for one saga: "payment succeeded, call inventory next; inventory failed, run the refund."

The two styles

Orchestration. The orchestrator calls each participant in order, records each result, and decides the next step. It is one place to read the whole flow.

Choreography. Orders publishes OrderCreated. Payments hears it, charges, and publishes PaymentSucceeded. Inventory hears that, reserves stock, and publishes InventoryReserved or InventoryFailed. Payments hears InventoryFailed and refunds. The flow exists only as the sum of the handlers.

OrchestrationChoreography
Where the flow livesOne place, easy to readSpread across services' event handlers
CouplingThe orchestrator knows every participantServices know only events
VisibilityQuery the orchestrator for saga stateRebuild the state from events and traces
Adding a stepChange the orchestratorAdd a listener, and hope nothing assumed the old flow
Good forFlows with more than about three steps, or branching logicShort, stable flows

Durable workflow engines

Most teams with non-trivial sagas end up with orchestration on a durable workflow engine, such as Temporal, AWS Step Functions or Azure Durable Functions. These engines persist each step's result. The orchestrator survives crashes and resumes exactly where it stopped. They also handle retries and timers.

Temporal lets you write the workflow as ordinary code. This sketch shows the shape. Activities are retried by policy, and each one must be idempotent:

TypeScript
// A sketch of a Temporal workflow (TypeScript SDK).
export async function placeOrder(order: Order): Promise<void> {
  await reserveInventory(order);
  try {
    await chargeCard(order);            // idempotent activity, keyed by order.id
  } catch (err) {
    await releaseInventory(order);      // compensation
    throw err;
  }
  await createShipment(order);
  await sendConfirmationEmail(order);
}

Choreography is elegant for two or three steps. Beyond that, it becomes very hard to reason about. The broader trade-off between events and direct calls is in event-driven architecture.

Common mistakes

  • Choreographing a long flow. Nobody can see the whole process, and changes break hidden assumptions.
  • An orchestrator with no durable state. A crash loses progress and can repeat steps.
  • Non-idempotent activities. The engine retries them, so a charge can run twice.

Check yourself

1. An order is stuck in a choreographed saga. Where do you look? Across several services' event logs and traces. The flow exists nowhere in one place.

2. Why does a durable workflow engine help an orchestrator? It saves each step's result, so after a crash the orchestrator resumes where it stopped instead of starting over.

Key takeaways

  • Orchestration keeps the flow in one place. Choreography spreads it across event handlers.
  • Choose choreography for short, stable flows, and orchestration for anything with branching or compensation.
  • Durable workflow engines make orchestrators survive crashes.
  • Every activity in the workflow must be idempotent, because retries will repeat it.

Further learning

The Temporal documentation covers workflows, activities, retries and timers in detail.

Temporal documentationTemporal Platform Documentationdocs.temporal.io

Next, see how a service updates its database and publishes an event without a dual write, in the transactional outbox.