Recipes

This chapter contains documentation for existing built-in state machine recipes.spring-doc.cn

Spring Statemachine is a foundational framework. That is, it does not have much higher-level functionality or many dependencies beyond Spring Framework. Consequently, correctly using a state machine may be difficult. To help, we have created a set of recipe modules that address common use cases.spring-doc.cn

What exactly is a recipe? A state machine recipe is a module that addresses a common use case. In essence, a state machine recipe is both an example that we have tried to make it easy for you to reuse and extend.spring-doc.cn

Recipes are a great way to make external contributions to the Spring Statemachine project. If you are not ready to contribute to the framework core itself, a custom and common recipe is a great way to share functionality with other users.

Persist

The persist recipe is a simple utility that lets you use a single state machine instance to persist and update the state of an arbitrary item in a repository.spring-doc.cn

The recipe’s main class is PersistStateMachineHandler, which makes three assumptions:spring-doc.cn

  • An instance of a StateMachine<String, String> needs to be used with a PersistStateMachineHandler. Note that states and Events are required to be type of String.spring-doc.cn

  • PersistStateChangeListener needs to be registered with handler to react to persist request.spring-doc.cn

  • The handleEventWithState method is used to orchestrate state changes.spring-doc.cn

You can find a sample that shows how to use this recipe at [statemachine-examples-persist].spring-doc.cn

Tasks

The tasks recipe is a concept to run DAG (Directed Acrylic Graph) of Runnable instances that use a state machine. This recipe has been developed from ideas introduced in [statemachine-examples-tasks] sample.spring-doc.cn

The next image shows the generic concept of a state machine. In this state chart, everything under TASKS shows a generic concept of how a single task is executed. Because this recipe lets you register a deep hierarchical DAG of tasks (meaning a real state chart would be a deeply nested collection of sub-states and regions), we have no need to be more precise.spring-doc.cn

For example, if you have only two registered tasks, the following state chart would be correct when TASK_id is replaced with TASK_1 and TASK_2 (assuming the registered tasks IDs are 1 and 2).spring-doc.cn

statechart9

Executing a Runnable may result an error. Especially if a complex DAG of tasks is involved, you want to have a way to handle task execution errors and then have a way to continue execution without executing already successfully executed tasks. Also, it would be nice if some execution errors can be handled automatically. As a last fallback, if an error cannot be handled automatically, the state machine is put into a state where the user can handle errors manually.spring-doc.cn

TasksHandler contains a builder method to configure a handler instance and follows a simple builder pattern. You can use this builder to register Runnable tasks and TasksListener instances and define StateMachinePersist hook.spring-doc.cn

Now we can take a simple Runnable that runs a simple sleep as the following example shows:spring-doc.cn

private Runnable sleepRunnable() {
	return new Runnable() {

		@Override
		public void run() {
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
			}
		}
	};
}
The preceding example is the base for all of the examples in this chapter.

To execute multiple sleepRunnable tasks, you can register tasks and execute runTasks() method from TasksHandler, as the following example shows:spring-doc.cn

TasksHandler handler = TasksHandler.builder()
		.task("1", sleepRunnable())
		.task("2", sleepRunnable())
		.task("3", sleepRunnable())
		.build();

handler.runTasks();

To listen to what is happening with a task execution, you can register an instance of a TasksListener with a TasksHandler. This recipe provides an adapter TasksListenerAdapter if you do not want to implement a full interface. The listener provides a various hooks to listen tasks execution events. The following example shows the definition of the MyTasksListener class:spring-doc.cn

private class MyTasksListener extends TasksListenerAdapter {

	@Override
	public void onTasksStarted() {
	}

	@Override
	public void onTasksContinue() {
	}

	@Override
	public void onTaskPreExecute(Object id) {
	}

	@Override
	public void onTaskPostExecute(Object id) {
	}

	@Override
	public void onTaskFailed(Object id, Exception exception) {
	}

	@Override
	public void onTaskSuccess(Object id) {
	}

	@Override
	public void onTasksSuccess() {
	}

	@Override
	public void onTasksError() {
	}

	@Override
	public void onTasksAutomaticFix(TasksHandler handler, StateContext<String, String> context) {
	}
}

You can either register listeners by using a builder or register them directly with a TasksHandler as the following example shows:spring-doc.cn

MyTasksListener listener1 = new MyTasksListener();
MyTasksListener listener2 = new MyTasksListener();

TasksHandler handler = TasksHandler.builder()
		.task("1", sleepRunnable())
		.task("2", sleepRunnable())
		.task("3", sleepRunnable())
		.listener(listener1)
		.build();

handler.addTasksListener(listener2);
handler.removeTasksListener(listener2);

handler.runTasks();

Every task needs to have a unique identifier, and (optionally) a task can be defined to be a sub-task. Effectively, this creates a DAG of tasks. The following example shows how to create a deep nested DAG of tasks:spring-doc.cn

TasksHandler handler = TasksHandler.builder()
		.task("1", sleepRunnable())
		.task("1", "12", sleepRunnable())
		.task("1", "13", sleepRunnable())
		.task("2", sleepRunnable())
		.task("2", "22", sleepRunnable())
		.task("2", "23", sleepRunnable())
		.task("3", sleepRunnable())
		.task("3", "32", sleepRunnable())
		.task("3", "33", sleepRunnable())
		.build();

handler.runTasks();

When an error happens and the state machine running these tasks goes into an ERROR state, you can call fixCurrentProblems handler method to reset the current state of the tasks kept in the state machine’s extended state variables. You can then use the continueFromError handler method to instruct the state machine to transition from the ERROR state back to the READY state, where you can again run tasks. The following example shows how to do so:spring-doc.cn

TasksHandler handler = TasksHandler.builder()
		.task("1", sleepRunnable())
		.task("2", sleepRunnable())
		.task("3", sleepRunnable())
		.build();

		handler.runTasks();
		handler.fixCurrentProblems();
		handler.continueFromError();