Alex Biddle     About     Archive     Feed

Create a simple, persistable state machine using enums and Spring Boot

In my post about SymfonyCon a while ago, I went to an interesting talk about state machines.

I have since used state machines a few times in e-commerce. I read a StackOverflow post that shows how to implement a state machine in Java.

I thought it would be interesting to couple this idea with JPA’s enumeration support so that it is easily persistable. There is actually a Spring State Machine project, but if something simpler could work, it is just too heavyweight.

The project was easy to setup using the Spring Initializr using the JPA and Test dependencies, coupled with H2 and jaxb-api (if you’re using Java > 8). The actual repository is on GitHub.

First, I created a Product entity (getters and setters omitted), which stores its state through an enum called ProductState.

@Entity
public class Product
{
    @Id
    @GeneratedValue
    private UUID id;

    @Column(nullable = false)
    @Enumerated(STRING)
    private ProductState productState = CREATED;

    @UpdateTimestamp
    private OffsetDateTime updatedAt;
}

If you just have the H2 database dependency in your project, this is enough for Spring Boot to tell Hibernate to automatically generate the database definition, as documented in the Spring Boot Common Application properties reference.

spring.jpa.hibernate.ddl-auto= # DDL mode. This is actually a shortcut for the “hibernate.hbm2ddl.auto” property. Defaults to “create-drop” when using an embedded database and no schema manager was detected. Otherwise, defaults to “none”.

Next we have the ProductState enum:

public enum ProductState
{
    CREATED
    {
        @Override
        public ProductState process(ProductEvent productEvent)
        {
            switch (productEvent)
            {
                case NO_STOCK_FOUND:
                    return OUT_OF_STOCK;
                case ACCEPT:
                    return PROCESSING;
                default:
                    throw new RuntimeException(String.format(UNRECOGNISED_EVENT, productEvent));
            }
        }
    };
    [snip]
    private static final String UNRECOGNISED_EVENT = "Unrecognised event %s";

    public abstract ProductState process(ProductEvent profileEvent);

    ProductState()
    {
    }
}

The idea is, is that the enumeration can be loaded from the database, and then can respond it through the process function. The events themselves are represented in another enum ProductEvent.

public enum ProductEvent
{
    NO_STOCK_FOUND,
    ACCEPT,
    DISPATCH,
    REDELIVER,
    DELIVER
}

The events are imperative actions. I have used a switch statement so that the states can respond to events. I have also seen this implemented using a table, which might be clearer, however it works for now.

One of the benefits of a state machine is that it is easier to debug than a long collection of if and else statements: you can look at the state in the database, and go directly to the state in the code, and look at the route it is allowed to take.

In the GitHub repository, the state machine can also handle the pathway of two failed deliveries, which results in the product being returned to the business. This avoids the need for an extra column in the database, such as “delivery attempts”, and the logic to branch at a certain number of attempts, as the product itself “knows where to go”.

All the client needs to do is send ProductEvents to the enumeration’s process function. I encapsulated this functionality into the Product entity itself to follow the “tell, don’t ask” principle.

product.processEvent(NO_STOCK_FOUND);
assertThat(actualProduct.getProductState(), is(OUT_OF_STOCK));

Have a look at the Github repository for a the whole code sample and some tests.