本文档提供了新业务开通系统子流程设计的方式,介绍了内嵌式子流程,调用式子流程,事件子流程,事物子流程这几种子流程在新集客中的实现。
文档还初步设计了如何对业务开通集客流程进行子流程划分的初步方案。
BPMN子流程
划分子流程可以增强流程的重用性以及对功能集进行归类分组。
子流程分为以下几种:
内嵌式子流程
内嵌式子流程是嵌入在父流程里的流程,属于同一个BPMN文件。在一个大流程中可以包含多个内嵌式子流程,内嵌式子流程可以有自己的变量域。
以下是一个收缩的内嵌式子流程样例:
当子流程展开时展示成这样:
内嵌式子流程的作用:
- 可以为子流程增加边界事件
这个是内嵌式子流程最常用的方式,当我们需要为一个流程中多个环节增加某些消息驱动的逻辑时,最简便的方式就是创建一个内嵌式子流程,然后在子流程的边界上添加一个事件。这样就不用在每个环节增加事件监听,当流程发送事件消息,流程会从子流程范围内的任意环节进入监听环节。 - 可以隐藏过多的细节只展示流程概览图,想看细节时可以展开查看
当某个流程功能比较内聚可以用一个大环节代替时也可以采用内嵌式子流程。
在设计内嵌式子流程时需要注意,内嵌式子流程有一个且只有一个普通的开始事件,内嵌式子流程内的箭头不能超越边界,内嵌式子流程至少有一个结束事件。
调用式子流程
调用式子流程实际上是调用另外一个流程。调用式子流程和内嵌式子流程都会进入一个子流程,不同的是,调用式子流程调用的是外部的一个流程,而内嵌式子流程是流程内部嵌入的子流程。调用式子流程的主要作用是为了流程能够被多个流程复用,而不是某一个流程特有的子流程。
调用式子流程收缩样例:
调用式子流程打开样例:
子流程内部:
通过样例可以看出调用式子流程和内嵌式子流程差不多,都可以作为缩略环节,可以增加边界事件。不同的是调用式子流程与父流程不在同一个BPMN文件内,这带来两个变化:
- 调用式子流程支持多个父流程而内嵌式子流程完全包含于某一个子流程。
父流程通过创建一个callActivity来引用一个子流程:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<bpmn:process id="callActivityProcess" name="调用式子流程调用" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>SequenceFlow_171clgt</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:endEvent id="EndEvent_1rj914l">
<bpmn:incoming>SequenceFlow_0q780lc</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="SequenceFlow_0q780lc" sourceRef="replySub" targetRef="EndEvent_1rj914l" />
<bpmn:task id="replySub" name="回复消息">
<bpmn:incoming>SequenceFlow_0inylwb</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_0q780lc</bpmn:outgoing>
</bpmn:task>
<bpmn:sequenceFlow id="SequenceFlow_171clgt" sourceRef="StartEvent_1" targetRef="callParseActivity" />
<bpmn:sequenceFlow id="SequenceFlow_0inylwb" sourceRef="callParseActivity" targetRef="replySub" />
<bpmn:callActivity id="callParseActivity" name="自然语言处理" calledElement="parseProcess">
<bpmn:incoming>SequenceFlow_171clgt</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_0inylwb</bpmn:outgoing>
</bpmn:callActivity>
</bpmn:process>
calledElement指定子流程的key。
- 调用式子流程无法和父流程共享变量域,因此需要从父流程带入变量,并且在结束时将变量带出到父流程。
我们使用调用式子流程时最希望的是子流程能被多个流程复用,为了这个效果我们将子流程单独使用BPMN文件描述,但是我们又希望子流程仍然是父流程的一部分,能够使用相同单据ID,至少看上去是同一个业务的流程,因此我们需要在启动子流程时将父流程的流程变量带入到子流程。
如下设置样例可以将父流程的所有变量带入到子流程中。1
2
3
4
5
6<callActivity id="callParseActivity" calledElement="parseProcess" >
<extensionElements>
<camunda:in variables="all" />
<camunda:out variables="all" />
</extensionElements>
</callActivity>
事件子流程
事件子流程是被事件触发的子流程可以理解为事件驱动的子流程。所有事件子流程都需要以一个非空事件作为开始。启动事件需要以下几种:
消息事件(message events), 异常事件(error events), 信号事件(signal events), 定时事件(timer events), 补偿事件(compensation events).
事件
- 消息事件
消息事件以一个消息触发订阅了事件的流程,消息总有一个目的地,消息事件不能存在多个订阅者。消息事件可以作为两个流程之间的通信机制。
消息事件分为三类: - 消息启动事件(Message Start Event)
流程的通过一个消息事件启动:1
runtimeService.startProcessInstanceByMessage("msg");
消息启动事件不允许存在多个流程定义订阅同一个消息的情况出现,否则会在启动时抛出异常。
- 消息边界事件(Message Boundary Event)
若一个人工任务,服务任务,子流程订阅了消息事件,当流程到达订阅的地方时,引擎会在订阅的宿主上创建一个捕获事件,等待消息事件到达。 消息中间捕获事件(Message Intermediate Catching Event)
我们可以通过代码发送消息,由于消息只能有一个接收者,因此当发送消息时需要针对某个流程发送消息事件,当有多个流程实例接收消息事件时只能通过遍历的方式发送消息事件。
以下是消息启动事件和中间捕获消息事件的样例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<definitions id="definitions"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:camunda="http://activiti.org/bpmn"
targetNamespace="Examples"
xmlns:tns="Examples">
<message id="newInvoice" name="newInvoiceMessage" />
<message id="payment" name="paymentMessage" />
<process id="invoiceProcess">
<startEvent id="messageStart" >
<messageEventDefinition messageRef="newInvoice" />
</startEvent>
...
<intermediateCatchEvent id="paymentEvt" >
<messageEventDefinition messageRef="payment" />
</intermediateCatchEvent>
...
</process>
</definitions>异常事件
异常事件是流程过程中抛出定义好的异常来触发的事件。<definitions> <error id="myError" errorCode="ERROR-OCCURED" name="ERROR-OCCURED"/> <!-- ... --> <process> <!-- ... --> <endEvent id="myErrorEndEvent"> <errorEventDefinition errorRef="myError" /> </endEvent> </process> </definitions>
在代理类中通过抛出异常进入到事件流程中:1
throw new BpmnError("ERROR-OCCURED");//入参errorCode和message
- 信号事件
信号事件以一个信号名称表示,信号事件以广播的形式通知所有的处理者。当一个信息事件发生,订阅了该事件的所有流程实例都能收到信号,这个是信号事件和消息事件的区别。 - 定时事件
定时事件通过设置一个定时器来触发。
例如:1
2
3<timerEventDefinition>
<timeDate>2019-05-14 11:00:00</timeDate>
</timerEventDefinition>
也可以使用CRON表达式设置一个定时触发触发器。定时事件可以挂在一个待办上,通过定时触发提醒用户环节已经超时或者其他操作。可以根据业务来思考是否可以通过定时事件来取代目前的催单方案。
- 补偿事件
中间抛出补偿事件能够触发补偿,可以为流程中的环节设置补偿边界事件,当补偿触发时会根据最后完成的环节触发补偿,当一个环节执行多次时会补偿相应的次数。
理解这些事件对于我们设计一个事件子流程有很大的帮助,事件子流程就是通过这些事件触发的子流程。事件子流程能够添加到流程级别和任意子流程级别。
下面讲解中断式和非中断式。
中断式(interrupting)边界事件和非中断式(non-interrupting)边界事件
子流程可以在边界设置中断事件和非中断事件。当我们设置一个中断事件子流程会取消到当前流程实例的所有执行实例,边界事件所在的子流程执行实例将会被删除,若为非中断事件则不会取消当前流程实例的所有执行实例而是产生一个新的并行执行实例。中断事件一个流程只能执行一次,非中断事件可以重复执行。
中断式(interrupting)子流程和非中断式(non-interrupting)子流程
事件子流程的开始事件也可以设置成中断和非中断事件,当一个事件子流程设置成中断事件父流程所有的执行实例将会被取消,边界事件所在的子流程执行实例将会被删除,若为非中断子流程则不会取消父流程,非中断事件子流程可以多次执行。
边界事件和事件子流程都是由事件触发的操作,那么我们当我们需要事件触发一个子流程时应该选择采用边界事件还是事件子流程呢?
中断的情况下边界事件和事件子流程有着以下区别:
- 边界事件一旦触发子流程的执行实例将被删除,子流程变量域也被清空。事件子流程和父流程则处于同一变量域中,因此事件子流程仍然能获取到变量域。
- 边界事件中的流程和子流程是处于同一级别的,边界事件结束时完成的是父流程。事件子流程完成时结束的是事件子流程自己。
这两个区别在感知上区别并不是很大,但是某些业务场景可能需要进行仔细的选择。
事务子流程
事物一致性子流程是一个特殊的嵌入式子流程,它能将一组环节放到同一个事物中,一个事物是指逻辑上的整体,事物子流程的全部环节要么全部成功,要么全部偿还。
事物子流程只能以:成功,取消,异常,这三种方式结束。
集客拆分子流程样例方案
根据互联网专线开通我整理了一个拆分子流程后的流程图,通过比较拆分前后对比拆分子流程带来的优劣,同时对拆分方案进行评估和改进。
拆分子流程前互联网专线开通流程图:
拆分前交维验收流程
拆分子流程后的总设计图
拆分方案介绍
互联网专线在拆分前有,集客互联网专线开通和交维验收两个流程定义。根据业务复用度的原则拆分后划分了以下子流程:
- 互联网专线开通流程
- 工程建设子流程
- 退单子流程
- IP资源审核子流程
- 资源释放子流程
- 回退子流程
- 报竣子流程
- 交维验收子流程
除了互联网专线开通流程无法直接复用外其他的流程应该设计为所有集客产品公共的子流程。其中工程建设子流程,交维验收子流程IP资源审核子流程在业务上在所有产品都是公用的,一次性开发就可以在所有产品中使用。
退单子流程,资源释放子流程,回退子流程,报竣子流程存在业务变化的风险,新增一种产品可能需要进行子流程的维护。
拆分方案实现的技术难点
由于该设计只是初步方案,并未在代码中进行验证可能还存在未知的风险。拆分流程增加了很多事件驱动的流程,导致流程之间的交互关系变得非常复杂,存在问题查找困难的问题。拆分后的子流程对于实现某些需求可能会提高开发难度,开发人员需要进一步掌握流程引擎的使用。对子流程的软件设计要求提高了,存在变化很大的子流程,这些子流程要适应所有产品,需要很高的设计技巧来应对业务上可能发生的变化。
总结
通过梳理BPMN子流程的实现方式,验证了集客流程拆分具有可行性,长远来说拆分带来的好处是非常多。