最近一个新的项目中的一个业务,状态的流转比较复杂,涉及到二十几个状态的流转,而且吸取了其他业务教训,我们决定使用状态机来解决状态流转的问题。
要使用状态机除了自己写状态模式下还研究了当下两个开源项目,一个是spring的state machine,一个是cola-state-machine。
spring的状态机可以做状态持久化,和spring结合比较好,但是太重了。 cola就比较简单,它只是简单做了一个抽象,我们只需要实现具体的行为就行了。 使用cola最重要的就是要记得”因为某个事件,导致了状态A向状态B进行了迁移”,当然这里的状态可以是同一个。
因为项目中使用的是springboot,所以我这里结合起来做了一定的改造,下面给出我在项目中使用的例子,仅供大家参考
引入依赖
1 2 3 4 5 6
| <dependency> <groupId>com.alibaba.cola</groupId> <artifactId>cola-component-statemachine</artifactId> <version>4.3.2</version> </dependency>
|
定义
因为某个事件,导致了状态A向状态B进行了迁移。所以需要定义状态,事件,流程。
事件
根据我们的流程我定义了以下事件
1 2 3 4 5 6 7 8 9 10 11
| @AllArgsConstructor @Getter public enum StatusChangeEventEnum { SAVE_AS_DRAFT_EVENT, DRAFT_SUBMIT_EVENT, SUBMIT_EVENT; }
|
状态
1 2 3 4 5 6 7 8 9 10 11 12
| @Getter @AllArgsConstructor public enum StatusEnum {
NONE(0, "None"), DRAFT(1, "Draft"), SUBMITTED(2, "Submitted");
private Integer code; private String desc; }
|
流程
定义状态迁移和事件的关系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| @Getter @AllArgsConstructor public enum StatusChangeEnum implements StatusChange {
SAVE_AS_DRAFT(NONE, DRAFT, SAVE_AS_DRAFT_EVENT), SUBMIT(NONE, SUBMITTED, SUBMIT_EVENT), DRAFT_SUBMIT(DRAFT, SUBMITTED, DRAFT_SUBMIT_EVENT);
private StatusEnum fromStatus; private StatusEnum toStatus; private StatusChangeEventEnum event;
@Override public StatusEnum from() { return fromStatus; }
@Override public StatusEnum to() { return toStatus; }
@Override public StatusChangeEventEnum event() { return event; }
}
public interface StatusChange {
StatusEnum from();
StatusEnum to();
StatusChangeEventEnum event();
}
|
使用Spring管理状态机
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| @Configuration @RequiredArgsConstructor public class StateMachineConfiguration { private final List<StateMachineHandler> handlerList; private static final String NEW_REQUEST_STATE_MACHINE = "newRequestStateMachine";
@Bean(NEW_REQUEST_STATE_MACHINE) public <C> StateMachine<StatusEnum, StatusChangeEventEnum, C> newRequestStateMachine() {
StateMachineBuilder<StatusEnum, StatusChangeEventEnum, C> builder = StateMachineBuilderFactory.create();
for (StatusChangeEnum changeEnum :StatusChangeEnum.values()) { build(builder, changeEnum); } return builder.build(NEW_REQUEST_STATE_MACHINE); }
private <C> void build(StateMachineBuilder<StatusEnum, StatusChangeEventEnum, C> builder, StatusChange statusChange) { StateMachineHandler<StatusEnum, StatusChangeEventEnum, C> handler = getHandler(statusChange);
StatusEnum fromStatus = statusChange.from(); StatusEnum toStatus = statusChange.to(); StatusChangeEventEnum changeEvent = statusChange.event(); if (fromStatus == toStatus) { builder.internalTransition() .within(fromStatus) .on(changeEvent) .when(handler::isSatisfied) .perform((from, to, event, ctx) -> handler.execute(from, to, event, ctx)); } else { builder.externalTransition() .from(fromStatus) .to(toStatus) .on(changeEvent) .when(handler::isSatisfied) .perform((from, to, event, ctx) -> handler.execute(from, to, event, ctx)); } }
private <C> StateMachineHandler<StatusEnum, StatusChangeEventEnum, C> getHandler(StatusChange statusChange) { return handlerList.stream().filter(handler -> handler.canHandle(statusChange)) .findFirst() .orElse(new DefaultStateMachineHandler<C>()); }
}
|
经过上面的定义后,后续有新的状态变更流程,我们只需要在 StatusChangeEnum
中添加就行了。
实现对应的handler
这里我举一个例子,比如说首次提交数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @Component @RequiredArgsConstructor public class NoneToSubmittedStatusHandler implements StateMachineHandler<StatusEnum, StatusChangeEventEnum, SubmitDTO> { @Override public boolean canHandle(StatusChange statusChange) { StatusChangeEnum changeEnum = StatusChangeEnum.SUBMIT; return statusChange == changeEnum; }
@Override public void execute(StatusEnum from, StatusEnum to, StatusChangeEventEnum event, SubmitDTO context) { }
@Override public boolean isSatisfied(SubmitDTO context) { return true; } }
|
这样子我们的每一个handler的功能就比较专一了,只需要处理对应状态的就行了,你可能回想要是有些状态的变成要做的事情类似,这样的代码不可能写两遍吧? 其实我们可以有一个抽象类可以将这些公用的逻辑放到抽象类里面,这样子有相同逻辑的就可以使用了。
使用
万事具备,现在只差在项目中使用了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @Component @RequiredArgsConstructor public class StateMachine {
private final StateMachine<StatusEnum, StatusChangeEventEnum, StatusChangeContext> newRequestStateMachine;
@Transactional(rollbackFor = Exception.class) public void statusChange(StatusChange changeEnum, StatusChangeContext context) { newRequestStateMachine.fireEvent(changeEnum.from(), changeEnum.event(), context); }
}
public abstract class StatusChangeContext { }
@Data public class SubmitDTO extends StatusChangeContext {
private Long id;
private Long String; }
|
只要涉及到状态变更的,就都可以调用StateMachie了。
写到最后
这种方式其实会导致类的数量变多,但是职责更加清晰,每个类的代码行数也并不多,而且以后想要找某个状态变更到某个状态做了什么时候很很好找。
这就是最近使用状态机的一些心得,希望能对你有所帮助。