CMMN 1.1
什么是 CMMN?
案例管理建模与表示法(CMMN)是由 对象管理组织 制定的一种标准表示法和正式规范,用于表示用例模型。
Flowable 包含:
- 一个 CMMN 1.1 建模器,用于创建 CMMN 1.1 用例模型。
- 一个 Java 引擎,可导入并执行 CMMN 1.1 用例模型。
- 一个演示 UI,用于执行用例模型,让用户可以查看并完成人工任务(及其表单)。
基本概念与术语
下图展示了一个简单的 CMMN 1.1 图:

用例模型总是可视化为某种 文件夹,其中包含所有用例元素。每个用例模型都包含一个计划模型,各项会被计划到其上。
计划模型的元素称为计划项。每个计划项都有一个计划项定义,给出其类型以及运行时可能的配置选项。例如,在上图中,有三个人工任务计划项和一个里程碑。计划项的其他例子有流程任务、用例任务和阶段。
将用例模型部署到 Flowable CMMN 引擎后,就可以基于该用例模型启动用例实例。用例模型中定义的计划项同样具有计划项实例这一运行时表示,它由 Flowable API 暴露并可通过其查询。计划项实例具有 CMMN 1.1 规范中定义的状态生命周期,这是引擎工作方式的核心。完整细节请查阅 CMMN 1.1 规范的 8.4.2 节。
计划项可以有哨兵:当一个哨兵"守护"计划项的激活时,称该计划项具有进入准则。这些准则指定了必须满足才能触发哨兵的条件。例如,在上图中,"Milestone One"计划项在用例实例启动后处于可用状态,但当人工任务 A 和 B 都完成时,它就被激活(用 CMMN 1.1 规范的术语说:它从可用状态转移到活动状态)。注意,哨兵在其 if 部分中可以有复杂表达式,这些表达式不被可视化,从而支持复杂得多的功能。还要注意,可以有多个哨兵,不过只需满足其中一个就能触发状态转换。
计划项和计划模型也可以带有退出准则的哨兵,用于指定触发退出该计划项的条件。在上图中,当人工任务 C 完成时,整个计划模型都会退出(此时处于活动状态的所有子元素也一并退出)。
CMMN 1.1 在作为规范一部分的 XSD 中定义了标准 XML 格式。供参考,上图中的示例用 XML 表示如下。
几点说明:
- 上面的四个计划项都在 XML 中,它们通过 definitionRef 引用各自的定义。实际的定义位于 casePlanModel 元素的底部。
- 计划项拥有准则(进入或退出),这些准则引用 sentry(而不是反过来)。
- XML 还包含图如何可视化的信息(x 和 y 坐标、宽度和高度等),下面省略了这些内容。当与其他 CMMN 1.1 建模工具交换用例模型时,这些元素对于保持正确的可视化表示很重要。
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/CMMN/20151109/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:flowable="http://flowable.org/cmmn"
xmlns:cmmndi="http://www.omg.org/spec/CMMN/20151109/CMMNDI"
xmlns:dc="http://www.omg.org/spec/CMMN/20151109/DC"
xmlns:di="http://www.omg.org/spec/CMMN/20151109/DI"
targetNamespace="http://www.flowable.org/casedef">
<case id="simpleExample" name="Simple Example">
<casePlanModel id="casePlanModel" name="My Case">
<planItem id="planItem1" name="Human task A"
definitionRef="sid-88199E7C-7655-439C-810B-8849FC52D3EB"></planItem>
<planItem id="planItem2" name="Milestone One"
definitionRef="sid-8BF8A774-A8A7-4F1A-95CF-1E0D61EE5A47">
<entryCriterion id="sid-62CC4A6D-B29B-4129-93EA-460253C45CDF"
sentryRef="sentry1"></entryCriterion>
</planItem>
<planItem id="planItem3" name="Human task B"
definitionRef="sid-A1FB8733-0DBC-4B38-9830-CBC4D0C4B802"></planItem>
<planItem id="planItem4" name="Human task C"
definitionRef="sid-D3970AFC-7391-4BA7-95BA-51C64D2F41E9"></planItem>
<sentry id="sentry1">
<planItemOnPart id="sentryOnPart1" sourceRef="planItem1">
<standardEvent>complete</standardEvent>
</planItemOnPart>
<planItemOnPart id="sentryOnPart2" sourceRef="planItem3">
<standardEvent>complete</standardEvent>
</planItemOnPart>
</sentry>
<sentry id="sentry2">
<planItemOnPart id="sentryOnPart3" sourceRef="planItem4">
<standardEvent>complete</standardEvent>
</planItemOnPart>
</sentry>
<humanTask id="sid-88199E7C-7655-439C-810B-8849FC52D3EB"
name="Human task A"></humanTask>
<milestone id="sid-8BF8A774-A8A7-4F1A-95CF-1E0D61EE5A47"
name="Milestone One"></milestone>
<humanTask id="sid-A1FB8733-0DBC-4B38-9830-CBC4D0C4B802"
name="Human task B"></humanTask>
<humanTask id="sid-D3970AFC-7391-4BA7-95BA-51C64D2F41E9"
name="Human task C"></humanTask>
<exitCriterion id="sid-422626DB-9B40-49D8-955E-641AB96A5BFA"
sentryRef="sentry2"></exitCriterion>
</casePlanModel>
</case>
<cmmndi:CMMNDI>
<cmmndi:CMMNDiagram id="CMMNDiagram_simpleExample">
...
</cmmndi:CMMNDiagram>
</cmmndi:CMMNDI>
</definitions>
编程式示例
在本节中,我们将构建一个简单的用例模型,并在一个简单的命令行示例中通过 Flowable CMMN 引擎的 Java API 以编程方式执行它。
我们要构建的用例模型是一个简化的员工入职用例,包含两个阶段:准员工入职前的阶段和入职后的阶段。在第一 阶段,由 HR 部门的人完成任务;在第二阶段,则由员工自己完成。此外,在任何时候,准员工都可以拒绝这份工作并停止整个用例实例。
注意,这里只使用了阶段和人工任务。在真实的用例模型中,很可能还会有其他计划项类型,例如里程碑、嵌套阶段、自动化任务等等。

该用例模型的 XML 如下:
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/CMMN/20151109/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:flowable="http://flowable.org/cmmn"
xmlns:cmmndi="http://www.omg.org/spec/CMMN/20151109/CMMNDI"
xmlns:dc="http://www.omg.org/spec/CMMN/20151109/DC"
xmlns:di="http://www.omg.org/spec/CMMN/20151109/DI"
targetNamespace="http://www.flowable.org/casedef">
<case id="employeeOnboarding" name="Simple Example">
<casePlanModel id="casePlanModel" name="My Case">
<planItem id="planItem5" name="Prior to starting"
definitionRef="sid-025D29E8-BA9B-403D-A684-8C5B52185642"></planItem>
<planItem id="planItem8" name="After starting"
definitionRef="sid-8459EF32-4F4C-4E9B-A6E9-87FDC2299044">
<entryCriterion id="sid-50B5F12D-FE75-4D05-9148-86574EE6C073"
sentryRef="sentry2"></entryCriterion>
</planItem>
<planItem id="planItem9" name="Reject job"
definitionRef="sid-134E885A-3D58-417E-81E2-66A3E12334F9"></planItem>
<sentry id="sentry2">
<planItemOnPart id="sentryOnPart4" sourceRef="planItem5">
<standardEvent>complete</standardEvent>
</planItemOnPart>
</sentry>
<sentry id="sentry3">
<planItemOnPart id="sentryOnPart5" sourceRef="planItem9">
<standardEvent>complete</standardEvent>
</planItemOnPart>
</sentry>
<stage id="sid-025D29E8-BA9B-403D-A684-8C5B52185642" name="Prior to starting">
<planItem id="planItem1" name="Create email address"
definitionRef="sid-EA434DDD-E1BE-4AC1-8520-B19ACE8782D2"></planItem>
<planItem id="planItem2" name="Allocate office"
definitionRef="sid-505BA223-131A-4EF0-ABAD-485AEB0F2C96"></planItem>
<planItem id="planItem3" name="Send joining letter to candidate"
definitionRef="sid-D28DBAD5-0F5F-45F4-8553-3381199AC45F">
<entryCriterion id="sid-4D88C79D-8E31-4246-9541-A4F6A5720AC8"
sentryRef="sentry1"></entryCriterion>
</planItem>
<planItem id="planItem4" name="Agree start date"
definitionRef="sid-97A72C46-C0AD-477F-86DD-85EF643BB97D"></planItem>
<sentry id="sentry1">
<planItemOnPart id="sentryOnPart1" sourceRef="planItem1">
<standardEvent>complete</standardEvent>
</planItemOnPart>
<planItemOnPart id="sentryOnPart2" sourceRef="planItem2">
<standardEvent>complete</standardEvent>
</planItemOnPart>
<planItemOnPart id="sentryOnPart3" sourceRef="planItem4">
<standardEvent>complete</standardEvent>
</planItemOnPart>
</sentry>
<humanTask id="sid-EA434DDD-E1BE-4AC1-8520-B19ACE8782D2"
name="Create email address"
flowable:candidateGroups="hr"></humanTask>
<humanTask id="sid-505BA223-131A-4EF0-ABAD-485AEB0F2C96"
name="Allocate office"
flowable:candidateGroups="hr"></humanTask>
<humanTask id="sid-D28DBAD5-0F5F-45F4-8553-3381199AC45F"
name="Send joining letter to candidate"
flowable:candidateGroups="hr"></humanTask>
<humanTask id="sid-97A72C46-C0AD-477F-86DD-85EF643BB97D"
name="Agree start date"
flowable:candidateGroups="hr"></humanTask>
</stage>
<stage id="sid-8459EF32-4F4C-4E9B-A6E9-87FDC2299044"
name="After starting">
<planItem id="planItem6" name="New starter training"
definitionRef="sid-DF7B9582-11A6-40B4-B7E5-EC7AC6029387"></planItem>
<planItem id="planItem7" name="Fill in paperwork"
definitionRef="sid-7BF2B421-7FA0-479D-A8BD-C22EBD09F599"></planItem>
<humanTask id="sid-DF7B9582-11A6-40B4-B7E5-EC7AC6029387"
name="New starter training"
flowable:assignee="${potentialEmployee}"></humanTask>
<humanTask id="sid-7BF2B421-7FA0-479D-A8BD-C22EBD09F599"
name="Fill in paperwork"
flowable:assignee="${potentialEmployee}"></humanTask>
</stage>
<humanTask id="sid-134E885A-3D58-417E-81E2-66A3E12334F9" name="Reject job"
flowable:assignee="${potentialEmployee}"></humanTask>
<exitCriterion id="sid-18277F30-E146-4B3E-B3C9-3F1E187EC7A8"
sentryRef="sentry3"></exitCriterion>
</casePlanModel>
</case>
</definitions>
首先,创建一个新项目并添加 flowable-cmmn-engine 依赖(这里以 Maven 为例)。同时还添加了 H2 依赖,因为稍后将使用 H2 作为嵌入式数据库。
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-cmmn-engine</artifactId>
<version>${flowable.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2.version}</version>
</dependency>
Flowable CMMN API 的设计与其他 Flowable API 和概念保持一致。因此,熟悉 BPMN 或 DMN API 的人很容易上手。与其他 引擎一样,第一行代码就是创建一个 CmmnEngine。这里使用默认的内存配置,以 H2 作为数据库:
public class Main {
public static void main(String[] args) {
CmmnEngine cmmnEngine
= new StandaloneInMemCmmnEngineConfiguration().buildCmmnEngine();
}
}
注意,CmmnEngineConfiguration 暴露了许多配置选项,用于调整 CMMN 引擎的各项设置。
把上面的 XML 放入一个文件,例如 my-case.cmmn(或 .cmmn.xml)。对于 Maven,应将其放在 src/main/resources 文件夹中。
要让引擎知道该用例模型,首先需要将其部署。这通过 CmmnRepositoryService 完成:
CmmnRepositoryService cmmnRepositoryService = cmmnEngine.getCmmnRepositoryService();
CmmnDeployment cmmnDeployment = cmmnRepositoryService.createDeployment()
.addClasspathResource("my-case.cmmn")
.deploy();
部署该 XML 会返回一个 CmmnDeployment。一个部署可以包含许多用例模型和构件。上面这个具体的用例模型定义会作为 CaseDefinition 存储。可以通过执行一次 CaseDefinitionQuery 来验证这一点:
List<CaseDefinition> caseDefinitions = cmmnRepositoryService.createCaseDefinitionQuery().list();
System.out.println("Found " + caseDefinitions.size() + " case definitions");
引擎中有了 CaseDefinition,现在就可以为该用例模型定义启动一个 CaseInstance。既可以使用查询结果并将其传入下面的代码片段,也可以直接使用用例定义的 key(如下所示)。
注意,在启动 CaseInstance 时,我们还传入了数据:一个作为变量的 potentialEmployee 标识符。稍后该变量会在人工任务中使用,以把任务分配给正确的人(参见人工任务上的 assignee="${potentialEmployee}" 属性)。
CmmnRuntimeService cmmnRuntimeService = cmmnEngine.getCmmnRuntimeService();
CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder()
.caseDefinitionKey("employeeOnboarding")
.variable("potentialEmployee", "johnDoe")
.start();
CaseInstance 启动后,引擎将决定模型中的哪些计划项应该被激活:
- 第一个阶段没有进入准则,因此它被激活。
- 第一个阶段的子人工任务没有进入准则,因此预期其中三个处于活动状态。
计划项在运行时由 PlanItemInstance 表示,并可以通过 CmmnRuntimeService 查询:
List<PlanItemInstance> planItemInstances = cmmnRuntimeService.createPlanItemInstanceQuery()
.caseInstanceId(caseInstance.getId())
.orderByName().asc()
.list();
for (PlanItemInstance planItemInstance : planItemInstances) {
System.out.println(planItemInstance.getName());
}
输出如下:
After starting
Agree start date
Allocate office
Create email address
Prior to starting
Reject job
Send joining letter to candidate
这里可能有一些出乎意料的地方:
- 阶段也是计划项,因此也有对应的 PlanItemInstance 表示。注意,子计划项实例在调用 .getStageInstanceId() 时会以该阶段作为父级。
- 结果中返回了 Send joining letter to candidate。原因在于,按照 CMMN 1.1 规范,该计划项实例处于可用状态,但尚未进入活动状态。
的确,当把上面的代码改为:
for (PlanItemInstance planItemInstance : planItemInstances) {
System.out.println(planItemInstance.getName()
+ ", state=" + planItemInstance.getState()
+ ", parent stage=" + planItemInstance.getStageInstanceId());
}
输出现在变为:
After starting, state=available, parent stage=null
Agree start date, state=active, parent stage=fe37ac97-b016-11e7-b3ad-acde48001122
Allocate office, state=active, parent stage=fe37ac97-b016-11e7-b3ad-acde48001122
Create email address, state=active, parent stage=fe37ac97-b016-11e7-b3ad-acde48001122
Prior to starting, state=active, parent stage=null
Reject job, state=active, parent stage=fe37ac97-b016-11e7-b3ad-acde48001122
Send joining letter to candidate, state=available, parent stage=fe37ac97-b016-11e7-b3ad-acde48001122
要只显示活动的计划项实例,可以在查询中加上 planItemInstanceStateActive():
List<PlanItemInstance> planItemInstances = cmmnRuntimeService.createPlanItemInstanceQuery()
.caseInstanceId(caseInstance.getId())
.planItemInstanceStateActive()
.orderByName().asc()
.list();
现在输出为:
Agree start date
Allocate office
Create email address
Prior to starting
Reject job
当然,PlanItemInstance 是底层表示,但每个计划项还有一个计划项定义,定义了它的类型。在本例中,我们只有人工任务。可以通过用例实例的计划项实例与其交互,例如以编程方式触发它们(如 CmmnRuntimeService.triggerPlanItemInstance(String planItemInstanceId))。然而,大多数情况 下,交互会通过实际计划项定义的结果来进行:在这里就是人工任务。
查询任务的方式与 BPMN 引擎完全相同(事实上,任务服务是一个共享组件,在 BPMN 或 CMMN 中创建的任务都可以通过两个引擎查询):
CmmnTaskService cmmnTaskService = cmmnEngine.getCmmnTaskService();
List<Task> hrTasks = cmmnTaskService.createTaskQuery()
.taskCandidateGroup("hr")
.caseInstanceId(caseInstance.getId())
.orderByTaskName().asc()
.list();
for (Task task : hrTasks) {
System.out.println("Task for HR : " + task.getName());
}
List<Task> employeeTasks = cmmnTaskService.createTaskQuery()
.taskAssignee("johndoe")
.orderByTaskName().asc()
.list();
for (Task task : employeeTasks) {
System.out.println("Task for employee: " + task);
}
输出:
Task for HR : Agree start date
Task for HR : Allocate office
Task for HR : Create email address
Task for employee: Reject job
当 HR 的三个任务完成后,'Send joining letter to candidate' 任务应该变为可用:
for (Task task : hrTasks) {
cmmnTaskService.complete(task.getId());
}
hrTasks = cmmnTaskService.createTaskQuery()
.taskCandidateGroup("hr")
.caseInstanceId(caseInstance.getId())
.orderByTaskName().asc()
.list();
for (Task task : hrTasks) {
System.out.println("Task for HR : " + task.getName());
}
的确,预期的任务现在被创建了:
Task for HR : Send joining letter to candidate
完成该任务后,用例实例将进入第二阶段,因为第一阶段的哨兵已满足。'Reject job' 任务由系统自动完成,同时为员工创建两个任务:
Task for employee: Fill in paperwork
Task for employee: New starter training
Task for employee: Reject job
完成所有任务后,用例实例结束:
List<Task> tasks = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).listPage(0, 1);
while (!tasks.isEmpty()) {
cmmnTaskService.complete(tasks.get(0).getId());
tasks = cmmnTaskService.createTaskQuery()
.caseInstanceId(caseInstance.getId())
.listPage(0, 1);
}
在执行用例实例时,引擎还会存储历史信息,可以通过查询 API 查询:
CmmnHistoryService cmmnHistoryService = cmmnEngine.getCmmnHistoryService();
HistoricCaseInstance historicCaseInstance = cmmnHistoryService.createHistoricCaseInstanceQuery()
.caseInstanceId(caseInstance.getId())
.singleResult();
System.out.println("Case instance execution took "
+ (historicCaseInstance.getEndTime().getTime() - historicCaseInstance.getStartTime().getTime()) + " ms");
List<HistoricTaskInstance> historicTaskInstances = cmmnHistoryService.createHistoricTaskInstanceQuery()
.caseInstanceId(caseInstance.getId())
.orderByTaskCreateTime().asc()
.list();
for (HistoricTaskInstance historicTaskInstance : historicTaskInstances) {
System.out.println("Task completed: " + historicTaskInstance.getName());
}
输出:
Case instance execution took 149 ms
Task completed: Reject job
Task completed: Agree start date
Task completed: Allocate office
Task completed: Create email address
Task completed: Send joining letter to candidate
Task completed: New starter training
Task completed: Fill in paperwork
与用例执行相关的历史数据会为各类特殊构造收集,例如任务(如上所见)、里程碑、用例、变量以及一般的计划项。这些数据与运行时数据同时持久化,但在用例实例结束时不会被删除。对历史数据的访问由 CmmnHistoryService 以查询 API 的形式提供。
当然,这只是 Flowable CMMN 引擎中可用的 API 和构造的一小部分。更详细的信息请查阅其他小节。
CMMN 1.1 构造
本章介绍 Flowable 支持的 CMMN 1.1 构造,以及对 CMMN 1.1 标准的扩展。
以下构造(哨兵和项控制除外)按照 CMMN 规范都被视为计划项。其实例执行的历史数据可以通过 CmmnHistoryService 使用 org.flowable.cmmn.api.history.HistoricPlanItemInstanceQuery 查询。
阶段
阶段用于把计划项分组到一起。它通常用于定义用例实例中的"阶段"。
阶段本身也是一个计划项,因此可以有进入和退出准则。包含在某个阶段内的计划项只有在父阶段转移到活动状态时才可用。阶段可以嵌套在其他阶段中。
阶段可视化为一个斜角矩形:

任务
一个"手动"任务,意味着该任务在引擎外部发生。
属性:
- name:在运行时解析为手动任务名称的表达式。
- blocking:一个布尔值,决定该任务是否阻塞。
- blockingExpression:一个求值为布尔值的表达式,指示该任务是否阻塞。
如果任务是非阻塞的,引擎在执行它时会自动完成它。如果任务是阻塞的,该任务的 PlanItemInstance 将保持活动状态,直到通过 CmmnRuntimeService.triggerPlanItemInstance(String planItemInstanceId) 方法以编程方式触发它。
任务可视化为一个圆角矩形:
人工任务
人工任务用于建模需要由人完成的工作,通常通过表单完成。当引擎执行到人工任务时,会在分配给该任务的所有用户或组的任务列表中创建一个新条目。
人工任务是一个计划项,这意味着除了人工任务条目之外,还会创建一个 PlanItemInstance,可以通过 PlanItemInstanceQuery 查询。
人工任务可以通过 org.flowable.task.api.TaskQuery API 查询。历史任务数据可以通过 org.flowable.task.api.history.HistoricTaskInstanceQuery 查询。
属性:
- name:在运行时解析为人工任务名称的表达式。
- blocking:一个布尔值,决定该任务是否阻塞。
- blockingExpression:一个求值为布尔值的表达式,指示该任务是否阻塞。
- assignee:一个表达式(可以是静态文本值),用于确定人工任务分配给谁。
- owner:一个表达式(可以是静态文本值),用于确定人工任务的所有者是谁。
- candidateUsers:一个表达式(可以是静态文本值),解析为逗号分隔的字符串列表,用于确定哪些用户是该人工任务的候选办理人。
- candidateGroups:一个表达 式(可以是静态文本值),解析为逗号分隔的字符串列表,用于确定该任务分配给哪些组。
- form key:一个表达式,用于在使用表单时确定一个 key。之后可以通过 API 获取。
- Due date:一个表达式,解析为 java.util.Date 或 ISO-8601 日期字符串。
- Priority:一个解析为整数的表达式。可在 TaskQuery API 中用于过滤任务。
人工任务可视化为一个圆角矩形,左上角有一个用户图标:
Java 服务任务
服务任务用于执行自定义逻辑。
自定义逻辑放在一个实现了 org.flowable.cmmn.api.delegate.PlanItemJavaDelegate 接口的类中。
public class MyJavaDelegate implements PlanItemJavaDelegate {
public void execute(DelegatePlanItemInstance planItemInstance) {
String value = (String) planItemInstance.getVariable("someVariable");
...
}
}
对于无法通过 PlanItemJavaDelegate 方式覆盖的更底层实现,可以使用 CmmnActivityBehavior(类似于 BPMN 引擎中的 JavaDelegate 与 ActivityBehavior)。
属性:
- name:服务任务的名称。
- class:实现自定义逻辑的 Java 类。
- class fields:调用自定义逻辑时传入的参数。
- Delegate expression:一个表达式,解析为实现了 PlanItemJavaDelegate 接口的类。
服务任务可视化为一个圆角矩形,左上角有一个齿轮图标:
外部 Worker 任务
说明
外部 Worker 任务允许你创建应由外部 Worker 获取并执行的作业。外部 Worker 可以通过 Java API 或 REST API 获取作业。它类似于异步服务任务。区别在于:不是由 Flowable 执行逻辑,而是由外部 Worker(可以用任意语言实现)向 Flowable 查询作业、执行作业并把结果发回 Flowable。注意,外部 Worker 任务不是 CMMN 规范的"官方"任务(因此也没有专用图标)。
定义外部 Worker 任务
外部 Worker 任务实现为一个专用的任务,通过将任务的 type 设置为 'external-worker' 来定义。
<task id="externalWorkerOrder" flowable:type="external-worker">
用法示例
下面的 XML 片段展示了使用外部 Worker 任务的示例。外部 Worker 是一种等待状态。当执行到达该任务时,会创建一个外部 Worker 作业,可由外部 Worker 获取。一旦外部 Worker 完成作业并通知 Flowable 完成,用例的执行将继续。
<task id="externalWorkerOrder" flowable:type="external-worker" flowable:topic="orderService" />
获取外部 Worker 作业
外部 Worker 作业通过 CmmnManagementService#createExternalWorkerJobAcquireBuilder 使用 ExternalWorkerJobAcquireBuilder 获取。
List<AcquiredExternalWorkerJob> acquiredJobs = cmmnManagementService.createExternalWorkerJobAcquireBuilder()
.topic("orderService", Duration.ofMinutes(30))
.acquireAndLock(5, "orderWorker-1");
使用上面的 Java 片段可以获取外部 Worker 作业。通过该片段,我们做了以下事情:
- 查询 topic 为 orderService 的外部 Worker 作业。
- 获取并锁定作业 30 分钟,等待外部 Worker 发出完成信号。
- 最多获取 5 个作业。
- 作业的所有者是 id 为 orderWorker-1 的 Worker。
AcquiredExternalWorkerJob 还可以访问用例变量。当外部 Worker 任务是排他的时,获取作业会锁定该用例实例。
完成外部 Worker 作业
外部 Worker 作业通过 CmmnManagementService#createCmmnExternalWorkerTransitionBuilder(String, String) 使用 CmmnExternalWorkerTransitionBuilder 完成。
cmmnManagementService.createCmmnExternalWorkerTransitionBuilder(acquiredJob.getId(), "orderWorker-1")
.variable("orderStatus", "COMPLETED")
.complete();
作业只能由获取它的 Worker 完成。否则将抛出 FlowableIllegalArgumentException。
使用上面的片段,任务即告完成,用例执行将继续。执行的后续推进在一个新事务中异步完成。这意味着完成一个外部 Worker 任务只会创建一个异步(新)作业来执行完成动作(当前线程在做完这件事后即返回)。模型中外部 Worker 任务之后的任何步骤都将在该事务中执行,类似于普通的异步服务任务。
也可以使用 CmmnExternalWorkerTransitionBuilder#terminate() 来终止外部 Worker 作业。
外部 Worker 作业的错误处理
ExternalWorkerJobFailureBuilder 用于让作业失败(将其调度为将来重新执行)。
要让作业失败,可以使用以下方式:
cmmnManagementService.createExternalWorkerJobFailureBuilder(acquiredJob.getId(), "orderWorker-1")
.errorMessage("Failed to run job. Database not accessible")
.errorDetails("Some complex and long error details")
.retries(4)
.retryTimeout(Duration.ofHours(1))
.fail();
该片段将完成以下事情:
- 错误消息和错误详情会设置到作业上。
- 作业的重试次数会设置为 4。
- 该作业将在 1 小时后可被获取。
作业只能由获取它的 Worker 标记为失败。如果未设置重试次数,Flowable 会自动将作业的重试次数减 1。当重试次数为 0 时,该作业将被移入 DeadLetter 作业表,不再可被获取。
查询外部 Worker 作业
外部 Worker 作业通过 ExternalWorkerJobQuery 查询,该查询由 CmmnManagementService#createExternalWorkerJobQuery 创建。
决策任务
决策任务调用一个 DMN 决策表,并把结果变量存储在用例实例中。
属性:
- Decision table reference:需要调用的、被引用的 DMN 决策表。
还可以通过设置'未命中规则时抛出错误'属性,在 DMN 决策表求值期间没有命中任何规则时抛出错误。
决策任务可视化为一个任务,左上角有 一个表格图标:

HTTP 任务
HTTP 任务是 服务任务 的开箱即用实现。当需要通过 HTTP 调用 REST 服务时使用它。
HTTP 任务有多种选项来定制请求和响应。所有配置选项的详细信息请参见 BPMN HTTP 任务文档。
HTTP 任务可视化为一个任务,左上角有一个火箭图标:

脚本任务
类型为 "script" 的任务,与 BPMN 中的对应物类似,脚本任务在计划项实例变为活动时执行一段脚本。
属性:
- name:指示任务名称的任务属性。
- type:任务属性,其值必须为 "script",以指示任务类型。
- scriptFormat:扩展属性,指示脚本语言(例如 javascript、groovy)。
- script:要执行的脚本,在名为 "script" 的 field 元素中以字符串定义。
- autoStoreVariables:可选的任务属性标志(默认:false),指示脚本中定义的变量是否会存储到计划项实例上下文中(参见下面的注意事项)。
- resultVariableName:可选的任务属性,存在时会把脚本求值结果以指定名称的变量存储到计划项实例上下文中(参见下面的注意事项)。
脚本任务可视化为一个任务,左上角有一个脚本图标:
<planItem id="scriptPlanItem" name="Script Plan Item" definitionRef="myScriptTask" />
<task name="My Script Task Item" flowable:type="script" flowable:scriptFormat="JavaScript">
<documentation>Optional documentation</documentation>
<extensionElements>
<flowable:field name="script">
<string>
sum = 0;
for ( i in inputArray ) {
sum += i;
}
</string>
</flowable:field>
</extensionElements>
</task>
注意:scriptFormat 属性的值必须是与 JSR-223(Java 平台脚本规范)兼容的名称。默认情况下,每个 JDK 都内置 JavaScript,因此不需要额外的 JAR 文件。如果想使用另一种(兼容 JSR-223 的)脚本引擎,只需把相应 JAR 加入 classpath 并使用合适的名称即可。例如,Flowable 单元测试经常使用 Groovy,因为它的语法与 Java 相似。
请注意,Groovy 脚本引擎打包在 groovy-jsr223 JAR 中。因此必须添加以下依赖:
<dependency>
<groupId>org.apache.groovy</groupId>
<artifactId>groovy-jsr223</artifactId>
<version>4.x.x<version>
</dependency>
可以通过到达脚本任务的计划项实例访问的所有用例变量,都可以在脚本中使用。在下面的例子中,脚本变量 'inputArray' 实际上是一个用例变量(一个整数数组)。
<flowable:field name="script">
<string>
sum = 0
for ( i in inputArray ) {
sum += i
}
</string>
</flowable:field>
注意:也可以在脚本中设置计划项实例变量,只需调用 planItemInstance.setVariable("variableName", variableValue) 即可。默认情况下,不会自动存储任何变量。通过把脚本任务上的 autoStoreVariables 属性设置为 true,可以自动存储脚本中定义的任何变量(例如上面例子中的 sum)。然而,最佳实践是不要这样做,而是显式调用 planItemInstance.setVariable(),因为在某些较新版本的 JDK 中,自动存储变量对某些脚本语言并不奏效。更多细节请参见此链接。
<task name="Script Task" flowable:type="script" flowable:scriptFormat="groovy" flowable:autoStoreVariables="false">
该参数的默认值为 false,意味着如果脚本任务定义中省略该参数,所有声明的变量只在脚本执行期间存在。
下面是在脚本中设置变量的示例:
<flowable:field name="script">
<string>
def scriptVar = "test123"
planItemInstance.setVariable("myVar", scriptVar)
</string>
</flowable:field>
以下名称是保留字,不能用作变量名:out、out:print、lang:import、context、elcontext。
注意 脚本任务的返回值可以通过将其名称作为字面值指定给脚本任务定义的 'flowable:resultVariable' 属性,赋值给一个已存在或新建的计划项实例变量。某个计划项实例变量的任何已有值都会被脚本执行的结果值覆盖。当未指定结果变量名时,脚本结果值会被忽略。
<task name="Script Task" flowable:type="script" flowable:scriptFormat="groovy" flowable:resultVariable="myVar">
<flowable:field name="script">
<string>#{echo}</string>
</flowable:field>
</task>
在上面的例子中,脚本执行的结果(解析后表达式 '#{echo}' 的值)会在脚本完成后设置到名为 'myVar' 的流程变量中。