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
        );
    }
}