简体   繁体   中英

State machines: multiple conditions for determining next state?

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 eg 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.

As a suggestion, I think you could consider whether self-referential transitions would be useful in your editor machine. For example the the grammar state would have an internal list of tagged errors that might be empty or non-empty. No transition occurs, but you can think of it as an infinite transition to the current state. Transition out of the grammar state occurs with a trigger event such as your Submit, and at that point the internal list is tested to determine the next state.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM