The state of the Finite State Machine.

There is a lot to learn about a "Finite State Machine".

A little intro, what is a FSM?

A Finite state machine is an abstract model of computation, which can be in only one finite state at a specific moment. Finite State Machines are used to model problems in different domains such as AI, Games, application flows, etc..

In simpler words: It describes how a program should behave, by specifying pre-specified states and routes between them.

A Real World Example

Let's imagine a safe lock:


Photo by Micah Williams / Unsplash

Simply, this lock has 2 states, locked and open. Depending on the transitions between these states, these are the routes/transitions.

Let's say every action is a transition, so every button you click on the lock, it will still be in the same state Button pressed.

Only after entering the correct combination, the lock will move to the open state. Afterwards, there is a security timeout, that returns to the locked state after a certain time Timeout expired.

Let's imagine a very simple manual way to code this lock in Javascript.

const OPEN_STATE = "open";
const LOCKED_STATE = "locked";
const lockTimeout = 3000;

class StateMachine {

  constructor(code){
    this.state = LOCKED_STATE;
    this.code = code;
    this.entry = "";
  }
  
  
  enterDigit(digit) {
    this.entry += digit;
  }
  
  unlockDevice() {
    if(this.entry === this.code) {
      this.state = OPEN_STATE;
      setTimeout(this.lockDevice,lockTimeout);
    }
  }
  
  lockDevice() {
        this.state = LOCKED_STATE;
        this.entry = ""; 
  }
  
}

const fsm = new StateMachine("123");
console.log(fsm.state);

fsm.enterDigit("1");
fsm.unlockDevice();
console.log(fsm.state); // prints "locked"

fsm.enterDigit("2");
fsm.unlockDevice();
console.log(fsm.state); // still "locked"

fsm.enterDigit("3");
fsm.unlockDevice();
console.log(fsm.state); // "unlocked"

Every time unlockDevice() is called, it checks the current entry if it matches the code, this is called the transition condition. If true, it allows the state to transition to the next (or previous state).

Here are some examples of FSM libraries in Javascript that you might find useful:

Our use case

At Zalando, we are responsible for building the Guest Checkout Flow to allow non-Zalando customers to be able to purchase without an account. We first started with the basic flow, didn't have much in mind on what's to come.

The basic flow was:

Product Page -> Personal Info -> Address Info -> Payment -> Confirmation -> Receipt

Every page in this design was responsible for the transition to the next page, example:

// product-detail.js

// ...
const buyButtonClicked() => {
    goToPersonalPage();
}
// ...

// personal.js

// ...
const confirmButtonClicked(personalInfo) => {
    if (personalInfoComplete(personalInfo)) {
        goToAddressPage();
    }
}
// ...

But there's one small flaw with this basic, simple design. It's not extendable, not even testable.

Our product team wanted to introduce some new functionality to the flow, namely "Login Functionality", which would completely break the whole design.

The logged in user would have different scenarios, some users have address information filled in, some don't, some have payment, others do not.

If we were to follow the same basic design we started with, we would have tons of if-else statements flying around everywhere, an example of these new flows would be:

Logged in users, without personal info or Address info:
Product Page -> Login -> Personal Info -> Address Info -> Payment -> Confirmation -> Receipt

Logged in users, without payment info:
Product Page -> Login -> Payment -> Confirmation -> Receipt

Logged in users, without address info, BUT HAVE PAYMENT:
Product Page -> Login -> Address -> Confirmation -> Receipt

Logged in users, without payment info:
Product Page -> Login -> Address Info -> Payment -> Confirmation -> Receipt

And what about Guest Users now? Too much if-else.

Enter The State Machine:

This design screams for a state-machine like design, we laid down the states we want, defined some rules between them, and let the state machine do it's magic.

This is a simplified example of how the FSM would work, if you notice, almost all pages return back to the FSM for consultancy on where to go next?. The FSM has validation rules that allows it to decide what to do next, it uses the Redux Store to decide.
We called this function, goNext(). We defined all the possible rules and transitions we have in the system, a fallback would be to just render the product page if the state is not compatible with any of the transitions.

The state machine takes the state, follows through the rules and keeps "going next" until it finally reaches the proper state.

An earlier example of a user with personal + address but with no payment would be:

Personal state: User? Has personal? Yes? Go next.
Address State: Has address? Yes? Go next.
Payment: Has payment? No? stay here.

A challenge to that design

A good challenge to this design was the implementation of going back. The state machine was design to always move forward, right? What happens if the user decides to go back to the previous page? Luckily the Redux State System manages this, however it was not implemented in our initial design with goNext(). The answer is simple. We implemented goPrev(), which would have the same concept of going forward, just the other way around. Same rules apply, different direction. It worked quite well, after ironing out some nasty bugs.

Pros of this FSM Design

  • Easily maintainable, transitions and states are clearly defined
  • Testable, unit tests can easily be written with pre-defined states for multiple scenarios
  • Easily extendable, allowing for new states to be just plugged in along with their rules

Cons of this FSM Design

If some scenarios are not well defined, the FSM just redirects the user to the product page when they were almost in the payment page, for example if some underlying backend service (ex: a payment provider) returns an unexpected response, the Redux state would get corrupted and the FSM wouldn't know what to do, redirecting the user to the product page, leaving the user confused on "what the hell happened to my credit card now?"

We try to cover as much scenarios as possible, also providing the user with a proper error page so that they do not get confused.

A next-step improvement would be allowing the FSM to "re-try" if something fails.

And as they say, computers & humans aren't perfect.