Simple State Machine Updates

Introduction

Some of you may remember my SimpleStateMachine project; I blogged about it here and here. It is available on GitHub and Nuget.

I made a few changes to it:

  • An alternative way to configure states
  • Adding state to states (pun intended)
  • Converting attributes to code state machines
  • The possibility to clear state transitions (unsure about this one, but, it's here, at least for now)

Let's see how these work.

Building a State Machine

We used to have two ways for building a simple state machine:

  1. From attributes
  2. From code

I'm going to introduce a new one, but, first, here is quick recap for the existing methods.

From Attributes

As simple as adding attributes to enumeration fields:

public enum TicketState
{
    [InitialState]
    [Transitions<TicketState>(TicketState.Ready, TicketState.Closed)]
    Created,
    [Transitions<TicketState>(TicketState.InProgress, TicketState.Blocked, TicketState.Closed)]
    Ready,
    [Transitions<TicketState>(TicketState.Blocked, TicketState.InReview, TicketState.Closed, TicketState.Ready)]
    InProgress, [Transitions<TicketState>(TicketState.InProgress, TicketState.Closed)]
    Blocked,
    [Transitions<TicketState>(TicketState.InProgress, TicketState.Closed)]
    InReview,
    Closed
}

From Code

Also simple to understand:

var created = TicketState.Created;
var stateMachine = created.Create();
stateMachine.CanTransitionTo(created, TicketState.Ready, TicketState.Closed);
stateMachine.CanTransitionTo(TicketState.Ready, TicketState.Blocked, TicketState.InReview, TicketState.Closed, TicketState.Ready);
stateMachine.CanTransitionTo(TicketState.InProgress, TicketState.InProgress, TicketState.Closed);
stateMachine.CanTransitionTo(TicketState.Blocked, TicketState.InProgress, TicketState.Closed);
stateMachine.CanTransitionTo(TicketState.InReview, TicketState.InProgress, TicketState.Closed);

From Loquacious Code

This is the new one, I called it loquacious for lack of a better name. We start from an existing state machine and then configure the state transitions individually:

var created = TicketState.Created;
var stateMachine = created.Create();
stateMachine
    .From(created)
    .To(TicketState.Ready, TicketState.Closed);
stateMachine
    .From(TicketState.Ready)
    .To(TicketState.InProgress, TicketState.Blocked, TicketState.Closed);
stateMachine
    .From(TicketState.InProgress)
    .To(TicketState.Blocked, TicketState.InReview, TicketState.Closed, TicketState.Ready);
stateMachine
    .From(TicketState.Blocked)
    .To(TicketState.InProgress, TicketState.Closed);
stateMachine
    .From(TicketState.InReview)
    .To(TicketState.InProgress, TicketState.Closed);

There is now an extension method From that can be used to add or append state transitions (To) to an existing state.

The three ways are perfectly analogous, the last two use an IStateMachine<T> instance while the first only uses attributes and extension methods over them.

Adding State to a State

It is now possible to add arbitrary state content to some state machine state. This can be, for example, some string or some number. All three ways support this.

Adding State Using Attributes

Let's introduce the new [State] attribute:

public enum TicketState
{
    [InitialState]
    [State("Initial State")]
    Created,
    //rest goes here...
}

The [State] attribute can be applied at most once to any enumeration field to add custom state. It is possible to retrieve the custom state for an enumeration field using the GetState extension method:

var initialState = TicketState.Created;
var state = initialState.GetState(); //returns "Initial State"

If no state was provided, GetState returns null.

Adding State Using Code

Similar using code, we have the SetState method on IStateMachine<T>:

var created = TicketState.Created;
var stateMachine = created.Create();
stateMachine.SetState(created, "Initial State");

And GetState too:

stateMachine.GetState(created); //returns "Initial State"

Adding State Using Loquacious Code

As for the new loquacious approach, the new To method can receive an optional state parameter:

stateMachine
    .From(created, "Initial State")
    .To(TicketState.Ready, TicketState.Closed);

GetState can be used to retrive the custom state, as in the previous example:

stateMachine.GetState(created); //returns "Initial State"

Converting Attributes to Code

It is also now possible to convert an attributes-based state machine to a code one (IStateMachine<T>). For that we use the Create method over an enumeration type:

var created = TicketState.Created;
var stateMachine = StateMachineExtensions.Create<TicketState>();

The returned IStateMachine<T> can be used as if it had been created normally.

Clearing Transitions

Now, if you pass an empty array (not null, mind you) to CanTransitionTo or To methods, the state transitions are cleared. Again, I'm still to see if this is useful or not, looking forward to hearing you on this.

Conclusion

As always, hope you find this useful, and I'm super interested in hearing your thoughts. The Nuget package hasn't been updated yet, but will be shortly. Do give it a try and let me hear from your!

GitHub: http://github.com/rjperes/SimpleStateMachine

Nuget: https://nuget.org/packages/SimpleStateMachine

Comments

Popular posts from this blog

OpenTelemetry with ASP.NET Core

ASP.NET Core Middleware

.NET Cancellation Tokens