Designing state machines
drivy.engineering
drivy.engineering
Statecharts (also called hierarchical state machines) are essentially generalized state machines which allow for nesting and parallel composition of states. The 'nesting' part is my favourite, since it allows one to delegate event handling logic shared by multiple states to a 'parent' state, reducing code duplication.
The great thing about this paper is that you can glean most of its key ideas by just looking at the diagrams.
the blog post wasn't particularly deep, but the (short) discussion here was worth perusing, as i'm considering employing a state machine for my rails app and have been generally looking for options and best practices (in a mental background thread). but it looks like most (all?) existing gems assume a fixed set of states and transitions, and i was hoping to find something designed primarily for user-defined states and transitions.
I can unequivocally say that FSM's have made me tons of money. By that I mean that they have simplified projects, made them far more robust, easily extensible, simple to modify and, in general terms, quicker to develop with less bugs and errors.
I've used FSM's in FPGA's (for example, optimized DDR memory controllers), embedded projects, robotics and system software. In other words, everywhere.
Googled looks like it is. That was a cool homework/project.
https://hoverbear.org/2016/10/12/rust-state-machine-pattern/
I have used an "improved" version of it https://github.com/andreineculau/cosmogol-abnf when working on https://github.com/for-GET/http-decision-diagram
0 -http://doc.akka.io/docs/akka/current/scala/index-network.htm...
1 - http://doc.akka.io/docs/akka/current/scala/cluster-usage.htm...
It produces pretty ugly graphs that can't be exported to any other format. Meanwhile just about ever graph manipulation program (e.g. Gephi) can handle GraphViz DOT format.
It was too hard to make that compiler or you have encountered another problem?
It's still on my list of short projects. I have a lot of learning to do, knowing nothing about writing parsers and such. Yes, I know I could probably hack it together with a bunch of garbage Python. But that just does not sound fun to write, let alone maintain.
I also find another project called Viz.js[1]. It's graphviz in javascript. Looks promising. It's hard to choose between this two. On the one hand is very simple syntax, and on the other hand more flexibility.
class MovieState < Model
belongs_to :movie
end
class InitialStep
def select
MovieState.where(state: 'initial')
end
def process(movie_state)
movie_state.state = 'in_production'
end
end
class InProductionStep
def select
MovieState.where(state: 'in_production')
end
def process(movie_state)
if movie_state.movie.finished_shooting?
movie_state.state = 'in_theaters'
end
end
end
class MovieWorkflow
def initialize
@steps = []
@steps << InitialStep.new
@steps << InProductionStep.new
end
def run
@steps.each do |step|
step.select.each do |state|
step.process(state)
state.save
end
end
end
end
This makes it very clear when things are happening. It's also easy to debug and test.Of course, there are a lot of limitations, most notably that it can't deal with an async event happening. In my experience though, this can be dealt with by storing the payload of that event somewhere in the db, where the step-selector can access it on next run. One thing this model deals very well with, is delays, which is often part of business processes.
Any thoughts? Does this design have a more formal name?
You know we did some pretty amazing things with computers before OO. We even landed people on the Moon.
Can anyone program without OO any more?
Just saying.
Anyone who came up in software development through that path has a full understanding of the cost of every bit of code written, every abstraction.
Today's programmers, unless they've taken university courses to expose them to the low level stuff, are extremely isolated from the innards of what they write. They reach for classes and objects by default and seem incapable of thinking outside of these boxes. All of this adds overhead and bloat, something you had to be keenly aware of with limited memory and horsepower.
I do understand that a higher level of abstraction in the context of massive amounts of memory and horsepower can be nice. What gets me is that this is often an unnecessary default.
Going back to state machines. One does not to create a bunch of classes and methods to implement them. I see that almost as a violation of the conceptual simplicity of state machines.
I mean, to go in a different direction, I use state machine in FPGA's all the time. The synthesized hardware works fine and represents the bare minimum necessary to implement these very high speed state machines. No classes involved. One can do the same in software development.
I'll call it idiomatic form to use OO style in Ruby, which the article (and my examples) were written in. Considering the interpreted and managed-memory model of the language runtime, it is utterly pointless to worry about a few object instances. Heck, this is a language where primitives are objects.
I suspect we work in quite different areas though, which probably gives us very different perspectives of what matters. The kind of applications I build on a daily basis, deal with business processes, and integration with external systems. I/O is going to be 90% of my machines load, if it's ever doing any real work in the first place. Most of the time it'll just sit waiting for a human to do something though.
Not saying OO has no place. Of course not. That would be crazy.
As the article does not really explain how state machines work let me try to do it here so maybe you will go back to your code and put one in place :) In general, if anywhere in your code you have some property/field called status/state/etc... that you update depending on events then chances are that a state machine would improve the robustness of your code.
Ok, so you have some states. For business apps this usually comes from business. For our dummy example we will simulate a day (using Haskell here for conciseness, but no worry, it's trivial and you don't need to know any Haskell to understand it):
data State = Awake | Dressed | Fed | Ready | Work | Cafe | Home | Asleep
Now most apps stop here and thus they don't have a machinery to control how to move between these. If we were to draw a graph where vertices are the above states and edges are how we go from one to another then, without any restriction, we would implicitly get a complete graph where you can go from any state to any state. Eg. from "Ready" I could go straight to "Asleep" without "Work" :) Our aim is thus to restrict transitions from one state to another to transitions that actually make sense. Making sense obviously depends on the problem we are solving. For our example let's introduce the following events: data Event = Alarm | Dress | Eat | GoToWork | Think | Yawn | Coffee | GoHome | Read | WatchTV
Now that we have states and corresponding events we can draw a graph where vertices are states and edges are events. The graph is now explicit in that we can only move between states (vertices) via events (edges). To put this graph into code, let's create a function that takes a pair of state and event, eg. (Asleep, Alarm) and returns a new state, eg. Awake: myDay :: (State, Event) -> State
As said above, myDay is a function that takes a (state, event) pair, which we can also call a transition, and returns a state. This function is nothing more than a lookup table, aka. transition table, where we will write down how to transition between states: myDay (Asleep, Alarm) = Awake
myDay (Awake, Dress) = Dressed
myDay (Awake, Eat) = Fed
myDay (Dressed, Eat) = Ready
myDay (Fed, Dress) = Ready
myDay (Ready, GoToWork) = Work
myDay (Work, Think) = Work
myDay (Work, Yawn) = Cafe
myDay (Cafe, Coffee) = Work
myDay (Work, GoHome) = Home
myDay (Home, Read) = Asleep
myDay (Home, WatchTV) = Asleep
myDay (_, _) = error "invalid state transition"
That's it, we have codified our graph. We explicitly stated the valid transitions and anything else is by definition invalid. We are pretty much done at this point. A state machine itself can be thought of as a generic function that takes a transition table, a transition and returns a state. This is only to decouple a specific transition table from the general machine itself, but it literally does nothing other than lookup whether the transition is in the table. If yes then it gives back the corresponding new state otherwise we get and error.You can see a pretty picture of the above graph and some code here: https://github.com/pwm/fsm
More generally I get worried about my ActiveRecord objects getting too bulky and business logic getting all wound up in post-transition callbacks and such. But that's something that can be managed with service objects and being disciplined about triggering state changes.
I replaced it with just this:
class StateMachine(object):
'''
A very simple FSM class.
Params:
initial: Initial state
table: A dict (current, event) -> target
'''
def __init__(self, initial, table):
self.current_state = initial
self.state_table = table
def __call__(self, event):
'''Trigger one state transition.'''
self.current_state = self.state_table[self.current_state, event]
class Foo(object):
STATE_TABLE = {
(current_state, event): next_state,
...
}
def __init__(self, xid, token, config):
self.fsm = StateMachine('start', self.STATE_TABLE)
def begin(self):
while self.fsm.current_state not in {'success', 'error'}:
method = getattr(self, self.fsm.current_state)
self.fsm(method())
if self.fsm.current_state == 'error':
self.report_error()
If you're using Python anything more involved than a dict mapping (current, transition) -> next is just overkill. Use it, don't abuse it. ;-)Depending on how much auditability you need on why transitions happened, this may be preferable to papertrail.
[1]: https://www.podcastinit.com/automat-state-machines-with-glyp...
Anyway, I find the concept of "events" a bit weird in this case and I prefer to work with "states" only (state transitions rather than events that change the state). It seems like an unnecessary abstraction to think in terms of an event rather than a state.
I have been thinking of using Postgresql CTE to do this. When a cycle occurs, I just create a new step to the dB.
Anyone from CRM startups who have interesting learnings here ?
If you were speaking about storing the instances lifecycles, a simple model using a RDBS is to store one row per transition event in a separate table. This is what the papertrail[1] gem does for example.
i was looking at https://sqlsunday.com/2014/05/25/directed-acyclic-graphs-vs-... https://www.qcode.co.uk/post/73 to learn how others have done it
Instead then of mapping CRUD operations to HTTP, you would map your application semantics to HTTP methods via link relations. Link relations let you go as far past CRUD as you would like to go depending on your domain and allow you to describe your states and state machine.
I always think a good way to think about a hypermedia state machine is in the context of HTML. Consider a todo app example. You might enter the application and get an empty list of todo items. If you have permissions to add a todo, you might have a "create todo" HTML form. Once you use that form, that todo has a new form called "mark complete." Once invoked, that todo has new transitions called "mark incomplete" or "archive." Once archived, you might see a new form for "unarchive." All of this captures the state machine and transitions in the REST API itself using domain-specific semantics.
Of course, there are other ways of solving this problem, but REST with hypermedia is a great way to work with state machines. There are lots of hypermedia JSON formats out there if you're interested in exploring (e.g. HAL, Siren, Collection+JSON, etc.).