状态机整体设计:
说明:
- 基本要素:状态(state)、事件(event)、流转(transition)、虚拟状态(virtual state)、条件(guard)、条件分支、默认分支;
- 基本流程:图中"开始"状态接收到事件"E1"后切换至"S1"状态,完成一次状态流转;
- 自转场景:图中"S1"接收"E11"后状态未发生改变,同样视为完整一次状态流转;
- 分叉场景:图中"S1"到"S21"或"S22"的状态实际流转需要根据不同的条件选择,这里插入一个虚拟状态"选择状态1","S1"到"选择状态1"的流转由事件"E2"触发,而"选择状态1"到"S21"或"S22"的流转则由于虚拟状态的存在自动进行;
- 子任务场景:图中"S3"到"S4"的流转需要多个"E4"事件触发,这里同样插入虚拟状态"选择状态2"来实现,不同的是将默认分支回溯到"S3"。
单个流程详细实现
1.同步调用
2.异步调用
图中按时间顺序包含两个部分:业务端发送事件并返回结果、状态机处理事件回调业务系统完成状态流转,两个部分分别在两个数据库事务中。
条件动态设计方案——表达式语言
状态机的应用需要各自业务系统根据自身的业务逻辑动态设计,对于状态机中逻辑条件的实现,这里选择使用java表达式语言,备选方案有Spring Expression Language(以下简称SpEL)和MVEL。
根据官方文档介绍,SpEL的诞生是为了给Spring社区提供一种能够与Spring生态系统所有产品无缝对接,能提供一站式支持的表达式语言。考虑到Spring的框架的应用,SpEL是是最合适的选择,后续研究发现在以下场景中支持不友好:订单存在子订单,并且订单状态需要根据所有子订单的信息来综合考虑流转情况,SpEL实现遍历集合的方案是通过注册函数使用Java Stream的方式,与MVEL支持原生foreach循环相比复杂极大提高,而考虑到状态机条件使用场景,最终选择MVEL的方式。
状态机设计交互数据格式
{
"smInfo": {
"smId": null, // long 状态机id,保存时需要,新增时为空
"flowId": null, // long 流程id
"version": null, // string 版本
"smStatus": null, // integer 状态机状态(1:测试中,2:已发布,3:已上线,4:已停用)
"creator": null, // string 创建人
"modifier": null // string 修改人
},
"smStates": [{
"stateId": null, // long 状态id,保存时需要,新增时为空
"stateValue": null, // string 状态值,状态唯一编码,同一个状态机下 不可重复
"stateDesc": null, // string 状态描述
"stateType": null, // integer 状态类型:1常规状态,2选择状态
"smId": null, // long 状态机id
}],
"smTransitions": [{
"transitionId": null, // long 流转id,保存时需要,新增时为空
"transitionDesc": null, // string 流转描述:便于标识流转,存在事件的时候建议跟事件描述一致
"smId": null, // long 状态机id
"eventValue": null, // string 事件值(code)
"eventDesc": null, // string 事件描述
"eventRole": null, // integer 事件角色,多个角色可以使用逗号隔开:1卖家(商家),2买家(用户)
"showCondition": null, // string 事件是否显示mvel条件
"sourceState": null, // string 源状态值
"targetState": null, // string 目标状态值
"caseCondition": null, // string 分支mvel条件(源状态为虚拟状态时有效)
"caseOrder": null, // integer 分支顺序
"caseDefault": null, // integer 是否默认分支:1是,2否
"guardCondition": null, // string 约束条件
"transitionAction": null, // string 回调业务系统对应接口http地址
}]
}
基本的状态机中插入规则引擎的方式来实现分叉场景,备选方案有Drools、Easy Rules、RuleBook。
名称 | 是否开源 | stars/forks | last_modified | 复杂度 | github地址 |
Drools | 是 | 1393/1341 | 2018/4/24 | 高,配置规则的时候需要新增指定格式文档 | |
Easy Rules | 是 | 770/217 | 2018/4/20 | 低,支持字符串创建规则,支持表达式语言 | |
RuleBook | 是 | 217/41 | 2018/4/21 | 低,支持Lambda表达式, |
其中Easy Rules复杂度低,而且提供了MVEL的方式支持字符串的配置,鉴于此,对于相对简单而且需要UI页面的配置方式的场景来说,Easy Rules是最合适的选择,另外对应开源代码的显示,还可以很方便地扩展成支持其他EL表达式语言,如上面提到的Spring EL。