《流程的永恒之道》(一):控制模式之串行、并发分裂及并发汇聚模式

阅读数:2826 2014 年 4 月 29 日

话题:语言 & 开发架构

控制模式是流程的中枢神经,它在作战小分队中负责将多个单独的作战活动组合在一起,并推动活动的自动化流转,形成作战流程。其重要性不言而喻,因此要设计一个好的流程,就必须学会应用各种各样的控制模式。

在探寻每个模式的究竟之前,我们首先定义一个统一的格式,对于控制模式,将按照如下统一的格式进行描述:

模式描述

我们在探寻每个控制模式时,将按照如下统一的格式进行描述。

  • 原型实例(故事片段)

    给出此模式的故事片段,通过鲜活的工作流故事展现此模式的应用场景。

  • 上下文(描述、动机)

    给出此模式的具体描述和动机:为什么有此模式,是为了解决什么问题。

  • 问题的本质

    此模式的本质是什么?即本质上要做什么事情?

  • 解决方案及技术实现

    给出此模式的解决方案及技术实现。

  • 约束及可能存在的问题

    此模式可能存在的约束和问题。模式并不是万能的,在软件中没有银弹,同样也没有包治百病的模式,每种模式都有可能存在一些约束及限制条件。应用此模式可能会引发什么问题,怎样解决这些问题。

  • 规范中的实现

    给出此模式在相关规范中的实现。目前流程有三大规范 XPDL、BPEL、BPMN,我们将按照每个规范的最新版本 XPDL 2.1(需要说明的是,本章中的 XPDL 示例,都是由 BizAgi Process Modeler 2.1.0.1 生成的)和 BPMN 2.0(所有 BPMN 2.0 的 XML 定义,都是由 signavio 提供的在线流程建模器生成的),来描述当前模式在其中的实现。对于 BPEL,我们始终认为它不是一个“流程”语言,其本质上是一个 Web 服务的编制语言,因此只有部分模式使用 BPEL 描述。

  • 与其他模式的关系

    此模式与其他模式有什么样的关系?是否有配对使用的要求?是否有与其他模式进行组合,解决复杂场景的情形?

1.1.1 房改购房审批流程中的串行模式

图 3.2 是江南市房管局房改购房立等可取的审批流程。在这个流程中,所有的作战活动都是串行在一起的,完成一个活动才能操作下一个活动,这就是工作流控制模式中的“串行模式”。串行模式极其简单,这里就不按照统一格式进行描述了。

图 3.2 房改购房立等可取的审批流程

1.1.2 房改购房审批流程中的“并发分裂“与”并发汇聚“模式

1. 并发分裂模式

  • 原型实例(故事片段)

图 3.3 房改购房审批流程的“并发分裂”原型实例

如图 3.3 所示,复杂的房改购房流程需要两个核查岗位进行核查,因此在“复审”环节之后,并发分裂为了两个活动:“查封核查一”与“查封核查二”。在这个故事片段中,为了提高效率,两个核查环节并行工作,从而将核查的时间缩短了一半。

  • 上下文(描述、动机)

描述:并发分裂,就是在某个活动(本例是“复审”)之后,并发地分裂出多个活动(“查封核查一”、“查封核查二”),这多个活动同时执行。

动机:通过增加流程中并行处理的活动的个数,缩短串行时间,提高整个流程的效率。在 1.2.2 节中,我们曾经提到过并行工程,并发模式是一种最有效的提高流程效率的模式。

  • 问题的本质

并发分裂模式的动机是尽量增加流程中可以并行处理的活动的个数,从而极大提高流程的效率。那么,怎样才能提高并行活动的数量呢?或者说,并行活动的增加依赖于什么呢?答案就是资源,即执行活动本身所需要的相关资源(3.4 节会重点讲述这一主题)。资源如果不够用就没法并行,在房改购房审批流程中,如果只有一个核查岗位当然就无法并行了,因为一个岗位不能同时做两件事情。本例设置了两个查封核查的岗位,因此可以设置为并行活动。

因此,活动能不能并行处理,就要看这个活动本身的相关资源能否并行。再如,在非计算机化的人工流程中,如请假流程,如果请假人拿一张纸质的请假单去找领导签字,那么他只能按照串行顺序去找所有的领导签字,因为物理存在的纸只有一张。而计算机的一个很大作用就是把纸质的数据电子化了,电子化的本质是电子数据本身可以被同步处理,这就是说在同一个电子请假单上,多位领导可以同时签字。因此,原来由于资源的限制而不能并行处理的活动,在计算机化之后可以并行处理了。

  • 解决方案及技术实现

解决方案。要实现并发分裂模式,可以在要并发的活动之后添加一个标识并发分裂的路由节点,如图 3.4 中的 AndSplit,被称为显式的实现方案。或者省略路由节点,直接在活动之后连接两路并发的活动(参见图 3.5),被称为隐式的实现方案。

图 3.4 并发分裂模式之显式实现方案

图 3.5 并发分裂模式之隐式实现方案

技术实现。在工作流中实现并发分裂模式,技术实现包括两部分:一部分是在流程定义期,一部分是在流程运行期。

(1) 定义期。通过可视化的流程设计器,采用此模式的两种方案中的任意一种,画出流程定义图,并持久化存储到数据库。持久化存储的流程定义,在运行期又逆向解析为多个工作流对象之间的关系,如图 3.6 和图 3.7 所示的结构。

图 3.6 活动与转移的类图

(2) 运行期。流程引擎需要读取持久化存储的流程定义(一般为 XML 格式),并解析为对象及对象集合之间的关系(活动集合 Activities、转移集合 Transitions),如图 3-7 所示。

图 3.7 并发分裂模式中隐式方案的对象关系图

可以看出,这是没有采用 AndSplit 网关的隐式实现方案:

(1) 将 XML 的流程定义解析为对象之间的关系,本质上就是 ActivityInfo 与 inComingTransitions、outgoingTransitions 的关系。inComingTransitions 集合存储进入当前活动的转移线对象,outgoingTransitions 集合存储离开当前活动的转移线对象。当某个活动实例(例如“活动 A”的实例)完成需要转出时,则首先取得“活动 A”这个 ActivityInfo 对象的 outgoingTransitions 集合并进行迭代,并对每个 transitionInfo 对象上的 condition 表达式(用来判断当前转移要执行的条件)进行求值判断,如果判断结果为 true,则执行此转移。

(2) 对于并发分裂模式,如果其转移线上的条件表达式 condition 不设置,即永远默认为 true,那么在并发分裂模式中,其所有的后继转移线(outgoingTransitions 集合)都将被执行,从而实现并发分裂模式。

(3) 每个 outgoingTransition 对象(TransitionInfo)执行完毕后,则根据 TransitionInfo 对象的 toActivityId 属性取得此 ID 对应的活动对象(ActivityInfo),在本模式中即“活动 B”与“活动 C”两个对象,在每次迭代中,对 ActivityInfo 对象进行实例化,并持久存储,即完成了一次迭代。所有迭代执行完毕,即实现了“活动 A”并发分裂为“活动 B”与“活动 C”的运行期功能。

注:后续所有控制模式的运行期实现,都将遵循 outgoingTransitons、incomingTransitions、fromActivityId、toActivityId、condition 这样的机制,因此在后续的控制模式中,不再单独讲述这一部分内容了。

对于更细层次的编码实现,目前有基于 petri 网的 Token 机制(例如 jBPM),也有基于实例的状态机方式。

  • 约束及可能存在的问题

驳回的问题。在本模式的描述、解决方案及实现中,我们可以看到并发分裂模式的 AndSplit 网关之后分裂出的所有分支都是同时执行的,因此如果驳回到 AndSplit 网关之前的活动(例如由“活动 B”驳回到“活动 A”,而“活动 C”并不驳回),再次执行到 AndSplit 网关时,“活动 B”重新执行了一次,而“活动 C”也会再次被执行,但是“活动 C”并没有被驳回,它还需要不需要被再次执行呢?在并发分裂模式中,“活动 C”必须再次被执行。但是这可能不是业务所期望的结果(业务上可能会要求“活动 C”不要再重复执行了),遗憾的是并发分裂模式解决不了这个问题。目前很多的工作流产品,虽然支持并发分裂模式,但是对于并发分裂的驳回只能做到“活动 B”与“活动 C”全部重复执行。如果要支持可控制“活动 C”是否重复执行,只能引入多选分裂模式与多选汇聚模式两者结合来实现(详见 10.3.2.2 节的的多选分裂模式与 10.3.2.3 节的多选汇聚模式)。

  • 规范中的实现

XPDL 2.1的实现(此XML内容由BizAgi Process Modeler直接导出,去掉了与控制模式无关的属性)

<WorkflowProcess Id="4df39a5e-9a54-490c-aca7-7cf45bf53ef0" Name="Process 1">
  <Activities>
    <Activity Id="a2dca2db-f0ab-4c1d-92c4-2f51d356f51e" Name="活动 A">
      <Description />
      <Implementation>
        <Task />
      </Implementation>
</Activity>
<Activity Id="67e100a6-f17c-4fb9-80da-c987d918c747" Name="并发分裂网关">
      <Description />
      <Route GatewayType="AND" />
    </Activity>
    <Activity Id="76f2bf26-f953-40b1-800b-25af4320dbd4" Name="活动 B">
      <Description />
      <Implementation>
        <Task />
      </Implementation>
    </Activity>
    <Activity Id="ba088ccb-d74f-409b-8d6c-08c7cf577f70" Name="活动 C">
      <Description />
      <Implementation>
        <Task />
      </Implementation>
    </Activity>
  </Activities>
  <Transitions>
    <Transition Id="344afd1f-90d9-4d4a-97a2-9b6d909b8601" From="a2dca2db-f0ab-4c1d-92c4-2f51d356f51e" To="67e100a6-f17c-4fb9-80da-c987d918c747" Name="">
      <Condition />
    </Transition>
    <Transition Id="b0b4d8d6-c86d-4262-afda-c6e03925c1d7" From="67e100a6-f17c-4fb9-80da-c987d918c747" To="76f2bf26-f953-40b1-800b-25af4320dbd4" Name="">
      <Condition />
    </Transition>
    <Transition Id="ed12d3a8-7ff4-4cd0-a19c-6eb89fbe5a56" From="67e100a6-f17c-4fb9-80da-c987d918c747" To="ba088ccb-d74f-409b-8d6c-08c7cf577f70" Name="">
      <Condition />
    </Transition>
  </Transitions>
  <ExtendedAttributes />
</WorkflowProcess>

如上所示,XPDL 规范采用的显式方案(参见图 3.4)来实现并发分裂模式。通过并发分裂网关(<Activity Id="67e100a6-f17c-4fb9-80da-c987d918c747" Name="并发分裂网关">)与转移(Transition Id=" b0b4d8d6-c86d-4262-afda-c6e03925c1d7"Transition Id=" ed12d3a8-7ff4-4cd0-a19c-6eb89fbe5a56")直接建立关系,实现并发分裂网关与分裂转移线的关联。再通过转移线上的 From 与 To 属性(见粗体部分),分别指向某个活动 Id,实现了线与活动的连接。

BPEL中的实现(XML内容由Eclipse BPEL Designer 生成,去掉了与控制模式无关的属性)

在 BPEL 规范中,通过 <flow> 活动提供并发分裂模式的实现。把需要并发执行的活动放置在 <flow>…</flow> 标签之内。

<bpel:process name="ParallelPattern"
         targetNamespace="http://sample.bpel.org/bpel/sample"
         suppressJoinFailure="yes"
         xmlns:tns="http://sample.bpel.org/bpel/sample"
         xmlns:bpel="http://docs.oasis-open.org/wsbpel/2.0/process/executable"
         >
	<bpel:sequence name="main">
  	<bpel:empty name="Empty"></bpel:empty>
    	<bpel:flow name="Flow">
        	<bpel:invoke name="Invoke1"></bpel:invoke>
        		<bpel:invoke name="Invoke2"></bpel:invoke>
      	</bpel:flow>
    	<bpel:invoke name="Invoke"></bpel:invoke>
  	</bpel:sequence>
</bpel:process>

BPMN 2.0中的实现(XML 内容由 Signavio Process Editor 导出,去掉了与控制模式无关的属性

<process id="sid-a0718229-83f5-4ab3-9fea-63676de90575" isExecutable="false">
    <task completionQuantity="1" id="sid-81CDFBD9-1D50-42CF-B67A-674591D44F4D" isForCompensation="false" name="活动 A" startQuantity="1">
       <outgoing>sid-FE93BF67-3DE5-4DF9-BAA7-A7F4BD8C3339</outgoing>
    </task>
    <parallelGateway gatewayDirection="Diverging" id="sid-681399DD-E962-4174-85DA-43384A9B0DD8" name="并发分裂网关">
       <incoming>sid-FE93BF67-3DE5-4DF9-BAA7-A7F4BD8C3339</incoming>
       <outgoing>sid-91DD542C-DBA5-49E0-9E56-A31DA8E4E432</outgoing>
       <outgoing>sid-9DEA8CF3-6EBE-4EB5-B649-42865D58C35D</outgoing>
    </parallelGateway>
    <task completionQuantity="1" id="sid-9FCD307D-BDED-404F-988B-100311E9C92A" isForCompensation="false" name="活动 B" startQuantity="1">
       <incoming>sid-91DD542C-DBA5-49E0-9E56-A31DA8E4E432</incoming>
    </task>
    <task completionQuantity="1" id="sid-65D2670A-F5CF-4721-854B-F1499C0F3239" isForCompensation="false" name="活动 C" startQuantity="1">
       <incoming>sid-9DEA8CF3-6EBE-4EB5-B649-42865D58C35D</incoming>
    </task>
    <sequenceFlow id="sid-FE93BF67-3DE5-4DF9-BAA7-A7F4BD8C3339" name="" sourceRef="sid-81CDFBD9-1D50-42CF-B67A-674591D44F4D" targetRef="sid-681399DD-E962-4174-85DA-43384A9B0DD8"/>
    <sequenceFlow id="sid-91DD542C-DBA5-49E0-9E56-A31DA8E4E432" name="" sourceRef="sid-681399DD-E962-4174-85DA-43384A9B0DD8" targetRef="sid-9FCD307D-BDED-404F-988B-100311E9C92A"/>
    <sequenceFlow id="sid-9DEA8CF3-6EBE-4EB5-B649-42865D58C35D" name="" sourceRef="sid-681399DD-E962-4174-85DA-43384A9B0DD8" targetRef="sid-65D2670A-F5CF-4721-854B-F1499C0F3239"/>
 </process>

如上所示,BPMN2.0 规范同样采用了显式的实现方案。需要注意的是,在 XPDL 2.1 规范中,通过在 Transition 的 From 与 To 属性实现了线对活动的连接,而在 BPMN 2.0 规范中,则是通过 sequenceFlow 的sourceReftargetRef属性(见粗体部分)实现线对活动的连接。

  • 与其他模式的关系

(1) 与会签模式的区别。此处需要注意的是,活动 B 和活动 C 是两个不同的活动(也就是说两个活动做不同的事情,例如在办公用品采购预算审批的流程中,活动 A 为总经理审批,审批通过后,活动 B 为通知采购员领款,活动 C 为从财务系统中扣款),因此要区别于同一个活动有两个办理人同时办理(即单步会签)的情况。

(2) 与并发汇聚模式(同步模式)的关系。并发分裂之后,一般都会在某个活动上进行汇聚。此时就需要用到“并发汇聚模式”。一般情况下,并发分裂模式与并发汇聚模式都是成对出现。当然这并不是必须的,因为并发分裂也可以不进行汇聚,例如传阅模式(异步多实例模式)。

(3) 与单选分裂(排他选择)模式组合使用,间接实现多选分裂模式

图 3.8 并发分裂模式与单选分裂(排他选择)模式的结合使用

从图 3.8 中可以看出,通过将并发分裂模式与排他选择模式相结合,可以部分地实现 M 选 N 分裂模式。图中实现的效果是,A 分裂为 BCD 或 BCE。在一定程度上实现了 4 选 3 的效果。当然活动 B 和活动 C 是必选的,活动 D 和活动 E 则是排他选择(即二选一)。所以,这与多选分裂又有一定的区别,在多选分裂模式中,选择结果可以是任意一个分支,并没有任何限制。

2. 并发汇聚模式(同步模式)

  • 原型实例(故事片段)

图 3.9 房改购房审批流程之“并发汇聚”片段图

如图 3.9 所示,在“查封核查一”和“查封核查二”两个活动之后,房改购房审批流程通过一个“并发汇聚”网关,继续向下流转到“制证”活动上。

  • 上下文(描述、动机)

描述:并发分裂出的多个活动(“查封核查一”和“查封核查二”)全部执行完毕后,后续活动才会被触发。

动机:为通过并发分裂模式创建出的分支提供一种再次汇聚的机制。

  • 问题的本质

当活动涉及的资源需要合并处理时,则采用同步机制。这里的资源包括某些成果的合并(例如,两个核查结果做合并)。

  • 解决方案及技术实现

解决方案。由于此模式一般都与并发分裂模式配对使用。对应于并发分裂模式中的显式方案和隐式方案,此模式的解决方案同样分为显式方案与隐式方案,如图 3.10 和图 3.11 所示。

图 3.10 同步模式的显式实现方案

图 3.11 并发汇聚(同步)模式的隐式实现方案

技术实现。在此模式中,显式方案与隐式方案的实现稍有不同。

(1) 定义期:对于显式方案(如图 3.10),直接在设计器中提供 AndJoin 活动节点即可,不需要另外设置。而对于隐式方案(如图 3.11),由于同步模式要求所有参与汇聚的节点必须全部完成才能触发后续分支,因此必须在“任务 D”上设置同步等待的标志(或者称之为“与汇聚”标志)。

(2) 运行期:如图 3.12 所示,对于“活动 D”这个对象,其属性 incomingTransitions 是一个集合,集合中包含 id=”tran_3”和 id=”tran_4”的两个转移对象,当两个转移对象都执行完毕时,触发活动 D 的执行,即可实现同步模式。

图 3.12 并发汇聚(同步)模式的隐式方案对象关系图

  • 约束及可能存在的问题

与并发分裂模式中的驳回相对应,此模式同样存在驳回问题:如果并发需要驳回,则必须采用多选汇聚模式来代替同步模式,才能满足驳回需求。

  • 规范中的实现

XPDL 2.1中的实现

<WorkflowProcess Id="b8043656-a95c-40e4-917e-2919a59fbfb3" Name="Process 1">
  <Activities>
    <Activity Id="1c38df40-d035-4155-b473-d81fa606c2a8" Name="活动 B">
      <Description />
      <Implementation>
        <Task />
      </Implementation>
    </Activity>
    <Activity Id="d666bc95-edda-446f-9f93-98f27c53a7f8" Name="活动 C">
      <Description />
      <Implementation>
        <Task />
      </Implementation>
    </Activity>
    <Activity Id="b2fc7f93-aca6-4d52-965a-432553f02c9c" Name="并发汇聚网关">
      <Description />
      <Route GatewayType="AND" />
    </Activity>
    <Activity Id="194e2c59-9218-49d5-baf4-e9feb715d9f0" Name="活动 D">
      <Description />
      <Implementation>
        <Task />
      </Implementation>
    </Activity>
  </Activities>
  <Transitions>
    <Transition Id="a6a47f65-cc0d-4302-870a-5f43556be0e4" From="1c38df40-d035-4155-b473-d81fa606c2a8" To="b2fc7f93-aca6-4d52-965a-432553f02c9c" Name="">
      <Condition />
    </Transition>
    <Transition Id="fe10657f-542a-4229-be3a-382b4aa2a7ad" From="d666bc95-edda-446f-9f93-98f27c53a7f8" To="b2fc7f93-aca6-4d52-965a-432553f02c9c" Name="">
      <Condition />
    </Transition>
    <Transition Id="735397b6-fb5f-4712-a944-59760458403c" From="b2fc7f93-aca6-4d52-965a-432553f02c9c" To="194e2c59-9218-49d5-baf4-e9feb715d9f0" Name="">
      <Condition />
    </Transition>
  </Transitions>
  <ExtendedAttributes />
</WorkflowProcess>

如上所示,在 XPDL 规范中,采用的是显式同步方案。

BPEL 2.0中的实现(XML内容由Eclipse BPEL Designer 生成,去掉了与控制模式无关的属性)

同样采用 <flow></flow> 标签进行实现。

<bpel:process name="ParallelPattern"
         targetNamespace="http://sample.bpel.org/bpel/sample"
         suppressJoinFailure="yes"
         xmlns:tns="http://sample.bpel.org/bpel/sample"
         xmlns:bpel="http://docs.oasis-open.org/wsbpel/2.0/process/executable"
         >
	<bpel:sequence name="main">
  	<bpel:empty name="Empty"></bpel:empty>
    	<bpel:flow name="Flow">
        	<bpel:invoke name="Invoke1"></bpel:invoke>
        		<bpel:invoke name="Invoke2"></bpel:invoke>
      	</bpel:flow>
    	<bpel:invoke name="Invoke"></bpel:invoke>
  	</bpel:sequence>
</bpel:process>

BPMN 2.0中的实现(XML内容由Signavio Process Editor导出,去掉了与控制模式无关的属性

<process id="sid-921359f3-d331-4fa7-aad9-7a6eb6e32d4a" isExecutable="false">
    <task completionQuantity="1" id="sid-4038BF10-88DD-4CD4-B51E-2A56B30D372F" isForCompensation="false" name="活动 B" startQuantity="1">
       <outgoing>sid-C3C65849-94FE-4D2A-B5F9-500BF6F01C6A</outgoing>
    </task>
    <parallelGateway gatewayDirection="Converging" id="sid-AA7B31AA-C305-4B94-B48B-BA695B497FBD" name="并发汇聚网关">
       <incoming>sid-C3C65849-94FE-4D2A-B5F9-500BF6F01C6A</incoming>
       <incoming>sid-D2F19E17-1F1E-4234-AF1C-D5F85D99AE2D</incoming>
       <outgoing>sid-8275F97F-34DB-4E4F-B73B-72FA44CAE1F5</outgoing>
    </parallelGateway>
    <task completionQuantity="1" id="sid-7E91DCFB-7845-48A6-8263-842B46D0744C" isForCompensation="false" name="活动 C" startQuantity="1">
       <outgoing>sid-D2F19E17-1F1E-4234-AF1C-D5F85D99AE2D</outgoing>
    </task>
    <task completionQuantity="1" id="sid-225CB518-1A4E-46A5-80D1-DAF2143B5233" isForCompensation="false" name="活动 D" startQuantity="1">
       <incoming>sid-8275F97F-34DB-4E4F-B73B-72FA44CAE1F5</incoming>
    </task>
    <sequenceFlow id="sid-C3C65849-94FE-4D2A-B5F9-500BF6F01C6A" name="" sourceRef="sid-4038BF10-88DD-4CD4-B51E-2A56B30D372F" targetRef="sid-AA7B31AA-C305-4B94-B48B-BA695B497FBD"/>
    <sequenceFlow id="sid-D2F19E17-1F1E-4234-AF1C-D5F85D99AE2D" name="" sourceRef="sid-7E91DCFB-7845-48A6-8263-842B46D0744C" targetRef="sid-AA7B31AA-C305-4B94-B48B-BA695B497FBD"/>
    <sequenceFlow id="sid-8275F97F-34DB-4E4F-B73B-72FA44CAE1F5" name="" sourceRef="sid-AA7B31AA-C305-4B94-B48B-BA695B497FBD" targetRef="sid-225CB518-1A4E-46A5-80D1-DAF2143B5233"/>
 </process>

如上所示,BPMN 规范同样采用的是显式同步模式方案。

  • 与其他模式的关系

一般与并发分裂模式配对使用。


感谢张龙对本文的审校,感谢张龙对本文的审核。

给 InfoQ 中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ)或者腾讯微博(@InfoQ)关注我们,并与我们的编辑和其他读者朋友交流。