Tasks StateMachine

Tasks StateMachine

The State Machine design pattern has been around for a very long time. Though there is a debate as to why a lot of programmers do not like to use it (see why developers never use state machines).

Spring has two projects that have a lot in common: Spring Integration, Spring StateMachine. Spring integration is a framework based on the Integration Patterns, and gives a very good solution for integrating different processes and systems (another competition is camel).

The State Machine project is a more specific solution of Spring Integration to state machines. The main difference is that in Spring Integration there is no state (all state is on the message), while a state machine has states and transitions. In addition a state machine is a DAG with specific logic, where the input to the machine can be external events.

The infrastructure of the spring statemachine is very rich, which includes sub graphs, and even a beta for distributed machine. In addition you have events for processes in the state machine, so monitoring and logging is very easy. In addition any error during the process goes to an error handler, so it is easy to build a flow including error handling.

The basic building blocks for the state machine, are states and transitions. For each state you can transit to new states based on the current event that is sent to the state machine. So to create the DAG, you need to define the transition for each state to the next with an event and action.

Though there are some cases where we have a simple State Machine where the graph is a strait line: each task moves to the next task. Spring does support this and gives an example as how to do it: statemachine-examples-tasks. The disadvantage of this solution is that we really need only one event (next), and there is a lot of boilerplate code to implement this. In addition each state needs to send an internal message to move to the next state.

What I had expected was an easy way to just list a list of actions and to have them automatically linked one to another.

Another issue is that with a standard state machine, the event to move within the machine is usually an external event. In our case each action needs to send an event to the machine to move to the next action.

Task Solution

For the implement, we need the following building blocks:

Event to move from one action to the next:

public enum TaskEvent {
  Next, Finally
}

We enhanced this so that we also can support a finally clause. This means that if there is any exception in the chain, all finally actions will be done before calling the error handler (so if you need to send emails or run summaries, this is the place).

The states you need to define for yourself, but for example:

public enum TaskStates {
   DataIDLE, DataA, DataB, DataC, DataFinish;
}

Then the code to create the state-machine is very short and simple:

StateMachineConfigFactory<TaskStates> smf = new StateMachineConfigFactory<>(TaskStates.DataIDLE,
     TaskStates.DataFinish, handleError(), null, endStep(TaskStates.DataFinish));
List<StageTask<TaskStates>> tasks = new ArrayList<>();
tasks.add(dataATask);
tasks.add(dataBTask);
tasks.add(dataCTask);

final StateMachineBuilder.Builder<TaskStates, TaskEvent> builder = smf.getConfig(tasks);

builder.configureConfiguration()
     .withConfiguration()
     .autoStartup(true)
     .taskExecutor(new SyncTaskExecutor());

As you can see, the order of the list will be the order of the actions that will be executed in the state-machine. You pass in the error handling method, with the start and finish states (once the state machine reaches the end it will be shutdown).

For the full code of the task state-machine please see:

https://github.com/chaimt/TurelUtils/tree/master/src/main/java/com/turel/spring/statemachine

Enhancements

The following are enhancements to the basic pattern.

Any error that happens during the flow will be logged as an error. Though most of the time the errors might be logical errors that the flow ends, but the errors are only warnings and not errors.

To solve this issue we have added our own exception: SMException. If you throw the exception with a message then the framework will assume that it is a warning, and if you throw the exception with a cause then the log event will be logged as an error.

Summary

As you can see spring brings a lot of tools to the table, and they are usually very easy to enhance to your specific needs.

Backend/Data Architect

Backend Group
Thank you for your interest!

We will contact you as soon as possible.

Send us a message

Oops, something went wrong
Please try again or contact us by email at info@tikalk.com