cola-statemachine

fxz大约 24 分钟状态机状态机开源组件

cola-statemachine

概述

平常后端开发,会面对众多的状态流转,比如电商场景,各种的状态流转就十分复杂,代码东一块西一块维护状态值,代码难以维护,而状态机正好可以解决这个问题。

状态机(State Machine)是一种用于建模对象或系统的不同状态及其之间转移的抽象模型。它通过明确地定义状态、事件、转移条件以及状态之间的转移过程,可以有效地管理和控制系统的复杂性。状态机在处理复杂状态流转时尤其有用,经常用于管理复杂的业务流程,如订单处理、工作流管理、用户权限控制等。通过状态机,可以将系统的状态流转逻辑集中管理,避免状态逻辑散布在代码的各个部分,提升代码的可维护性和可读性。

状态机的基本概念

  1. 状态(State): 系统在某一时刻的具体条件或情形。比如,订单的状态可以是“待支付”、“已支付”、“待发货”、“已发货”等。

  2. 事件(Event): 触发状态转移的事件或动作。例如,用户支付完成的操作可以是“支付完成”事件。

  3. 转移(Transition): 系统从一个状态转移到另一个状态的过程。转移通常由特定的事件触发,并且可以附带条件。例如,“待支付”状态下,当发生“支付完成”事件时,状态转移到“已支付”。

  4. 初始状态(Initial State): 系统启动时的初始状态,比如订单初始状态可能是“待支付”。

  5. 结束状态(Final State): 系统不再进行状态转移时的状态,比如订单的结束状态可能是“已完成”或“已取消”。

状态机的类型

  1. 有限状态机 (Finite State Machine, FSM): 最常见的状态机,只有有限数量的状态,且系统只能处于其中一个状态。每个状态通过事件或条件转移到下一个状态。

  2. 分层状态机 (Hierarchical State Machine, HSM): 允许状态嵌套,提供了更高层次的状态抽象,适合处理复杂的系统。

  3. 并行状态机: 支持多个状态同时进行,可以在不同的状态之间独立地进行状态转换。

实现状态机的方式

  1. 手动编码: 通过条件判断(如 if-else 或 switch-case 语句)实现状态流转,这是最基本的方式,适用于简单的状态流转。

  2. 状态机框架: 使用专门的状态机框架,如 StateMachine(Spring 提供的状态机框架)、Squirrel(轻量级状态机框架),这些框架提供了更加结构化和灵活的方式来管理状态流转。

对比

开源的状态机很多,使用比较多的应该有Spring Statemachine和Squirrel statemachine。他们的优点是功能很完备,缺点也是功能很完备,太重了。

对于大部分项目来说不需要那么多状态机的高级玩法:比如状态的嵌套(substate),状态的并行(parallel,fork,join)、子状态机等等。

同时像Spring Statemachine的实现是有状态的,在并发场景下要么加锁,要么重新new一个实例,单实例显然是线程不安全的。

我们本文着重看一下阿里开源的Cola-StateMachine。

优点缺点
Spring StateMachine1.强大的生命周期管理
2.易于集成
3.良好的文档和社区支持
1.学习曲线较陡峭
2.增加项目复杂性,上手难度
Squirrel StateMachine1.轻量级
2.简单易用
3.性能高效
1.功能相对有限
Cola-StateMachine1.高度可扩展
2.语义清晰、可读性强
3.线程安全
1.文档和社区支持相对较少

使用

Cola-StateMachine包含一下概念:

  • State:状态
  • Event:事件,状态由事件触发,引起变化
  • Transition:流转,表示从一个状态到另一个状态
  • External Transition:外部流转,两个不同状态之间的流转
  • Internal Transition:内部流转,同一个状态之间的流转。可以理解为特定事件的发生不会改变状态,但是会触发一些动作。
  • Condition:条件,表示是否允许到达某个状态
  • Action:动作,到达某个状态之后,可以做什么
  • StateMachine:状态机

作者的文章里做了介绍,可以看一下: https://blog.csdn.net/significantfrank/article/details/104996419open in new window

/**
 * @author dingchenchen
 * @since 2021/1/6
 */
public class StateMachineChoiceTest {

    static class Context{
        private String condition;

        public Context(String condition){
            this.condition = condition;
        }

        public String getCondition() {
            return condition;
        }
    }

    /**
     * 测试选择分支,针对同一个事件:EVENT1
     * if condition == "1", STATE1 --> STATE1
     * if condition == "2" , STATE1 --> STATE2
     * if condition == "3" , STATE1 --> STATE3
     */
    @Test
    public void testChoice(){
        StateMachineBuilder<StateMachineTest.States, StateMachineTest.Events, Context> builder = StateMachineBuilderFactory.create();
      	// 内部转换 STATE1 -》 EVENT1 -》checkCondition1()-》doAction()
        builder.internalTransition()
            .within(StateMachineTest.States.STATE1)
            .on(StateMachineTest.Events.EVENT1)
            .when(checkCondition1())
            .perform(doAction());
      	// 外部转换 STATE1 -》EVENT1 -》checkCondition2() -》STATE2 -》doAction()
        builder.externalTransition()
            .from(StateMachineTest.States.STATE1)
            .to(StateMachineTest.States.STATE2)
            .on(StateMachineTest.Events.EVENT1)
            .when(checkCondition2())
            .perform(doAction());
      	// 外部转换 STATE1 -》EVENT1 -》checkCondition3() -》STATE3 -》doAction()
        builder.externalTransition()
            .from(StateMachineTest.States.STATE1)
            .to(StateMachineTest.States.STATE3)
            .on(StateMachineTest.Events.EVENT1)
            .when(checkCondition3())
            .perform(doAction());

        StateMachine<StateMachineTest.States, StateMachineTest.Events, Context> stateMachine = builder.build("ChoiceConditionMachine");
      
      	// STATE1 -》 EVENT1 -》checkCondition1()-》doAction()
        StateMachineTest.States target1 = stateMachine.fireEvent(StateMachineTest.States.STATE1, StateMachineTest.Events.EVENT1, new Context("1"));
        Assertions.assertEquals(StateMachineTest.States.STATE1,target1);
      
      	// STATE1 -》EVENT1 -》checkCondition2() -》STATE2 -》doAction()
        StateMachineTest.States target2 = stateMachine.fireEvent(StateMachineTest.States.STATE1, StateMachineTest.Events.EVENT1, new Context("2"));
        Assertions.assertEquals(StateMachineTest.States.STATE2,target2);
      
      	// STATE1 -》EVENT1 -》checkCondition3() -》STATE3 -》doAction()
        StateMachineTest.States target3 = stateMachine.fireEvent(StateMachineTest.States.STATE1, StateMachineTest.Events.EVENT1, new Context("3"));
        Assertions.assertEquals(StateMachineTest.States.STATE3,target3);
    }

    private Condition<Context> checkCondition1() {
        return  (ctx) -> "1".equals(ctx.getCondition());
    }

    private Condition<Context> checkCondition2() {
        return (ctx) -> "2".equals(ctx.getCondition());
    }

    private Condition<Context> checkCondition3() {
        return (ctx) -> "3".equals(ctx.getCondition());
    }

    private Action<StateMachineTest.States, StateMachineTest.Events, Context> doAction() {
        return (from, to, event, ctx)->{
            System.out.println("from:"+from+" to:"+to+" on:"+event+" condition:" + ctx.getCondition());
        };
    }
}

原理

连贯接口 Fluent Interfaces

COLA状态机的定义使用了连贯接口Fluent Interfaces,连贯接口的一个重要作用是,限定方法调用的顺序。比如,在构建状态机的时候,我们只有在调用了from方法后,才能调用to方法,Builder模式没有这个功能。

下图中可以看到,我们在使用的时候是被严格限制的:

img
img

实现原理就是通过java的接口,层层嵌套。

我们以这段代码为例:

        StateMachineBuilder<StateMachineTest.States, StateMachineTest.Events, Context> builder = StateMachineBuilderFactory.create();

      builder.externalTransition()
          .from(StateMachineTest.States.STATE1)
          .to(StateMachineTest.States.STATE2)
          .on(StateMachineTest.Events.EVENT1)
          .when(checkCondition2())
          .perform(doAction());

状态机构建工厂

这是构建状态机是的工厂类,通过调用create方法创建状态机builder类。

public class StateMachineBuilderFactory {
  	// 创建状态机构建实现类
    public static <S, E, C> StateMachineBuilder<S, E, C> create(){
        return new StateMachineBuilderImpl<>();
    }
}

状态机builder实现类

上一步返回的状态机builder类用来构建状态机,状态机需要啥,需要状态以及状态转换的逻辑(原状态、目标状态、事件等信息)。

可以看到下面他维护了一个stateMap的map,塞到了状态机的实例中,也就是状态机可以获取到所有注册的状态了,那状态转换的逻辑在那啊,在State这个类里。

所以可以初步得出结论,一个状态机内维护了若干的状态,每一个状态又维护了他若干的转换逻辑(原状态、目标状态、事件等信息)。stateMap是核心。

public class StateMachineBuilderImpl<S, E, C> implements StateMachineBuilder<S, E, C> {

    /**
     * StateMap is the same with stateMachine, as the core of state machine is holding reference to states.
     */
    private final Map<S, State<S, E, C>> stateMap = new ConcurrentHashMap<>();
    private final StateMachineImpl<S, E, C> stateMachine = new StateMachineImpl<>(stateMap);
  	// 失败时的回调
    private FailCallback<S, E, C> failCallback = new NumbFailCallback<>();

  	// 执行外部转换
    @Override
    public ExternalTransitionBuilder<S, E, C> externalTransition() {
        return new TransitionBuilderImpl<>(stateMap, TransitionType.EXTERNAL);
    }

  	// 加了个s 表示一个状态可以转换成若干状态,下崽呢,有啥用吗?
    @Override
    public ExternalTransitionsBuilder<S, E, C> externalTransitions() {
        return new TransitionsBuilderImpl<>(stateMap, TransitionType.EXTERNAL);
    }

  	// 内部转换
    @Override
    public ExternalParallelTransitionBuilder<S, E, C> externalParallelTransition() {
        return new ParallelTransitionBuilderImpl<>(stateMap, TransitionType.EXTERNAL);
    }

  	// 同上
    @Override
    public InternalTransitionBuilder<S, E, C> internalTransition() {
        return new TransitionBuilderImpl<>(stateMap, TransitionType.INTERNAL);
    }

  	// 状态机builder的时候设置失败的回调
    @Override
    public void setFailCallback(FailCallback<S, E, C> callback) {
        this.failCallback = callback;
    }

  	// 创建状态机实例,同时用StateMachineFactory把状态机注册起来,维护到一个map里。其实如果我们用spring的话,注册成bean就行了。。
    @Override
    public StateMachine<S, E, C> build(String machineId) {
        stateMachine.setMachineId(machineId);
        stateMachine.setReady(true);
        stateMachine.setFailCallback(failCallback);
        StateMachineFactory.register(stateMachine);
        return stateMachine;
    }

}

状态机工厂

注册状态机用的。

public class StateMachineFactory {
    static Map<String /* machineId */, StateMachine> stateMachineMap = new ConcurrentHashMap<>();

    public static <S, E, C> void register(StateMachine<S, E, C> stateMachine){
        String machineId = stateMachine.getMachineId();
        if(stateMachineMap.get(machineId) != null){
            throw new StateMachineException("The state machine with id ["+machineId+"] is already built, no need to build again");
        }
        stateMachineMap.put(stateMachine.getMachineId(), stateMachine);
    }

    public static <S, E, C> StateMachine<S, E, C> get(String machineId){
        StateMachine stateMachine = stateMachineMap.get(machineId);
        if(stateMachine == null){
            throw new StateMachineException("There is no stateMachine instance for "+machineId+", please build it first");
        }
        return stateMachine;
    }
}

转换器构建

转换器里封装了状态转换的逻辑,可以看到类里主要的几个熟悉,原状态、目标状态、转换的条件、转换器。

调用on方法传入event,其实会把Transition和event包存在state中,看方法签名的返回值也可以看出:on->when->perform。

class TransitionBuilderImpl<S,E,C> extends AbstractTransitionBuilder<S,E,C> implements ExternalTransitionBuilder<S,E,C>, InternalTransitionBuilder<S,E,C> {


    private State<S, E, C> source;
    private Transition<S, E, C> transition;

    public TransitionBuilderImpl(Map<S, State<S, E, C>> stateMap, TransitionType transitionType) {
        super(stateMap, transitionType);
    }

    @Override
    public From<S, E, C> from(S stateId) {
        source = StateHelper.getState(stateMap, stateId);
        return this;
    }

    @Override
    public To<S, E, C> within(S stateId) {
        source = target = StateHelper.getState(stateMap, stateId);
        return this;
    }

    @Override
    public When<S, E, C> when(Condition<C> condition) {
        transition.setCondition(condition);
        return this;
    }

    @Override
    public On<S, E, C> on(E event) {
        transition = source.addTransition(event, target, transitionType);
        return this;
    }

    @Override
    public void perform(Action<S, E, C> action) {
        transition.setAction(action);
    }


}

状态实现

前面说的:每一个状态又维护了他若干的转换逻辑(原状态、目标状态、事件等信息)。

可以看到eventTransitions这个类其实是维护了state的所有转换逻辑,key是事件,value是当前状态对该事件的转换逻辑。从而验证了我们上面的结论。转换器这个类包含原状态、目标状态、事件等信息。

public class StateImpl<S,E,C> implements State<S,E,C> {
    protected final S stateId;
    private EventTransitions eventTransitions = new EventTransitions();

    StateImpl(S stateId){
        this.stateId = stateId;
    }

    @Override
    public Transition<S, E, C> addTransition(E event, State<S,E,C> target, TransitionType transitionType) {
        Transition<S, E, C> newTransition = new TransitionImpl<>();
        newTransition.setSource(this);
        newTransition.setTarget(target);
        newTransition.setEvent(event);
        newTransition.setType(transitionType);

        Debugger.debug("Begin to add new transition: "+ newTransition);

        eventTransitions.put(event, newTransition);
        return newTransition;
    }

    @Override
    public List<Transition<S, E, C>> addTransitions(E event, List<State<S, E, C>> targets, TransitionType transitionType) {
        List<Transition<S, E, C>> result = new ArrayList<>();
        for (State<S, E, C> target : targets) {
            Transition<S, E, C> secTransition = addTransition(event, target, transitionType);
            result.add(secTransition);
        }

        return result;
    }

    @Override
    public List<Transition<S, E, C>> getEventTransitions(E event) {
        return eventTransitions.get(event);
    }

    @Override
    public Collection<Transition<S, E, C>> getAllTransitions() {
        return eventTransitions.allTransitions();
    }

    @Override
    public S getId() {
        return stateId;
    }

    @Override
    public String accept(Visitor visitor) {
        String entry = visitor.visitOnEntry(this);
        String exit = visitor.visitOnExit(this);
        return entry + exit;
    }

    @Override
    public boolean equals(Object anObject){
        if(anObject instanceof State){
            State other = (State)anObject;
            if(this.stateId.equals(other.getId()))
                return true;
        }
        return false;
    }

    @Override
    public String toString(){
        return stateId.toString();
    }
}

转换器实现

核心是transit方法执行的转换。转换器这个类包含原状态、目标状态、事件等信息。

public class TransitionImpl<S,E,C> implements Transition<S,E,C> {

    private State<S, E, C> source;

    private State<S, E, C> target;

    private E event;

    private Condition<C> condition;

    private Action<S,E,C> action;

    private TransitionType type = TransitionType.EXTERNAL;

    @Override
    public State<S, E, C> getSource() {
        return source;
    }

    @Override
    public void setSource(State<S, E, C> state) {
        this.source = state;
    }

    @Override
    public E getEvent() {
        return this.event;
    }

    @Override
    public void setEvent(E event) {
        this.event = event;
    }

    @Override
    public void setType(TransitionType type) {
        this.type = type;
    }

    @Override
    public State<S, E, C> getTarget() {
        return this.target;
    }

    @Override
    public void setTarget(State<S, E, C> target) {
        this.target = target;
    }

    @Override
    public Condition<C> getCondition() {
        return this.condition;
    }

    @Override
    public void setCondition(Condition<C> condition) {
        this.condition = condition;
    }

    @Override
    public Action<S, E, C> getAction() {
        return this.action;
    }

    @Override
    public void setAction(Action<S, E, C> action) {
        this.action = action;
    }

    @Override
    public State<S, E, C> transit(C ctx, boolean checkCondition) {
        Debugger.debug("Do transition: "+this);
      	// 转换的合法性校验 你不能瞎转换对吧 内部转换不能改变状态。
        this.verify();
      
      	// 是否满足条件
        if (!checkCondition || condition == null || condition.isSatisfied(ctx)) {
            if(action != null){
              	// 满足条件执行action 这一步就是perform方法设置的,用来在转换时执行一些动作
                action.execute(source.getId(), target.getId(), event, ctx);
            }
          	// 这个是转换的目标状态 返回
            return target;
        }

        Debugger.debug("Condition is not satisfied, stay at the "+source+" state ");
        return source;
    }

    @Override
    public final String toString() {
        return source + "-[" + event.toString() +", "+type+"]->" + target;
    }

    @Override
    public boolean equals(Object anObject){
        if(anObject instanceof Transition){
            Transition other = (Transition)anObject;
            if(this.event.equals(other.getEvent())
                    && this.source.equals(other.getSource())
                    && this.target.equals(other.getTarget())){
                return true;
            }
        }
        return false;
    }

    @Override
    public void verify() {
        if(type== TransitionType.INTERNAL && source != target) {
            throw new StateMachineException(String.format("Internal transition source state '%s' " +
                    "and target state '%s' must be same.", source, target));
        }
    }
}

状态机实现

客户端通过调用fireEvent方法触发状态的流转。

前面说了,一个状态机内维护了若干的状态,每一个状态又维护了他若干的转换逻辑(原状态、目标状态、事件等信息)。所以我们在执行状态流转的时候,可以通过状态、事件获取目标状态。

public class StateMachineImpl<S, E, C> implements StateMachine<S, E, C> {

    private String machineId;

    private final Map<S, State<S, E, C>> stateMap;

    private boolean ready;

    private FailCallback<S, E, C> failCallback;

    public StateMachineImpl(Map<S, State<S, E, C>> stateMap) {
        this.stateMap = stateMap;
    }

    @Override
    public boolean verify(S sourceStateId, E event) {
        isReady();

        State sourceState = getState(sourceStateId);

        List<Transition<S, E, C>> transitions = sourceState.getEventTransitions(event);

        return transitions != null && transitions.size() != 0;
    }

    @Override
    public S fireEvent(S sourceStateId, E event, C ctx) {
        isReady();
      	// 通过原状态、事件、上下文判断命中哪个转换器
        Transition<S, E, C> transition = routeTransition(sourceStateId, event, ctx);

        if (transition == null) {
            Debugger.debug("There is no Transition for " + event);
          	// 没有转换器的话 调用失败回调 默认的那个没有任何行为
            failCallback.onFail(sourceStateId, event, ctx);
            return sourceStateId;
        }

      	// 执行转换
        return transition.transit(ctx, false).getId();
    }
  
  	// 转换成多个状态
    @Override
    public List<S> fireParallelEvent(S sourceState, E event, C context) {
        isReady();
        List<Transition<S, E, C>> transitions = routeTransitions(sourceState, event, context);
        List<S> result = new ArrayList<>();
        if (transitions == null||transitions.isEmpty()) {
            Debugger.debug("There is no Transition for " + event);
            failCallback.onFail(sourceState, event, context);
            result.add(sourceState);
            return result;
        }
        for (Transition<S, E, C> transition : transitions) {
            S id = transition.transit(context, false).getId();
            result.add(id);
        }
        return result;
    }

  	// 通过原状态、事件、上下文判断命中哪个转换器
    private Transition<S, E, C> routeTransition(S sourceStateId, E event, C ctx) {
      	// 从map里获取原状态
        State sourceState = getState(sourceStateId);
				
      	// 状态有了通过事件获取转换去
        List<Transition<S, E, C>> transitions = sourceState.getEventTransitions(event);

        if (transitions == null || transitions.size() == 0) {
            return null;
        }

      	// 这里取第一个命中的
        Transition<S, E, C> transit = null;
        for (Transition<S, E, C> transition : transitions) {
            if (transition.getCondition() == null) {
                transit = transition;
            } else if (transition.getCondition().isSatisfied(ctx)) {
                transit = transition;
                break;
            }
        }

        return transit;
    }
  
  	// s版本
    private List<Transition<S,E,C>> routeTransitions(S sourceStateId, E event, C context) {
        State sourceState = getState(sourceStateId);
        List<Transition<S, E, C>> result = new ArrayList<>();
        List<Transition<S, E, C>> transitions = sourceState.getEventTransitions(event);
        if (transitions == null || transitions.size() == 0) {
            return null;
        }

        for (Transition<S, E, C> transition : transitions) {
            Transition<S, E, C> transit = null;
            if (transition.getCondition() == null) {
                transit = transition;
            } else if (transition.getCondition().isSatisfied(context)) {
                transit = transition;
            }
            result.add(transit);
        }
        return result;
    }

    private State getState(S currentStateId) {
        State state = StateHelper.getState(stateMap, currentStateId);
        if (state == null) {
            showStateMachine();
            throw new StateMachineException(currentStateId + " is not found, please check state machine");
        }
        return state;
    }

    private void isReady() {
        if (!ready) {
            throw new StateMachineException("State machine is not built yet, can not work");
        }
    }

    @Override
    public String accept(Visitor visitor) {
        StringBuilder sb = new StringBuilder();
        sb.append(visitor.visitOnEntry(this));
        for (State state : stateMap.values()) {
            sb.append(state.accept(visitor));
        }
        sb.append(visitor.visitOnExit(this));
        return sb.toString();
    }

    @Override
    public void showStateMachine() {
        SysOutVisitor sysOutVisitor = new SysOutVisitor();
        accept(sysOutVisitor);
    }

    @Override
    public String generatePlantUML() {
        PlantUMLVisitor plantUMLVisitor = new PlantUMLVisitor();
        return accept(plantUMLVisitor);
    }

    @Override
    public String getMachineId() {
        return machineId;
    }

    public void setMachineId(String machineId) {
        this.machineId = machineId;
    }

    public void setReady(boolean ready) {
        this.ready = ready;
    }

    public void setFailCallback(FailCallback<S, E, C> failCallback) {
        this.failCallback = failCallback;
    }
}