流程标准
Camunda在业务流程管理范围内实施了三种不同的标准:BPMN 2.0、CMMN 1.1和DMN 1.3。
BPMN(业务流程模型与标记法)
定义与用途:
BPMN(Business Process Model and Notation)是一种图形化表示法,用于描述业务流程的各个步骤和流程之间的关系。它旨在为业务分析师、技术开发者和管理人员提供一个通用的语言,帮助他们共同理解和改进业务流程。BPMN的主要用途包括业务流程建模、沟通和协作、流程分析和改进以及自动化执行。
关键元素:
BPMN模型由多种图形元素组成,这些元素可以表示不同类型的任务、事件、网关等。主要元素包括:
- 任务(Task):表示流程中的一个原子工作单元,可以细分为用户任务、服务任务等。
- 事件(Event):表示流程中的某个事件,可以是开始事件、中间事件或结束事件。
- 网关(Gateway):用于控制流程路径的分支和汇聚,常见的网关类型包括并行网关、排他网关和事件网关。
- 流程线(Sequence Flow):表示流程元素之间的连接和执行顺序。
使用场景:
BPMN适用于需要结构化、可预测流程的业务场景,如金融服务、制造业、零售业等。
CMMN(案例管理模型与标记法)
定义与用途:
CMMN(Case Management Model and Notation)是一种图形化表示法,用于建模和管理复杂、动态和不可预见的业务流程。与BPMN侧重于定义结构化、可预测的流程不同,CMMN专注于灵活和非结构化的工作流程,这些流程通常需要根据具体情况动态调整。CMMN的主要用途包括案例管理、事件驱动的流程管理、知识密集型流程管理和长期流程管理。
关键元素:
CMMN模型由多种图形元素组成,用于表示不同类型的任务、事件、阶段等。主要元素包括:
- 任务(Task):表示需要完成的工作,可以是手动任务、用户任务、阶段任务等。
- 事件(Event):表示案例中的某个事件,如状态变更事件或外部触发事件。
- 阶段(Stage):表示一个包含多个任务和事件的分组,可以嵌套其他阶段或任务。
- 里程碑(Milestone):表示达到某个重要进展的点。
- 计划项(Plan Item):表示任务、阶段或里程碑等可以执行的元素。
使用场景:
CMMN适用于需要灵活和自适应的业务流程场景,如客户服务、保险理赔、法律事务等。
DMN(决策模型与标记法)
定义与用途:
DMN(Decision Model and Notation)是一种标准化的图形表示法,用于描述和建模业务决策。DMN的目标是使业务分析师和技术开发者能够协作定义和管理业务规则和决策逻辑,确保决策过程透明、可理解和可执行。DMN的主要用途包括决策建模、自动化决策、业务规则管理和分析优化。
关键元素:
DMN模型由多种图形元素组成,用于表示决策表、决策节点、输入数据等。主要元素包括:
- 决策(Decision):表示一个业务决策节点,定义了决策逻辑。
- 决策节点(Decision Node):用来表示一个具体的决策过程,包含决策逻辑。
- 输入数据(Input Data):表示决策所需的输入信息。
- 决策表(Decision Table):一种常见的决策逻辑表示方法,通过表格定义不同条件下的决策输出。
使用场景:
DMN适用于需要定义和管理复杂业务规则和决策逻辑的场景,如金融服务、保险业、零售业等。
引擎架构
嵌入式流程引擎
在这种情况下,流程引擎作为应用程序库(框架jar包)添加到自定义应用程序中。这样,流程引擎可以很容易地随着应用程序的生命周期而启动和停止。可以在共享数据库上运行多个嵌入式流程引擎。
共享、容器管理的流程引擎
在这种情况下,流程引擎在运行时容器(Servlet容器、Application Server等)内启动。流程引擎作为容器服务提供,可以由部署在容器内的所有应用程序共享。
独立(远程)进程引擎服务器
在这种情况下,流程引擎作为网络服务提供。网络上运行的不同应用程序可以通过远程通信通道与流程引擎交互。使流程引擎可远程访问的最简单方法是使用内置的REST API。
集群模式
为了提供扩展或故障转移功能,流程引擎可以分布到集群中的不同节点。每个流程引擎实例都必须连接到共享数据库。
各个流程引擎实例不维护跨事务的会话状态。每当流程引擎运行事务时,完整的状态都会被刷新到共享数据库中。这使得可以将在同一流程实例中工作的后续请求路由到不同的集群节点。简而言之是无状态的。
流程引擎作业执行器Job Executor也是集群的,并在每个节点上运行。这样,就流程引擎而言,就没有单点故障。
多租户
字段隔离
优点:简单、易用。
缺点:数据库压力大,垂直扩容有上限。
单引擎多数据库
优点:代码复杂度相比无租户只有略微增加。
缺点:引擎自动发起的调度任务、事件、异步,没有租户id特征进行路由。
多引擎多数据库
优点:性能与水平扩容能力优秀。
缺点:多引擎集成、业务和引擎的事务原子性保障,代码有复杂度。
环境支持和要求
数据库支持
事务隔离级别要求 READ COMMITTED
Database Configuration | docs.camunda.org
Java支持
Java 11 / 17
Service API
RepositoryService
功能:操作和管理流程定义(BPMN 2.0 XML 文件)及其部署。
操作:部署流程定义、查询部署和流程定义、挂起/激活流程定义等。
RuntimeService
功能:操作和管理流程实例(即流程定义的执行)。
操作:启动流程实例、查询流程实例、获取流程变量、挂起/激活流程实例等。
TaskService
功能:操作和管理任务(即流程实例中的具体工作项)。
操作:查询任务、创建任务、分配任务、完成任务等。
IdentityService
功能:管理用户、组和角色等身份信息。
操作:创建/更新/删除用户、组和角色,提供身份验证和授权功能等。Camunda 支持多种身份验证机制,如基于表单、LDAP 和 SSO 等。
HistoryService
功能:查询历史数据,如流程实例、任务和活动的历史记录。
操作:查询历史流程实例、历史任务、历史活动等。
AuthorizationService
功能:提供授权相关服务,确保只有经过授权的用户才能执行特定操作。
操作:检查权限、创建/更新/删除授权规则等。
FormService
功能:操作和管理流程表单,包括开始表单和任务表单。
操作:提交表单数据、渲染表单等。虽然 FormService 是可选的,但它简化了表单的集成和管理。
ManagementService
功能:执行数据库和作业相关的管理操作。
操作:查询数据库表结构、管理作业(如定时器作业)等。
CaseService
功能:提供对 Case Management Model and Notation (CMMN) 的支持。
操作:管理案例定义、案例实例等。CMMN 是一种用于更复杂业务流程建模的规范。
FilterService
功能:允许创建和管理过滤器,以便根据特定条件查询任务或流程实例。
操作:创建/更新/删除过滤器,使用过滤器查询任务等。
ExternalTaskService
功能:提供对外部任务的支持,这些任务可以在流程引擎外部处理。
操作:查询、锁定、解锁、完成外部任务等。
DecisionService
功能:提供对 Decision Model and Notation (DMN) 的支持,用于在流程中做出决策。
操作:评估 DMN 决策表或决策树等。
流程生命周期
参与者
主要包括泳池(Pool)和泳道(Lane)。
任务Task
用户任务
User Task
用户任务用于对需要由人类参与者完成的工作进行建模。当流程执行到达这样的用户任务时,会在分配给该任务的用户或组的任务列表中创建一个新任务。
用户任务在XML中定义如下。
id属性是必需的,而name属性是可选的。
documentation
相当于description。
Due date
指示该任务的到期日期。
查询API可用于查询在某个日期之前或之后到期的任务。
<userTask id=“theTask”name=“重要任务”camunda:dueDate=“${dateVariable}”/>
还可以使用TaskService或在TaskListeners中使用传递的DelegateTask更改任务的截止日期。
Follow up date
指示该任务的跟随日期。
查询API可用于查询在某个日期之前或之后需要跟进的任务。
<userTask id=“theTask”name=“重要任务”camunda:followUpDate=“${dateVariable}”/>
Assignee
将用户任务直接分配给给定用户。
<userTask id=“theTask”name=“我的任务”camunda:assignee=“kermit”/>
Candidate Users
将用户设置为任务的候选人。
<userTask id=“theTask”name=“my task”camunda:candidateUsers=“kermit,gonzo”/>
Candidate Groups
将组设置为任务的候选人。
<userTask id=“theTask”name=“my task”camunda:candidateGroups=“管理、会计”/>
candidateUsers
和 candidateGroups
可以同时在一个用户任务上定义。
Assignment Expressions
camunda提供了扩展方式来给任务进行分配。
- 使用流程变量
<startEvent id="startEvent" camunda:initiator="starter" />
...
<userTask id="task" name="Clarify Invoice" camunda:assignee="${ starter }"/>
...
- 使用Service Bean
<userTask id="task" name="My Task" camunda:assignee="${ldapService.findManagerForEmployee(emp)}"/>
<userTask id="task" name="My Task" camunda:candidateUsers="${ldapService.findAllSales()}"/>
public class FakeLdapService {
public String findManagerForEmployee(String employee) {
return "Kermit The Frog";
}
public List<String> findAllSales() {
return Arrays.asList("kermit", "gonzo", "fozzie");
}
}
- 使用监听器
<userTask id="task1" name="My task" >
<extensionElements>
<camunda:taskListener event="create" class="org.camunda.bpm.MyAssignmentHandler" />
</extensionElements>
</userTask>
public class MyAssignmentHandler implements TaskListener {
public void notify(DelegateTask delegateTask) {
// Execute custom identity lookups here
// and then for example call following methods:
delegateTask.setAssignee("kermit");
delegateTask.addCandidateUser("fozzie");
delegateTask.addCandidateGroup("management");
...
}
}
服务任务
Service Task
服务任务用于执行特定的业务逻辑,这些逻辑通常不需要人工干预,而是由系统自动完成。
它有以下几种实现(使用)方式。
Java class
使用java类实现接口JavaDelegate,以完整类名进行配置。
<serviceTask id="javaService"
name="My Java Service Task"
camunda:class="org.camunda.bpm.MyJavaDelegate" />
Delegate expression
使用Spring Bean的名称。
<serviceTask id="beanService"
name="My Bean Service Task"
camunda:delegateExpression="${myDelegateBean}" />
expression
返回值可以作为已有的或新的流程变量。特定流程变量的任何现有值都将被服务执行的结果值覆盖。如果不指定结果变量名,则忽略服务执行结果值。
<serviceTask id="aMethodExpressionServiceTask"
camunda:expression="#{myService.doSomething()}"
camunda:resultVariable="myVar" />
External
External Tasks | docs.camunda.org
外部任务在概念上与用户任务非常相似。
用户任务由流程引擎创建并添加到任务列表中。然后,流程引擎等待人类用户查询列表,然后完成它。
外部任务类似:创建外部任务,将其添加到topic中。然后,外部应用程序查询topic并锁定任务。锁定任务后,应用程序可以处理并完成它。
<serviceTask id="anExternalServiceTask"
camunda:type="external"
camunda:topic="ShipmentProcessing" />
执行外部任务的流程在概念上可以分为三个步骤
流程引擎:创建外部任务。
外部工作者:获取并锁定外部任务。
外部工作者和流程引擎:完成外部任务。
这种模式的本质是,执行实际工作的实体独立于流程引擎,并通过轮询流程引擎的API来接收工作项。这有以下好处:
跨越系统边界:外部worker不需要与流程引擎在同一Java进程、同一台机器、同一集群甚至同一地区上运行。所需要的只是它可以访问流程引擎的API(通过REST或Java)。由于轮询模式,worker不需要暴露任何接口供流程引擎访问。
跨越技术边界:外部worker不需要在Java中实现。相反,可以使用任何最适合执行工作项并且可以用于访问流程引擎的API(通过REST或Java)的技术。
专业人员:外部人员不需要是通用应用程序。每个外部任务实例都会收到一个主题名称,用于标识要执行的任务的性质。工作人员只能轮询任务中他们可以处理的主题。
细粒度扩展:如果有高负载集中在服务任务处理上,则可以独立于流程引擎扩展各个主题的外部工作人员数量。
独立维护:工人可以独立于流程引擎进行维护,而不会中断操作。例如,如果特定主题的工作人员停机(例如,由于更新),则不会对流程引擎产生直接影响。这些worker的外部任务执行会优雅地降级:它们会存储在外部任务列表中,直到外部worker恢复操作。
Connector
Connect提供了一个简单的API,用于连接HTTP服务和其他内容。
Camunda Connect提供了HTTP和SOAP HTTP连接器。也可以扩展添加自己的连接器。
Camunda Connector Reference | docs.camunda.org
在外部系统集成方面与Java class相比
Connector:专门设计用于与外部系统或服务进行交互。它允许BPMN模型中的Service Task节点与外部系统或服务进行通信,而无需在Java Delegate中编写复杂的集成逻辑。
Java Delegate:虽然也可以用于与外部系统交互,但其主要目的是执行特定的业务逻辑。使用Connector可以更加专注于集成逻辑,使代码更加清晰和模块化。
发送任务
Send Task
发送任务的定位是发送消息,而实际上它与Service Task具有相同的实现。
接收任务
Receive Task
接收任务是一个等待特定消息到达的简单任务。当流程执行到达接收任务时,流程状态被提交到持久性存储。流程将保持在这种等待状态,直到引擎收到特定消息,然后继续流程。
Receive Task的主要作用是在流程中等待外部系统或服务发送消息。当Receive Task接收到消息后,它会继续执行后续的流程步骤。这一机制使得流程能够在发送和接收消息之间进行交互,从而实现与外部系统的集成和协作。
Receive Task还可以与定时器事件结合使用,以便在指定的时间内等待消息。如果在这段时间内没有接收到消息,流程可以执行其他预设的逻辑,如发送提醒、触发错误处理等。
<definitions ...>
<message id="newInvoice" name="newInvoiceMessage"/>
<process ...>
<receiveTask id="waitState" name="wait" messageRef="newInvoice">
...
ProcessInstance pi = runtimeService.startProcessInstanceByKey("processWaitingInReceiveTask");
EventSubscription subscription = runtimeService.createEventSubscriptionQuery()
.processInstanceId(pi.getId()).eventType("message").singleResult();
runtimeService.messageEventReceived(subscription.getEventName(), subscription.getExecutionId());
手动任务
Manual Task
手动任务定义了引擎外部的任务。它用于模拟由引擎不需要知道的人完成的工作,并且没有已知的系统或UI界面。对于引擎,手动任务作为传递活动处理,当流程执行到达时自动继续流程。
Manual Task用于在流程执行过程中暂停执行,直到人工干预完成某个任务。与User Task不同,Manual Task没有分配给特定用户或用户组,而是需要手动启动并指定下一步流程。
<manualTask id="myManualTask" name="Manual Task" />
在复杂的业务流程中,可能存在多个分支和条件判断。Manual Task可以用作这些分支和条件判断之间的断点,以便在必要时进行人工干预和调整。
当流程执行到 Manual Task
节点时,流程引擎会暂停执行,等待外部干预。此时,流程实例的状态会停留在该 Manual Task
上,但不会在Camunda的任务列表中显示为一个待办任务(与 User Task
不同)。
为了继续流程的执行,需要有人工干预。这种干预可能是通过以下方式之一进行的:
- 手动触发信号:在某些情况下,可能需要手动向Camunda发送一个信号(如通过REST API),以指示
Manual Task
已完成,并允许流程继续执行。这种方式需要开发人员或系统管理员具备相应的权限和接口知识。 - 更新流程变量:有时,
Manual Task
的完成可能依赖于某些流程变量的值被更新。这些更新可以由外部系统或用户通过Camunda提供的接口(如REST API、Cockpit、Tasklist等)完成。一旦这些变量被更新到满足继续执行的条件,流程可能会自动继续执行,或者需要额外的信号来触发。 - 外部系统通知:在某些集成场景中,
Manual Task
的完成可能由外部系统通过某种机制(如Webhooks、消息队列等)通知Camunda。当Camunda接收到这种通知时,它可能会触发相应的逻辑来继续流程执行。
脚本任务
Script Task
脚本任务是一种自动化活动。当流程执行到达脚本任务时,会执行相应的脚本。
通过指定脚本和scriptFormat来定义脚本任务。
<scriptTask id="theScriptTask" name="Execute script" scriptFormat="groovy">
<script>
sum = 0
for ( i in inputArray ) {
sum += i
}
</script>
</scriptTask>
scriptFormat属性的值必须是与JSR-223(Java平台脚本)兼容的名称,例如Groovy、JavaScript、JRuby和Jython。
通过脚本任务中的执行可访问的所有流程变量都可以在脚本中使用,例如inputArray实际上是一个变量(一个整数数组)。
也可以在脚本中设置流程变量。可以使用VariableScope接口提供的setVariable(…)方法设置变量:
<script>
sum = 0
for ( i in inputArray ) {
sum += i
}
execution.setVariable("sum", sum);
</script>
通过将过程变量名称指定为脚本任务定义的camunda:resultVariable属性的文字值,可以将脚本任务的返回值分配给以前存在的或新的变量。特定流程变量的任何现有值都将被脚本执行的结果值覆盖。如果未指定结果变量名,则忽略脚本结果值。
<scriptTask id="theScriptTask" name="Execute script" scriptFormat="juel" camunda:resultVariable="myVar">
<script>#{echo}</script>
</scriptTask>
在上述示例中,脚本执行的结果(解析表达式#{echo}的值)在脚本完成后被设置为名为myVar的进程变量。
业务规则任务
Business Rule Task
Camunda的Business Rule Task(业务规则任务)是一种非常灵活的任务类型,它允许在流程执行过程中同步执行一个或多个业务规则。这些规则通常通过DMN(Decision Model and Notation,决策模型和符号)决策表来定义,从而实现流程的自动化决策。
以下是使用Camunda的Business Rule Task的基本步骤:
- 定义DMN决策表
例如创建一个自动计算贷款金额的决策树,id=loan-amount-cal,name=贷款金额计算
- 在流程中创建Business Rule Task,Type选择DMN,Decision reference输入决策表对应的id即以上的loan-amount-cal。
- (可选)设置决策的结果变量,用于存储DMN决策表的执行结果。
网关Gateway
排他网关
Exclusive gateway
排他网关用于根据条件表达式或规则选择流程的下一个执行路径,确保在多个可能的分支中只选择一条执行。
在BPMN 2.0流程图中,排他网关通常被表示为一个带有“X”标记的菱形节点。
<exclusiveGateway id="exclusiveGw" name="Exclusive Gateway" default="flow4" />
<sequenceFlow id="flow2" sourceRef="exclusiveGw" targetRef="theTask1" name="${x==1}">
<conditionExpression xsi:type="tFormalExpression">${x == 1}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow3" sourceRef="exclusiveGw" targetRef="theTask2" name="${x==2}">
<conditionExpression xsi:type="tFormalExpression">${x == 2}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow4" sourceRef="exclusiveGw" targetRef="theTask3" name="else">
</sequenceFlow>
条件和默认序列流
Sequence flow
序列流是流程中两个元素之间的连接器。在流程执行期间访问元素后,将遵循所有传出的序列流。这意味着BPMN 2.0的默认特性是并行的:两个输出序列流将创建两个单独的并行执行路径。
说明:当2个任务A和B使用Sequence flow都直接指向1个任务C时,任务C只会创建1个而不是2个。
Conditional sequence flow
条件序列流可以根据条件决定流程路径。
Default sequence flow
所有BPMN 2.0任务和网关都可以有一个默认的序列流。如果无法选择其他序列流,则仅选择此序列流作为该活动的传出序列流。
并行网关
Parallel gateway
网关也可用于对流程中的并发性进行建模。在流程模型中引入并发性的最直接的网关是并行网关,它允许分叉到多个执行路径或加入多个传入的执行路径。
并行网关的作用是
fork:并行跟踪所有传出的序列流,为每个序列流创建一个并发执行。
join:所有到达并行网关的并发执行都在网关处等待,直到每个传入序列流的执行都到达为止。
一个并行网关可以同时具有fork和join行为,在这种情况下,网关将首先join所有传入的序列流,然后fork为多个并发执行路径。
并行网关也可以设计的更为复杂:
包含网关
Inclusive gateway
包含网关可以被视为排他和并行网关的组合。与排他网关一样,可以定义传出序列流的条件。
主要区别在于,包含网关可以传出多个序列流,就像并行网关一样。
fork:评估所有传出的序列流条件,对于评估为“true”的序列流情况,并行跟踪流,为每个序列流创建一个并发执行。
join:所有到达包含网关的并发执行都在网关处等待,直到每个具有进程令牌的传入序列流的执行都到达为止。这是与并行网关的一个重要区别。换句话说,包含网关将只等待执行的传入序列流。
包含网关也支持更复杂的设计:
- 在以下场景中,并行网关1创建了三个执行令牌,但只有两个序列流加入到包容性网关中。在这种情况下,即使只有两个令牌,包容性网关也会触发,因为并行网关2将任务1和任务2的令牌合并到一个令牌中。
- 在以下场景中,并行网关1创建了两个执行令牌,三个序列流加入到包容性网关中。在这种情况下,包容性网关将使用三个令牌触发,因为并行网关2将任务1中的单个令牌拆分为任务3和任务4的两个单独的令牌。
- 在下图中,并行网关创建了两个执行令牌。第一个执行令牌将在用户任务1处等待,第二个将到达包容性网关。当两个令牌到达同一序列流时,包容性网关将立即触发第一个令牌,并再次触发第二个令牌。因此,将有两个用户任务2的实例需要完成。
- 在最后一个场景中,并行网关创建了两个执行令牌。第一个执行令牌将在用户任务1处等待,第二个将到达包容性网关2并等待网关触发。然而,在用户任务1完成并且第二令牌到达网关之前,包容性网关2不会触发加入。因此,包容性网关2将仅触发一次,而不是两次。根据BPMN 2.0规范,由于两个令牌都通过相同的序列流(true),因此包容性网关应触发两次。最后,由于这种行为,只需要完成用户任务2的一个实例,而不是预期的两个实例。在这种情况下,建议使用专用网关而不是包容性网关1。
事件网关
Event-based gateway
事件网关用来捕获中间事件。与其他网关不通,它不是通过条件来选择路径,而是通过捕获到的事件来选择。
事件网关与排他网关在功能上相似,但不同的是事件网关通过中间事件驱动,在等待的事件发生后才会触发,事件网关只关心第一件发生的事情。
以下流程是一个基于事件的网关流程示例。当执行到达基于事件的网关时,流程执行将暂停。此外,流程实例订阅警报信号事件,并创建一个在10分钟后触发的计时器。这有效地导致流程引擎等待信号事件十分钟。如果信号事件发生在10分钟内,则计时器将被取消,并在信号发出后继续执行。如果信号未被触发,则执行将在计时器结束后继续,信号订阅将被取消。
事件Events
开始事件
Start Events
定义了流程或子流程的启动位置。
流程引擎支持不同类型的启动事件:空白事件、定时器事件、消息事件、信号事件、条件事件。
引擎需要至少一个启动事件来实例化进程。每个流程定义最多只能有一个空白或计时器启动事件。可以有多个消息或信号启动事件。
空白事件
None Events
空白开始事件在技术上意味着流程启动实例的触发器是未指定的。
消息事件
Message Events
消息事件是引用命名消息的事件。消息有一个名称和一个有效载荷。与信号不同,消息事件始终指向单个收件人。
消息开始事件
Message Start Event
消息开始事件的名称要求在所有部署的流程定义中唯一,否则引擎会抛出异常。
启动流程实例时,可以在RuntimeService上使用以下方法触发消息启动事件:
ProcessInstance startProcessInstanceByMessage(String messageName);
ProcessInstance startProcessInstanceByMessage(String messageName, Map<String, Object> processVariables);
ProcessInstance startProcessInstanceByMessage(String messageName, String businessKey, Map<String, Object> processVariables);
消息启动事件仅在顶级流程中受支持。嵌入式子流程中不支持消息启动事件。
如果流程定义有多个消息启动事件,runtimeService.startProcessInstanceByMessage(…)允许选择适当的启动事件。
如果流程定义有多个消息启动事件和一个无启动事件,则runtimeService.startProcessInstanceByKey(…)和runtimeService.startProcessInstanceById(…)将使用无启动事件启动流程实例。
如果进程定义有多个消息启动事件,但没有无启动事件,则runtimeService.startProcessInstanceByKey(…)和runtimeService.setartProcessInstanceByteId(…)会抛出异常。
如果流程定义只有一个消息启动事件,runtimeService.startProcessInstanceByKey(…)和runtimeService.startProcessInstanceById(…)将使用消息启动事件启动新的流程实例。
如果流程是从call activity启动的,则只有在以下情况下才支持消息启动事件
除了消息启动事件外,该流程还有一个无启动事件。
该流程只有一个消息启动事件,没有其他启动事件。
消息中间捕获事件
Message Intermediate Catching Event
当到达消息中间捕获事件时,它将在那里等待,直到具有正确名称的消息到达。如前所述,必须通过适当的API调用将消息传递到引擎中。
以下示例显示了流程模型中的不同消息事件:
消息边界事件
Message Boundary Event
消息边界事件是捕获附加到活动的事件。这意味着,在活动运行时,消息边界事件正在侦听命名消息。当捕获到这一点时,根据边界事件的配置,可能会发生两件事:
中断边界事件:活动被中断,并遵循事件外的序列流。
非中断边界事件:一个令牌留在活动中,并创建一个附加令牌,该令牌遵循事件中的序列流。
消息中间抛出事件
Message Intermediate Throwing Event
消息中间抛出事件向外部服务发送消息。此事件的行为与Service Task相同。
它将在收到相应的消息后,触发服务行为。
消息结束事件
Message End Event
当流程执行到达消息结束事件时,流程结束并发送消息。消息结束事件的行为与Service Task相同。
它将在收到相应的消息后,触发服务行为。
定时器事件
Timer Events
定时器事件是由定义的定时器触发的事件。它们可以用作开始事件、中间事件或边界事件。边界事件可以中断也可以不中断。
定时器只有在 Job Executor 开启时才会生效执行。
定时器的时间格式支持:
- 在指定时间执行 2011-03-11T12:13:14Z
- 周期执行 P10D 每10天一次
- 重复次数 R3/PT10H 重复3次,每次间隔10小时;也可以是cron 0 0/5 * * * ?
定时器启动事件
Timer Start Event
定时器启动事件用于在给定时间创建流程实例。它既可用于只应启动一次的进程,也可用于应在特定时间间隔内启动的进程。
注意:子流程不能有计时器启动事件。
注意:一旦部署了进程,就会安排计时器启动事件。不需要调用startProcessInstanceBy,尽管调用start进程方法不受限制,并且会在startProcessInstanceBy时启动流程。
计时器启动事件的XML表示是正常的启动事件声明,带有计时器定义子元素。以下示例流程将从2022年3月11日12:13(UTC+01,24小时制)开始,以5分钟为间隔启动4次:
<startEvent id="theStart">
<timerEventDefinition>
<timeCycle>R4/2022-03-11T12:13+01/PT5M</timeCycle>
</timerEventDefinition>
</startEvent>
以下将在指定的时间启动1次:
<startEvent id="theStart">
<timerEventDefinition>
<timeDate>2022-03-11T12:13:14Z</timeDate>
</timerEventDefinition>
</startEvent>
定时器中间捕获事件
Timer Intermediate Catching Event
定时器中间捕获事件充当秒表。
当执行到达捕获事件活动时,计时器会启动。当定时器触发时(例如,在指定的时间间隔后),将沿着离开定时器的序列流继续执行。作用就像延迟执行一样。
定时器边界事件
Timer Boundary Event
定时器边界事件充当秒表和闹钟。把定时器中间捕获事件挂在活动上就会自动变成定时器边界事件。
当执行到达与边界事件关联的活动时,计时器会启动。当定时器触发时(例如,在指定的时间间隔后),活动会被中断,并沿着离开定时器的序列流继续执行。
中断和非中断定时器事件之间存在区别。中断事件是默认事件。不中断事件导致原始活动不被中断,活动保持不变。相反,会创建一个额外的执行,并在事件的传出转换过程中发送。在XML表示中,cancelActivity属性设置为false:
<boundaryEvent id="escalationTimer" cancelActivity="false" />
<timerEventDefinition>
<timeDuration>PT4H</timeDuration>
</timerEventDefinition>
</boundaryEvent>
错误事件
错误开始事件
Error Start Event
错误开始事件只能用于触发事件子流程,不能用于启动流程实例。错误启动事件总是中断。
注意要使用错误开始事件,在流程设计时要把子流程的类型选为EVENT SUB PROCESS才能支持。
错误结束事件
Error End Event
即上图中的错误结束事件。
当流程执行到达错误结束事件时,当前执行路径结束并抛出错误。此错误可以通过匹配的中间错误边界事件捕获。如果没有找到匹配的错误边界事件,则执行语义默认为无结束事件语义。
错误边界事件
Error Boundary Event
活动边界上的中间捕获错误事件,简称错误边界事件,捕获在其定义的活动范围内抛出的错误。
定义错误边界事件在嵌入式子流程或调用活动上最有意义,因为子流程为子流程内的所有活动创建了一个范围。错误由错误结束事件引发。这样的错误将向上传播其父作用域,直到找到一个作用域,在该作用域上定义了与错误事件定义匹配的错误边界事件。
当捕获到错误事件时,定义边界事件的活动将被销毁,同时销毁其中的所有当前执行(例如并发活动、嵌套子流程等)。流程执行继续遵循边界事件的传出序列流。
捕获错误并重新抛出
错误可以由事件子流程中的错误启动事件处理,也可以从事件子流程抛出相同的错误,以在更高级别的范围内处理错误(在下面的示例中,从子流程中抛出的错误由子流程的错误边界事件处理)。
信号事件
信号事件是指引用命名信号的事件。信号是全局范围的事件(广播语义),并传递给所有活动处理程序。
以下是使用信号进行通信的两个单独进程的示例。如果保险单被更新或更改,则启动第一个过程。在人类参与者审查了更改后,会抛出一个信号事件,表示策略已更改:
可以使用API进行信号的发送
// broadcast signal
runtimeService
.createSignalEvent("signalName")
.setVariables(variables)
.send();
// deliver a signal to a single execution
runtimeService
.createSignalEvent("signalName")
.executionId(executionId)
.setVariables(variables)
.send();
RuntimeService.signalEventReceived(String signalName);
RuntimeService.signalEventReceived(String signalName, String executionId);
如果指定了执行id,则信号仅传递给特定的执行。否则,信号将全局抛出给所有订阅的处理程序(广播语义)。
也可以查询订阅了指定信号的执行
List<Execution> executions = runtimeService.createExecutionQuery()
.signalEventSubscriptionName("alert")
.list();
信号开始事件
Signal Start Event
信号启动事件的名称在给定的流程定义中必须是唯一的,否则引擎在部署流程定义时抛出异常。
与消息启动事件相反,信号启动事件的名称在所有部署的流程定义中不必是唯一的。
当抛出具有正确名称的信号时,将启动具有一个或多个信号启动事件的流程定义的流程实例。信号可以由流程实例抛出(即在中间抛出信号事件或信号结束事件上),也可以在RuntimeService上使用以下方法抛出:
void signalEventReceived(String signalName);
void signalEventReceived(String signalName, Map<String, Object> processVariables);
信号中间捕获事件
Signal Intermediate Catching Event
当令牌到达信号中间捕获事件时,它将在那里等待,直到具有正确名称的信号到达。
信号中间抛出事件
信号被广播到所有活动处理程序(即所有捕获信号事件)。信号可以同步或异步发布。
在默认配置中,信号是同步传递的。这意味着抛出流程实例会等待,直到信号被传递给所有捕获流程实例。捕获流程实例也会在与抛出流程实例相同的事务中得到通知,这意味着如果其中一个被通知的实例产生技术错误(抛出异常),所有涉及的实例都会失败。
信号也可以异步传递。在这种情况下,确定在到达投掷信号事件时哪些处理程序是活动的。对于每个活动处理程序,JobExecutor都会存储和传递异步通知消息(Job)。
信号边界事件
当执行到达信号边界事件所关联的活动时,信号边界事件会捕获具有正确名称的信号。
注意:与其他事件(如错误边界事件)相反,信号边界事件不仅捕获从其附加的作用域抛出的信号事件。信号事件具有全局作用域(广播语义),这意味着信号可以从任何地方抛出,甚至可以从不同的流程实例抛出。
当任务1还未处理,收到了信号,则流程会按信号边界事件的流向继续流程。
信号结束事件
信号结束事件的行为与信号中间投掷事件相同,当前的Execution会结束。
取消和补偿事件
取消结束事件
取消结束事件只能与事务子流程结合使用。当到达取消结束事件时,会抛出一个取消事件,该事件必须被取消边界事件捕获。然后,取消边界事件取消交易并触发补偿。
取消边界事件
当事务被取消时,会触发事务子流程边界上的附加中间捕获取消事件,或简称为取消边界事件。当触发取消边界事件时,它首先中断当前作用域中的所有活动执行。接下来,它开始对事务范围内的所有活动补偿边界事件进行补偿。补偿是同步执行的,即边界事件在补偿完成之前等待,然后再离开事务。补偿完成后,事务子流程将使用超出取消边界事件的序列流。
注意:事务子流程只允许有一个取消边界事件。
注意:如果事务子流程承载嵌套的子流程,则仅对成功完成的子流程触发补偿。
注意:如果取消边界事件放置在具有多实例特征的事务子流程上,如果一个实例触发取消,则边界事件将取消所有实例。
补偿事件
Cancel and Compensation Events | docs.camunda.org
条件事件
条件事件定义了一个在给定条件被评估为真时触发的事件。它可以用作事件子流程的开始事件、作为中间事件和边界事件。开始和边界事件可以是中断和非中断。
中间条件事件就像一个等待,一直等到条件得到满足为止。在这个例子中,如果处理器变得可用,${processorAvailable==true},则条件将得到满足,执行过程将继续到下一个活动。
如果满足检查应用程序是否已更改的条件边界事件的条件,则相应的用户任务将被中断。
在流程实例的整个执行过程中,可以取消应用程序。如果满足条件启动事件的条件,则流程实例的执行将被事件子流程中断。这将取消当前对申请的处理。
连接事件
Link Events | docs.camunda.org
终止事件
有时候需要并行的执行多个任务,当其中一个任务执行完成后,希望终止其他任务的执行。
子流程
嵌入式子流程
嵌入式子流程是一个包含其他活动、网关、事件等的活动,它本身构成了一个更大流程的一部分。子流程完全在父流程内定义(这就是为什么它通常被称为嵌入式子流程)。
子流程有两个主要用例:
子流程允许分层建模。许多建模工具允许折叠子流程,隐藏子流程的所有细节,并显示业务流程的高级端到端概述。
子流程为事件创建了一个新的范围。在子流程执行期间抛出的事件可以由子流程边界上的边界事件捕获,从而为该事件创建一个范围,仅限于子流程。
使用子流程确实会施加一些约束:
子流程只能有一个空白启动事件,不允许有其他启动事件类型。子流程必须至少有一个结束事件。请注意,BPMN 2.0规范允许省略子流程中的开始和结束事件,但当前的引擎实现不支持这一点。
序列流不能跨越子流程边界。
如果子流程折叠,则只显示名称和加号
如果子流程被展开,则子流程的步骤将显示在子流程边界内
使用子流程的主要原因之一是为事件定义范围。以下过程模型显示了这一点:如果我们自发地被邀请吃饭,我们将取消我们的烹饪过程。然而,如果我们已经在吃饭了,我们就不会再对邀请做出反应了。从更专业的角度来看,消息事件的范围是子流程,因此只有在子流程处于活动状态时才能接收消息。
调用活动
BPMN 2.0对嵌入式子流程和调用活动进行了区分。从概念上讲,当流程执行到达活动时,两者都会调用子流程。
不同之处在于,调用活动引用了流程定义外部的流程,而子流程嵌入在原始流程定义中。调用活动的主要用例是有一个可重用的流程定义,可以从多个其他流程定义中调用。虽然还不是BPMN规范的一部分,但也可以调用CMMN案例定义。
当流程执行到达调用活动时,会创建一个新的流程实例,用于执行子流程,就像常规流程中创建并行的子执行。主流程实例等待子流程完全结束,然后继续原始流程。
例如Shipping和Billing可以在很多流程中共享,所以可以使用调用活动来建模。
调用活动的可视化方式与折叠的嵌入式子流程相同,但具有粗边框。根据建模工具的不同,调用活动也可以展开,但默认的可视化是折叠表示。
调用活动是一种常规活动,它需要一个被调用的元素,该元素通过其键引用流程定义。在实践中,这意味着流程的id在调用的Element中使用:
<callActivity id="callCheckCreditProcess" name="Check credit" calledElement="checkCreditProcess" />
子流程的流程定义是在运行时解析的。这意味着子流程可以独立于调用流程进行部署。
Called Element
这是调用活动的主要配置项,它应该指向你想要调用的子流程定义的 ID。
参数传递
可以将流程变量传递给子流程,反之亦然。数据在子流程启动时被复制到子流程中,在主流程结束时被复制回主流程中。
<callActivity id="callSubProcess" calledElement="checkCreditProcess" >
<extensionElements>
<camunda:in source="someVariableInMainProcess" target="nameOfVariableInSubProcess" />
<camunda:out source="someVariableInSubProcss" target="nameOfVariableInMainProcess" />
</extensionElements>
</callActivity>
也可以双向传递所有参数
<callActivity id="callSubProcess" calledElement="checkCreditProcess" >
<extensionElements>
<camunda:in variables="all" />
<camunda:out variables="all" />
</extensionElements>
</callActivity>
也可以使用表达式
<callActivity id="callSubProcess" calledElement="checkCreditProcess" >
<extensionElements>
<camunda:in sourceExpression="${x+5}" target="y" />
<camunda:out sourceExpression="${y+5}" target="z" />
</extensionElements>
</callActivity>
输入/输出参数组合
调用活动也可以与输入/输出参数相结合。这允许将变量更灵活地映射到被调用的流程中。
<callActivity id="callSubProcess" calledElement="checkCreditProcess" >
<extensionElements>
<!-- Input/Output parameters -->
<camunda:inputOutput>
<camunda:inputParameter name="var1">
<camunda:script scriptFormat="groovy">
<![CDATA[
sum = a + b + c
]]>
</camunda:script>
</camunda:inputParameter>
<camunda:inputParameter name="var2"></camunda:inputParameter>
</camunda:inputOutput>
<!-- Mapping to called instance -->
<camunda:in variables="all" local="true" />
</extensionElements>
</callActivity>
设置local=“true”意味着执行调用活动的执行的所有局部变量都映射到被调用的流程实例中。这些正是声明为输入参数的变量。
输出参数也可以这样做。
传递Business Key
可以将Business Key传递给子流程。
<callActivity id="callSubProcess" calledElement="checkCreditProcess" >
<extensionElements>
<camunda:in businessKey="#{execution.processBusinessKey}" />
</extensionElements>
</callActivity>
事件子流程
事件子流程是由事件触发的子流程。可以在流程级别或任何子流程级别添加事件子流程。用于触发事件子流程的事件是使用启动事件配置的。因此,事件子流程不支持无启动事件。事件子流程可能会使用消息事件、错误事件、信号事件、计时器事件或补偿事件等事件触发。当创建承载事件子流程的作用域(流程实例或子流程)时,将创建对启动事件的订阅。范围结束时,订阅将被删除。
事件子流程可能会中断或不中断。中断的子流程会取消当前范围内的任何执行。一个不中断的事件子流程会产生一个新的并发执行。虽然中断事件子流程在每次激活承载它的作用域时只能触发一次,但非中断事件子进程可以多次触发。使用触发事件子流程的启动事件配置子流程是中断还是不中断。
事件子流程可能没有任何传入或传出的序列流。由于事件子流程是由事件触发的,因此传入的序列流没有意义。当事件子流程结束时,当前范围结束(在中断事件子流程的情况下),或者为非中断子流程生成的并发执行结束。
事件子流程被可视化为一个带有虚线轮廓的嵌入式子流程。
事务子流程
事务子流程是一个嵌入式子流程,可用于将多个活动分组到一个事务中。事务是一个逻辑工作单元,它允许对一组单独的活动进行分组,以便它们共同成功或失败。