spring flow state machine core

Spring state machine for entities, not for application

License

License

Categories

Categories

SBE Data Data Structures
GroupId

GroupId

ru.sberned.statemachine
ArtifactId

ArtifactId

state-machine-core
Last Version

Last Version

1.1.1
Release Date

Release Date

Type

Type

jar
Description

Description

spring flow state machine core
Spring state machine for entities, not for application
Project Organization

Project Organization

Pivotal Software, Inc.

Download state-machine-core

How to add to project

<!-- https://jarcasting.com/artifacts/ru.sberned.statemachine/state-machine-core/ -->
<dependency>
    <groupId>ru.sberned.statemachine</groupId>
    <artifactId>state-machine-core</artifactId>
    <version>1.1.1</version>
</dependency>
// https://jarcasting.com/artifacts/ru.sberned.statemachine/state-machine-core/
implementation 'ru.sberned.statemachine:state-machine-core:1.1.1'
// https://jarcasting.com/artifacts/ru.sberned.statemachine/state-machine-core/
implementation ("ru.sberned.statemachine:state-machine-core:1.1.1")
'ru.sberned.statemachine:state-machine-core:jar:1.1.1'
<dependency org="ru.sberned.statemachine" name="state-machine-core" rev="1.1.1">
  <artifact name="state-machine-core" type="jar" />
</dependency>
@Grapes(
@Grab(group='ru.sberned.statemachine', module='state-machine-core', version='1.1.1')
)
libraryDependencies += "ru.sberned.statemachine" % "state-machine-core" % "1.1.1"
[ru.sberned.statemachine/state-machine-core "1.1.1"]

Dependencies

compile (4)

Group / Artifact Type Version
org.springframework.boot : spring-boot-starter jar 1.5.3.RELEASE
org.springframework : spring-tx jar
com.google.guava : guava jar 20.0
org.slf4j : slf4j-api jar 1.7.25

test (4)

Group / Artifact Type Version
org.springframework.boot : spring-boot-starter-test jar 1.5.3.RELEASE
org.springframework.boot : spring-boot-starter-jdbc jar 1.5.3.RELEASE
com.h2database : h2 jar 1.4.195
org.hsqldb : hsqldb jar 1.8.0.10

Project Modules

There are no modules declared in this project.

Spring Flow State Machine badge Build Status License MIT yellow badgeMaintainability

Goal

This project was born when we understood that spring-statemachine doesn’t meet our requirements. Our requirements are very simple:

  • It should be easy to use

  • It should be easy to integrate

  • It should manage not the state of our application, but the state of entities, which our application works with

  • It should be easy to configure and extend it

Actually, spring-statemachine doesn’t meet any of requirements.

Almost every system we work with has some entities which have its own lifecycle.

So we’ve created our own library, which allows you to achieve the following goals:

  • Define your own states for any entity

  • Define allowed transitions between states

  • Define actions, which should be performed before and after specific transition

  • Define actions, which should be performed before/after any transition

Non-goals

It is a non-goal to replace spring-statemachine (and we didn’t)

It is a non-goal to provide connectors to any available storage provider

Components

  1. core module provides all needed functionality for you to use spring-flow-statemachine

  2. starter modules is Spring Boot Starter, which will configure as much as possible in your application for you.

Installation

Spring Boot

If you use Spring Boot then you can set up dependency on starter package

Maven

<dependency>
    <groupId>ru.sberned.statemachine</groupId>
    <artifactId>spring-flow-state-machine-starter</artifactId>
    <version>1.1.1</version>
</dependency>

Gradle

compile 'ru.sberned.statemachine:spring-flow-state-machine-starter:1.1.1'

Spring without Boot

Otherwise you can set up dependency on state-machine-core artefact

How To

Configure

Step 1: Define state changer

StateChanger is interface, which implementations are responsible for moving entity from one state to another (i.e. it can just update one field in your relational DB)

Step 2: Define item provider

ItemWithStateProvider is interface which is responsible for finding object in store

Step 3: Define lock provider

It’s always possible in distributed systems that several nodes of your system will want to change the state of your entity simultaneously. We’re prepared for this case and ask you to provide us with Lock which will guarantee, that only one transition can happen at a time. If you work in single-node environment — you can just return simple in-memory, otherwise you’ll need something distributed.

Interface, which should be implemented for this aim is LockProvider. By default we provide you with very simple lock implementation, MapLockProvider

Step 4: Create and configure state machine

    @Bean
    public StateMachine<SimpleItem, SimpleState, String> stateMachine() {
        StateRepository<SimpleItem, SimpleState, String> repository = StateRepositoryBuilder.<SimpleItem, SimpleState, String>configure() //
                .setAvailableStates(EnumSet.allOf(SimpleState.class)) //
                .setUnhandledMessageProcessor((item, state, type, ex) -> LOGGER.error("Got unhandled item with id {}, issue is {}", item, type)) //
                .setAnyBefore((BeforeAnyTransition<SimpleItem, SimpleState>) (item, state) -> {
                    LOGGER.info("Started working on item with id {}", item.getId());
                    return true;
                }) //
                .defineTransitions()
                .from(STARTED) //
                .to(IN_PROGRESS) //
                .after((AfterTransition<SimpleItem>) item -> LOGGER.info("Moved from STARTED to IN_PROGRESS"))
                .and() //
                // omitted for brevity
                .build();

        StateMachine<SimpleItem, SimpleState, String> stateMachine = new StateMachine<>(stateProvider(), stateChanger(), lockProvider); //
        stateMachine.setStateRepository(repository); //
        return stateMachine;
    }

① — Everything starts with strongly-typed StateRepository#configure method, where we define

  1. Entity class

  2. State class

  3. Key class (it should be possible to fetch item with its state from your store by key of this type)

② — We think that it should be possible to use not all of the available states (i.e. if your application is in early stages of development), so you should pass subset of allowed states into method setAvailableStates

③ — You should, but not necessarily, provide an implementation of UnhandledMessageProcessor. It’s always possible in distributed system that something will go wrong and we give you the ability to handle this.

④ — You can define several types of handlers for your state machine:

  1. anyBefore handlers will be executed before any transition

  2. before handlers will be executed before concrete transition

  3. after handlers will be executed after the concrete transition

  4. anyAfter handlers will be executed after any transition

⑤ — from should be read as "Transition may start at any of these states"

⑥ — to should be read as "and can stop at any of these ones"

⑦ — and is delimiter method between defining several transition rulesets

⑧ — Create StateMachine itself

⑨ — Configure state machine behavior rules by providing it with StateRepository

Use

You have 2 ways to interact with state machine

Inject StateMachine

If you choose to inject StateMachine into your service, then you can call changeState method. It returns map of your entity id to Future of results of execution

Use event publisher

You can inject ApplicationEventPublisher into your service and send `StateChangedEvent`s there. It is the type of one-way communication when you actually don’t care about the final result.

On-the fly state loading

Since 1.1.0 You can load states and transitions on the fly in runtime, but you need to think about several things:

  1. All states should have correctly defined equals and hashCode methods

  2. If you update state repository at runtime — it’s your responsibility to make all items not to have removed state as current (if any). But it’s considered to be a bad practice to remove states at runtime.

  3. If you update state repository in your state machine then no guarantees is made for currently queued transitions. It’s entirely possible that some of them will fail because of new rules.

You can watch an example of dynamically loading and changing repository in samples

Requirements

Project requires Java 8 and Spring 4+

Tests and readiness

We’ve done our best to write as many tests as we can. Also, we use this project at work, so we think that this project is production-ready

Examples

You can find examples of usage in state-machine-samples module

Release notes

  • 1.1.1: Fixes multithreading bug. Raises pitest coverage of core classes to 100%

  • 1.1.0: Adds ability to use non-enum states

  • 1.0.2: Initial release

Versions

Version
1.1.1
1.1.0
1.0.2