跳到主要内容

BPMN 2.0 简介

Doubao 中英对照 BPMN 2.0 Introduction

什么是 BPMN?​

BPMN 是一种被广泛接受和支持的、用于表示流程的标准建模语言 OMG BPMN Standard。

定义流程​

注意

本简介的编写假设你使用 Eclipse IDE 来创建和编辑文件。不过其中几乎没有什么是 Eclipse 特有的,你可以使用任何自己喜欢的其他工具来创建包含 BPMN 2.0 的 XML 文件。

新建一个 XML 文件(在任意项目上右键,选择 New→Other→XML-XML File)并给它命名。请确保该文件以 .bpmn20.xml 或 .bpmn 结尾,否则引擎不会在部署时拾取它。

new.bpmn.procdef

BPMN 2.0 模式的根元素是 definitions 元素。在该元素内部,可以给出多个流程定义(不过我们建议每个文件只放一个流程定义,因为这样可以简化后续开发过程中的维护)。一个空的流程定义如下所示。请注意,最小化的 definitions 元素只需要 xmlns 和 targetNamespace 声明。targetNamespace 可以是任意值,可用于对流程定义进行分类。

<definitions
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:flowable="http://flowable.org/bpmn"
targetNamespace="Examples">

<process id="myProcess" name="My First Process">
..
</process>

</definitions>

你也可以选择添加 BPMN 2.0 XML 模式的在线 schema 位置,作为 Eclipse 中 XML catalog 配置的替代。

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL
http://www.omg.org/spec/BPMN/2.0/20100501/BPMN20.xsd

process 元素有两个属性:

  • id:该属性必填,映射到 Flowable ProcessDefinition 对象的 key 属性。之后可以通过 RuntimeService 上的 startProcessInstanceByKey 方法,使用该 id 启动该流程定义的新流程实例。该方法总是使用该流程定义的最新部署版本。
  • 这里需要注意,这与调用 startProcessInstanceById 方法不同:后者期望的是 Flowable 引擎在部署时生成的 String ID(可以通过调用 processDefinition.getId() 方法获取该 ID)。生成 ID 的格式为 'key:version',且长度限制为 64 个字符。如果收到 FlowableException 提示生成的 ID 过长,请限制流程 key 字段中的文本长度。
  • name:该属性可选,映射到 ProcessDefinition 的 name 属性。引擎本身不使用该属性,因此可以用于在用户界面中显示更友好的名称。
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myProcess");

入门:10 分钟教程​

本节将介绍一个非常简单的业务流程,我们将用它来引入一些基本的 Flowable 概念和 Flowable API。

前提条件​

本教程假设你已经运行了 Flowable 演示环境,并且正在使用独立的 H2 服务器。编辑 db.properties,设置 jdbc.url=jdbc:h2:tcp://localhost/flowable,然后按照 H2 文档运行独立服务器。

目标​

本教程的目标是了解 Flowable 以及一些基本的 BPMN 2.0 概念。最终产物将是一个简单的 Java SE 程序:它部署一个流程定义,然后通过 Flowable 引擎 API 与该流程交互。我们还会简单提及 Flowable 周边的一些工具。当然,在围绕你的业务流程构建自己的 Web 应用时,本中学到的内容同样适用。

用例​

用例很简单:我们有一家公司,就叫它 BPMCorp。在 BPMCorp,每个月需要为公司股东编写一份财务报告。这是会计部门的职责。报告完成后,高层管理人员中的一员需要在文件发送给所有股东之前批准该文件。

流程图​

上面描述的业务流程可以使用 Flowable Designer 以图形方式定义。不过在本教程中,我们将亲自编写 XML,因为在这个阶段这样学习效果最好。我们流程的图形化 BPMN 2.0 表示如下:

financial.report.example.diagram

我们看到的是一个空开始事件(左侧圆圈),后面跟着两个用户任务:'Write monthly financial report' 和 'Verify monthly financial report',最后以一个空结束事件结束(右侧粗边框圆圈)。

XML 表示​

该业务流程的 XML 版本(FinancialReportProcess.bpmn20.xml)如下所示。可以很容易地识别出我们流程的主要元素(点击链接可跳转到该 BPMN 2.0 构造的详细章节):

  • (空)开始事件告诉我们流程的入口是什么。
  • 用户任务声明是我们流程中人工任务的表示。请注意,第一个任务分配给 accountancy 组,而第二个任务分配给 management 组。关于如何将用户和组分配给用户任务,详见用户任务分配章节。
  • 流程在到达空结束事件时结束。
  • 各元素之间通过顺序流相互连接。这些顺序流有源和目标,定义了顺序流的方向。
<definitions id="definitions"
targetNamespace="http://flowable.org/bpmn20"
xmlns:flowable="http://flowable.org/bpmn"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL">

<process id="financialReport" name="Monthly financial report reminder process">

<startEvent id="theStart" />

<sequenceFlow id="flow1" sourceRef="theStart" targetRef="writeReportTask" />

<userTask id="writeReportTask" name="Write monthly financial report" >
<documentation>
Write monthly financial report for publication to shareholders.
</documentation>
<potentialOwner>
<resourceAssignmentExpression>
<formalExpression>accountancy</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>
</userTask>

<sequenceFlow id="flow2" sourceRef="writeReportTask" targetRef="verifyReportTask" />

<userTask id="verifyReportTask" name="Verify monthly financial report" >
<documentation>
Verify monthly financial report composed by the accountancy department.
This financial report is going to be sent to all the company shareholders.
</documentation>
<potentialOwner>
<resourceAssignmentExpression>
<formalExpression>management</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>
</userTask>

<sequenceFlow id="flow3" sourceRef="verifyReportTask" targetRef="theEnd" />

<endEvent id="theEnd" />

</process>

</definitions>

启动流程实例​

我们现在已经为业务流程创建了流程定义。从这样的流程定义出发,可以创建流程实例。在本场景中,一个流程实例对应某个月份一份财务报告的创建和审核。任意月份的所有流程实例共享同一个流程定义。

要想从给定的流程定义创建流程实例,必须先部署该流程定义。部署一个流程定义意味着两件事:

  • 流程定义将被存储在为你的 Flowable 引擎配置的持久化数据存储中。因此,通过部署我们的业务流程,可以确保引擎在重启后仍能找到该流程定义。
  • BPMN 2.0 流程 XML 将被解析为内存对象模型,可以通过 Flowable API 对其进行操作。

更多关于部署的信息可以在专门的部署章节中找到。

如该节所述,部署可以通过多种方式进行。其中一种方式是通过 API,如下所示。请注意,与 Flowable 引擎的所有交互都是通过其服务完成的。

Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("FinancialReportProcess.bpmn20.xml")
.deploy();

现在可以使用我们在流程定义中定义的 id(见 XML 中的 process 元素)启动一个新的流程实例。请注意,在 Flowable 术语中,这个 id 被称为 key。

ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("financialReport");

这将创建一个流程实例,它会首先经过开始事件。开始事件之后,它沿着所有外向顺序流(本例中只有一条)前进,到达第一个任务('write monthly financial report')。Flowable 引擎现在会将一个任务存储在持久化数据库中。此时,附加到任务上的用户或组分配会被解析并存入数据库。需要重点注意的是,Flowable 引擎会持续执行流程步骤,直到到达等待状态,例如用户任务。在这样的等待状态下,流程实例的当前状态会被存储在数据库中。它会保持在该状态,直到某个用户决定完成自己的任务。到那时,引擎将继续执行,直到到达新的等待状态或流程结束。如果在此期间引擎重启或崩溃,流程状态在数据库中是安全可靠的。

任务创建之后,startProcessInstanceByKey 方法就会返回,因为用户任务活动是等待状态。在我们的场景中,任务分配给了一个组,这意味着该组的每个成员都是执行该任务的候选人。

现在我们可以把这些内容组合起来,创建一个简单的 Java 程序。新建一个 Eclipse 项目,并将 Flowable JAR 及其依赖添加到 classpath(这些可以在 Flowable 发行版的 libs 文件夹中找到)。在调用 Flowable 服务之前,必须先构建一个 ProcessEngine,通过它获得对服务的访问。这里我们使用 'standalone' 配置,它构建的 ProcessEngine 使用与演示环境相同的数据库。

你可以在此下载流程定义 XML。该文件包含上面所示的 XML,同时还包含必要的 BPMN图交换信息,用于在 Flowable 工具中可视化该流程。

public static void main(String[] args) {

// Create Flowable process engine
ProcessEngine processEngine = ProcessEngineConfiguration
.createStandaloneProcessEngineConfiguration()
.buildProcessEngine();

// Get Flowable services
RepositoryService repositoryService = processEngine.getRepositoryService();
RuntimeService runtimeService = processEngine.getRuntimeService();

// Deploy the process definition
repositoryService.createDeployment()
.addClasspathResource("FinancialReportProcess.bpmn20.xml")
.deploy();

// Start a process instance
runtimeService.startProcessInstanceByKey("financialReport");
}

任务列表​

现在我们可以通过添加以下逻辑,使用 TaskService 检索这个任务:

List<Task> tasks = taskService.createTaskQuery().taskCandidateUser("kermit").list();

请注意,我们传给该操作的用户必须是 accountancy 组的成员,因为这是在流程定义中声明的:

<potentialOwner>
<resourceAssignmentExpression>
<formalExpression>accountancy</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>

我们也可以使用任务查询 API,通过组名得到相同结果。现在可以在代码中添加以下逻辑:

TaskService taskService = processEngine.getTaskService();
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("accountancy").list();

由于我们将 ProcessEngine 配置为使用与演示环境相同的数据库,现在可以登录 Flowable IDM。以 admin/test 登录,创建 2 个新用户 kermit 和 fozzie,并给他们两人 Access the workflow application 权限。然后创建 2 个新的组织组,名为 accountancy 和 management,并将 fozzie 添加到新的 accountancy 组,将 kermit 添加到 management 组。
然后以 fozzie 登录 Flowable task 应用,我们会发现:选择 Task App,再进入其 Processes 页面,选择 'Monthly financial report' 流程,即可启动我们的业务流程。

bpmn.financial.report.example.start.process

如前所述,流程会执行到第一个用户任务为止。由于我们是以 fozzie 登录的,启动流程实例后,可以看到有一个新的候选任务可供他处理。选择 Tasks 页面查看这个新任务。请注意,即使流程是由其他人启动的,该任务对 accountancy 组的每个人来说仍会作为候选任务可见。

bpmn.financial.report.example.task.assigned

认领任务​

现在会计师需要认领该任务。认领任务后,该特定用户将成为任务的办理人(assignee),该任务将从 accountancy 组其他成员的每个任务列表中消失。以编程方式认领任务如下:

taskService.claim(task.getId(), "fozzie");

该任务现在位于认领该任务的用户的个人任务列表中。

List<Task> tasks = taskService.createTaskQuery().taskAssignee("fozzie").list();

在 Flowable Task 应用中,点击 claim 按钮会调用同一操作。任务现在将移到登录用户的个人任务列表中。你还会看到任务的办理人已更改为当前登录用户。

bpmn.financial.report.example.claim.task

完成任务​

会计师现在可以开始编写财务报告。报告完成后,他可以完成任务,这意味着该任务的所有工作都已完成。

taskService.complete(task.getId());

对 Flowable 引擎而言,这是一个外部信号,表示流程实例的执行现在可以继续。任务本身会从运行时数据中移除。任务的唯一外向迁移被遵循,执行前进到第二个任务('verification of the report')。分配第二个任务时将使用与第一个任务相同的机制,唯一的小差别是该任务分配给 management 组。

在演示环境中,完成任务是通过点击任务列表中的 complete 按钮来完成的。由于 Fozzie 不是会计师,我们需要退出 Flowable Task 应用,并以 kermit(他是经理)身份登录。第二个任务现在出现在未分配的任务列表中。

结束流程​

审核任务可以按与之前完全相同的方式检索和认领。完成第二个任务后,流程执行将前进到结束事件,从而结束该流程实例。流程实例及所有相关运行时执行数据都会从数据存储中移除。

以编程方式,你也可以使用 historyService 验证流程已经结束:

HistoryService historyService = processEngine.getHistoryService();
HistoricProcessInstance historicProcessInstance =
historyService.createHistoricProcessInstanceQuery().processInstanceId(procId).singleResult();
System.out.println("Process instance end time: " + historicProcessInstance.getEndTime());

代码概览​

把前几节的所有代码片段组合起来,应该就能得到类似下面的代码。该代码考虑到你可能已经通过 Flowable 应用 UI 启动过几个流程实例。它检索的是任务列表而不是单个任务,因此总是有效:

public class TenMinuteTutorial {

public static void main(String[] args) {

// Create Flowable process engine
ProcessEngine processEngine = ProcessEngineConfiguration
.createStandaloneProcessEngineConfiguration()
.buildProcessEngine();

// Get Flowable services
RepositoryService repositoryService = processEngine.getRepositoryService();
RuntimeService runtimeService = processEngine.getRuntimeService();

// Deploy the process definition
repositoryService.createDeployment()
.addClasspathResource("FinancialReportProcess.bpmn20.xml")
.deploy();

// Start a process instance
String procId = runtimeService.startProcessInstanceByKey("financialReport").getId();

// Get the first task
TaskService taskService = processEngine.getTaskService();
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("accountancy").list();
for (Task task : tasks) {
System.out.println("Following task is available for accountancy group: " + task.getName());

// claim it
taskService.claim(task.getId(), "fozzie");
}

// Verify Fozzie can now retrieve the task
tasks = taskService.createTaskQuery().taskAssignee("fozzie").list();
for (Task task : tasks) {
System.out.println("Task for fozzie: " + task.getName());

// Complete the task
taskService.complete(task.getId());
}

System.out.println("Number of tasks for fozzie: "
+ taskService.createTaskQuery().taskAssignee("fozzie").count());

// Retrieve and claim the second task
tasks = taskService.createTaskQuery().taskCandidateGroup("management").list();
for (Task task : tasks) {
System.out.println("Following task is available for management group: " + task.getName());
taskService.claim(task.getId(), "kermit");
}

// Completing the second task ends the process
for (Task task : tasks) {
taskService.complete(task.getId());
}

// verify that the process is actually finished
HistoryService historyService = processEngine.getHistoryService();
HistoricProcessInstance historicProcessInstance =
historyService.createHistoricProcessInstanceQuery().processInstanceId(procId).singleResult();
System.out.println("Process instance end time: " + historicProcessInstance.getEndTime());
}

}

未来增强​

很容易看出,这个业务流程过于简单,无法在现实中使用。不过,随着你逐步了解 Flowable 中可用的 BPMN 2.0 构造,你将能够通过以下方式增强该业务流程:

  • 定义网关,让经理可以拒绝财务报告并为会计师重新创建任务,从而走与批准报告不同的路径。
  • 声明并使用变量来存储或引用报告,使其可以在表单中可视化。
  • 在流程末尾定义一个服务任务,将报告发送给每位股东。
  • 等等。