State Machines in Domain Models


I was reminded today of Stateless, a little project that I’m quite fond of.

Stateless is a hierarchical state machine framework based on Simple State Machine for Boo, but configured using C# 3.0.

When I announced Stateless last year, I hardly even explained its purpose, let alone its tongue-in-cheek name. I think I owe it a better start in life!

If you’re doing heavy duty state-machine based programming (writing parsers, radiotherapy machines or flight control systems) then Stateless is probably not what you’re looking for.

If you’re doing domain-driven design, and anxious about the ugly nested ‘if’ and ‘switch’ statements building up around _state variables, Stateless can help.

State-based behaviour is awkward because despite careful programming, it is difficult to see how the states relate to each other. Adding and removing states is error-prone, repetition creeps in, and refactoring gets tricky.

There’s more than one way out. The State pattern can be one very elegant solution.

Another solution that works nicely is to create a declarative model of the states, transitions and associated actions, and have a state machine framework ‘run’ it for you. This is the approach enabled by Stateless.

image

The Telephone Call state chart is broken down into the following states and triggers:

image

You can use any types to represent states and triggers, but enumerations are convenient.

After creating an instance of StateMachine, each state is configured independently. Configuration for a state revolves around the triggers that the state can accept, the transitions that these triggers will cause (to new states) and the actions that will be performed when entering or leaving a state.

image

Once the state machine has been configured, the triggers can be ‘fired’. The side-effects from firing triggers drive the program.

image

You might wonder how this fits into a domain model. StateMachine certainly doesn’t belong on the public surface area of your domain model classes. It’s an implementation detail – like, for example, StringBuilder or Regex.

image

Users of the class interact with the state machine indirectly, by calling public methods on the domain object.

image

The state machine is configured to call two methods when entering and leaving the Connected state (see the configuration above.)

image

Stateless does all of the heavy lifting. For example, the state machine understands that because OnHold is a sub-state of Connected, the Connected entry/exit actions aren’t called when moving between these two states.

Now for a justification of the silly name…

Notice the way that the _state field belongs to the PhoneCall object, rather than the state machine? This allows the field to be easily mapped to a database column by NHibernate or your ORM of choice. The state machine reads and writes the _state value using the pair of lambdas provided to its constructor. In that sense you can almost say that the state machine itself is ‘stateless’. Almost.

There’s some more basic documentation and a source download at the site. I hope you’ll try Stateless and be pleasantly surprised by how expressive it can make your code. Search your domain model for “State” or “Status” and see what Stateless can do!

This post also comes with a challenge: Stateless is fairly simple and very hackable – if you’re a fluent-interface aficionado and think you can improve the configuration API, join the project!

Comments (13)

  1. ckapilla says:

    great followup to the DSL Devcon Nick — looking forward to exploring it

  2. Will Smith says:

    This book really inspired me to think about HSMs. In particular I liked the term "Behavioral Inheritance".  If you get a chance, check it out:

    http://www.amazon.com/Practical-Statecharts-Quantum-Programming-Embedded/dp/1578201101/

    Instead of workflow, I wish WF has gone more toward Data Flow like the MS Robotics guys have gone.  Combining your statemachine and the CCR go be a great combo.  Lead to some very interesting and robust multi-threaded programs.  Sticking with a DSL makes it very interesting.

    Here is some clever us of the CCR:

    http://www.extreme.indiana.edu/multicore/SOX.htm

    Add a state machine controller and you have something pretty powerful.

  3. FYI, with the way you have it programmed, your _totalMinutes TimeSpan will not have the value you intended after a call to StopCallTimer. I submitted a documentation error on this recently to MS (actually, on DateTime, but it applies to TimeSpan as well, and presumably other structs), and hopefully this points it out even more. It claims to change the value of the instance, but of course, since it’s a value-type, it actually just returns the value, not, "Changes the value of the instance."

    It’s silly that anyone would write a piece of documentation to say that, and then at the very bottom buried in the remarks of the function it says that it doesn’t change the ACTUAL value of the instance. Ugh.

  4. Omer Mor says:

    Cool implmentation.

    I am also maintaining an open-source state-machine project. It’s a descendant of Leslie Sanford’s state machine toolkit that was published on CodeProject. I took his project (with his permission of course) and added generics support and lots of other features.

    You can find my source here: https://sourceforge.net/projects/smtoolkit/

    A good intro page can be found here: http://smtoolkit.wiki.sourceforge.net/ with links to the original project at CodeProject, and the added features.

  5. niblumha says:

    @Will, thanks for the link, will definitely check it out!

    @Kyle, good spotting, if I had a dollar for every time I messed that up 🙂

    @Omer, thanks also for the links, it looks like an  interesting project.

  6. Omer Mor says:

    I think your concept is a not completely faithful to the state machine pattern.

    As I understand, a superstate contains substates, of which one of them is the initial substate. When entering a superstate, you end in the initial substate (unless you support history, and then you have some other options too). You can never be in a superstate without also being in one of its substates.

    In your implementation, the superstate is a state of its own, and you have to explicitly transition to one of its substates.

    In your example, to be faithful to the pattern, your Connected superstate should have a Talking substate which is the initial substate. You can transition from Talking to OnHold or vice versa.

    You should also not handle the HungUp trigger in the OnHold state: It enough to handle it in the Connected superstate.

  7. saqibmax says:

    how to deal with the delays …

  8. I have discarded the state machine in Windows Workflow for the stateless library.  Integrating it with my project took less than a day, and I was able to jettison a ton of code written to integrate the WF state machine with a web application.

    The beauty of this library is in its simplicity.  With WF you typically need 2 – 4 projects that support passing data in and out of the workflow, mechanisms to persist the workflow, and potentially MSMQ for high volume transactions.  Stateless eliminates all of that complexity.

    Taken from the perspective of maintaining a workflow, since Stateless is just a class there are many ways of making the state machines it would support configurable.  Representing states and triggers as data types means I can make adjustments or version state machines based on data in a database, JSON or XML datasource.  I can’t do that with Windows WF without re-compiling as there is no way to add states and triggers.  

    In short, you saved me a ton of time, and I am really grateful.  Good work.

  9. niblumha says:

    Wow, thanks David! I’m really glad to hear that it’s been useful.

  10. niblumha says:

    @Omer, sorry about the lagging response 🙂

    It’s the implementer’s decision whether to use the superstate as a valid state as I have done, or to require an ‘initial substate’ as you suggest.

    Stateless supports both of these styles, I’ve chosen the one that fits best in this particular example.

  11. Dave says:

    Thanks for sharing this project!  I’ve been going through the code samples and reading this document over and over again.  I’m not sure why, but I was wondering if this could be used to control machines — surely it looks like I can apply it to my project, but when you’re controlling something like a machine, it seems as though all of the meat is going to be in the transitions between states, rather than really caring about the state itself.  Does this make sense?  I really like the design, so I’m going to give it a go, but I’d like to know if my interpretation of your suggestion regarding machine control makes sense.  🙂

  12. Dave says:

    You need to change the example here to use Permit instead of Allow.  🙂

  13. MB says:

    David Robbins above mentioned driving this from a datasource.  Any examples on doing this?  Need to build a workflow engine and will never know ahead of time what the steps are.  How can you programmatically add .Permits(trigger) to a StateMachine?   How can you differentiate between Permit, PermitIf, OnExit, etc… from a data model and add to the Machine?

Skip to main content