大家好,我是陶朱公Boy,
前言
上一篇文章《關于狀態機的技術選型,最后一個真心好》我跟大家聊了一下關于”狀態機“的話題,從眾多技術選型中我也推薦了一款阿里開源的狀態機—“cola-statemachine”, 于是就有小伙伴私信我,自己專案也考慮引入這款狀態機,但網上資料實在太少,能不能系統的介紹一下如何使用這款工具, 讀者有需求,是必須要滿足的,誰叫
也是剛好前段時間因作業需要徒手寫了一個簡易版的作業流引擎(需要滿足任意節點動態編排),里面涉及比較復雜的作業流狀態流轉,之前的if-else方案,實在搞的一團亂麻,自從引入了這款組件,一下子就解放了生產力,
▲原來的狀態(if-else版本)
上面還只是if-else實作版本中很小一部分代碼,基本都是多個switch嵌套,最里面的switch又涉及多個if-else判斷,可維護性和健壯性不言而喻,,
▲改造后的狀態(cola-statemachine版本)
StateMachineBuilder<ProcessStatusEnum, NodeTypeEnum, Context> builder = StateMachineBuilderFactory.create();
builder.internalTransition().within(ProcessStatusEnum.INIT).on(NodeTypeEnum.HEAD).when(alwaysTrue()).perform(doNextProcessStatus());
builder.externalTransition().from(ProcessStatusEnum.INIT).to(END).on(NodeTypeEnum.HEAD).when(checkNextNodeIfEndComponet()).perform(doNextProcessStatus());
builder.externalTransition().from(ProcessStatusEnum.SOURCE_YUNYIN).to(ProcessStatusEnum.SUBMIT_APPLY_PASS).on(NodeTypeEnum.SUBMIT_APPLY_COMPONET).when(checkIfPass()).perform(doNextProcessStatus());
builder.externalTransition().from(ProcessStatusEnum.SOURCE_YUNYIN).to(ProcessStatusEnum.SUBMIT_APPLY_NOT_PASS).on(NodeTypeEnum.SUBMIT_APPLY_COMPONET).when(checkIfNotPass()).perform(doNextProcessStatus());
builder.externalTransition().from(ProcessStatusEnum.SOURCE_FK).to(ProcessStatusEnum.FK_PASS).on(NodeTypeEnum.FK_COMPONET).when(checkIfPass()).perform(doNextProcessStatus());
builder.externalTransition().from(ProcessStatusEnum.SOURCE_FK).to(ProcessStatusEnum.FK_AUDIT_NOT_PASS).on(NodeTypeEnum.FK_COMPONET).when(checkIfNotPass()).perform(doNextProcessStatus());
builder.externalTransition().from(ProcessStatusEnum.SOURCE_FK).to(ProcessStatusEnum.FK_REFUSE).on(NodeTypeEnum.FK_COMPONET).when(checkIfRefuse()).perform(doNextProcessStatus());
builder.externalTransition().from(ProcessStatusEnum.SOURCE_CW).to(ProcessStatusEnum.CW_PASS).on(NodeTypeEnum.CW_COMPONET).when(checkIfPass()).perform(doNextProcessStatus());
builder.externalTransition().from(ProcessStatusEnum.SOURCE_CW).to(ProcessStatusEnum.CW_NOT_PASS).on(NodeTypeEnum.CW_COMPONET).when(checkIfNotPass()).perform(doNextProcessStatus());
builder.externalTransition().from(ProcessStatusEnum.SOURCE_CW).to(ProcessStatusEnum.CW_REFUSE).on(NodeTypeEnum.CW_COMPONET).when(checkIfRefuse()).perform(doNextProcessStatus());
builder.externalTransition().from(ProcessStatusEnum.SOURCE_AUDIT_COMPLETE).to(ProcessStatusEnum.AUDIT_TERMINATE).on(NodeTypeEnum.AUDIT_TERMINATE).when(alwaysTrue()).perform(doNextProcessStatus());
builder.externalTransition().from(SOURCE_OP_CHANGE_LICENSE).to(ProcessStatusEnum.UPDATE_LICENSE_SUCCESS).on(NodeTypeEnum.CHANGE_LICENSE_COMPONET).when(checkIfPass()).perform(doNextProcessStatus());
builder.externalTransition().from(SOURCE_OP_CHANGE_LICENSE).to(ProcessStatusEnum.UPDATE_LICENSE_FAILURE).on(NodeTypeEnum.CHANGE_LICENSE_COMPONET).when(checkIfNotPass()).perform(doNextProcessStatus());
builder.externalTransition().from(SOURCE_END).to(END).on(NodeTypeEnum.TAIL).when(checkCurrentNodeIfEndComponet()).perform(doNextProcessStatus());
return builder.build("processStatusMachine");
這么點代碼基本能滿足復雜作業流狀態流轉,足見這款組件是解決狀態流轉的利器,
github地址:https://github.com/alibaba/COLA/tree/master/cola-components/cola-component-statemachine 目前在github上:Fork:2.4k;Star:8.8k
那接下來,廢話不多說,我們先實戰一把,先學會如何使用它,如果你想更加深入、全面的去了解組件的架構,可以看下架構設計部分章節,
快速開始
接下來,我以一個員工請假案例作為背景,手把手帶大家演示一下如何使用此組件,▲第一步:專案中引入Maven依賴
<dependency>
<groupId>com.alibaba.cola</groupId>
<artifactId>cola-component-statemachine</artifactId>
<version>4.3.1</version>
</dependency>
▲第二步:初始化狀態機
@Configuration
public class StateMachineRegist {
private final String STATE_MACHINE_ID="stateMachineId";
/**
* 構建狀態機實體
*/
@Bean
public StateMachine<ApplyStatusEnum, Event, LeaveContext> stateMachine() {
StateMachineBuilder<ApplyStatusEnum, Event, LeaveContext> stateMachineBuilder = StateMachineBuilderFactory.create();
//員工請假觸發事件
//源狀態和目標狀態一致,我們可以用內部流轉表示
stateMachineBuilder.internalTransition().within(ApplyStatusEnum.LEAVE_SUBMIT).on(Event.EMPLOYEE_SUBMIT).perform(doAction());
//部門主管審批觸發事件(依賴上一個源狀態:LEAVE_SUBMIT)
stateMachineBuilder.externalTransition().from(ApplyStatusEnum.LEAVE_SUBMIT).to(ApplyStatusEnum.LEADE_AUDIT_PASS).on(Event.DIRECTLEADER_AUDIT).when(checkIfPass()).perform(doAction());
stateMachineBuilder.externalTransition().from(ApplyStatusEnum.LEAVE_SUBMIT).to(ApplyStatusEnum.LEADE_AUDIT_REFUSE).on(Event.DIRECTLEADER_AUDIT).when(checkIfNotPass()).perform(doAction());
//hr事件觸發(依賴上一個源狀態:LEADE_AUDIT_PASS)
stateMachineBuilder.externalTransition().from(ApplyStatusEnum.LEADE_AUDIT_PASS).to(ApplyStatusEnum.HR_PASS).on(Event.HR_AUDIT).when(checkIfPass()).perform(doAction());
stateMachineBuilder.externalTransition().from(ApplyStatusEnum.LEADE_AUDIT_PASS).to(ApplyStatusEnum.HR_REFUSE).on(Event.HR_AUDIT).when(checkIfNotPass()).perform(doAction());
return stateMachineBuilder.build(STATE_MACHINE_ID);
}
}
我們執行stateMachine.showStateMachine()方法后,看下狀態機的詳細配置資訊:
上述頂部顯示的"leaveStateMachineId"是我們自定義的狀態機ID值,
我們在看內容左側部分即State值,詳細羅列了我們配置的狀態(包括from和to),這里我們知道總共有五種狀態分別是:
LEAVE_SUBMIT、LEADE_AUDIT_PASS、LEADE_AUDIT_REFUSE、HR_PASS、HR_REFUSE,?這里我們著重看”LEADE_AUDIT_PASS、LEAVE_SUBMIT“兩部分, 這兩個狀態都是代表了狀態機的源狀態,里面包含了多個狀態流轉配置項即Transition部分, Transition代表著狀態的流轉(分內部、外部流轉),當客戶端觸發相應事件,狀態機內部就能回應這個事件,一旦滿足檢查條件,最終就會回傳目標狀態,
▲第三步:使用狀態機狀態機的使用分兩步走:
第一步:獲取狀態機實體
StateMachine<ApplyStatusEnum, Event, LeaveContext> stateMachine = StateMachineFactory.get("leaveStateMachineId");
第二步:向狀態機觸發一個fireEvent事件
ApplyStatusEnum state=stateMachine.fireEvent(ApplyStatusEnum.LEAVE_SUBMIT, Event.EMPLOYEE_SUBMIT,context);
fireEvent方法的第一個入參是源狀態(對應狀態機配置的from),第二個傳遞的是觸發的事件(對應配置的on),第三個引數是一個自定義背景關系引數(對應配置的context),
示例代碼:
@DisplayName("員工提交請假申請單")
@Test
public void employSubmitRequest(){
StateMachine<ApplyStatusEnum, Event, LeaveContext> stateMachine = StateMachineFactory.get("leaveStateMachineId");
LeaveContext context = new LeaveContext();
ApplyStatusEnum state=stateMachine.fireEvent(ApplyStatusEnum.LEAVE_SUBMIT, Event.EMPLOYEE_SUBMIT,context);
Assert.assertEquals(ApplyStatusEnum.LEAVE_SUBMIT.getCode(),state.getCode());
}
@DisplayName("部門主管審批通過")
@Test
public void leaderAuditPass(){
StateMachine<ApplyStatusEnum, Event, LeaveContext> stateMachine = StateMachineFactory.get("leaveStateMachineId");
LeaveContext context = new LeaveContext();
//主管審批通過
context.setIdea(0);
ApplyStatusEnum state=stateMachine.fireEvent(ApplyStatusEnum.LEAVE_SUBMIT, Event.DIRECTLEADER_AUDIT,context);
Assert.assertEquals(ApplyStatusEnum.LEADE_AUDIT_PASS.getCode(),state.getCode());
}
@DisplayName("部門主管審批不通過")
@Test
public void leaderAuditNotPass(){
StateMachine<ApplyStatusEnum, Event, LeaveContext> stateMachine = StateMachineFactory.get("leaveStateMachineId");
LeaveContext context = new LeaveContext();
//主管審批不通過
context.setIdea(1);
ApplyStatusEnum state=stateMachine.fireEvent(ApplyStatusEnum.LEAVE_SUBMIT, Event.DIRECTLEADER_AUDIT,context);
Assert.assertEquals(ApplyStatusEnum.LEADE_AUDIT_REFUSE.getCode(),state.getCode());
}
@DisplayName("HR審批通過")
@Test
public void hrAuditPass(){
StateMachine<ApplyStatusEnum, Event, LeaveContext> stateMachine = StateMachineFactory.get("leaveStateMachineId");
LeaveContext context = new LeaveContext();
//HR通過
context.setIdea(0);
ApplyStatusEnum state=stateMachine.fireEvent(ApplyStatusEnum.LEADE_AUDIT_PASS, Event.HR_AUDIT,context);
Assert.assertEquals(ApplyStatusEnum.HR_PASS.getCode(),state.getCode());
}
@DisplayName("HR審批不通過")
@Test
public void hrAuditNotPass(){
StateMachine<ApplyStatusEnum, Event, LeaveContext> stateMachine = StateMachineFactory.get("leaveStateMachineId");
LeaveContext context = new LeaveContext();
//HR審批不通過
context.setIdea(1);
ApplyStatusEnum state=stateMachine.fireEvent(ApplyStatusEnum.LEADE_AUDIT_PASS, Event.HR_AUDIT,context);
Assert.assertEquals(ApplyStatusEnum.HR_REFUSE.getCode(),state.getCode());
}
上面示例代碼,我以員工請假流程為背景涉及部門審批流程,期間涉及如下幾個狀態:
LEAVE_SUBMIT(1,"已申請"),
LEADE_AUDIT_PASS(2,"直屬領導審批通過"),
LEADE_AUDIT_REFUSE(3,"直屬領導審批失敗"),
HR_PASS(4,"HR審批通過"),
HR_REFUSE(5,"HR審批拒絕");
我用cola-statemachine實作了整個生命周期的狀態流轉,完整代碼我已開源在github上,感興趣的小伙伴可以自取,
github地址:https://github.com/TaoZhuGongBoy/enumstatemachine
架構設計
▲核心語意模型
我們一起看下狀態機的類關系圖,
一個狀態機(StateMachine)包含多個狀態(State),一個狀態(State)包含多個流轉(Transition),一個Transition各包含一個Condition和Action,狀態State分源狀態(Source)和目標狀態(Target),源狀態回應一個事件后,滿足一定觸發條件,經過流轉,執行Action動作,最后回傳Target狀態,語意模型偽代碼如下:
//StateMachine
public class StateMachineImpl<S,E,C> implements StateMachine<S, E, C> {
private String machineId;
//一個狀態機持有多個狀態(from、to)
private final Map<S, State<S,E,C>> stateMap;
...
}
//State
public class StateImpl<S,E,C> implements State<S,E,C> {
protected final S stateId;
//同一個Event可以觸發多個Transition
private Map<E, List<Transition<S, E,C>>> transitions = new HashMap<>();
...
}
//Transition
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;
...
}
▲原始碼決議
原始碼部分,我將從客戶端執行fireEvent方法說起: ▲fireEvent方法@Override
public S fireEvent(S sourceStateId, E event, C ctx) {
isReady();
//根據sourceStateId找到符合條件的Transition
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;
}
//找到transition后執行transit方法(最終執行Action后回傳目標State)
return transition.transit(ctx, false).getId();
}
fireEvent方法內部首先會根據原狀態ID去路由尋找具體的Transition,找到Transition后執行其transit方法,內部會執行perform函式,最侄訓傳目標State,
▲我們再一起看下路由Transition部分即routeTransition方法:
/**
* 路由Transition
* @param sourceStateId 源狀態ID
* @param event 事件
* @param ctx 背景關系引數
* @return
*/
private Transition<S, E, C> routeTransition(S sourceStateId, E event, C ctx) {
//根據源狀態ID查找源狀態實體
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)) {
//一旦匹配when函式內的觸發條件,回傳transition
transit = transition;
break;
}
}
return transit;
}
▲最后我們再一起看下transition.transit方法細節
@Override
public State<S, E, C> transit(C ctx, boolean checkCondition) {
Debugger.debug("Do transition: "+this);
this.verify();
//checkCondition為false或不指定when觸發條件亦或匹配when觸發條件;都將執行自定義的perform函式
if (!checkCondition || condition == null || condition.isSatisfied(ctx)) {
//如果自定義的perform函式有指定,將執行perform函式
if(action != null){
action.execute(source.getId(), target.getId(), event, ctx);
}
return target;
}
Debugger.debug("Condition is not satisfied, stay at the "+source+" state ");
return source;
}
總結
好了,文章即將進入尾聲,讓我們一起做個總結: 前言部分,花了點時間簡單給大家介紹了一下,在多狀態屬性場景中,狀態機給我們帶來的諸多好處, 快速開始部分我比較細致的給大家介紹了代碼層面如何正確使用該組件,也給出了一個基于"員工請假"案例的示例代碼,用狀態機實作內部審批狀態流轉, 架構設計部分我先給大家介紹了一下該組件的核心語意模型,用類圖來渲染,大家一看就能清楚知曉該狀態機的內部構造及內部組件與組件之間的關系,原始碼部分,我從客戶端觸發的fireEvent方法開始,給大家講解了一下它是如何從源狀態開始,回應事件,匹配指定的Transition,執行具體的action動作,回傳目標狀態全程序, 希望看完本文,對你能幫助你更加深入的了解這款優秀的開源狀態機有所幫助,謝謝大家! 本文完,▲寫到最后
作為996的程式員,寫這篇文章基本都是利用作業日下班時間和周六周日雙休的時間才最終成稿,比較不易, 如果你看了文章之后但凡對你有所幫助或啟發,真誠懇請幫忙關注一下作者,點贊、在看此文,你的肯定與贊美是我未來創作最強大的動力,我也將繼續前行,創作出更加優秀好的作品回饋給大家,在此先謝謝大家了!關注我
如果這篇文章你看了對你有幫助或啟發,麻煩點贊、關注一下作者,你的肯定是作者創作源源不斷的動力,
公眾號

里面不僅匯集了硬核的干貨技術、還匯集了像左耳朵耗子、張朝陽總結的高效學習方法論、職場升遷竅門、軟技能,希望能輔助你達到你想夢想之地!
公眾號內回復關鍵字“電子書”下載pdf格式的電子書籍(JAVAEE、Spring、JVM、并發編程、Mysql、Linux、kafka、分布式等)、“開發手冊”獲取阿里開發手冊2本、"面試"獲取面試PDF資料,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/538812.html
標籤:其他
上一篇:Kafka牛逼在哪里?
下一篇:Python中5大模塊的使用教程(collections模塊、time時間模塊、random模塊、os模塊、sys模塊)
