Saga Guide
Sagas coordinate long-running business processes as a sequence of events and actions with state tracking and compensation.
Defining a Saga
Section titled “Defining a Saga”public enum CheckInState{ Initial, GuestVerified, RoomAssigned, Completed, Cancelled}
[Saga<CheckInState>]public partial class CheckInSaga : ISaga<CheckInState>{ public Guid Id { get; set; } public CheckInState State { get; set; } public string CorrelationId { get; set; } = ""; public DateTimeOffset StartedAt { get; set; } public DateTimeOffset? CompletedAt { get; set; }
// Saga-specific data public string? RoomNumber { get; set; }
[SagaStart] public VerifyGuestAction Handle(GuestArrived @event) { return new VerifyGuestAction(@event.GuestId); }
[InState(CheckInState.GuestVerified, NextState = CheckInState.RoomAssigned)] [CompensateWith<CancelCheckInAction>] public AssignRoomAction Handle(IdentityVerified @event) { return new AssignRoomAction(@event.GuestId); }
[InState(CheckInState.RoomAssigned, NextState = CheckInState.Completed)] public object? Handle(RoomAssigned @event) { RoomNumber = @event.RoomNumber; CompletedAt = DateTimeOffset.UtcNow; return null; // Terminal -- no further action }}ISaga<T> Interface
Section titled “ISaga<T> Interface”public interface ISaga<TState> where TState : struct, Enum{ Guid Id { get; set; } TState State { get; set; } string CorrelationId { get; set; } DateTimeOffset StartedAt { get; set; } DateTimeOffset? CompletedAt { get; set; }}Saga Attributes
Section titled “Saga Attributes”| Attribute | Target | Description |
|---|---|---|
[Saga<TState>] | Class | Marks a saga with state enum |
[SagaStart] | Method | Entry point handler (Initial state) |
[InState(state, NextState)] | Method | Handler for a specific state |
[CompensateWith<TAction>] | Method | Action dispatched on failure (rollback) |
[SagaTimeout(Seconds)] | Class | Auto-cancel if not completed within timeout |
SG-Generated Orchestrator
Section titled “SG-Generated Orchestrator”The SG generates {Saga}.Orchestrator.g.cs with:
- State routing:
switchon current state + incoming event type - Compensation chain: on failure, dispatches
[CompensateWith]actions in reverse order - Transition validation: compile-time graph analysis from
[InState]+NextState
Persistence
Section titled “Persistence”msg.EnableSagas(); // InMemory repositories (dev)msg.EnableSagaPersistence(); // EF Core repositories (prod)EF Core persistence uses two tables:
| Table | Purpose |
|---|---|
__SagaInstances | Saga state, correlation, timestamps |
__SagaSteps | Individual step execution history |
Compensation
Section titled “Compensation”When a step fails, the orchestrator walks the compensation chain in reverse order. Each [CompensateWith<TAction>] action is dispatched as a message, allowing the compensating logic to be asynchronous and independently retriable.
The compensation chain includes only steps that have already been executed. If step 3 fails, only compensations for steps 2 and 1 are dispatched (in that order).
Timeouts
Section titled “Timeouts”Use [SagaTimeout(Seconds)] on the saga class to auto-cancel sagas that do not complete within the specified duration. The timeout is checked by a background service that polls for stale saga instances.
[Saga<CheckInState>][SagaTimeout(Seconds = 300)] // 5-minute timeoutpublic partial class CheckInSaga : ISaga<CheckInState> { ... }When a saga times out, its state is set to the terminal/cancelled state and any configured compensation actions are dispatched.
Diagnostics
Section titled “Diagnostics”| ID | Severity | Message |
|---|---|---|
| PRAG0810 | Error | Inconsistent saga state transition |
| PRAG0811 | Info | Saga state has no handler (terminal states OK) |
| PRAG0812 | Warning | Unreachable saga states |
| PRAG0813 | Error | [Saga<T>] where T is not an enum |
| PRAG0814 | Error | Saga without [SagaStart] |