返回介绍

8.6. 子流程与调用活动

发布于 2023-09-17 23:40:35 字数 11174 浏览 0 评论 0 收藏 0

8.6.1. 子流程

描述

子流程(sub-process)是包含其他的活动、网关、事件等的活动。其本身构成一个流程,并作为更大流程的一部分。子流程完全在父流程中定义(这就是为什么经常被称作嵌入式子流程)。

子流程有两个主要的使用场景:

  • 子流程可以分层建模。很多建模工具都可以折叠子流程,隐藏子流程的所有细节,而只显示业务流程的高层端到端总览。

  • 子流程会创建新的事件范围。在子流程执行中抛出的事件可以通过子流程边界上的边界事件捕获,为该事件创建了限制在子流程内的范围。

使用子流程也要注意以下几点:

  • 子流程只能有一个空启动事件,而不允许有其他类型的启动事件。请注意BPMN 2.0规范允许省略子流程的启动与结束事件,但目前Flowable的实现尚不支持省略。

  • 顺序流不能跨越子流程边界。

图示

子流程表示为标准活动(圆角矩形)。若折叠了子流程,则只显示其名字与一个加号,以展示流程的高层概览:

bpmn.collapsed.subprocess

展开了子流程,则在子流程内显示子流程的所有步骤:

bpmn.expanded.subprocess

使用子流程的一个主要原因是为事件定义范围。下面的流程模型展示了这种用法:investigate software(调查硬件)/investigate hardware(调查软件)两个任务需要并行执行,且需要在给定时限内,在Level 2 support(二级支持)响应前完成。在这里,定时器的范围(即需要按时完成的活动)通过子流程进行限制。

bpmn.subprocess.with.boundary.timer

XML表示

子流程通过subprocess元素定义。子流程中的所有活动、网关、事件等,都需要定义在这个元素内。

<subProcess>

  <startEvent />

  ... 其他子流程元素 ...

  <endEvent />

 </subProcess>

8.6.2. 事件子流程

描述

事件子流程(event sub-process)是BPMN 2.0新定义的。事件子流程是通过事件触发的子流程。可以在流程级别,或者任何子流程级别,添加事件子流程。用于触发事件子流程的事件,使用启动事件进行配置。因此可知,不能在事件子流程中使用空启动事件。事件子流程可以通过消息事件、错误事件、信号时间、定时器事件或补偿事件等触发。在事件子流程的宿主范围(流程实例或子流程)创建时,创建对启动事件的订阅。当该范围销毁时,删除订阅。

事件子流程可以是中断或不中断的。中断的子流程将取消当前范围内的任何执行。非中断的事件子流程将创建新的并行执行。宿主范围内的每个活动,只能触发一个中断事件子流程,而非中断事件子流程可以多次触发。子流程是否是中断的,通过触发事件子流程的启动事件配置。

事件子流程不能有任何入口或出口顺序流。事件子流程是由事件触发的,因此入口顺序流不合逻辑。当事件子流程结束时,要么同时结束当前范围(中断事件子流程的情况),要么是非中断子流程创建的并行执行结束。

目前的限制:

  • Flowable支持错误、定时器、信号与消息启动事件触发事件子流程。

图示

事件子流程表示为点线边框的嵌入式子流程。

bpmn.subprocess.eventSubprocess

XML表示

事件子流程的XML表示形式与嵌入式子流程相同。但需要将triggeredByEvent属性设置为true

<subProcess triggeredByEvent="true">
	...
</subProcess>

示例

下面是使用错误启动事件触发事件子流程的例子。该事件子流程处于“流程级别”,即流程实例的范围:

bpmn.subprocess.eventSubprocess.example.1

事件子流程在XML中是这样的:

<subProcess triggeredByEvent="true">
  <startEvent>
    <errorEventDefinition errorRef="error" />
  </startEvent>
  <sequenceFlow sourceRef="catchError" targetRef="taskAfterErrorCatch" />
  <userTask name="Provide additional data" />
</subProcess>

前面已经指出,事件子流程也可以添加到嵌入式子流程内。若添加到嵌入式子流程内,可以代替边界事件的功能。例如在下面两个流程图中,嵌入式子流程都抛出错误事件。错误事件都被捕获,并由用户任务处理。

bpmn.subprocess.eventSubprocess.example.2a

对比:

bpmn.subprocess.eventSubprocess.example.2b

两种情况下都执行相同的任务。然而,两种模型有如下不同:

  • 嵌入式(事件)子流程使用其宿主范围的执行来执行。这意味着嵌入式(事件)子流程可以访问其范围的局部变量。当使用边界事件时,执行嵌入式子流程的执行,会被边界事件的出口顺序流删除。意味着嵌入式子流程创建的变量将不再可用。

  • 使用事件子流程时,事件完全由其所在的子流程处理。当使用边界事件时,事件由其父流程处理。

这两点可以帮助你判断哪种方式更适合解决特定的流程建模或实现问题,以选择使用边界事件还是嵌入式(事件)子流程。

8.6.3. 事务子流程

描述

事务子流程(transaction sub-process)是一种嵌入式子流程,用于将多个活动组织在一个事务里。事务是工作的逻辑单元,可以组织一组独立活动,使得它们可以一起成功或失败。

事务的可能结果:事务有三种不同的结果:

  • 若未被取消,或被意外终止,则事务成功(successful)。若事务子流程成功,将使用出口顺序流离开。若流程后面抛出了补偿事件,成功的事务可以被补偿。请注意:与“普通”嵌入式子流程一样,可以使用补偿抛出中间事件,在事务成功完成后补偿。

  • 若执行到达取消结束事件时,事务被取消(canceled)。在这种情况下,所有执行都将被终止并移除。只会保留一个执行,设置为取消边界事件,并将触发补偿。在补偿完成后,事务子流程通过取消边界事件的出口顺序流离开。

  • 若由于抛出了错误结束事件,且未被事务子流程所在的范围捕获,则事务会被意外(hazard)终止。错误被事件子流程的边界捕获也一样。在这种情况下,不会进行补偿。

下面的流程图展示这三种不同的结果:

bpmn.transaction.subprocess.example.1

与ACID事务的关系:要注意不要将BPMN事务子流程与技术(ACID)事务混淆。BPMN事务子流程不是划分技术事务范围的方法。要理解Acitivit中的事务管理,请阅读并发与事务章节。BPMN事务与ACID事务有如下区别:

  • ACID事务生存期一般比较短,而BPMN事务可以持续几小时,几天甚至几个月才完成。考虑一个场景:事务包括的活动中有一个用户任务。通常人的响应时间要比程序长。或者,在另一个场景下,BPMN事务可能等待某些业务事件发生,像是特定订单的填写完成。这些操作通常要比更新数据库字段、使用事务队列存储消息等,花长得多的时间完成。

  • 不可能将业务活动的持续时间限定为ACID事务的范围,因此一个BPMN事务通常会生成多个ACID事务。

  • 一个BPMN事务可以生成多个ACID事务,也就不能使用ACID特性。例如,考虑上面的流程例子。假设"book hotel(预订酒店)"与"charge credit card(信用卡付款)"操作在分开的ACID事务中处理。再假设"book hotel(预订酒店)"活动已经成功。这时,因为已经进行了预订酒店操作,而还没有进行信用卡扣款,就处在中间不一致状态(intermediary inconsistent state)。在ACID事务中,会顺序进行不同的操作,因此也处在中间不一致状态。在这里不一样的是,不一致状态在事务范围外可见。例如,如果通过外部预订服务进行预定,则使用该预订服务的其他部分将能看到酒店已被预订。这意味着,当使用业务事务时,完全不会使用隔离参数(的确,当使用ACID事务时,我们通常也会降低隔离级别,以保证高并发级别。但ACID事务可以细粒度地进行控制,而中间不一致状态也只会存在于一小段时间内)。

  • BPMN业务事务不使用传统方式回滚。这是因为它生成多个ACID事务,在BPMN事务取消时,部分ACID事务可能已经提交。这样它们没法回滚。

因为BPMN事务天生需要长时间运行,因此就需要使用不同的方式缺乏隔离与回滚机制造成的问题。在实际使用中,通常只能通过领域特定(domain specific)的方式解决这些问题:

  • 通过补偿实现回滚。如果在事务范围内抛出了取消事件,就补偿所有成功执行并带有补偿处理器的活动所造成的影响。

  • 缺乏隔离通常使用特定领域的解决方案来处理。例如,在上面的例子里,在我们确定第一个客户可以付款前,一个酒店房间可能被第二个客户预定。这可能不满足业务预期,因此预订服务可能会选择允许一定量的超量预定。

  • 另外,由于事务可以由于意外而终止,预订服务需要处理这种情况,比如酒店房间已经预定,但从未付款(因为事务可能已经终止)。在这种情况下,预定服务可能选择这种策略:一个酒店房间有最大预留时间,若到时还未付款,则取消预订。

总结一下:尽管ACID事务提供了对回滚、隔离级别,与启发式结果(heuristic outcomes)等问题的通用解决方案,但仍然需要在实现业务事务时,为这些问题寻找特定领域的解决方案。

目前的限制:

  • BPMN规范要求,流程引擎响应底层事务协议提交的事务。如果在底层协议中发生了取消事件,则取消事务。作为嵌入式的引擎,Flowable当前不支持这点。查看下面介绍一致性的段落,了解其后果。

基于ACID事务与乐观锁(optimistic concurrency)的一致性:BPMN事务在如下情况保证一致性:所有活动都成功完成;或若部分活动不能执行,则所有已完成活动都被补偿。两种方法都可以达到最终一致性状态。然而需要了解的是:Flowable中BPMN事务的一致性模型,以流程执行的一致性模型为基础。Flowable以事务的方式执行流程,并通过乐观锁标记处理并发。在Flowable中,BPMN的错误、取消与补偿事件,都建立在相同的ACID事务与乐观锁之上。例如,只有在实际到达时,取消结束事件才能触发补偿。如果服务任务抛出了非受检异常,导致并未实际到达取消结束事件;或者,由于底层ACID事务中的其他操作,将事务设置为rollback-only(回滚)状态,导致补偿处理器的操作不能提交;或者,当两个并行执行到达一个取消结束事件时,补偿会被两次触发,并由于乐观锁异常而失败。这些情况下都不能真正完成补偿。想说明的是,当在Flowable中实现BPMN事务时,与实施“普通”流程与子流程,需要遵守相同的规则。因此实现流程时需要有效地保证一致性,需要将乐观锁、事务执行模型纳入考虑范围。

图示

事务子流程表示为带有两层边框的嵌入式子流程。

bpmn.transaction.subprocess

XML表示

事务子流程在XML中通过transaction标签表示:

<transaction >
	...
</transaction>

示例

下面是一个事务子流程的例子:

bpmn.transaction.subprocess.example.2

8.6.4. 调用活动(子流程)

描述

尽管看起来很相像,但在BPMN 2.0中,调用活动(call activity)有别于一般的子流程——通常也称作嵌入式子流程。从概念上说,两者都在流程执行到达该活动时,调用一个子流程。

两者的区别为,调用活动引用一个流程定义外部的流程,而子流程嵌入在原有流程定义内。调用活动的主要使用场景是,在多个不同流程定义中调用一个可复用的流程定义。

当流程执行到达调用活动时,会创建一个新的执行,作为到达调用活动的执行的子执行。这个子执行用于执行子流程,也可用于创建并行子执行(与普通流程中行为类似)。父执行将等待子流程完成,之后沿原流程继续执行。

图示

调用过程表现为带有粗边框(折叠与展开都是)的子流程。取决于建模工具,调用过程可以展开,但默认表现为折叠形式。

bpmn.collapsed.call.activity

XML表示

调用活动是一个普通活动,在calledElement中通过key引用流程定义。在实际使用中,通常在calledElement中配置流程的ID

<callActivity name="Check credit" calledElement="checkCreditProcess" />

请注意子流程的流程定义在运行时解析。这意味着如果需要的话,子流程可以与调用流程分别部署。

传递变量

可以向子流程传递与接收流程变量。数据将在子流程启动时复制到子流程,并在其结束时复制回主流程。

<callActivity calledElement="checkCreditProcess">
  <extensionElements>
    <flowable:in source="someVariableInMainProcess"
      target="nameOfVariableInSubProcess" />
    <flowable:out source="someVariableInSubProcess"
      target="nameOfVariableInMainProcess" />
  </extensionElements>
</callActivity>

可以将inheritVariables设置为true,将所有流程变量传递给子流程。

<callActivity calledElement="checkCreditProcess" flowable:inheritVariables="true"/>

除了需要按照BPMN 2.0标准的方式声明流程变量的BPMN标准元素dataInputAssociationdataOutputAssociation之外, Flowable还提供了扩展作为快捷方式。

也可以在这里使用表达式:

<callActivity calledElement="checkCreditProcess" >
  <extensionElements>
    <flowable:in sourceExpression="${x+5}" target="y" />
    <flowable:out source="${y+5}" target="z" />
  </extensionElements>
</callActivity>

因此最终 z = y+5 = x+5+5 。

调用活动元素还提供了一个自定义Flowable属性扩展,businessKey,用于设置子流程实例的businessKey。

<callActivity calledElement="checkCreditProcess" flowable:businessKey="${myVariable}">
...
</callActivity>

inheritBusinessKey属性设置为true,会将子流程的businessKey值设置为调用流程的businessKey的值。

<callActivity calledElement="checkCreditProcess" flowable:inheritBusinessKey="true">
...
</callActivity>

引用同一部署中的流程

默认会使用引用流程最后部署的流程定义版本。但有的时候也会想引用与主流程一起部署的引用流程定义。这需要将主流程与引用流程放在同一个部署单元中,以便引用相同的部署。

callActivity元素中,将sameDeployment属性设置为true,即可引用相同部署的流程。

如下例所示:

<callActivity calledElement="checkCreditProcess" flowable:sameDeployment="true">
...
</callActivity>

sameDeployment默认值为false。

示例

下面的流程图展示了简单的订单处理流程。因为检查客户的信用额度的操作在许多其他流程中都通用,因此将check credit step(检查信用额度步骤)建模为调用活动。

bpmn.call.activity.super.process

流程像是下面这样:

<startEvent />
<sequenceFlow sourceRef="theStart" targetRef="receiveOrder" />

<manualTask name="Receive Order" />
<sequenceFlow sourceRef="receiveOrder" targetRef="callCheckCreditProcess" />

<callActivity name="Check credit" calledElement="checkCreditProcess" />
<sequenceFlow sourceRef="callCheckCreditProcess" targetRef="prepareAndShipTask" />

<userTask name="Prepare and Ship" />
<sequenceFlow sourceRef="prepareAndShipTask" targetRef="end" />

<endEvent />

子流程像是下面这样:

bpmn.call.activity.sub.process

与子流程的流程定义相比没什么特别。但调用活动的流程也可以不通过其他流程调用而直接使用。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文