Basic State Machine setup using Stateless

Vaccano picture Vaccano · Feb 2, 2012 · Viewed 11.3k times · Source

I have some fairly simple state needs (for now). I think I would like to model these using the Stateless api. (But I don't really know much about state machines, so I could be wrong.)

But I am getting caught up in the terminology (Specifically State and Trigger)

Here is an example: I have an order class. It is setup with several states. They are: New, Filled, Shipping, Completed, Cancelled.

A few simple state rules I would like is that these state transitions are allowed:

  • New (is the default)
  • New -> Filled
  • New -> Cancelled
  • Filled -> Shipping
  • Filled -> Cancelled
  • Filled -> Shipping
  • Shipping -> Complete

So where I am getting tripped up here is what is my "Trigger"?

Just in case a more specific example is needed, say I want a method like this:

public bool UpdateOrderStatus(int OrderId, OrderStatusEnum NewOrderStatus)

that will return true if the status updated successfully. How can setup and use Stateless to make this happen?

Answer

Sergey Brunov picture Sergey Brunov · Feb 2, 2012

The machine is in only one state at a time; the state it is in at any given time is called the current state. It can change from one state to another when initiated by a triggering event or condition, this is called a transition. from Finite-state machine on Wiki

I believe, the trigger is this triggering event.

Update:

Of course trigger name sometimes can be equal to some of state names.

New (initial state)
New -> Filled (trigger "Filled")
New -> Cancelled (trigger "Cancelled")
Filled -> Shipping (trigger "ToBeShipped")
Filled -> Cancelled (trigger "Cancelled")
Shipping -> Complete (trigger "Completed").

Update:

stateless is really nice framework! I've tried to implemented the functionality.

States:

public enum State
{
    New,
    Filled,
    Shipping,
    Cancelled,
    Completed
}

Triggers:

public enum Trigger
{
    Filled,
    Cancelled,
    ToBeShipped,
    Completed
}

Order class:

public class Order
{
    private readonly StateMachine<State, Trigger> _stateMachine;

    public Order()
    {
        _stateMachine = CreateStateMachine();
    }

    public bool TryUpdateOrderStatus(Trigger trigger)
    {
        if (!_stateMachine.CanFire(trigger))
            return false;

        _stateMachine.Fire(trigger);
        return true;
    }

    public State Status
    {
        get
        {
            return _stateMachine.State;
        }
    }

    private StateMachine<State, Trigger> CreateStateMachine()
    {
        StateMachine<State, Trigger> stateMachine = new StateMachine<State, Trigger>(State.New);
        stateMachine.Configure(State.New)
            .Permit(Trigger.Filled, State.Filled)
            .Permit(Trigger.Cancelled, State.Cancelled);

        stateMachine.Configure(State.Filled)
            .Permit(Trigger.ToBeShipped, State.Shipping)
            .Permit(Trigger.Cancelled, State.Cancelled);

        stateMachine.Configure(State.Shipping)
            .Permit(Trigger.Completed, State.Completed);

        stateMachine.OnUnhandledTrigger((state, trigger) =>
            {
                Console.WriteLine("Unhandled: '{0}' state, '{1}' trigger!");
            });
        return stateMachine;
    }
}

Tester for Order class:

Order order = new Order();
bool result = order.TryUpdateOrderStatus(Trigger.Completed);
Console.WriteLine("Attemp to complete order: {0}", result);
Console.WriteLine("Order status: {0}", order.Status);

result = order.TryUpdateOrderStatus(Trigger.ToBeShipped);
Console.WriteLine("Attemp to ship order: {0}", result);
Console.WriteLine("Order status: {0}", order.Status);

result = order.TryUpdateOrderStatus(Trigger.Cancelled);
Console.WriteLine("Attemp to cancel order: {0}", result);
Console.WriteLine("Order status: {0}", order.Status);