Orchestration (saga-lite)
ACL vs Event covers most bridges. Some product flows need several steps across modules (reserve → pay → confirm → ship). Core allows a thin orchestration pattern without collapsing module boundaries.
Related: Transactions · Events · Decision tree.
When you need it
| Signal | Prefer |
|---|---|
| One peer answer in the same request | Sync ACL |
| One side effect after success | Domain Event |
| Multiple modules, multiple steps, compensations | Orchestration (this page) |
Rules (mandatory)
- No peer Application/Domain imports — orchestration still uses ACL + Events only.
- Orchestrator is not a second Domain — it coordinates; business invariants stay in owning modules.
- Placement options (pick one per flow):
- Owning module Application — a first-level Use Case that sequences local work + ACL + dispatches events (simplest).
- Infrastructure process manager / saga — listens to events, calls inbound Use Cases / ACL ports, records step state (for long-running flows).
- State of the saga (step reached, reservation ids) lives in Infrastructure of the orchestrating side — not in Shared business tables.
- Compensations are explicit Use Cases / façade methods (
ReleaseReservation,MarkPaymentFailed), not silent DB rollbacks across modules.
Sketch — place order with reservation
Long-running variant: after OrderPlaced, a Warehouse or Ordering Infrastructure process manager waits for PaymentCaptured / timeouts and calls inbound Use Cases accordingly.
Anti-patterns
| Smell | Fix |
|---|---|
| “Shared OrderWorkflow entity” with everyone’s enums | Keep steps as events + local state |
| Orchestrator calling peer Use Cases by class name | ACL façade or inbound Use Case via listener only |
| God Use Case that imports three modules’ Domains | Split; only Infrastructure may see foreign contracts |
Orchestration is optional complexity. Default to ACL + Event until a real multi-step failure mode forces it.