引言与动机:世界是那么的广阔无垠,姿态万千,我们梦想着计算设备的多元化,而如今我们已经梦想成真,但同时业务模型同样变得纷繁复杂。如果不考虑我们拥有的繁杂的业务模型,就很难谈得上去探索行业发展的方向。
MyWF 是基于有限状态机 FSM(Finite State Machine)理论的工作流引擎。状态机、状态机顾名思义着眼点在“状态(State)”上,以“状态”的不同为设计的参考物。一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。每请求一次,状态就更换一次,就执行对应的行为。State 模式采用了对这些不同的状态进行封装的方式处理这类问题,当状态改变的时候进行处理然后再切换到另一种状态,也就是说把状态的切换责任交给了具体的状态类去负责。
本文涉及的名词其关系如下图:
首选是 Workflow。Workflow 也可以说是案例(Case),相当于一次工作流的实作对象。例如处理案例,保险索赔、绩效考核、抵押申请等等都是案例。案例在出现和消失之间总是处于某个特定状态,这个状态有三个元素组成: 案例相关的属性,指出特定条件下案例是否被执行或者忽略;已经满足的条件,说明案例的进展;案例的内容,可能是文档、文件、档案或者数据库。
MyWF 把工作流视为一组关联的Steps,通过执行与 Step 相关的 Action,实现从一个 Step 到另一个 Step 的转换,从而引起工作流状态的流转 Transtion(又叫转换/转移)来推动工作的前进。换句话说,流转依赖于 Action 的发生。
<steps>
<step name="Step1" id="1">
<actions>
<action name="Step1_Action" id="11">
<results>
<unconditional-result old-status="Finished" status="Waiting" step="2"/>
</results>
</action>
</actions>
</step>
<step name="Step2" id="2">
......
</step>
<steps>
如上例,在 Step1 上执行了一个 Action,这个 Action 的结果是转向到 Step2。这样就达成了 Step1 到 Step2 之间的转换。
status 代表流程在某个 step中 的状态。譬如“待认领”、“审核不通过”之类的。对于 status 和 old-status 区别,就是前者表示转向到 Step2 后的状态值(Step2 的状态)下一个 Next 的状态;后者表示离开 Step1 后状态值(Step1 的状态)原来的状态。MyWF 的有 Underway(进行中)、Queued(等候处理中)、Finished(完成)三种 Status。一个实际 State(状态)真正是由两部分组成:State = (Step + Status) 。当转向的 Step 是类似于<step name="end" id="2" />
时,则流程会自动终止。当转向的 Step 中包含的 Action 集合中存在一个 auto 属性为 true 的 Action 时,转向之后 Action 自动触发。如:
<step name="Step2" id="2">
<actions>
<action name="Step2_Action" id="21" auto="true">
<results>
<unconditional-result old-status="Finished" status="Ready1" split="100"/>
</results>
</action>
</actions>
</step>
进入 Step2 后,Step2_Action 将自动执行。
如图一,除了上述的 Step,Action,Status,Result 几个基本的 Workflow 元素外还有两个特别的元素:分拆 Split 和合并 Join,用于描述并行工作的2个的元素。Split 提供多个 result;Join 则判断多个 current step 的状态,提供一个 Result。
到此小结一下:Step是一个工作流的逻辑单元。每个Step可以有多种状态(Status)和多个动作(Action),用 Workflow.getCurrentSteps()
可以获得所有当前的Step,相当于流程所在的位置(注:如果有并列流程则可能同时有多个Step)。怎么设计 Step?Step 可以被定义为不能再细分的过程,即原子过程,这里存在主观因素。在定义或者分派 Step 的人看来,Step 是原子性的,但是对于执行它的人来说经常是非原子性(给程序封装了,执行它的人也就是用户只知道的是他的结果或应该下一步怎么做)。关于 Step 的颗粒度。
一个 Action 典型地由两部分组成:可以执行此动作的条件(conditions 可用脚本来判断),以及执行此动作的结果(results)。result 分为两种,conditional-result 和 unconditional-result。执行一个动作之后,首先判断所有 conditional-result 的条件是否满足,满足则使用该结果;如果没有任何 contidional-result 满足条件,则使用 unconditional-result。unconditional-result 需要指定两部分信息:old-status,表示“当前step的状态变成什么”;后续状态,可能是用 step+status 指定一个新状态,也可能进入 split 或者 join。
如何定义并行工作中的 Split 和 Join?在MyWF中与之对应的则是:<split>
和<join>
。从 Step2 分拆成2个并行任务(Step31,Step32),再合并并转向 Step4。可以如此理解:在 Step2 上执行一个 Action,其结果会导致 Step31 和 Step32 的产生;分别执行 Step31 和 Step32 的 Action,转向合并点;合并点判断 Step31 和 Step32 都完成后,那么转向 Step4。因此可描述为:
<steps>
......
<step name="Step2" id="2">
<actions>
<action name="Step2_Action" id="21">
<results>
<unconditional-result old-status="Finished" status="Ready1" split="100"/>
</results>
</action>
</actions>
</step>
<step name="Split_Step" id="31">
<actions>
<action name="Split_Step_Action" id="311">
<results>
<unconditional-result old-status="finished" status="Ready2" join="200"/>
</results>
</action>
</actions>
</step>
<step name="Split_Step" id="32">
<actions>
<action name="Split_Step_Action" id="321">
<results>
<unconditional-result old-status="finished" status="Ready3" join="200"/>
</results>
</action>
</actions>
</step>
<step name="Join_Step" id="4">
......
</step>
</steps>
<splits>
<split id="100">
<unconditional-result old-status="finished" status="Ready5" step="31" />
<unconditional-result old-status="finished" status="Ready6" step="32" />
</split>
</splits>
<joins>
<join id="200">
<conditions>
<condition type="beanshell">
<arg name="script">
"finished".equals( jn.getStep(31).getStatus())&&"finished".equals( jn.getStep(32).getStatus());
</arg>
</condition>
</conditions>
<unconditional-result old-status="finished" status="Ready7" step="4" />
</join>
</joins>
这里<steps>
、<splits>
和<joins>
可以理解为<step>
、<split>
和<join>
对应的集合。关键是<result>
(或<unconditional- result>
),使得这些离散的建立了关联,确定了状态转换的目标。
何时该使用工作流的模式?
任何事物都有是否被适用的场景。我们应该尽可能划清楚边界,凸现某一样事物的作用性,发挥其长处、真正优秀的地方。工作流模式有许多种,可以很容易延伸到我们想到可以使用它的地方,连编程语言中也有 flow 流的概念,那这样的话,是不是就可以一概“套用”上去呢?
以我们一般的发布“博客”记录为例子,例如“POST New Entity-->表单验证-->写入数据库-->MVC耦合-->静态HTML
”的例子。若非要采用工作流的概念去考量,其本质上是对工作流的路径建模,将路径信息都编码到了模型之中。当前的这种模式按工作流模式来说叫做“流模式”,“流模式”是工作流模式中的一种。实现上,就需要画出许多复杂的路径流程,但是这些复杂的路径却并非我们所关注的问题。明显,我们并不关心路径,而只关心结果,并且不关心结果是如何实现的。
状态机模式则不同。它并非完全是顺序执行的,可以“取回”,可以“退回”,执行的路径并非一成不变地走下去,可以接收外部事件或者启动并行任务。于是,各个点之间是可以跳跃的,令到参与者的自主性比效大,因此,使用工作流模式,一般的方案就集中在讨论状态机模式 State Machine 身上。
尽管我们谈到工作流模式的使用时机如何,但有时候,根据不同场景和角度切入,讨论哪一种模式不是并不是泾渭分明的。举一个例子,一个请假的工作流,参与都有两种:申请人、审批人:
- 在申请人眼里,他所参与的工作流是[流模式]
- 在审批人眼里,他所参与的工作流是[状态机模式]
虽然是同一种事情,结果也是不变的,但是设身处地地看,在不同参与者的眼中就有不同的模式。
工作流带来的好处
一个好的架构设计是一个项目快速成功完成的基础技术保证,没有这个技术基础,再先进的项目管理手段也是没有效率的,或者是笨拙的。
以一个游戏系统为例,在没有状态模式对状态提炼的情况下,状态改变由每个程序员想当然实现,为判断状态而产生的巨大的if或case语句,导致每个程序员开发的功能在整合时就无法开展。因为这个程序员可能不知道那个程序员的代码在什么运行条件下改变了游戏状态,结果导致自己的代码无法运行。可以说,最大的危险是系统没有一个一抓就灵的主体结构!这种现象实际上拒绝了项目管理的协作性,大大地拖延项目进度(程序员之间要反复商量讨论对方代码设计)。
因为,我需要做一个状态机 API,或者说状态机框架,供具体系统调用:类如公文流转应用或信息发报送应用等。将对象行为交给状态类维护后,对于上层程序而言,仅需要维护状态之间的转换规则从系统上的高度谈状态机状态模式,对于很多系统来说,它是架构组成一个重要部分。
参考
关于状态模式很经典的一篇文章《圣斗士星矢的状态模式和观察者模式》和设计模式参考《一句话总结GOF的23种设计模式》。
工作流可以用 XML 描述,也可以用脚本编码,这两者都是手工的编码,当然,如果更进一步发展的话,就是 GUI 图形协助工作流的编辑,甚至成熟的话,便是发展为IDE级别的。XML 模式的一个显著特点是,除了编写计算机语言之外,一个编辑的环境,最大的用途是用来交换数据、和数据扁平化。