State Machine States
Saga state machines are stateful consumers designed to retain the outcome of preceding events when a subsequent event is consumed. The current state is stored within a saga state machine instance. A saga state machine instance can only be in one state at a given time.
A newly created saga state machine instance starts in the internally defined Initial state. When a saga state machine has completed, an instance should
transition to the Final state (which is also already defined).
Initial State
Section titled “Initial State”The Initial state is the starting point of all saga state machine instances. When an existing saga state machine instance cannot be found that correlates to
an event behavior defined for the Initial state, a new instance is created. The defined behavior should initialize the newly created instance and transition
the instance to the next state.
Final State
Section titled “Final State”The Final state is the last (or terminal) state that a saga state machine instance should transition to when the instance has completed. When an instance
is in the Final state, no further events should be handled.
SetCompletedWhenFinalized
Section titled “SetCompletedWhenFinalized”To remove the saga state machine instance from the repository when the instance is in the Final state, specify SetCompletedWhenFinalized() in the saga
state machine.
public class OrderStateMachine : MassTransitStateMachine<OrderState>{ public State Submitted { get; private set; } = null!; public State Accepted { get; private set; } = null!;
public OrderStateMachine() { SetCompletedWhenFinalized(); }}SetCompleted
Section titled “SetCompleted”To remove the saga state machine instance from the repository when the instance is in any other state, use the SetCompleted() method and pass a predicate to
determine if the state machine instance is completed.
protected void SetCompleted(Func<TInstance, Task<bool>> completed);
protected void SetCompleted(Func<BehaviorContext<TInstance>, Task<bool>> completed);Declaring States
Section titled “Declaring States”States are declared as public properties on the saga state machine with the State property type. In the example below, two states are defined:
Submitted and Accepted.
public class OrderState : SagaStateMachineInstance{ public Guid CorrelationId { get; set; }
/// <summary> /// The saga state machine instance current state /// </summary> public string CurrentState { get; set; }}
public class OrderStateMachine : MassTransitStateMachine<OrderState>{ public State Submitted { get; private set; } = null!; public State Accepted { get; private set; } = null!;}Transition to a state
Section titled “Transition to a state”When configuring saga state machine behavior for an event, the last activity configured is usually TransitionTo, signaling the transition of the state machine
instance to the next state. By transitioning to another state, the saga state machine adapts its behavior so that the next event consumed will pick up in the
new state and execute the appropriate behavior for the event in that state.
For example, when an OrderSubmitted event is consumed by a new saga state machine instance, the saga state machine will transition to the Submitted state.
public record OrderSubmitted(Guid OrderId, string CustomerNumber);
public class OrderStateMachine : MassTransitStateMachine<OrderState>{ public Event OrderSubmitted<OrderSubmitted> { get; private set; } = null!;
public State Submitted { get; private set; } = null!; public State Accepted { get; private set; } = null!;
public OrderStateMachine() { Initially( // Event is consumed, new instance is created in Initial state When(OrderSubmitted) // copy some data from the event to the saga .Then(context => context.Saga.CustomerNumber = context.Message.CustomerNumber) // transition to the Submitted state .TransitionTo(Submitted) ); }}State Transition Anti-Pattern
Section titled “State Transition Anti-Pattern”Saga state machine instances are persisted after all state machine activities have completed. If there were additional activities after the TransitionTo,
such as a Publish, those activities execute before the instance is persisted. After all the activities have completed, the instance is persisted and the
message is acknowledged with the message broker.
In the example below, the newly created saga state machine instance would not be persisted because of the exception thrown by the activity following the first
TransitionTo call.
public class OrderStateMachine : MassTransitStateMachine<OrderState>{ public State Submitted { get; private set; } = null!; public State Accepted { get; private set; } = null!;
public OrderStateMachine() { Initially( When(OnSubmit) .Then(context => context.Saga.CustomerNumber = context.Message.CustomerNumber) .TransitionTo(Submitted) .Then(context => throw new InvalidOperationException()) .TransitionTo(Accepted) ); }}