Choreography, Orchestration, and Process Visibility

LESSON

Event-Driven Architecture and Streaming Foundations

009 30 min intermediate

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:

Use orchestration when the business process needs an explicit owner.

Good signs:

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:

It does not solve:

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:

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

Key Takeaways

PREVIOUS Delivery Semantics and Retry Discipline NEXT Event Sourcing, CQRS, and Projection Boundaries