最近一個(gè)新的項(xiàng)目中的一個(gè)業(yè)務(wù),狀態(tài)的流轉(zhuǎn)比較復(fù)雜,涉及到二十幾個(gè)狀態(tài)的流轉(zhuǎn),而且吸取了其他業(yè)務(wù)教訓(xùn),我們決定使用狀態(tài)機(jī)來解決狀態(tài)流轉(zhuǎn)的問題。
要使用狀態(tài)機(jī)除了自己寫狀態(tài)模式下還研究了當(dāng)下兩個(gè)開源項(xiàng)目,一個(gè)是spring的state machine,一個(gè)是cola-state-machine。
spring的狀態(tài)機(jī)可以做狀態(tài)持久化,和spring結(jié)合比較好,但是太重了。 cola就比較簡單,它只是簡單做了一個(gè)抽象,我們只需要實(shí)現(xiàn)具體的行為就行了。 使用cola最重要的就是要記得"因?yàn)槟硞€(gè)事件,導(dǎo)致了狀態(tài)A向狀態(tài)B進(jìn)行了遷移",當(dāng)然這里的狀態(tài)可以是同一個(gè)。
因?yàn)轫?xiàng)目中使用的是springboot,所以我這里結(jié)合起來做了一定的改造,下面給出我在項(xiàng)目中使用的例子,僅供大家參考
引入依賴
<dependency>
<groupId>com.alibaba.cola</groupId>
<artifactId>cola-component-statemachine</artifactId>
<version>4.3.2</version>
</dependency>
定義
因?yàn)槟硞€(gè)事件,導(dǎo)致了狀態(tài)A向狀態(tài)B進(jìn)行了遷移。所以需要定義狀態(tài),事件,流程。

事件
根據(jù)我們的流程我定義了以下事件
@AllArgsConstructor
@Getter
public enum StatusChangeEventEnum {
// save as draft
SAVE_AS_DRAFT_EVENT,
// draft submit
DRAFT_SUBMIT_EVENT,
// submit
SUBMIT_EVENT;
}
狀態(tài)
@Getter
@AllArgsConstructor
public enum StatusEnum {
NONE(0, "None"),
DRAFT(1, "Draft"),
SUBMITTED(2, "Submitted");
private Integer code;
private String desc;
}
流程
定義狀態(tài)遷移和事件的關(guān)系
@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;
}
}
// 抽象狀態(tài)變更的接口,因?yàn)榭赡軙?huì)存在多個(gè)不同的狀態(tài)變更流程
public interface StatusChange {
StatusEnum from();
StatusEnum to();
StatusChangeEventEnum event();
}
使用Spring管理狀態(tài)機(jī)
@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) {
// 找到對(duì)應(yīng)的handler來處理
StateMachineHandler<StatusEnum, StatusChangeEventEnum, C> handler = getHandler(statusChange);
StatusEnum fromStatus = statusChange.from();
StatusEnum toStatus = statusChange.to();
StatusChangeEventEnum changeEvent = statusChange.event();
// 只產(chǎn)生了事件,但是狀態(tài)未發(fā)生變化
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));
}
// 直接在handler中拋出更詳細(xì)異常
//builder.setFailCallback(new AlertFailCallback<>());
}
private <C> StateMachineHandler<StatusEnum, StatusChangeEventEnum, C> getHandler(StatusChange statusChange) {
return handlerList.stream().filter(handler -> handler.canHandle(statusChange))
.findFirst()
.orElse(new DefaultStateMachineHandler<C>());
}
}
經(jīng)過上面的定義后,后續(xù)有新的狀態(tài)變更流程,我們只需要在 StatusChangeEnum 中添加就行了。
實(shí)現(xiàn)對(duì)應(yīng)的handler
這里我舉一個(gè)例子,比如說首次提交數(shù)據(jù)
@Component
@RequiredArgsConstructor
public class NoneToSubmittedStatusHandler implements
StateMachineHandler<StatusEnum, StatusChangeEventEnum, SubmitDTO> {
@Override
public boolean canHandle(StatusChange statusChange) {
// handler能處理的變更流程
StatusChangeEnum changeEnum = StatusChangeEnum.SUBMIT;
return statusChange == changeEnum;
}
@Override
public void execute(StatusEnum from, StatusEnum to, StatusChangeEventEnum event, SubmitDTO context) {
// 執(zhí)行具體的業(yè)務(wù)邏輯,比如插入數(shù)據(jù)
}
@Override
public boolean isSatisfied(SubmitDTO context) {
// 判斷是否滿足條件,比如是否是對(duì)應(yīng)的用戶等
return true;
}
}
這樣子我們的每一個(gè)handler的功能就比較專一了,只需要處理對(duì)應(yīng)狀態(tài)的就行了,你可能回想要是有些狀態(tài)的變成要做的事情類似,這樣的代碼不可能寫兩遍吧? 其實(shí)我們可以有一個(gè)抽象類可以將這些公用的邏輯放到抽象類里面,這樣子有相同邏輯的就可以使用了。
使用
萬事具備,現(xiàn)在只差在項(xiàng)目中使用了
@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;
}
只要涉及到狀態(tài)變更的,就都可以調(diào)用StateMachie了。
寫到最后
這種方式其實(shí)會(huì)導(dǎo)致類的數(shù)量變多,但是職責(zé)更加清晰,每個(gè)類的代碼行數(shù)也并不多,而且以后想要找某個(gè)狀態(tài)變更到某個(gè)狀態(tài)做了什么時(shí)候很很好找。
這就是最近使用狀態(tài)機(jī)的一些心得,希望能對(duì)你有所幫助。