Last week I evaluated couple of State Machines. It wasn’t some structural evaluation, rather quick overview what is today in the market. I was just about to have something lightweight and easy to use so I have looked into Appccelerate and Stateless from Nicolas Blumhard. As I went through my sample I asked myself: Why we don’t use State Machines frequently? I asked couple of colleagues if they do. They don’t. Do I? No.
I have done some research and found some interesting blog posts and discussions by shopify, hacker news or this one written by Alan Skorkin. As Alan points: most of us have learned about State Machines at the University, as I did. And don’t forget, almost everyone is using one of them every day. You don’t have a idea where? Ask your coffee machine.
So state machines are neither unknown nor obsolete so I ask myself once again: why we don’t use them? As I was looking again I found blog post of Alex J. Champandard “10 Reasons the Age of Finite State Machines Over”. Although he discussed the usage of State Machines in context of game developing and the post is 8 years old, it is still interesting point of view from today’s perspective. I agree, maybe “they’re are unorthodox” but they also undervalued.
Sample
Look at this workflow featured as a finite state machine.

How would I implement this workflow without any state machine framework? I asked two of my colleagues to do this and it was interesting to see what they have done: one has implemented the workflow by Observer Pattern the other created own state machine.
Stateless Sample
As counterexample I used Stateless and the experience was great. The definition was very straightforward.
public class CreditRequest
{
public CreditRequest(double availableCredit, double requestedAmount)
{
AvailableCredit = availableCredit;
RequestedAmount = requestedAmount;
}
public double AvailableCredit { get; }
public double RequestedAmount { get; }
}
public class CreditRequestWorkflow
{
public enum Event
{
Approve,
Cancel,
CheckIfAmountMustByApprovedByGroup,
Decline,
ReviewByCOO,
ReviewByCFO
}
public enum State
{
Approved,
CancelledByOriginator,
Declined,
NotStarted,
WaitingOnGroupReview,
WaitingOnCOOReview,
WaitingOnCFOReview
}
private readonly CreditRequest _creditRequest;
public CreditRequestWorkflow(CreditRequest creditRequest)
{
if (creditRequest == null)
{
throw new ArgumentNullException(nameof(creditRequest));
}
_creditRequest = creditRequest;
StateMachine = new StateMachine<State, Event>(State.NotStarted);
StateMachine.Configure(State.NotStarted)
.PermitIf(Event.CheckIfAmountMustByApprovedByGroup, State.WaitingOnGroupReview, () => _creditRequest.RequestedAmount > _creditRequest.AvailableCredit)
.PermitIf(Event.CheckIfAmountMustByApprovedByGroup, State.Approved, () => _creditRequest.RequestedAmount <= _creditRequest.AvailableCredit);
StateMachine.Configure(State.WaitingOnGroupReview)
.SubstateOf(State.NotStarted)
.Permit(Event.ReviewByCOO, State.WaitingOnCOOReview)
.Permit(Event.Cancel, State.CancelledByOriginator);
StateMachine.Configure(State.WaitingOnCOOReview)
.SubstateOf(State.WaitingOnGroupReview)
.Permit(Event.ReviewByCFO, State.WaitingOnCFOReview)
.Permit(Event.Decline, State.Declined);
StateMachine.Configure(State.WaitingOnCFOReview)
.SubstateOf(State.WaitingOnCOOReview)
.Permit(Event.Approve, State.Approved)
.Permit(Event.Decline, State.Declined);
StateMachine.Configure(State.Approved)
.SubstateOf(State.WaitingOnCFOReview)
.Ignore(Event.Decline)
.Ignore(Event.Cancel);
StateMachine.Configure(State.Declined)
.SubstateOf(State.WaitingOnCOOReview)
.SubstateOf(State.WaitingOnCFOReview)
.Ignore(Event.Cancel)
.Ignore(Event.Approve);
StateMachine.Configure(State.CancelledByOriginator)
.SubstateOf(State.WaitingOnCOOReview)
.SubstateOf(State.WaitingOnCFOReview)
.SubstateOf(State.WaitingOnGroupReview)
.Ignore(Event.Cancel)
.Ignore(Event.Approve);
}
public StateMachine<State, Event> StateMachine { get; }
}
Take we look at the usage. In this example I’m going through the workflow until the credit request will be approved.
[TestMethod]
public void AvailableCreditIsLessThanRequestedAmount_FireEventApprove_CurrentStateIsApproved()
{
//Arrange
var availableCredit = 50000D; var requestedAmount = 100000D;
var creditRequest = new CreditRequest(availableCredit, requestedAmount);
var workflow = new CreditRequestWorkflow(creditRequest);
//Act workflow.StateMachine.Fire(Event.CheckIfAmountMustByApprovedByGroup);
workflow.StateMachine.Fire(Event.ReviewByCOO); workflow.StateMachine.Fire(Event.ReviewByCFO);
workflow.StateMachine.Fire(Event.Approve); var currentState = workflow.StateMachine.State;
//Assert
Assert.IsTrue(currentState == State.Approved);
}
I don’t know how you, but I think it’s elegant. Would you consider using State Machine next time? I will.
Excellent work here – clear, concise with smart separation of your domain model and state machine. In your workflow class wouldn’t you want to have interface more like:
workflow.CheckIfAmountMustByApprovedByGroup()
…
workflow.Approve()
to hide the details of the state machine? I don’t think I’d want those artifacts in my domain namespace.
Thanks much Anton!
LikeLike
Hi Doug, you are right! Normally you would hide the details of state machine. This post is only food for thought.
LikeLike
https://polldaddy.com/js/rating/rating.jsfor a long running workflow, would you store the state of the statemachine in the database, and instantiate it when needed passing in the save state to resume the machine. Or let the statemachine run silently in the background?
LikeLike
I don’t know the concrete scenario, but what will happen on hardware fail? You will lose the state. So I would tend to use the database.
LikeLike