In a choreographed system, services react to each other's events: order placed, so payment listens; payment captured, so fulfilment listens. Nobody is in charge, coupling is low, and adding a new participant requires changing nothing. It is elegant, and after two years nobody in the company can tell you what happens when an order is placed.
In an orchestrated system, a coordinator holds the process and calls each step. The workflow is readable in one place, failure handling is explicit, and the trade is that the coordinator knows about everyone and becomes a point of coupling and change.
The failure that decides it
Ask what happens when step four fails after steps one through three have already committed. In an orchestrated process, the coordinator runs compensation, in order, with a record of what it did. In a choreographed one, compensation is a second cascade of events that must be designed, and reasoning about whether it is complete requires holding the entire graph in your head.
That is why my default is choreography for notifications — facts broadcast to interested parties with no required outcome — and orchestration for anything transactional, where a partially completed process leaves the business in a state somebody has to reconcile.
# Choreography: each service knows only its own trigger and its own output.
# Nobody knows the whole sequence. Adding a listener changes no existing code.
@on(OrderPlaced)
async def reserve_stock(event): ...
@on(StockReserved)
async def capture_payment(event): ...
# Orchestration: one place states the process, including how it unwinds.
async def place_order(order):
saga = Saga(order.id)
try:
reservation = await saga.step(reserve_stock, order,
compensate=release_stock)
payment = await saga.step(capture_payment, order,
compensate=refund_payment)
await saga.step(schedule_delivery, order, compensate=cancel_delivery)
except StepFailed:
await saga.compensate() # runs in reverse, recorded, resumable
raise
# The second version is longer. It is also the one you can hand to someone
# on their first week and have them understand the business process.Choosing by question
- Does a partial failure leave the business inconsistent? If yes, orchestrate — someone must own the unwind.
- Do participants need to change independently and frequently? Choreography reduces coordination cost between teams.
- Can you answer 'where is order 4417 in the process' without reading four services' logs? If not, you needed a coordinator.
- Is the set of participants open-ended? Broadcasting facts suits an unknown audience; commanding steps does not.
- Is the ordering between steps essential? Choreography gives you causal ordering at best, and only if you designed for it.
Choreography does not remove the workflow. It distributes it across services and removes the place where anyone could read it.
Most mature systems end up hybrid, and deliberately so: orchestrated transactional cores where correctness and compensation matter, surrounded by choreographed reactions where loose coupling is worth more than visibility. What causes trouble is not mixing them — it is mixing them by accident, so that half a process is coordinated and the other half is a set of listeners nobody documented.