Building a Finite-State Machine from Scratch Using a Domain-Driven Design Approach
Introduction
Finite-State Machine (FSM) is an essential model of computing used across the software development world. They are used in both modern front-end and back-end development. Some popular libraries such as Redux (an application state library for front-end development) use state machines as the core tool behind its application logic. But what is it exactly? This article aims to break down what FSM is, how to build one from scratch using Domain-Driven Design (DDD), and how we use state machines at SSENSE.
What is a Finite-State Machine (FSM)?
At its core, a state machine is an abstract machine that can be at a single state of any given number of states. The state machine can move from one state to another as a response to a given input, which is referred to as a transition.
Simple everyday devices are common implementations of state machines, these include vending machines, elevators, and traffic lights. A common illustration is a turnstile, which can be one of two states (locked or unlocked), depending on the coin trigger.
How to build a FSM from scratch
Now that we know what a state machine is, why is it useful for modern software development and how do we go about building one? First and foremost, state machines can be used to build domain models, and gracefully add complexity based on the requirements of the model. As domain models evolve, they will need to have representations in different states; state machines can help us implement these various states and how to transition between them.
Using Domain-Driven Design, we can use the concepts of domain modeling as our basis for building our state machine. Let’s use the example of an e-commerce business to build our state machine; a shopping cart is a prime example to use as our domain, and is relevant to various e-commerce scenarios. If you’d like to learn more about DDD, read Domain-Driven Design: Everything You Always Wanted to Know About it, But Were Afraid to Ask, or jump to the bottom of the article for more resources.
Domain Modelling and Setup
Let’s start by building our shopping cart domain using TypeScript to represent the basis of our model and our state class.
Let’s dive into what we implemented on our fictitious Shopping Cart domain model. Our ShoppingCart class represents the interface of interest to clients. It has a reference to an instance of a State subclass, which we will implement in the next steps; this will represent the current state of the ShoppingCart .
We created a transitionTo method on the interface to allow our Shopping Cart domain to transition to a different state at runtime. We also delegated part of the Shopping Cart’s behaviour to the current State object, which we will get into in the following steps.
Implementing the base State Class and Transitions
Our next step is to implement our base state class and the transitions that will build on top of it. For the sake of this article, we will just focus on possible states that a shopping cart can be: ready, locked, and abandoned.
In our base State class, we declared methods that all states should implement, in addition to providing a back-reference to our domain model. This back-reference is extremely important, as it will be used by the client to transition the Shopping Cart to another state.
Let’s see how simple it is to expand upon this base state class.
As shown above, we easily expanded upon our first state transition by building our base State class, we created three states to represent the possible states. Our shopping cart can be ready and transition to locked (i.e. items are added to our shopping cart), but it cannot transition to ready if it is already ready. Likewise, a locked cart can transition to an abandoned cart if we remove items out of our shopping cart, but it cannot transition back to a ready cart.
This design pattern allows us to easily create new states and easily transition between one another. An important aspect to notice, even in this contrived example, is that the decision to allow the state transition can use not only the current state but also additional properties, such as the number of items in the cart.
The State Machine in Action
Using our newly created domain model for our Shopping Cart, base state class, and states (Ready, Locked, and Abandoned), we can tie all three together to see how it could be used in client code.
As observed in the example, we created a new Shopping Cart using our method `createNewShoppingCart()`, this method will set a default state to ready. Once we add items to the cart, we first verify that the number of items has been added in before transitioning into a locked state.
And, that’s it! We have created a robust domain model to represent our Shopping Cart, and have created extendable state transitions to allow our domain model to easily transition from one state to another.
How does Domain-Driven Design apply to this?
Domain-Driven Design is a powerful methodology to model, structure, organize, and represent domains in our code. As shown in the examples above, we applied DDD by making loosely coupled, specific classes, and objects to represent what we were building — a shopping cart in our scenario — and respect the business invariants. DDD is used to make sure that our code is highly tailored to represent the domain we are trying to create; with an emphasis on extensibility and maintainability. We have created easily expandable classes such as our base State class, that can be easily expanded on to create new states and transitions.
State Machines at SSENSE
At SSENSE, we utilize state machines in conjunction with DDD across multiple domains in various applications. The flexibility of state machines can be applied to various aspects relevant to our business. From representing customer orders to inventory changes and transfers between our locations, state machines help us to capture and protect business rules.
Conclusion & Next Steps
Now that we have learned how to build a state machine and applied Domain-Driven Design principles, what’s next? We have merely scratched the surface of what is possible with both state machines and Domain-Driven Design, and there is still much more one can learn to expand upon our knowledge base.
As mentioned earlier, state machines are used across the software development world to build amazing libraries, developer tools, and open source projects. Here are a few examples to get you started on what can be built with state machines:
Redux-Popular open source library to manage state in front end projects
XState-Popular open source state machine library
FSM Academia-Academic research into state machines
Regarding Domain-Driven Design, there are many valuable and informative learnings on how we apply it in real life situations at SSENSE. It is a great way to see how Domain-Driven Design works across a variety of our domains across the SSENSE Tech Team:
Domain-Driven Design: Effective Domain Modelling and its Perks
Domain-Driven Design: Everything You Always Wanted to Know About it, But Were Afraid to Ask
And, that’s a wrap! If you found this useful, don’t forget to hit the clap button and share.
Editorial reviews by Liela Touré, and Mario Bittencourt. Want to work with us? Click here to see all open positions at SSENSE!