enkrio是一个极简的业务流程引擎。
业务流程(business process)是一组相关的、具有结构的活动,这组活动按特定顺序被执行可产出产品或服务。BPMN是当前应用最广的业务流程描述规范之一,主流的业务流程执行引擎如Activiti、Camunda、Flowable等也以支持BPMN业务模型为主。
虽然BPMN应用广泛,但它在某些场景下仍然存在不足。例如我们经常会使用BPMN建模一个如下图所示“审批”流程。
但实际运行过程中,审批业务一般允许进行退回操作,如下图所示。
当然还可能存在更复杂的退回逻辑。例如下图所示,任意一个后续活动可以退回到任意一个前序活动。
若需要支持“回退”逻辑,那采用BPMN这类的活动图来进行审批流程建模就需要在模型中表达出这样的活动关系。当流程节点数量增加时,回退关系的数量会呈几何级数增长。
此外,实际业务中可能还存在“回退后可直接提交至回退发起节点”这样的需要。如下图所示,如果“总办审批”直接回退到“发起申请”活动,则发起者可以修改后直接提交至总办进行重新审批。
这一问题存在的根本是BPMN模型的执行引擎只依据设计时的静态模型进行执行,所以执行时退回、退回提交等复杂情况需要在BPMN中进行显式建模方可实现。
为此我们开发enkrino引擎以解决这一问题,在建模过程简单化和执行过程灵活化之间取得平衡。
enkrino的核心思想是在运行时基于流程模型执行的历史记录动态决定流程的下一个合法状态。
引擎内为每个部署运行的模型自动构建一个“镜像模型”,初始状态下,镜像模型只包含设计模型中的所有节点,并且在每个节点的状态数据中维护一个执行上下文栈(下简称ECS),用以存放每个节点执行时的各类状态数据。
我们用以下5个步骤说明enkrino的运行过程。
流程启动后进入S1
执行,我们在S1的ECS中加入start->S1|starting context
在此之后引擎如果完成S1
,则下一个状态的选择根据设计模型与镜像模型共同决定:两个模型中S1的后续节点都可以是引擎可选的下一个执行节点。根据当前状态,S1
在设计模型中存在后续节点S2
或S3
(此处省略了条件选择状态),镜像模型中S1
无后续节点,所以引擎可选择S2
或S3
。
假设引擎选择进入S2
状态继续执行,则我们在镜像模型中记录S1
执行中的上下文S1->S2|context1
,并在镜像模型中添加S2
到S1
的后向边,表示此时S2
除了model中的路径外,额外添加了一条可以回退到S1
的路径。
在此之后引擎如果完成S2
,根据前述下一节点选择规则,S2
后续可进入S4
(设计模型中的后续)或S1
(镜像模型中的后续,即S1
一个合法的回退节点)。
假设引擎选择进入S4
状态继续执行,则我们在镜像模型中记录S4
执行中的上下文S2->S4|context2
,并在镜像模型中添加S4
到S2
的后向边。
在此之后引擎如果完成S4
,根据前述下一节点选择规则,S4
后续可进入S2
(设计模型中的后续)或S1
(镜像模型中的后续,即S1
一个合法的回退节点)。
假设选择S1
,则为回退到合法的历史节点,此时我们在镜像模型中记录S1
执行中的上下文S4->S1|context3
,并在镜像模型中添加S1
到S4
的前向边,表示S1
除了model中的路径以外,额外增加了一条直接到达S4
的路径,
最后,我们再用图示表现S1->S3->S4
的状态转换过程,这与前述S1->S2->S4
的过程是类似的。
enkrino提供纯函数接口,其过程为从当前状态到下一个状态的求解。即enkrino不保存状态,而是接受当前状态作为输入,计算下一状态作为输出。
状态转移接口如下:
#启动流程
start(state) -> nextState
#获取当前节点的所有合法后继节点,包括设计模型中和镜像模型中的所有当前节点的后继节点
getNexts(state, current) -> [nextSteps]
#由当前节点前往指定节点,如指定节点为非法后继节点,不可到达,则状态保持不变
goNext(state, current, next) -> nextState
#结束流程
finish(state) -> finishedState