本文是本系列的第二篇文章,该系列所描述的典型建模错误,是从几百个用不同的工具(包括
IBM® WebSphere® Business Modeler)创建的真实流程模型中精选出来的。第
1 部分讨论了在您描绘某个业务流程中的分支和迭代行为时出现的反模式。本文则讨论了分层流程模型中的数据流建模、事件和触发器、终止某个流程的正确方法,以及对活动的重用。
摘自
IBM WebSphere 开发者技术期刊。
真实世界中的业务流程总会与某种形式的数据打交道。它们需要数据,它们修改和更新数据,它们还常常将各种不同的数据源揉和在一起,派生出新的数据。因此,捕获流程中的数据流,通常是业务流程建模项目中的一个重要阶段。将这一信息添加到流程模型绝不是微不足道的小事,它常常会导致错误发生。除了会出现错误之外,您的模型可能会很快变得杂乱无章。这一部分重点关注与数据流建模有关的问题。
悬空输入和输出
流程模型中出现的一个常见现象是悬空输入和输出,即模型中保持未连接状态的活动或网关的输入和输出。这种现象通常是当您在
IBM WebSphere Business Modeler(以下称为 Business Modeler)的基本编辑模式下编辑模型时出现的,Business
Modeler 不会以可视化的方式显示输入和输出,而是只显示活动和网关之间的连接边界。悬空输入和输出往往作为您决定删除或重定向的连接的残余部分遗留下来。当您删除某个连接时,Business
Modeler 不会删除曾连接的输入和输出,因为您可能希望重新连接它们。
在图 1 显示的示例流程中,有一个 fork 和一个 join,其中有悬空控制流(白色小箭头)和数据流(灰色小箭头)输入和输出。
图 1. 流程模型中的悬空输入和输出只在 Business Modeler
的高级编辑模式下可见
不幸的是,悬空输入往往会导致模拟错误,甚至会使模拟根本无法运行,因为活动或网关一直在等待某个永远不会接收到的输入。图
1 中的 fork 和 join 由于有悬空输入,它们将无法执行。目前,在 Business Modeler
中,网关的所有分支必须拥有相同的数据输入和输出。编辑器将强制实施这一要求,也就是说,每当您将输入或输出添加到某个分支的一个网关时,Business
Modeler 会自动向所有分支添加一个输入或输出。您不可能将不同的业务项与不同的分支关联起来。因此,如果您只是连接某些输入和输出,那么您马上就能获得悬空输入和输出,它们是无法在基本编辑模式中直接显示的,如图
2 所示。只有那些拥有丰富经验的用户才会注意到网关中代表输入和输出分支的较大的图形,这些图形暗示会有问题出现。
图 2. 在 Business Modeler 的基本编辑模式下不可见的悬空输入和输出
悬空输出的危害性弱于悬空输入,因为它们通常不会阻止流程模型的正确执行。不过,悬空的数据输出表明任务或流程产生了某些数据,或数据参与了在某个网关中建模的分支,但是这个数据没有被用在流程中的任何一个地方。
图 3 中的反模式总结的悬空输入(用红色圈出)是您必须避免的,而它总结的悬空输出(用橙色圈出)则是您理应避免的。
图 3. 反模式:避免悬空输入和输出
一个悬空输入,如果它是某个活动或网关的控制输入、某个活动的数据输入,或某个活动必需的数据输入,那么它会导致死锁。如果某个数据输入的最小增殖次数大于零,说明该输入是必需的。(若是某个输入或输出的最小增殖次数为零,说明它是可选的)。图
3 和图 4 中的输入和输出逻辑选项卡显示了已经定义的最大(和最小)输入和输出数目。图 4 中的模式,简要介绍了如何利用单个输入和输出条件,正确地建立网关和活动的输入和输出模型。
图 4. 模式:正确地定义和连接数据输入和输出
为了避免出现死锁,您必须连接所有必需的控制输入和数据输入。您还应当连接所有的控制输出和必需的数据输出。最小增殖次数被设置为
0 的数据输入和输出是可选的,建议您将它们全部连接起来,不过您也可以不这样做。您应当删除活动中所有的非必需控制输入和输出。移除数据输入和输出会更改数据要求,因此有时候您不可能移除它们。例如,业务项
C 只能作为图 3 左侧的 merge 中靠下的分支中的一条输入进行连接。由于它不是作为输出进行连接的(即它没有被任何一个后继活动所使用),我们在图
4 的模式中,将它从 merge 中移除。
建议
- 在基本编辑模式中工作,可以加快模型的编辑速度,尤其是从头创建模型的时候。不过,在结束某个建模会话之前,您应当切换到高级编辑模式,以查找和清除悬空输入和输出。
减少数据流模型中的混乱
悬空输入和输出是否在某种情况下也能成为有用之物?是的,因为它们可以只显示某些选定的流,从而成为一种用来减少模型中的混乱的有效方法。在我们发现的两种可能的建模方法中,您可以安全地使用悬空输入和输出,而不会对执行流程模型的能力造成影响。
图 5 显示的是第一种方法,它使用已连接的控制流,将所有数据输入和输出置于单独的输入和输出条件之下。
图 5. 某个单独的输入条件中的悬空输入,允许执行流程
在这种方法背后隐藏着一种直观的想法,即,数据不流经此流程,而是让任务和子流程访问由在活动间共享的数据源提供的数据。特定的控制流会决定以何种顺序访问数据。将已连接的控制流与未连接的数据输入和输出分离开来,将后者置于单独的输入和输出条件中,这可以确保该流程能沿着已连接控制流正确地执行。而且,所有的网关只涉及控制流,而与数据无关。当在高级编辑模式下查看模型时,数据输入和输出是可见的;在基本模式下则只能看到控制流。
图 6 显示的是第二种方法,该方法只使用单个输入和输出条件,但它会把所有未连接的输入和输出的最小增殖次数设置为零。
图 6. 将最小增殖次数设置为零的悬空输入表示可选输入,它们可以在可执行的流程模型中保持未连接状态
这种方法只关注如何在某个流程模型中显示选定的数据流。在活动和网关之间已经存在某个数据流连接的情况下,不应再有其他数据流连接。而且,只能有一个遍历网关的业务项,以使数据流尽可能保持简单。这种表示方法有一个优点,即网关与数据相关联,这样您就可以捕获基于数据的分支
decision 了。第一种方法只能显示控制流,因此您无法用它建立这些 decision 的模型。如果您只使用某个单独的输入和输出条件,会使模型的编辑变得更容易。这有一个小小的缺点,即它会导致控制流与涉及不同业务项的数据流混杂起来,从而使模型难以理解(与此相比,如果模型只显示带有未连接输入和输出的控制流,会显得容易理解)。建模项目中不同的参与者有时无法就哪种数据相关性最强、哪种数据可被视为可选项达成一致,这使事态更趋复杂。
建议
- 为了减少复杂的数据流模型中出现的混乱,请显示未连接的数据输入和输出。未连接的输入应被放入某个单独的输入条件,或是标记为可选,即把它们的最小增殖次数设置为零,以允许执行流程。
活动间的多个连接
复杂的控制和数据流容易导致流程模型中出现多个连接,这是产生混乱的模型的另一个来源。多个连接(或称多连接
)都是在某个相同的连接或网关中开始,并在另一个活动或网关中结束的。如果多连接只涉及控制流,那么这些连接会导致不必要的冗余。如果多连接与同一个业务项相关,它们容易导致建模错误。图
7 显示了一个示例。
图 7. 一个由于多连接而产生混乱的模型
由于控制和数据流的关系,图 7 中的模型显得非常混乱,很难理解。有两个控制流离开 Task 1
,并在 Task 3 中终结。在源和目标建模元素间,像这样的控制流多连接是冗余的,因为它没有向模型中添加任何额外的信息。从源元素到目标元素,控制只需要流动一次。而且,如果您已经在源和目标之间建立了一条数据连接,那么您无需再建立控制流连接,因为数据流中总是暗含着控制流。
您还会注意到,业务项 A 四次离开 Task 1 ,而业务项
B 则有两次离开这个任务。业务项 A 各向 Task
2 和 Task 3 流动一次,它有两次流向任务 Task 4。业务项
B 流向 Task2 和 Task 3。这类数据多连接通常会指向一个建模问题,在此问题中,用户试图将同一个项目传递给几个活动,或希望为传递的这个项目建立两个不同的实例。
例如协商流程,在该流程中您会交换 offer 和 counter offer。您有两个选项用于正确地获取这两个项目:
- 第一个选项是,将使用业务项的任务的输入和输出命名为有意义的不同名称。在图形模型中,Business
Modeler 只显示业务项的名称。在 Attributes 视图中,我们可以看到输入和输出的名称,以便区分业务项的目的。
- 第二个选项是,定义一个业务项模板,并使几个不同的业务项与之关联。例如您可以定义一个 offer
模板,然后再定义两个业务项 initial offer 和 counter
offer,它们会继承 offer 模板的一般属性。如果流经模型的两个不同数据流拥有一个共同的属性集,那么第二个解决方案会更合适。
建议
- 尝试避免将模型中的数据和控制流混杂起来。判断是否有可能采用一个纯控制流或纯数据流的模型。不要使用控制流多连接。
数据流的网关形式和活动形式
我们首先介绍当您使用数据流时,网关和活动形式的正确用法。流程模型中的网关形式和活动形式可以互换使用,如本文第
1 部分中的背景部分所述。不过对于使用数据流的流程模型而言,它的行为会根据您是否使用了网关形式或活动形式而有所不同。为了正确地捕获复杂的数据流,您可能需要在单个流程模型中混合使用这两种表。
正如我们先前指出的那样,Business Modeler 现在要求网关的所有传入和传出分支(即 fork、join、decision
和 merge)必须始终附有一个相同的业务项。在 Business Modeler 中,网关的不同分支不能拥有不同的业务项。因此,如果我们希望相同的共享信息沿着可选分支(对于某个
decision)或并行分支(对于某个 fork)流动,则应当使用网关形式。在图 8 显示的示例中,项目
A 和 B 流入 Task 2 和 Task
3,而 A 和 C 则从它们中流出。网关形式描述数据流是如何根据业务项属性的值被路由到业务模型中的可选分支的。您可以定义这些
decision 的输出条件,以便捕获详细信息,了解这些值是如何判定项目应流入的分支的。
图 8. 使用网关形式对几个分支共享的数据流进行正确建模
如果您需要对没有被流程模型中各分支共享的不同业务项进行建模,您必须使用活动形式。如果您要通过捕获来了解数据是如何根据信息的类型(即业务项)流入分支的,这是唯一的方法。在图
9 显示的模型中,非共享的业务项沿着几个并行分支流动。
图 9. 使用活动形式的流程模型,不同类型的未共享信息沿着并行分支流动
在图 9 中,Task 1 生成输出 A 、B
和 C ,并将它们以并行的方式路由到 Task 2 和 Task
3。Task 2 接收项目 A ,而 Task 3
则接收项目 B 和 C 。我们使用了单一输出和输入条件(注意输入和输出下的箭头符号),因为在我们进行建模的并行分支中,Task
1 充当了一个 fork,Task 4 则是一个将不同数据流汇集起来的 join。
图 10 显示的数据流与之相同,但该数据流是沿着可选分支而不是并行分支流动的。这意味着 Task
1 会成为一个 decision,而 Task 4 则会成为一个 merge,而您需要针对这些任务,定义两个输出条件以及相应的的输入条件。
图 10. 使用活动形式的流程模型,不同类型的未共享信息沿着可选分支流动
通过定义不同的输出条件,我们规定,在模型中,由 Task 1 提供可选输出,即项目 A
或项目 B 和 C 。并根据输入,执行
Task 2 或 Task 3 。您同样只能在 Business Modeler
的高级编辑模式下正确地查看这个流逻辑。在基本编辑模式中,图 9 和图 10 中的流程模型看起来是一模一样的。
现在我们可用修正后的模型表示图 7 中的流程。对于此模型,最常见的解释是,用户希望显示共享信息是如何传递到几个任务的。Task
1 不大可能将生成的项目 A 的几个副本作为输出。因此,当初应当使用网关形式建立流程模型,如图
11 所示。我们移除了冗余的控制流连接,并添加了一个新的业务项,即 A-prime ,以便将项目
A 的两种用途区分开来。
图 11. 用网关形式建立的修正模型,用来表示图 7 中的流程
建议
- 利用 decision、fork、merge 和 join,通过网关形式建立的模型,可以表示共享信息是如何沿流程中的几个分支流动的,以及根据业务项属性值,应在哪里进行分支。
- 在模型中,如果数据流是根据信息类型进行分支的,而且未共享的业务项会沿着不同的分支移动,那么该模型应该使用带有输入和输出条件的活动形式,但其中不含网关。
- 在某些流程片段中,数据流会根据来自流程片段的业务项进行分支,而在另一些流程片段中,则会根据某个项目属性进行分支,将这些流程片段分离,可以避免将两种形式混合起来。
数据流错误一般出现在数据沿几个执行分支流动的时候,这些执行分支可以捕获可选或并行行为。由此可产生三种建模情景:
- 相同的共享数据沿流程模型中的几个分支传递。
- 不同的非共享数据沿各个分支传递。
- 共享和非共享数据混合起来进行传递(最复杂)。
在下列各部分中,我们将分别讨论每一种情况。
沿着几个分支传递共享数据
理解使用活动形式或网关形式的数据流模型在行为方面的差异,可以为您提供一个基础,使您可以对三种数据流建模情况进行详细调查,并讨论典型的建模错误。在大多数情况下,并行和可选分支过程应用的是相同的数据流反模式和模式。因此,我们重点介绍并行分支,只在出现值得关注的差异时才讨论可选分支。我们首先讨论共享数据必须沿几条分支进行路由的情况。
图 12 中反模式中显示的错误,是我们在使用活动形式以指定共享业务项应沿几支分支流动时经常见到的。
图 12. 反模式:活动多次提供相同的数据输出,以便在并行分支中使用相同的共享数据
使用活动形式,会导致活动的输入和输出中出现重复的数据。Task 1 通常会生成业务项 A
和 B 作为输出。为了将项目并行路由到两个流程片段(即用蓝色框住的区域),modeler
复制了 Task 1 的输出。与之类似的是,modeler 还复制了 Task 4
的输入。这不仅是一个很坏的建模习惯,它还改变了模型的语义,因为它添加了多余的业务项,而且复制了活动的输入和输出。当您设计可供重用的活动时,这种复制会成为严重的限制,因为任何重用过程都必须提供两个
A 和两个 C 作为 Task 4
的输入,否则任务就无法执行。图 13 中的模式显示了这一情景的正确建模解决方案。
图 13. 模式:在几条路径中使用相同的共享数据时,使用网关进行分支
为了进行可选分支,会产生一个相同的模式,在其中 fork 被替换为 decision,join 被替换为
merge。当沿着几个分支使用和生成共享数据时,您使用网关模式进行分支。组成这些分支的流程片段理所当然地具有修改数据的能力。我们可以在模式中看到,项目
B 进入了两个分支,但没有离开蓝色的流程片段。相反,项目 C 被两个分支作为输入提供给
join。注意您必须在两个分支中向 join 提供相同的项目 C ,以便能正确地执行。
目前在 Business Modeler 6.0.2 中进行的模拟表明,各有两个 A
和 C 离开了图 13 所示的模式中的 join,这使 Task 4
被执行了两次。因此,模拟实现了一个语义,在其中,项目被某个 fork 增殖成几份,而 join 不会做出与之对称的行为,也就是说,它不会撤消增殖操作。join
的做法更像是对数据流模型进行合并,所以可能有缺乏同步的现象出现,不过如果 modeler 有意按 join
多次执行流程片段,那就另当别论了。
在图 13 的流程模型中,您可以将输入 A 和 C 的最大和最小增殖次数设置为
2,以防止出现缺乏同步的现象,因为每个项目都会出现两次,每次 Task 4 都要进行同步。不幸的是,这种解决方案使得在其他流程模型中重用
Task 4 变得更加困难了。而且,在一个使用更复杂的 fork-join 结构的模型中,要计算出正确的增殖次数也并非易事。
沿着几个分支传递非共享数据
对于非共享数据沿着不同的可选或并行分支流动的情况,很多用户会经不住诱惑而使用网关形式。这往往会导致
join 或 merge 中出现悬空输入,并进而导致死锁。图 14 中的反模式演示了这一错误。
图 14. 反模式:使用网关沿并行分支传递未共享数据,会因为悬空输入而出现死锁
join 中的悬空输入,会使 join 无法执行,并因此阻塞后面的所有活动。这意味着,虽然反模式中的任务
Task 4 的所有输入都已经连接了,但该任务仍然无法执行。fork 的悬空输出不会造成执行问题,但是它们会使流程模型无法使用某些输出。在蓝框中的流程片段生成额外数据的情况下,会出现其他悬空输入。例如,在这个流程片段中将提供一个新业务项
D ,作为某个任务的输出,它将取代输入项目 C 。
通常,为了纠正这个错误,您可以让所有业务项穿过全部任务,虽然这些任务并不需要访问全部业务项。例如,项目
B 和 C 会额外流经上面的并行分支。您有几个很好的理由,可以解释为什么不让不必要的数据穿过活动。首先,这会使活动的重用变得更加困难,因为活动会需要额外的输入和输出,这些并不是所有流程都能提供的。其次,这会把信息暴露给不需要该信息的活动,稍后,在严格遵循流程模型设计
IT 解决方案的实现过程时,会导致安全和性能问题。
图 15 中的模式显示了使用活动形式的正确解决方案:
图 15. 模式:当在几条路径上使用不同的非共享数据时,利用活动形式进行分支
该模式显示了各个并行分支。在为使用非共享数据的可选分支流建立模型时,您需要正确地定义活动的输入和输出条件。需要特别说明的是,如果活动充当了隐式的
merge,则它们的输入条件必须与可选分支精确匹配。若该条件与分支不匹配,可能会出现死锁或缺乏同步的现象。如果我们更改图
15 中的模式以显示两个可选分支,我们需要为 Task 4 定义两个输入条件,第一个输入条件包括项目
A ,第二个输入条件包括项目 B 和 D 。
沿着几个分支传递共享和非共享数据
在第三种情景中,数据的某个子集会在所有分支上共享,不过一个单独的分支例外,它使用的是特定于该分支的数据。单单使用活动或网关形式,并不能保证建立一个正确的模型。在图
16 的反模式中显示的情况下,上面的分支使用项目 A ,下面的分支使用项目 C
并生成项目 D ,但两个分支都使用了项目 B 。这个模式使用的是活动形式。我们很快就会注意到一个问题:在
Task 1 的输出和 Task 4 的输入中,有一个重复的项目 B
。
图 16. 反模式:使用活动形式,沿着几条路径传递共享和非共享数据,会导致输入和输出出现重复
图 17 的反模式显示,网关形式会导致 join 中出现悬空输入,并进而导致死锁。
图 17. 反模式:使用网关形式沿几条路径传递共享和非共享数据会导致由悬空输入引起的死锁
唯一的解决方案是混合使用两种形式:使用网关对各分支共享的业务项进行路由,并使用输入和输出条件路由特定于某个分支的业务项。图
18 显示了一个针对并行分支的解决方案。
图 18. 模式:在并行流中,绕过网关是可行的
共享的项目 A 和 B 在穿过 fork 时分支,并在
join 中重新联接。为了避免出现悬空输入,join 不应期待其他输入。项目 C 只能作为输入,提供给需要它的靠下的那条分支。当它在
fork 中传递时,会在其中的一个分支中创建一个悬空输出。这并不是一个理想的建模解决方案,但至少它允许该流程得以执行。或者,C
可以绕过 fork,直接进入蓝色的流程片断。不过,D 必须绕过
join,直接进入 Task 4 ,以避免出现死锁。
注意,A 流经 fork 和 join,但它绕过了 fork 的靠下那条分支中的流程片断,因为它并不是这个片断中的活动所必需的。A
可以绕过 fork 和 join,但它仍然需要进入和离开 fork 的靠上那条分支中的流程片断。它还必须进入
Task 4。我们在该模式中显示了一个变相的绕过动作,可以消除致命的死锁。可以有绕过网关的其他流,但是这样解决方案会使关系图很快就变得一团糟。
绕过并行流中的 fork 和 join 不会导致死锁,因为所有分支都是并列执行的,所有业务项总会通过连接到达终点,也就是说,所有任务都会按规定收到它们的输入。例如,Task
1 生成所有的输入,把它们提供给 Task 4。对于某个期待从几个分支获得输入的活动而言,可选流无法保证这些输入的可用性。
图 19 中的反模式显示了一个含有可选分支的流程模型,其中的项目 D 只能由下面的那条分支生成,它绕过
merge,直接进入 Task 4 。D 是 Task
4 必需的输入。
图 19. 反模式:绕过可选分支的网关会导致死锁
不幸的是,在上面的那个分支执行时,D 是不可用的。一个必不可少的输入必须始终由所有的可选分支提供。如果某个输入不是必需的,而是可选的,那么有两种可能的解决方案可以用来修正
Task 4 的输入行为。图 20 中的模式显示的是第一种解决方案,在其中,项目 D
被作为 Task 4 的输入,它的最小增殖次数被设置为零。
图 20. 模式:只沿某些可选路径提供的输入必须是可选输入
图 21 的模式显示了第二种解决方案,它为 Task 4 定义了几个输入和输出条件,这些条件可与可选分支正确匹配。Task
4 有一个输入条件,只需将业务项 A 和 B
提供给上面的分支,而它针对下面分支的则是一个单独的条件,其中包含业务项 A 、B
和 D 。
图 21. 模式:输入条件必须与沿着可选路径流动的数据精确匹配
两种解决方案都能使 Task 4 在不依赖于 decision 网关中的分支决策的情况下正确地执行。
建议
- 当为复杂的数据流建立模型时,请根据共享或非共享数据是否要沿几个流传递,区分为不同的情况,并在此基础上采取一种系统化的方法。然后再确定这些流在流程模型中是否是以并行或可选分支的形式出现的。您可以使用活动形式来捕捉流,但如果各个分支有共享的数据,那么请注意可能会出现的重复活动输入和输出。对于这种情况,网关形式是个更好的解决方案。
- 当为可选分支建立模型时,请注意那些只对某一分支可用的数据。如果某个活动会合并可选分支,那么对于该活动而言,这一数据必须是可选输入。
通常,用户希望在某个流程模型中对事件触发器进行建模。
Business Modeler 支持业务度量上下文中的事件,您可以利用业务度量定义关键性能指标以便进行流程监视,但是这些事件不能直接充当流程模型本身的一级建模元素。目前,Business
Modeler 支持以通知 (notification) 形式出现的事件,通知可以由通知接收器
(notifaction receiver) 接收,并由通知广播器 (notifcation
broadcaster) 播发,也就是说,Business Modeler 支持对发布-订阅通信的抽象建模。目前,您还无法对基于事件的点到点通信进行建模,也不能捕获事件在流程模型中的流动情况。
将事件作为控制流?
在我们研究的模型中,我们发现控制流常被用来捕捉事件。不过,在 Business Modeler 中,控制流的语义只定义了活动的执行顺序,它并不像通常的事件那样,能够承载信息。因此,意在用控制流捕获事件的建模行为会导致多种语义问题。在图
22 显示的示例中,是用控制流捕获流程中发生的几个初始和最终事件背后的逻辑的。
图 22:复杂的事件触发逻辑被错误地捕获为控制流
图 22 用三个开始节点表示三个可以对流程执行进行初始化的不同事件。由这些开始节点发起的控制连接的另一头,是以事件命名的各个后续网关;不过这些名称并没有显示出来,只能通过在连接上单击或打开属性视图才能看到它们。在由此产生的模型中,关于事件的重要信息不会直接显示在图中。它还使用了一个
merge 和一个 join 来捕获事件逻辑。在这里,用户意在描述一个由某一单一事件或两个必须同时发生的事件触发的流程。为了表现事件逻辑
(event1 AND event2),用户引入了上面的两个开始节点,并将它们连接到一个 join。下面的开始节点代表第三个事件。用户将这个开始节点和
join 连接到一个 merge,以表示事件逻辑 (event1 AND event2) OR event3。
不过,Business Modeler 中各个开始节点的语义规定,流程模型中所有开始节点都是立即执行的。在本例中,三个事件表示控制连接都被立即触发,然后
join 将会执行。merge 执行了两次,在接收到来自下面的开始节点的控制时会执行一次,而在接收到来自
join 的控制时会再次执行。因此,Task 1 当该流程每次执行时都会执行两次。您可以在
Business Modeler 的模拟过程中看到这一行为。用户试图用三个开始节点捕获的那三个可选事件总是同时发生,不可能作为后续任务的可选触发器。
当在一个带有两个传出分支(这两个分支直接终止于某个停止节点)的 decision 中成功执行之后,用户会捕获由任务触发的那两个最终事件。停止节点和结束节点无法将任何事件信息传递到流程之外,它们只起到停止控制流的作用。因此使用控制流来描述事件并不是一个好主意。
图 23 中的反模板使这一观点得以泛化。直接与网关相连的多个开始节点并不适合捕获流程的触发逻辑。将几个开始节点直接连接到某个
merge,这会导致出现缺乏同步的现象。您应当用一个直接连接到某个任务或子流程的开始节点,替换一个只使用开始节点作为输入的
join。将某个 decision 或 fork 的所有传出分支直接连接到各个结束或停止节点,这意味着这个决策或
fork 是不必要的。一个网关应当生成包含任务或子流程的分支。通常,只有其中的一个分支可以捕获“无所事事”的条件,并直接连接到某个结束或停止节点。场景
5 将详细讨论结束和停止节点在语义方面的区别。
图 23. 反模式:merge 或 join 之前只有开始节点,以及
decision 或 merge 之后只接某个结束或停止节点的做法是错误的
将事件作为控制流
我们自己的做法是使用两个可选解决方案来捕获事件。在流程模型(该模型的目的不是导出到 IT级别,在该模型中,我们希望捕获流程的开始和结束事件,但无需显示事件流)中,我们使用了接收器和通知广播器。关于如何正确使用这些建模元素的详细信息,请参阅
Business Modeler 的文档。在流程模型的某些部分中,我们希望能描述流程中的信息是如何通过事件(这些事件是在活动间流动的)被接收到的,为此我们将使用业务项和数据流连接。
在图 24 左边显示的三种不同的业务项分类中,将事件、通知和“普通”的业务项彼此区分开来。
图 24. 表示为特定类型的业务项流的事件流
每种类型的信息都与某个图标关联起来,您可以在 Business Modeler 中很容易地自定义它们。通知
(notification) 和业务项 (Business items) 是作为预定义类别提供的,而事件
(Events) 子类别则是用户定义的。在本图的流程模型片断中显示了一个示例,在该示例中,如果
Task 1 接收到一个业务项 A 和事件 event
3,或发生了 events 1 和 events 2 ,那么 Task
1 将会执行。Task 1 把业务项 B 发送给 Task
2,一同发送的还有一个复杂事件 (complex event),它现在是以与其他业务项类似的方式在流程中流动的。因为我们在模型中将事件表示为几种特定类型的业务项,所以我们可以为事件定义一些属性,用来捕获关于它们承载的信息的详情。您还可以在建立决策条件模型时访问这一信息。
图 25 中的模式显示了使用数据流表示事件的方法。它通过流程输入接口接收初始事件,而最终事件则通过流程输出接口离开该流程。
图 25. 模式:事件可以在模型中表示为业务项
建议
- 不要使用控制流为流程模型中的事件和触发器建模。您可以使用用于捕获通知的建模元素,也可以将事件表示为某种特定的业务项流。
- 不要将网关或活动的所有输入或输出直接连接到开始、结束和停止节点。
Business Modeler 提供了两种类型的节点,用来终止流程,它们分别称为结束和停止节点。结束节点用一个含有十字交叉图案的圆圈表示,停止节点则显示为一个含有黑点的圆圈。停止节点会停止流程模型中的所有活动和流,因此它将终止整个流程模型中所有正在执行的分支,也就是说,它会导致整个流程的“全局关闭”效果。
如果有多个分支同时执行(例如模型中存在某种并行机制),那么停止节点总是终止所有并行的分支。与之相反,结束节点只具有局部效果;它只会终止它到达的那个分支。由于停止节点对整个节点具有全局效果,在有几个分支并列执行的流程模型中,我们在放置停止节点时必须十分小心。
从语义上看,如果模型只沿一条路线连续执行(如不使用 fork、包含性 decision、循环连接和有分支的输出条件的模型),我们可以在该模型中互换使用这两种节点。从工具的视角看来,Business
Modeler 6.0.2 中的每个流程、子流程和循环中至少应有一个停止节点。在 Business Modeler
6.0.2 中进行的模拟,要求流程中的每条路径必须以一个停止节点终止,也就是说,结束节点是很少使用的。在模拟数据流模型时,停止节点显得尤为重要,因为需要用它来释放数据。这意味着当遇到一个停止节点,或子流程的高级输出逻辑
(advanced output logic)(请参见“Advanced output logic”选项卡)被设置为
streaming(即子流程在运行状态下释放数据)时,父流程只能从该子流程中接收数据。
我们来详细研究一下结束节点和停止节点的语义区别,特别是停止节点的全局关闭效果。
并列执行的分支中的停止节点
通常,用户不会察觉到停止节点的全局效果,而只是用它来终止某个流程中的每个单独的分支。图 26 显示的是一个典型示例。
图 26. 在某个具有并列执行分支的流程模型中使用停止节点
我们看到,紧跟开始节点后面的是一个带有两个分支的包含性 decision。上面的分支通向一个 fork,后者可以使
Task 1 和 Task 2 并列执行。这两个任务各与一个停止节点直接相连。包含性
decision 的下面那条分支通向一个循环流程片段,在该片段中 Task 3 会反复迭代,直到满足了决策条件为止。“yes”这条分支直接与停止节点相连。
在其中一个分支到达停止节点的同时,整个流程,甚至包括没有完成执行的任务都会终止。有时这种“全局关闭”效果是有意为之的,可以被正确地模拟出来。不过,如果念及
IT 实现,这可能是您最不希望看到的流程行为;与此相反,并列运行的分支应在正确地完成各自拥有的任务后分别结束。
如果初始的 decision 确实是包含性的,而且激活了两个分支,则会有问题出现。如果它只激活了下面的分支,就不会出现问题,因为循环流程片段只包含一个顺序循环,也就是说,Task
3 会被反复执行,但是不会同时运行该任务的多个实例。如果该 decision 只激活了上面的分支,那么若是
Task 1 和 Task 2 代表的活动具有不同的持续时间,则 fork
中仍然有出现问题的可能。当其中一个任务完成时,可能会导致另一个任务立即终止。当包含性 decision
的两个传出分支都被激活时,如果 Task 1 或 Task 2 有一个极短的执行持续时间,这在理论上会导致出现永远不会执行的循环,因为当执行到上面的某个停止节点时,整个流程(包括循环)都将终止。图
27 中的反模式显示的是在并行分支中使用的停止节点的问题。
图 27. 反模式:用于结束并行分支的停止节点总是导致整个流行的终止(即使该停止节点的本意是终止某个单独的执行分支)
如果我们将包含性 decision 和 fork 替换为两个独占性 decision,该流程将只有一条后续执行路径。在这种情况下,结束和停止节点可以互换使用。在不存在并行的情况下,停止节点与结束节点的效果完全一样,也就是说,它将终止到达它的分支。
由于目前的模拟要求,我们建议您在模型中使用停止节点。不过,您应当注意到,在模拟运行期间,当某个路径到达了停止节点时,其他并行的路径可能尚未结束。Business
Modeler 中导出的 BPEL 将结束和停止节点映射为 BPEL 流程的一个隐式结束点。它不会在 BPEL
中为停止节点生成一个显式全局终止行为,如 BPEL terminate 活动。
图 28 中的模式总结了我们的讨论和意见,提出在添加一个用来终止流程的终止节点之前,应重新联接并行的分支。
图 28. 模式:停止节点建立了全局关闭行为模型。如果您想故意实现这种行为,或只发生了一个单独的后续执行,又或者并行的分支在到达某个单独的停止节点之前已经重新联接,那么您可以安全地使用这些停止节点。
建议
- 如果您要为某个流程的“全局关闭”行为建模,请使用停止节点。
- 利用一个 join 节点重新联接并行分支,然后放置一个结束或停止节点,而不是分别结束每个并行分支。
某一流程终止时的数据输出
最后,我们来看看流程边界以及某个流程的输入和输出。通常,流程会将接收的数据作为输入,将生成的数据作为输出,这与流程模型内部的活动类似。因此,您可以为流程定义输入和输出条件,以形成流程接口。
图 29 显示了某个流程的示例,该流程的输入和输出已经得到了定义。
图 29. 某一流程的输入和输出数据,与开始和停止节点组合使用
该流程把接收到的项目 A 作为输入,并将其传递给 Task 1。该任务也有一个开始节点,不过,这个节点是可选的,不会改变任务的执行语义,这个任务只有在该输入可用的情况下才会开始执行。附加的开始节点能更清楚地显示流程的开始部分。该流程生成项目
A 和 B ,或 A 和
C ,并将它们输出。A 、B 和
C 分别是 Task 4、Task 2 和
Task 3 的输出。项目 D 是 Task 3 的输出,不能被其他任何任务使用,也不能作为流程的输出来提供。因此它是连接到某个结束节点的。此处如果换成停止节点将是一个错误的解决方案,因为它会立即终止整个流程,Task
4 将不会执行。项目 E 是 Task 4 的输出,它连接到一个停止节点,因为它不能被流程中任何其他活动所使用。在此处使用停止节点是正确的,因为Task
4 是该流程执行的最后一个任务,而且没有与之并行执行的活动。流程接口显示了两个可选输出条件,以便与可选输出进行匹配。在图
30 的反模式总结的典型问题中,流程接口与它们的传入流不匹配。
图 30. 反模式:流程输出接口与它们的传入流不匹配,会导致不确定性(左边)或死锁(右边)
您在定义流程接口时使用的方法,与您定义流程内各种活动的输入和输出时是相同的。流程接口定义需要对不同执行分支进行建模,在其中,各个分支将通向业务项的不同组合,而业务项则会到达或离开某个活动。场景
3 对此进行了详细讨论。流程的输入和输出条件与它可能的执行分支之间的错误匹配,可能会成为错误的来源。将可选分支与可选输入和输出链接起来,这是十分重要的。
图 30 左边显示的是两个并行分支,它们并行提供输出 A 、B
和 C 。流程输出接口希望每个输出条件只为它提供一个输出,这意味着流程需要在内部决定通过输出接口释放哪一个数据,而这一数据在每次流程运行时都会有所不同。因此一个重用流程必须有能力处理任何可能的输出。右边是两个可选分支,它们提供
A 、B 或 C 作为流程的输出。这两个可选分支是由
decision 引出的。此外,Task 2 还有 B 或 C
作为可选输出。输出条件总是希望获得另一输出条件的全部三个业务项。在这种情况下,一个重用流程将永远无法收到由被重用流程的输出接口指定的所有数据,因为反模式左边那个流程片段不能将所需的数据全部释放出来,从而处于死锁状态。正确的解决方案是用右边的单一输出条件与左边的流程片段匹配,反之亦然。另外还要注意,反模式中没有提供一个停止节点,而在运行模拟时,如果您要释放数据,这是必不可少的。
流程中的每个分支都必须以开始节点作为开端,或是将接收到的数据作为输入。用一个终止节点结束流程的每个分支,或在分支结束时提供数据作为输出,这也是一种很好的建模方法。为了提高流程模型的良构度,我们建议,对于那些由
fork 和 decision 分出的分支,如果它们使用了某种共享数据,请用 join 和 merge
把它们重新联接起来。我们在场景 3 中对此进行了详细讨论。
有时候,流程中的活动拥有的输出数据并不属于流程输出的一部分。这个输出往往会保持未连接的状态,变成悬空输出。一个可选的方法是将这个输出连接到某个结束或停止节点,以强调该流程的模型已经完全建好了。为此,Business
Modeler 针对数据流,在开始、结束和停止节点之间提供了一种不对称性。开始节点只能发出控制流,而停止和结束节点还可以接收数据流。
图 31 中的模式显示了当前正在匹配的流程输出接口和一个被放置在正确位置的停止节点,后者不会导致意外的全局关闭现象。不过,在进行模拟时,该流程将不会释放它的数据,因为涉及
Task 2 和 Task 3 的分支不是以停止节点结束的。
图 31. 模式:流程输出接口必须与它们的传入流正确匹配
图 32 中的模式显示的是得到更一步改善的模型。注意,我们添加了一个 join,以使两个并行的分支重新联接起来,引向某个停止节点。现在已经不再需要
Task 3 先前连接的结束节点了。意外的关闭将不再发生,所有数据输出都将被正确地释放。
图 32. 模式:流程输出接口必须与它们的传入流正确匹配,并行分支在到达某个停止节点之前必须重新联接起来以释放数据,并避免出现意外的全局关闭现象
建议
- 流程接口必须与流程的数据流正确匹配。
- 用一个结束或停止节点终止每个流程分支
- 用一个停止节点终止分支并释放数据。在添加停止节点之前,先重新联接各个分支,以避免出现意外的全局关闭现象。
- 将未释放到流程输出接口的的活动数据输出连接到某个结束或停止节点,以避免出现悬空输出。
到目前为止,我们从单个流程模型的角度(也就是从局部的角度)讨论了子流程和任务。不过,不同的流程可能会共享相同的活动,所以我们应当在流程模型中尽可能地增加对活动的重用。为了方便重用,Business
Modeler 允许您直接将任务和子流程定义为项目树中的全局建模元素,您可以将它们拖放到其他流程模型中以便重用。
图 33 显示了一个组合流程,它重用了其他全局子流程和全局任务。
图 33. 分层流程模型的详细视图
图 34 的左边显示的是一个项目树,右边则是组合流程各层级的精简视图。
图 34. 在项目树中定义,并在流程的几个级别中重用的全局任务和流程
项目树包含各个可供重用的全局流程和全局任务的定义。Main Process 的分解层次结构的精简视图使我们可以直观地看到流程和任务在各层级中的位置。
只要有可能,您应尽量只对每个活动进行一次定义和实现。该活动的定义可以在以后被任何其他流程重用。
两个图都显示了示例流程的四个级别。在顶层,我们看到的是 Main Process,它重用了
Subprocess 1 和 Global Task 1。Subprocess
1 重用了 Global Task 2 和 Subprocess 2。Subprocess
2 重用了 Subprocess 3。被重用的子流程之后是一个带有两个分支的 decision。两个分支都重用了
Global Task 3。由于该任务在 Subprocess 2 中出现了两次,为了以示区别,该任务第二次出现时,会由工具在名称上添加一个“:2”。decision
的上面一条分支重用了 Global Task 1,后者也被 Main Process
重用。最后,Subprocess 3 再次调用 Main Process 。
由于本文关注的是常见的建模错误,我们不会对详细的层次化分解的各个方面进行讨论,只是重点介绍了当您通过重用子流程和全局任务组成某个流程时可能出现的问题。上面的例子展示了两种错误来源。首先,我们可以看到
Main Process 在分层组成结构中出现了两次,这意味着它会在本身的分解中再次出现。某个流程在其本身的分解层次结构中出现,这将导致一个递归的细化过程。在我们的示例流程中,该递归过程是无限的;Subprocess
3 总会再次调用 Main Process 。在一个适当的递归流程定义中,Subprocess
3 会包含一个带有两个可选分支的 decision,其中一个分支包含递归调用,另一个则通向某个停止或结束节点。
在较大的组合流程中容易出现无限分解的现象,而且维护模型的全局视图也变得相当困难。通常,用户不会有意创建递归流程模型。它们是用户通过将流程或任务拖放到流程中的不同层级而创建的。在图
33 所示的重用链中,一个低级别流程突然重用了自己的“父”流程,您很容易创建这样的重用链。为了避免出现类似的递归细化,您应当把可重用的流程分为几个抽象级别。处于较高抽象级别的流程只能用来自较级别的流程进行细化,级别最低的流程只能包含全局和本地任务。
我们观察到的第二种建模错误是由某一流程模型中对同一活动的重用造成的。例如,Global Task
3 在 Subprocess 2 的细化过程中出现了两次。您应当检查活动是否多次出现,因为这种情况意味着您对控制流的捕获还做得不够。有观点认为应将
decision 置于 Global Task 3 任务之后,而且这个任务只能在流程模型中出现一次。图
35 显示了经过修正的 Subprocess 2。
图 35. 经过修正的子流程,在其中被重用的全局任务只出现了一次
某个活动的多次出现,不一定会导致流程执行错误,但是它会影响模型的可读性。一个常见的有关冗余性的示例出现在用户分析迭代行为的时候。它不会引出一个循环,而是添加一个
decision,然后将重复的活动多次放入流程模型中。这种方法使业务分析师可以分离流程中重复的路径,为分析业务场景中的流程模型提供了方便。不过,在一个实现场景中,可选的后续路径不能正确地捕获要实现的行为,这样就无法根据模型生成有意义的
BPEL 了。一个更好的解决方案是将重复的活动放在一个循环中,如本文第 1 部分中的场景
2。为了创建循环,您可以在一个 merge 后接一个 decision,或使用 Business
Modeler 中提供的循环建模元素。
最后,如果您在使用数据流的流程模型中重用活动,那么您必须仔细检查可重用活动的输入和输出。图 36 显示了一个全局任务,它提供可选的输入和输出接口以便重用。
图 36. 一个全局任务的输入和输出条件,该全局任务会指定备用的任务接口以便重用
任务在接收到业务项 A 和 B ,或 C
和 D ,或B 和 C
作为输入时,该任务将执行。我们用三个不同的输入条件定义了这些可能的组合。任务提供业务项 A 、B
或 C ,将它们作为输出。每个业务项会被放置在一个单独的输出条件中。输入和输出条件的定义是活动定义的一部分,它们不能在任何重用这一任务的流程模型中被更改。当您将某个活动拖放到采用重用方法的流程模型中之后,只能向该活动添加更多的控制流输入和输出。
为了正确地重用某个任务或子流程,每个重用流程必须为至少一个输入条件提供输入,这意味着您必须将这些输入与重用流程模型中的其他活动连接起来。重用流程还应当有能力处理被重用活动可能的输出。在理想情况下,您应该把所有输入条件中的全部输入连接起来。如果有某个输出无法连接,由于它不能被重用流程所使用,您可以将这个输出连接到一个结束或停止节点,并将来自被重用的活动的另一条控制流连接添加到在流程模型中处于下游的其他活动。
在我们的示例中,重用流程必须有能力处理可选输出 A 、B
或 C 。在重用流程中,我们需要后续的流程片断以接收作为单独输入的那些业务项。如果与某个特定输入相关的特定输出出现(例如,仅在
A 和 B 被作为输入提供的情况下,产生业务项 A
),那么重用流程只需有能力处理这些输出即可。
建议
- 要使分支模型具有更好的可读性,请将可重用的子流程根据同样的抽象级别进行分组,并且只对含有来自更低抽象级别流程的那些流程进行细化。
- 除非您想建立一个无限递归的流程细化模型,否则分层流程分解结构的不同级别中不应含有同一流程。添加一个可到达的退出分支以停止递归调用。
- 检查同一个被重用活动是否多次出现,以找出冗余,以及是否有可能对流程的控制流做出改进。当在一个包含数据流的流程模型中重用某一活动时,应确保活动的输入和输出与可能的数据流匹配,这些数据流可以与重用流程模型中的已重用活动产生联系。
在本文中,我们研究了过去两年中根据从不同建模工具中挑选出的数百个真实流程模型总结的典型建模错误。建模错误被分为六种通用建模场景。在本文的第
2 部分中,我们讨论了下列场景:数据流的建模,事件和触发器的建模,正确终止流程的方法,和分层流程模型中对活动的重用。
对于数据流建模,我们为用户提供了一套系统化的方法,用户可以用这一方法评估他们是否是沿着几条可选或并行分支传递共享或非共享数据的。我们还提供了关于如何避免使数据流模型中出现混乱的提示。最后,我们阐述了如何正确地终止流程,以及如何传递已终止流程的数据。
非常感谢我们的同事 Thomas Gschwind、Jochen Küster、Cesare Pautasso、Ksenia
Ryndina、Michael Wahler、Olaf Zimmermann 以及其他 IBM 专业人员为本文提出的宝贵意见。我们还要感谢很多其他同事,他们发来了自己的模型供我们分析借鉴。
学习
获得产品和技术
|