大家好,我是陶朱公Boy。

前言

上一篇文章《关于状态机的技术选型,最后一个真心好》我跟大家聊了一下关于”状态机“的话题。
于是就有小伙伴私信我,自己项目也考虑引入cola-statemachine这款状态机,但网上资料实在太少,能不能系统的介绍一下如何使用这款工具。
读者有需求,是必须要满足的,谁叫
 
 
也是刚好前段时间因工作需要徒手写了一个简易版的工作流引擎(需要满足任意节点动态编排),里面涉及比较复杂的工作流状态流转,之前的if-else方案,实在搞的一团乱麻,自从引入了这款组件,一下子就解放了生产力。

▲原来的状态

 
上面还只是if-else实现版本中很小一部分代码,基本都是多个switch嵌套,最里面的switch又涉及多个if-else判断,可维护性和健壮性不言而喻。。
 

▲改造后的状态(完整版)

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资料。

github上fork2.4k,star8.7k的这款状态机,原来长这样!的更多相关文章

  1. python爬虫---->github上python的项目

    这里面通过爬虫github上的一些start比较高的python项目来学习一下BeautifulSoup和pymysql的使用.我一直以为山是水的故事,云是风的故事,你是我的故事,可是却不知道,我是不 ...

  2. Github上值得关注的前端项目-转自好友trigkit4

    http://microjs.com/# 该网站的资源都托管到了github,microjs.com是一个可以让你选择微型的js类库的网站,该网站里的js库都是压缩后不大于5KB的,非常实用 http ...

  3. Github上的沙雕项目,玩100遍都不够

    这段时间大家在家自我隔离.居家办公憋坏了吧.为了打发这种无聊的生活,我决定拿出我在github上珍藏多年的沙雕项目,让大家在无聊的时候可以打发时间. Github作为互联网上最大的开源社区,一直备受程 ...

  4. 工作中经常用到github上优秀、实用、轻量级、无依赖的插件和库

    原文收录在我的 GitHub博客 (https://github.com/jawil/blog) ,喜欢的可以关注最新动态,大家一起多交流学习,共同进步,以学习者的身份写博客,记录点滴. 按照格式推荐 ...

  5. GitHub上个最有意思的项目合集(技术清单系列)

    没有1K以上的星星都不好意思推荐给大家!林子大了,啥项目都有,这里给大家搜罗了10个Github上有趣的项目.如果你就着辣椒食用本文,一定会激动的流下泪来...... 1.一行代码没有 | 18k s ...

  6. Github 上热门的 Spring Boot 项目实战推荐

    最近经常被读者问到有没有 Spring Boot 实战项目可以学习,于是,我就去 Github 上找了 10 个我觉得还不错的实战项目.对于这些实战项目,有部分是比较适合 Spring Boot 刚入 ...

  7. GitHub 上的 12306 抢票神器,助力回家过年

    又到周末了,不过本周末有些略微的特殊. 距离每年一次的全球最大规模的人类大迁徙活动已经只剩下一个多月了,各位在外工作一年的小伙伴大多数人又要和小编一样摩拳擦掌的对待史上最难抢的抢票活动. 然鹅,身为一 ...

  8. 工作中经常用到 github 上优秀、实用、轻量级、无依赖的插件和库

    原文收录在 GitHub博客 ( https://github.com/jawil/blog ) ,喜欢的可以关注最新动态,大家一起多交流学习,共同进步,以学习者的身份写博客,记录点滴. 由于gith ...

  9. 清空Github上某个文件的历史版本

    title: 清空Github上某个文件的历史版本 author: 青南 date: 2015-01-08 16:04:53 categories: [经验] tags: [Github,histor ...

  10. 手把手教从零开始在GitHub上使用Hexo搭建博客教程(四)-使用Travis自动部署Hexo(2)

    前言 前面一篇文章介绍了Travis自动部署Hexo的常规使用教程,也是个人比较推荐的方法. 前文最后也提到了在Windows系统中可能会有一些小问题,为了在Windows系统中也可以实现使用Trav ...

随机推荐

  1. vCenter 升级错误 VCSServiceManager 1603

    近日,看到了VMware发布的vCenter 6.7 Update 1b的更新消息.其中有一条比较震撼.有误删所有VM的概率,这种BUG谁也承受不起. Removing a virtual machi ...

  2. KMP&Z函数详解

    KMP 一些简单的定义: 真前缀:不是整个字符串的前缀 真后缀:不是整个字符串的后缀 当然不可能这么简单的,来个重要的定义 前缀函数: 给定一个长度为\(n\)的字符串\(s\),其 \(前缀函数\) ...

  3. 万星开源项目强势回归「GitHub 热点速览 v.22.38」

    本周霸榜的 GitHub 项目多为老项目,比如:老面孔的 theatre 凭借极其优秀的动画功底连续三天霸榜 TypeScript 类目.借 Figma 被 Adobe 收购之风,又出现在 GitHu ...

  4. Python数据科学手册-Pandas:数据取值与选择

    Numpy数组取值 切片[:,1:5], 掩码操作arr[arr>0], 花哨的索引 arr[0, [1,5]],Pandas的操作类似 Series数据选择方法 Series对象与一维Nump ...

  5. Python微服务实战

    文档地址:https://www.qikqiak.com/tdd-book/ GitHub地址:https://github.com/cnych/flask-microservices-users 源 ...

  6. fastdfs-zyc管理FastDFS的web界面

    俩压缩包根据大小重命名以下,按图片所示 把1_fastdfs-zyc.7z重命名为fastdfs-zyc.7z.001 把2_fastdfs-zyc.7z重命名为fastdfs-zyc.7z.002 ...

  7. 天翼云上新增IP备案具体操作步骤

    0.点击右上角的备案,进入到备案中心 1.已备案信息管理 点击左侧的已备案信息管理,右侧出现的页面中找到已备案网站信息,网站负责人后面的操作里有5个图标,点击第三个(变更接入),提交订单,进入到下一步 ...

  8. Elastic:Elastic Maps 基于位置的警报 - 7.10

    文章转载自:https://elasticstack.blog.csdn.net/article/details/112535618

  9. td-agent的v2,v3,v4版本区别

    官方地址:https://docs.fluentd.org/quickstart/td-agent-v2-vs-v3-vs-v4

  10. PTA 520钻石争霸赛 2021

    7-1 自动编程 签到题 #include<bits/stdc++.h> typedef long long ll; const int maxm = 1e5 + 5; const int ...