Choreography, Orchestration, and Process Visibility
LESSON
Choreography, Orchestration, and Process Visibility
By the end of this lesson, you will be able to...
Compare choreography and orchestration for one business process.
Trace where process state is visible, hidden, or duplicated across services.
Choose a coordination style by naming the ownership, recovery, and operational trade-off.
Idea in one sentence: Choreography lets services react independently, while orchestration makes one process owner responsible for the overall state and next step.
Core Insight
An online store receives an order.
The user should see a simple promise:
order placed -> payment captured -> inventory reserved -> package shipped
The system is not that simple.
The payment service knows whether money was captured. The inventory service knows whether stock was reserved. The shipping service knows whether a label was created. The order service wants to show one status to the user.
After the previous lesson, we can ask a useful delivery question:
If an event moves twice, arrives late, or cannot be processed, who owns the consequence?
This lesson adds the process question:
Who can see whether the whole order process is healthy?
In pure choreography, each service listens for events and reacts. No service necessarily owns the whole process.
In orchestration, one process owner decides the next step, records the process state, and sends commands or requests to participants.
Neither style is automatically better. The design choice is about visibility, autonomy, coupling, and recovery.
The Small Situation
Start with four services:
| Service | Owns | Publishes |
|---|---|---|
| Orders | order lifecycle and user status | OrderPlaced, OrderCancelled |
| Payments | payment authorization and capture | PaymentCaptured, PaymentFailed |
| Inventory | stock reservation | StockReserved, StockRejected |
| Shipping | shipment creation | ShipmentCreated, ShipmentFailed |
The product promise is:
If payment is captured and stock is reserved, create a shipment.
If either payment or stock fails, do not ship.
Show the user a truthful order status.
The hard part is not sending messages.
The hard part is knowing where the process lives.
Plain meaning:
A process is the story that connects several local facts into one user-visible outcome.
In this scenario:
PaymentCaptured and StockReserved are local facts. "This order is ready to ship" is a process-level decision that depends on both facts.
Technical name:
The design choice is between choreography and orchestration.
The Naive Choreography
The simplest event-driven design looks like this:
Orders publishes OrderPlaced
Payments reacts and publishes PaymentCaptured
Inventory reacts and publishes StockReserved
Shipping reacts when it sees enough events to create a shipment
This is choreography.
Each service knows its own local rule:
When I see event X, I may do action Y and publish event Z.
That is attractive.
It keeps services independent. The order service does not need to call every participant directly. New subscribers can be added without changing the producer. A fraud service, email service, or analytics service can listen to the same facts without asking for a new synchronous endpoint.
For a small process, choreography may be enough.
Example:
When PaymentCaptured happens, send a receipt email.
The receipt email is a local reaction. If it fails, the email service owns retry, idempotency, and a repair path. The whole order process does not depend on the email before it can continue.
Where Choreography Breaks
Now make the process slightly harder.
Payment succeeds. Inventory rejects the reservation because the last item was sold. Shipping has not created a label yet.
Who records the order as blocked?
One possible answer is:
The Orders service listens to PaymentCaptured, StockReserved, StockRejected, and ShipmentCreated.
That works, but notice the shape.
Orders has become a process observer. It is rebuilding the process state from events.
Another possible answer is:
Each service records its own state, and the UI asks all of them.
That spreads the process across multiple services. Now the UI or API gateway must assemble partial truth. When one service is late, the user may see a confusing status.
A third possible answer is:
Shipping waits for both PaymentCaptured and StockReserved.
That gives Shipping a hidden process rule. Shipping now needs to know that payment and stock together mean "ready to ship." If a new fraud check is added later, Shipping must learn that rule too.
The failure is not that choreography is bad.
The failure is hidden process ownership.
When no one owns the process, the process still exists. It is just distributed across event handlers, database rows, dashboards, and human memory.
The Orchestration Alternative
In an orchestrated design, one process owner holds the order process state.
It might be called:
OrderWorkflow
OrderProcess
FulfillmentCoordinator
The name matters less than the responsibility.
The process owner records:
order_id = O-812
state = waiting_for_payment_and_stock
payment = pending
stock = pending
shipment = not_started
Then it reacts to facts and decides the next command.
It may receive:
PaymentCaptured(order_id=O-812, payment_id=P-91)
StockReserved(order_id=O-812, reservation_id=R-44)
Then it decides:
send CreateShipment(order_id=O-812, reservation_id=R-44)
state = shipping_requested
This is orchestration.
The process owner does not own payment, inventory, or shipping internals. It owns the process state and the next step.
This design makes visibility easier. Operators can ask one place:
Where is order O-812?
What is it waiting for?
Which step failed?
Which command was sent?
Can this step be retried safely?
The cost is stronger coupling to the process model. The process owner must know the participant steps. Changing the business process often changes the orchestrator.
A Worked Process Comparison
Trace the same order in both styles.
Input:
OrderPlaced(order_id=O-812)
Choreography Trace
| Step | Event or action | Intermediate state | Who decides next? |
|---|---|---|---|
| 1 | Orders publishes OrderPlaced. |
Orders knows the order exists. | Payments and Inventory react independently. |
| 2 | Payments captures money and publishes PaymentCaptured. |
Payments knows money moved. | Any subscriber may react. |
| 3 | Inventory reserves stock and publishes StockReserved. |
Inventory knows stock is held. | Shipping or another subscriber must combine facts. |
| 4 | Shipping sees both facts and creates shipment. | Shipping knows label exists. | Shipping publishes ShipmentCreated. |
| 5 | Orders observes ShipmentCreated. |
Orders can show shipped. |
Orders updates user status. |
Output:
The order ships if all required event handlers see the right facts and apply compatible rules.
Naive failure contrast:
Suppose Shipping sees PaymentCaptured but misses or delays StockReserved. Payments is correct. Inventory may be correct. Orders may still show "processing." The process is not necessarily broken, but the question "what is this order waiting for?" is spread across several local states and logs.
Orchestration Trace
| Step | Event or command | Intermediate state | Who decides next? |
|---|---|---|---|
| 1 | Orders starts OrderProcess(O-812). |
Process state is waiting_for_payment_and_stock. |
Process owner waits for facts. |
| 2 | Process owner sends or observes payment request. | Payment is pending. |
Payments owns payment result. |
| 3 | PaymentCaptured arrives. |
Process marks payment done. |
Process owner waits for stock. |
| 4 | StockReserved arrives. |
Process marks stock done. |
Process owner sends CreateShipment. |
| 5 | ShipmentCreated arrives. |
Process state is complete. |
Process owner updates user status. |
Output:
The order ships when the process owner has observed enough facts and issued the next command.
Naive failure contrast:
If the process owner crashes, the whole process may pause until it recovers. That is a new dependency. Orchestration improves visibility, but it also creates a component whose state and availability matter.
So far:
Choreography distributes decisions across event handlers.
Orchestration centralizes the process decision in one owner.
Both still need delivery discipline, idempotency, and clear event contracts.
How To Choose
Use choreography when the reactions are local and the process can tolerate loose visibility.
Good signs:
- Each subscriber owns a local side effect.
- Adding a subscriber should not change the producer.
- The process does not require one visible state machine.
- Delayed reactions are acceptable and easy to repair.
- Operators can debug from local service signals.
Use orchestration when the business process needs an explicit owner.
Good signs:
- Users or operators ask, "Where is this process now?"
- Several facts must be combined before the next step.
- A timeout, escalation, compensation, or manual review path is part of the promise.
- The same business process changes often and should be reviewed in one place.
- Hidden event-handler rules are causing incidents or duplicate work.
The design review question is:
Is this mostly a set of independent reactions, or is it one process with a visible state?
Trade-offs and Limits
The trade-off is not "decentralized good" versus "centralized bad."
Choreography improves autonomy.
It costs process visibility. The more services participate in one user-visible outcome, the more you need dashboards, correlation IDs, event history, and clear ownership to answer process questions.
Orchestration improves process visibility.
It costs process coupling. The orchestrator knows the process steps, stores process state, and may become a bottleneck for changes or availability if designed poorly.
This helps when:
- the coordination style matches the real ownership boundary
- events carry stable IDs so state can be correlated
- participants keep local idempotency and retry rules
- operators can see both local service health and process state
It does not solve:
- invalid event boundaries
- unsafe side effects
- missing schema compatibility
- all saga or compensation design
- workflow engine runtime details
Signals to watch:
| Signal | Likely review question |
|---|---|
| Operators cannot answer "where is this order?" | Does the process need an owner or a better process view? |
| One service contains many rules about other services | Is orchestration already happening in the wrong place? |
| Every small business change requires many event handlers to change | Is the process too hidden in choreography? |
| The orchestrator is required for unrelated local reactions | Has orchestration become too broad? |
| Replays trigger duplicate commands | Are process decisions idempotent and correlated by stable IDs? |
Common Confusions
Confusion: Choreography means there is no coupling
Why it is tempting:
The producer does not call consumers directly, so the services look independent.
Better model:
Choreography moves coupling into event names, schemas, timing expectations, and hidden process rules. The coupling is looser than direct calls, but it still exists.
Confusion: Orchestration means one service owns all business logic
Why it is tempting:
The orchestrator decides the next process step, so it can look like the owner of every domain decision.
Better model:
The orchestrator owns process state. Participants still own their local facts. Payments decides whether payment was captured. Inventory decides whether stock was reserved. The process owner decides what the whole process does after those facts are known.
Confusion: A workflow engine removes event design work
Why it is tempting:
A workflow tool can store state, schedule retries, and show progress.
Better model:
The tool can help implement orchestration. It does not choose event boundaries, ownership, idempotency keys, compatibility rules, or user promises for you.
Check Your Understanding
Check: A fraud service wants to listen to PaymentCaptured and score payments after the fact. The order can continue without waiting for the score. Is this a stronger fit for choreography or orchestration?
Think first, then reveal.
Answer: Choreography is likely enough. The fraud service is an independent subscriber with a local reaction. If the order process must block on fraud approval, then the fraud result becomes part of a visible process and orchestration may be appropriate.
Check: The support team often asks why an order is stuck. Engineers answer by checking payment logs, inventory rows, shipping logs, and broker offsets manually. What design smell is visible?
Think first, then reveal.
Answer: The process state is hidden across services. The fix might be an orchestrator, a process projection, or better correlated observability, but the current design lacks a clear place to ask "what is this order waiting for?"
Practice
Review this design:
OrderPlaced starts payment and inventory in parallel.
Payment publishes PaymentCaptured or PaymentFailed.
Inventory publishes StockReserved or StockRejected.
Shipping creates a label when it has seen PaymentCaptured and StockReserved.
Orders updates user status when it sees ShipmentCreated.
If either payment or stock fails, support manually cancels the order.
There is no process view.
Write a review note that recommends either choreography, orchestration, or a mixed design.
A good answer should mention:
- which service owns each local fact
- where the user-visible process state should live
- what happens when payment succeeds but stock fails
- how operators answer "where is this order?"
- what idempotency or correlation key protects repeated events
Model answer:
This design has local facts with clear owners, but the overall order process is hidden. Payment should still own PaymentCaptured and PaymentFailed. Inventory should still own StockReserved and StockRejected. The user-visible process needs a clearer owner or projection because support should not infer stuck orders manually from logs. A mixed design may work: keep payment and inventory as event publishers, but add an OrderProcess owner or process projection that records payment, stock, shipment, timeout, and cancellation state per order_id. Shipping should create a label only after the process has observed payment and stock success, or it should at least make its decision idempotent and correlated by order_id. If stock fails after payment succeeds, the process owner needs a cancellation, refund, or review path.
Resources
- [ARTICLE] Martin Fowler - What do you mean by Event-Driven?
- Link: https://martinfowler.com/articles/201701-event-driven.html
- Focus: Use the event notification and event-carried state transfer sections to compare independent reactions with process-level meaning.
- [REFERENCE] Enterprise Integration Patterns - Process Manager
- Link: https://www.enterpriseintegrationpatterns.com/patterns/messaging/ProcessManager.html
- Focus: Connect orchestration to a component that tracks process state and decides the next message.
- [ARTICLE] Chris Richardson - Saga
- Link: https://microservices.io/patterns/data/saga.html
- Focus: Read for the high-level split between choreography and orchestration in multi-service transactions.
- [BOOK] Martin Kleppmann - Designing Data-Intensive Applications
- Link: https://dataintensive.net/
- Focus: Use the messaging and consistency chapters to keep coordination claims grounded in delivery, state, and failure.
Key Takeaways
- Choreography lets services react to events independently, but the process can become hard to see.
- Orchestration gives one owner the process state and next-step decision, but it couples that owner to the process model.
- Local facts still belong to their owning services; the coordinator owns process progress, not every domain decision.
- Process visibility is an architecture requirement when users, operators, or business rules need to answer "where is this now?"
- The coordination style does not replace delivery discipline, idempotency, event contracts, or operational evidence.
← Back to Event-Driven Architecture and Streaming Foundations