cola-statemachine
cola-statemachine
概述
平常后端开发,会面对众多的状态流转,比如电商场景,各种的状态流转就十分复杂,代码东一块西一块维护状态值,代码难以维护,而状态机正好可以解决这个问题。
状态机(State Machine)是一种用于建模对象或系统的不同状态及其之间转移的抽象模型。它通过明确地定义状态、事件、转移条件以及状态之间的转移过程,可以有效地管理和控制系统的复杂性。状态机在处理复杂状态流转时尤其有用,经常用于管理复杂的业务流程,如订单处理、工作流管理、用户权限控制等。通过状态机,可以将系统的状态流转逻辑集中管理,避免状态逻辑散布在代码的各个部分,提升代码的可维护性和可读性。
状态机的基本概念
状态(State): 系统在某一时刻的具体条件或情形。比如,订单的状态可以是“待支付”、“已支付”、“待发货”、“已发货”等。
事件(Event): 触发状态转移的事件或动作。例如,用户支付完成的操作可以是“支付完成”事件。
转移(Transition): 系统从一个状态转移到另一个状态的过程。转移通常由特定的事件触发,并且可以附带条件。例如,“待支付”状态下,当发生“支付完成”事件时,状态转移到“已支付”。
初始状态(Initial State): 系统启动时的初始状态,比如订单初始状态可能是“待支付”。
结束状态(Final State): 系统不再进行状态转移时的状态,比如订单的结束状态可能是“已完成”或“已取消”。
状态机的类型
有限状态机 (Finite State Machine, FSM): 最常见的状态机,只有有限数量的状态,且系统只能处于其中一个状态。每个状态通过事件或条件转移到下一个状态。
分层状态机 (Hierarchical State Machine, HSM): 允许状态嵌套,提供了更高层次的状态抽象,适合处理复杂的系统。
并行状态机: 支持多个状态同时进行,可以在不同的状态之间独立地进行状态转换。
实现状态机的方式
手动编码: 通过条件判断(如 if-else 或 switch-case 语句)实现状态流转,这是最基本的方式,适用于简单的状态流转。
状态机框架: 使用专门的状态机框架,如
StateMachine
(Spring 提供的状态机框架)、Squirrel
(轻量级状态机框架),这些框架提供了更加结构化和灵活的方式来管理状态流转。
对比
开源的状态机很多,使用比较多的应该有Spring Statemachine和Squirrel statemachine。他们的优点是功能很完备,缺点也是功能很完备,太重了。
对于大部分项目来说不需要那么多状态机的高级玩法:比如状态的嵌套(substate),状态的并行(parallel,fork,join)、子状态机等等。
同时像Spring Statemachine的实现是有状态的,在并发场景下要么加锁,要么重新new一个实例,单实例显然是线程不安全的。
我们本文着重看一下阿里开源的Cola-StateMachine。
优点 | 缺点 | |
---|---|---|
Spring StateMachine | 1.强大的生命周期管理 2.易于集成 3.良好的文档和社区支持 | 1.学习曲线较陡峭 2.增加项目复杂性,上手难度 |
Squirrel StateMachine | 1.轻量级 2.简单易用 3.性能高效 | 1.功能相对有限 |
Cola-StateMachine | 1.高度可扩展 2.语义清晰、可读性强 3.线程安全 | 1.文档和社区支持相对较少 |
使用
Cola-StateMachine包含一下概念:
- State:状态
- Event:事件,状态由事件触发,引起变化
- Transition:流转,表示从一个状态到另一个状态
- External Transition:外部流转,两个不同状态之间的流转
- Internal Transition:内部流转,同一个状态之间的流转。可以理解为特定事件的发生不会改变状态,但是会触发一些动作。
- Condition:条件,表示是否允许到达某个状态
- Action:动作,到达某个状态之后,可以做什么
- StateMachine:状态机
作者的文章里做了介绍,可以看一下: https://blog.csdn.net/significantfrank/article/details/104996419
/**
* @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模式没有这个功能。
下图中可以看到,我们在使用的时候是被严格限制的:
实现原理就是通过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;
}
}