TL;DR:
Where / how should a state machine framework determine what the next state should be? Or, is this in fact out of scope for state machines, which are actually only for tracking the current state and validating whether a requested transition is allowed?
Background & details:
Consider a simple magazine article publishing workflow. The following diagram shows a basic, conceptual understanding of the process, which we'd like to turn into code (in this case using Stateless). It covers the basic, "happy path" of publication, plus a few possible issues:
┌───┬──────────────────────────────────────────┐
│ W │ ┌───────────┐ │
│ r │ │ │ │
│ i │ │ Write ◄────────┐ │
│ t │ │ │ │ │
│ e │ └───────────┘ │ │
│ r │ │ │ │
├───┼────────Submit───────────│────────────────┤
│ │ │ │ │
│ │ ┌─────▼─────┐ │ │
│ │ │ │ │ │
│ │ │ Review ◄──────┬─│──────────┐ │
│ │ │ │ │ │ │ │
│ │ └───────────┘ │ │ │ │
│ │ │ │ │ │ │
│ │ ____▼___ │ │ │ │
│ E │ / \ │ │ │ │
│ d │ / Grammar \───No──│─┘ │ │
│ i │ \ OK? / │ │ │
│ t │ \________/ │ Resolve │
│ o │ │ Date │ │
│ r │ Yes Passed │ │
│ │ │ │ │ │
│ │ ____▼___ │ │ │
│ │ / \ │ │ │
│ │ / Libel \────Yes│────────┐ │ │
│ │ \ Risk? / │ │ │ │
│ │ \________/ │ │ │ │
│ │ │ ┌───│───┐ │ │ │
│ │ No │ Defer │ │ │ │
│ │ │ └───▲───┘ │ │ │
│ │ ____▼___ │ │ │ │
│ │ / \ │ │ │ │
│ │ / Embargo? \─Yes───┘ │ │ │
│ │ \ / │ │ │
│ │ \________/ │ │ │
│ │ │ │ │ │
├───┼──────────No────────────────────│───│─────┤
│ L │ │ │ │ │
│ e │ │ ┌─────▼───│─┐ │
│ g │ │ │ Legal │ │
│ a │ │ │ Review │ │
│ l │ │ └───────────┘ │
├───┼──────────│───────────────────────────────┤
│ P │ │ │
│ r │ ┌─────▼───────┐ │
│ i │ │ │ │
│ n │ │ Print it! │ │
│ t │ │ │ │
│ e │ └─────────────┘ │
│ r │ │
└───┴──────────────────────────────────────────┘
My question is around how to think about the state transitions, specifically the transition after the editor has completed the review.
One way to do it (A) would be to put the onus on the user to choose the appropriate transition; so in this case the editor would have separate buttons called Revert to Check Spelling
, Refer for Legal Review
and Defer Publication
. These would be wired up to corresponding methods on the Article
object, which internally call .Fire(...)
on a _stateMachine
with the respective Triggers
, per the recommended approach.
There are at least two disadvantages to that however. First, it increases cognitive load on the user (marginally, in this case, but stay with me for the sake of the example). She shouldn't have to choose what to do, she should just be able to do all of the data input in one go, on a form like this:
┌─────────────────┬──────────────────────┐
│ Spelling OK? │ Yes [ ] No [ ] │
│ Libel Risk? │ Yes [ ] No [ ] │
│ Embargo? │ Date [ dd/mm/yyyy ] │
└─────────────────┴──────────────────────┘
And then the application should decide what to do. That would also prevent the user from making the wrong choice: it's easier to answer factual questions about the content than decide the correct course of action given potentially many inputs (imagining a more complex example).
The second disadvantage is that the review could be interrupted prematurely: whenever one issue is found and the corresponding action triggered, the editor doesn't get to continue the review to potentially find other issues. Once the first issue is resolved and the article comes back for review again, it in practice starts again from scratch.
Another recommendation (B) I've seen is to model more states: a Grammar Issue
state, a Contains Libel
state and so on. However, that comes back to the same problem: a user would have to trigger the transition to those states individually (assuming not all of these issues are determinable automatically by e.g. a grammar checker or such).
Furthermore, this feels like moving away from our relatively clean model of the world, or at least from the supposed cleanliness offered by a state machine library. Imagine the proliferation of states in a more complex example, with the user entering a greater number of variables. I was looking forward to using Stateless' export to DOT graph functionality to achieve, as they say, the code as the authoritative source and diagrams as by-products. However, the value of that for stakeholder communication would diminish if the outputs are full of fairly unintuitive "states" which no longer correspond to commonly understood "phases" of the workflow.
That seems to leave me with (C), the option of giving the editor a Submit
function which just contains a bunch of if
statements to determine the correct next trigger to .Fire()
. On one hand that feels like backsliding from the advantages which a state machine framework is meant to provide (that's no slight against Stateless—the purpose of this question is to determine whether I'm Holding It Wrong). On the other hand, I recognise that I'm still getting the benefits of its structure in relation to the other states, with simpler transitions.
There is another SO question which provides a simpler example:
Let's take the simple ATM example. If user presses "Confirm" AND PIN is correct, go to State 2. If user presses "Confirm" AND PIN is not correct, go to State 3.
That question seems to be asking more about modelling / notation than implementation, but this example is analogous to my example of the editor pressing Submit
on her completed form.