State
States represent previously consumed events resulting in an instance being in a current state. An instance can only be in one state at a given time. A new
instance defaults to the Initial
state, which is automatically defined. The Final
state is also defined for all state machines and is used to signify the
instance has reached the final state.
Declaring States
In the example, two states are declared. States are automatically initialized by the MassTransitStateMachine
base class constructor.
public class OrderState : SagaStateMachineInstance
{
/// <inheritdoc />
public Guid CorrelationId { get; set; }
/// <summary>
/// the current saga state
/// </summary>
public string CurrentState { get; set; }
}
public class OrderStateMachine :
MassTransitStateMachine<OrderState>
{
// public State Initial { get; private set; }
public State Submitted { get; private set; } = null!;
public State Accepted { get; private set; } = null!;
// public State Final { get; private set; }
}
Mandatory States
The Initial
state is the starting point of all sagas. When we can't find an existing saga for the correlation id, you can configure behavior to happen when the saga goes from the `Initial`` state to your first configured state. The goal is to take that initiating message and construct a valid saga state at
before you transition to the next state.
The Final
state, is the last state a saga can exist in. When the saga is complete and will
never get another event again, you can transition the saga to the final state. If you have
SetCompletedWhenFinalize()
configured, it is at this point that the Saga will be removed from the data
store.
Configuring States
States don't need much in the way of configuring besides telling the state machine where to store the current state on the model.
public class OrderStateMachine :
MassTransitStateMachine<OrderState>
{
public State Submitted { get; private set; } = null!;
public OrderStateMachine()
{
// Tell the saga where to store the current state
InstanceState(x => x.CurrentState);
}
}
Transitioning States
The last activity in a behavior should be a call to TransitionTo
. This
then sets the saga up to know how it should behave when the next message comes
in.
public class OrderStateMachine :
MassTransitStateMachine<OrderState>
{
// public State Initial { get; private set; }
public State Submitted { get; private set; } = null!;
public State Accepted { get; private set; } = null!;
// public State Final { get; private set; }
public OrderStateMachine()
{
Initially(
// Behavior Starts
When(OnSubmit)
// Activities
.TransitionTo(Submitted)
// Behavior completes and state persisted
);
}
}
Anti-Pattern
Multiple Transitions in a Behavior
It's not uncommon for us to see users put multiple TransitionTo
calls inside of one behavior.
There is a certain comfort in doing this. However, please note that the TransitionTo
calls only set
the current state. They do not trigger any kind of persistence.
TransitionTo
does NOT persist the saga. It is not a checkpoint.The rationale for this is that a message is only successfully processed once it completes ALL activities in a behavior. Then and only then is the saga persisted, and the message acknowledged back to the broker.
public class OrderStateMachine :
MassTransitStateMachine<OrderState>
{
// public State Initial { get; private set; }
public State Submitted { get; private set; } = null!;
public State Accepted { get; private set; } = null!;
// public State Final { get; private set; }
public OrderStateMachine()
{
Initially(
// Behavior Starts
When(OnSubmit)
// Activities
.TransitionTo(Accepted) // !! NOT SAVED
// Activities
.TransitionTo(Submitted)
// Behavior completes and state persisted
);
}
}