Sagas and Orchestration
Overview
Mississippi models a saga as explicit workflow state plus ordered step execution.
A saga state record implements ISagaState. A start command enters the same aggregate-style command pipeline used elsewhere in Mississippi, but the work that follows is long-running and step-oriented rather than a single aggregate decision. SagaOrchestrationEffect<TSaga> reacts to saga lifecycle events, resolves ordered ISagaStep<TSaga> implementations, and emits further saga events as each step succeeds, fails, or compensates.
The Problem This Solves
Some business operations span multiple aggregates and need coordinated execution with rollback capability.
Money transfer is the clearest example in the Spring sample: debit one account, credit another, and undo the debit if the credit fails. That kind of workflow is easy to describe but hard to build reliably when the alternative is ad hoc service code with scattered try/catch blocks and manual state tracking.
Mississippi addresses that by making workflow progress explicit in evented saga state - every step, every failure, and every compensation action is a first-class event in the stream.
Core Idea
Sagas reuse the aggregate-style event pipeline, but with specialized orchestration semantics.
- Saga state is stored like other event-sourced state.
- Starting a saga emits lifecycle events rather than performing all work immediately.
- Ordered steps run in response to those lifecycle events.
- Compensation is explicit and opt-in for steps that implement
ICompensatable<TSaga>.
How It Works
This page starts where the single-aggregate write model stops: when one business operation needs ordered work across several steps, and often across several aggregates.
This diagram shows the saga control flow.
The runtime behavior is:
StartSagaCommandHandler<TSaga, TInput>checks that the saga has not already started and that step metadata exists.- It emits
SagaStartedEventandSagaInputProvided<TInput>. SagaOrchestrationEffect<TSaga>listens for saga lifecycle events.- On
SagaStartedEvent, it resolves the first step usingISagaStepInfoProvider<TSaga>and dependency injection. - Each successful step may emit domain events and then yields
SagaStepCompleted. - When there is no next step, the effect yields
SagaCompleted. - On failure, the effect yields
SagaStepFailedfollowed bySagaCompensating. - Compensation walks backward through prior steps. When no earlier step remains, the effect yields
SagaCompensated.
Guarantees
- Saga state has a defined contract through
ISagaState, includingSagaId,Phase,LastCompletedStepIndex,StartedAt, andStepHash. - Saga steps are explicitly ordered through
[SagaStep<TSaga>(index)]metadata andISagaStepInfoProvider<TSaga>. - Start commands capture input into saga state through
SagaInputProvided<TInput>so later steps can read the original input. - Compensation runs only for steps that implement
ICompensatable<TSaga>. - Saga lifecycle transitions are represented as explicit events such as
SagaStartedEvent,SagaStepCompleted,SagaStepFailed,SagaCompensating,SagaCompleted,SagaCompensated, andSagaFailed.
Non-Guarantees
- Mississippi sagas are not distributed transactions. They coordinate work and compensation, but they do not make several aggregates commit atomically.
- Compensation is business-defined. The framework can call compensating steps, but it cannot infer what a safe undo operation should be.
- A saga can still end in
Failedstate during compensation if a compensating step cannot complete successfully.
Trade-Offs
- Explicit lifecycle events make saga progress observable and testable, but they also add more state and event types than a one-off workflow service would.
- Ordered steps are easier to reason about than implicit orchestration, but they require developers to model forward progress and rollback rules carefully.
- Saga orchestration reuses the aggregate/event infrastructure, which keeps the model consistent. It also means teams need to learn the same evented thinking for workflows, not just for aggregates.
Testability
Saga orchestration remains testable for the same reason the rest of Mississippi's write path remains testable: workflow progress is expressed through explicit events and explicit state.
Start commands, lifecycle events, ordered steps, and compensation outcomes are all modeled directly. That makes it easier to test saga progress and failure handling at the domain level instead of hiding workflow behavior inside broad service methods with scattered control flow.
Related Tasks and Reference
- Use Write Model for single-aggregate command handling.
- Use Read Models and Client Sync for status projections and client update paths.
- Use Domain Modeling when you need the package boundary around saga abstractions and runtime support.
Summary
Mississippi sagas turn multi-step workflows into observable, compensatable event streams - making workflow progress, failure, and recovery explicit rather than buried in ad hoc service code.