Practice of stateless state machine in code

Posted by fogofogo on Fri, 15 Oct 2021 20:07:37 +0200

Stateless state machine

1: Foreword

In the project, there are often some work orders, applications and so on, which need to flow the status. This kind of demand is generally what conditions are met, and then the state is reversed. These processes are logically similar in structure and can be handled abstractly. Using a general structure can make the system cleaner and the code logic more single.

Ali found an open source, lightweight stateless machine component. After careful study, it is really suitable for decoupling code logic in this scenario. Compared with if else code, it is easier to understand and more elegant.

2: Model of state machine

  • State: state
  • Event: event. The status is triggered by the event and changes
  • Transition: flow, indicating the transition from one state to another
  • External Transition: External Transition, which is the transition between two different states
  • Internal Transition: internal flow, flow between the same states
  • Condition: condition, indicating whether a certain state is allowed to be reached
  • Action: action. What can be done after reaching a certain state
  • StateMachine: state machine

3: How to use

Here we assume that several simple logic needs to be processed:

  • We need to transfer from the status to be approved to the status approved. Before approval, we need to make an audit verification. After approval, we need to send a text message to inform the user.

Realization of external status flow:

  1. Define status enumeration (to be approved, approved, rejected)
 static enum States {
    WAITE, PASS, REJECT
}
  1. Define an event enumeration, including approval event / approval rejection / application submission / internal status flow
static enum Events {
   AUDIT, AUDIT_REJECT, COMMIT, COMPLETE_INFORMATION
}

// Context object
static class Context{
   String operator = "flw";
   String entityId = "7758258";
 }
  1. Configure a state machine according to the above requirements
   StateMachineBuilder<States, Events, Context> builder = StateMachineBuilderFactory.create();
        builder.externalTransition()    // External state flow
                .from(States.WAITE)     // Start status: to be approved
                .to(States.PASS)        // Purpose status: approved
                .on(Events.AUDIT)       // Event: audit event
                .when(checkCondition()) // If the flow requires verification, doAction will not be performed if the verification fails
                .perform(doAction());   // Execute flow operation

   StateMachine<States, Events, Context> stateMachine = builder.build(MACHINE_ID);
   // Print the flow chart in the state machine
   stateMachine.showStateMachine();

   private Condition<StateMachineMyTest.Context> checkCondition() {
        return (ctx) -> {return true;}; // Returns true by default
    }

    private Action<StateMachineMyTest.States, StateMachineMyTest.Events, StateMachineMyTest.Context> doAction() {
        return (from, to, event, ctx)->{
            System.out.println(ctx.operator+" is operating "+ctx.entityId+" from:"+from+" to:"+to+" on:"+event);
        };
    }

Print map results of execution state machine:

-----StateMachine:TestStateMachine-------
State:PASS
State:WAITE
    Transition:WAITE-[AUDIT, EXTERNAL]->PASS
------------------------
  1. Execution state machine
// Perform the audit operation of the status to be audited through the state machine,
States target = stateMachine.fireEvent(States.WAITE, Events.AUDIT, new Context());
Assert.assertEquals(States.PASS, target); // Success will return to the target state machine, and failure will return to the original state

Execution results:

flw is operating 7758258 from:WAITE to:PASS on:AUDIT

If the checkCondition returns false, the doAction operation will not be executed.

Realization of internal state flow

  • It is assumed that users only need to complete the data, and only need to update the data without status flow. This demand can be realized through internal state flow
 @Test
    public void testExternalNormal(){
        StateMachineBuilder<States, Events, Context> builder = StateMachineBuilderFactory.create();
        builder.externalTransition()// Internal circulation
                .from(States.WAITE)
                .to(States.PASS)
                .on(Events.COMPLETE_INFORMATION)
                .when(checkCondition())
                .perform(doAction());

        StateMachine<States, Events, Context> stateMachine = builder.build(MACHINE_ID);
        // Print the flow chart in the state machine
        stateMachine.showStateMachine();
        // Perform the audit operation of the status to be audited through the state machine,
        States target = stateMachine.fireEvent(States.WAITE, Events.AUDIT, new Context());
        Assert.assertEquals(States.WAITE, target);
    }

Execution results:

-----StateMachine:TestStateMachine-------
State:PASS
State:WAITE
    Transition:WAITE-[COMPLETE_INFORMATION, EXTERNAL]->PASS
------------------------

Exceptions in the method will also be thrown, which also supports transaction operations.

The overall flow chart is as follows:

4: Summary

The state machine is very lightweight and supports transaction operations. For the scenario of state flow, the logical decoupling is relatively elegant.

Link: https://github.com/alibaba/COLA/tree/master/cola-components/cola-component-statemachine

Topics: Design Pattern architecture