目录
1. BPMN介绍
可以类比上小学时学的流程图😂,
将流程图和BPMN做个最简单的类比如下表:
流程图元素 | 流程图示例 | BPMN对应元素 | BPMN示例 |
---|---|---|---|
开始 | 开始事件Start Event | ||
活动 | 任务Task | ||
判断 | 网关Gateway | ||
结束 | 结束事件End Event | ||
连接线 | 顺序流Sequence flow |
可以发现从流程图可以很平滑的过渡到BPMN模型,
简单理解:BPMN就是流程图的升级版,
当然BPMN的功能远不止表格中列出的这些,
接下来的会逐步探索BPMN的相关功能。
1.1 Event - 事件
接收事件(等待事件发生),触发事件(发送事件),
进而触发流程执行、改变流程走向、结束流程等。
关于事件的分类详见下表(其中橘色的事件为Camunda支持的)
捕获事件
(阻塞等待事件通知) 都是空心图标
抛出事件
(抛出结束事件 或者 抛出事件通知后继续执行) 都是实心图标
中断事件
(中断原流程 且 开启新流程) 都是实线圆
(子流程中断开始事件、边界型中断中间事件)
非中断事件
(不中断原流程 且 开启新流程) 都是虚线圆
(子例程非中断开始事件、边界型非中断中间事件)
开始事件(单实线圆,空心图标
,子流程虚线中断)都是捕获事件
结束事件(加粗单实线圆,实心图标)
都是抛出事件
中间事件(双实线圆)
包括捕捉事件
(空心图标)、抛出事件
(实心图标)
边界型中间事件(双实线圆,附加在活动、子流程边界
,虚线中断)都是捕获事件
1.1.1 开始事件
- 空白开始事件Blank
- 定时器开始事件Timer
- *消息开始事件Message
- *信号开始事件Signal
- 条件开始事件Conditional
1.1.2 中间事件
-
捕获事件(Catch Event,空心图标)- 接收事件通知后,触发流程继续执行
-
抛出事件(Throw Event,实心图标)- 主动抛出事件通知,然后继续执行流程
1.1.3 边界事件
- 边界事件(Boundary Evant,亦成为附加中间事件,Attached Intermediate Events),附加到任务、子流程上的中间事件
- 中断 - 边界事件
若任务执行过程中发生附加事件,则取消当前任务,继续执行附加事件对应的流程,
否则继续执行当前任务正常流程(非附件事件对应分支) - 非中断 - 边界事件
若任务执行过程中发生附加事件,则继续执行当前任务(即后续流程),同时执行附加事件对应的流程,
若当前任务执行过程中重复发生附加事件,则重复执行附加事件对应的流程,
否则继续执行当前任务正常流程(非附件事件对应分支) - 补偿 - 边界事件
仅在任务执行完成后,才会执行补偿边界事件连接的的补偿任务,
补偿边界事件可由补偿结束事件
、补偿中间抛出事
件触发,亦可由事务子流程
的取消结束事件
触发。
- 中断 - 边界事件
1.1.4 结束事件
1.2 Tasks - 任务
Task即为流程活动(对应单个执行步骤)的抽象。
1.2.1 Servcie Task - 调用外部服务
Service Task用于调用外部服务。
调用服务方式
调用服务的方式分为:
调用Java代码 - 实现JavaDelegate
Java Class
- 指定代理类全路径(实现接口JavaDelegate、ActivityBehavior,抛异常则回滚到上一步),如:com.luo.demo.MyDelegateDelegate Expression
- 使用表达式来提取代理对象(实现接口JavaDelegate、ActivityBehavior),更适合SpringBoot环境,如:${myDelegateBean}Expression
- 调用方法表达式,如:${myBean.doWork()}Expression
- 解析值表达式(Evaluating a value expression)
External Task
External (External Task)
- 指定task对应的topic
- 由外部Worker通过REST API(Long Polling)主动获取并锁定(可指定超时时间)任务(亦支持Java API)
- 外部worker通知流程 完成Complete 或 失败Failure
Java API示例代码(需配合外层循环):
List<LockedExternalTask> tasks = processEngine.getExternalTaskService()
//最多一次拉取10条task,设置当前workerId为externalWorkerId(需唯一)
.fetchAndLock(10, "externalWorkerId")
//指定查询的topic,及任务锁定超时时间(超时后可被其他worker获取到)
.topic("AddressValidation", 60L * 1000L)
.topic("ShipmentScheduling", 120L * 1000L)
.execute();
for (LockedExternalTask task : tasks) {
try {
String topic = task.getTopicName();
// work on task for that topic
...
// if the work is successful, mark the task as completed
if(success) {
externalTaskService.complete(task.getId(), variables);
}
else {
// if the work was not successful, mark it as failed
externalTaskService.handleFailure(
task.getId(),
"externalWorkerId",
"Address could not be validated: Address database not reachable",
1, 10L * 60L * 1000L);
}
}
catch(Exception e) {
//... handle exception
}
}
REST API代码示例:
ExternalTaskClient client = ExternalTaskClient.create()
.baseUrl("http://localhost:8080/engine-rest")
.asyncResponseTimeout(10000) // long polling timeout
.build();
// subscribe to an external task topic as specified in the process
client.subscribe("charge-card")
.lockDuration(1000) // the default lock duration is 20 seconds, but you can override this
.handler((externalTask, externalTaskService) -> {
// Put your business logic here
// Get a process variable
String item = (String) externalTask.getVariable("item");
Long amount = (Long) externalTask.getVariable("amount");
LOGGER.info("Charging credit card with an amount of '" + amount + "'€ for the item '" + item + "'...");
try {
Desktop.getDesktop().browse(new URI("https://docs.camunda.org/get-started/quick-start/complete"));
} catch (Exception e) {
e.printStackTrace();
}
// Complete the task
externalTaskService.complete(externalTask);
})
.open();
}
SpringBoot ExternalTaskHandler示例代码:
@ExternalTaskSubscription(topicName = "payment-biz")
@Bean
public ExternalTaskHandler paymentBizHandler() {
return (externalTask, externalTaskService) -> {
//获取流程变量
String productName = externalTask.getVariable(PRODUCT_NAME);
Double productPrice = externalTask.getVariable(PRODUCT_PRICE);
String paymentAssignee = externalTask.getVariable(PAYMENT_ASSIGNEE);
Double productDiscountPrice = externalTask.getVariable(PRODUCT_DISCOUNT_PRICE);
log.info("The External Task {} has been checked!", externalTask.getId());
//完成任务
externalTaskService.complete(externalTask);
//完成任务且添加流程变量
//externalTaskService.complete(externalTask, Variables.putValueTyped("creditScores", creditScoresObject));
log.info("Payment SUCCESS - paymentAssignee={}, productName={}, productPrice={}, productDiscountPrice={}",
paymentAssignee,
productName,
productPrice,
productDiscountPrice);
//任务失败
//externalTaskService.handleFailure(
// externalTask,
// "errorMsg",
// "errorDetails",
// 1,
// 10L * 60L * 1000L);
};
}
调用web服务(REST、SOAP)
connector
- 支持Connector ID:http-connector
,soap-http-connector
关于使用connector的细节可参见:
https://docs.camunda.org/manual/latest/user-guide/process-engine/connectors
https://github.com/camunda/camunda-bpm-examples/tree/master/servicetask
外部任务优先级Priorities
External Task Priority,数字类型,值越大优先级越大,越先被外部worker查询到
List<LockedExternalTask> tasks =
//第三个参数为true,则表示启用安装优先级查询external task
externalTaskService.fetchAndLock(10, "externalWorkerId", true)
.topic("AddressValidation", 60L * 1000L)
.topic("ShipmentScheduling", 120L * 1000L)
.execute();
for (LockedExternalTask task : tasks) {
String topic = task.getTopicName();
// work on task for that topic
...
}
Field注入
其中Java Class、Delegate Expression、External实现方式,支持Field注入,
可通过Service Task对应的标签页Field Injections进行设置,
Camunda官方建议提供Setter方法支持Filed注入(兼容CDI)。
JavaDelegate示例代码:
//实现JavaDelegate(每次执行task都会新建对象实例)
public class ReverseStringsFieldInjected implements JavaDelegate {
//定义待注入的Field(Camunda建议配置Setter方法进行注入)
private Expression text1;
private Expression text2;
public void execute(DelegateExecution execution) {
//获取流程变量
String var = (String) execution.getVariable("input");
//处理逻辑实现
var = var.toUpperCase();
//重写 或 设置新的流程变量
execution.setVariable("input", var);
//获取注入的Field
String value1 = (String) text1.getValue(execution);
execution.setVariable("var1", new StringBuffer(value1).reverse().toString());
String value2 = (String) text2.getValue(execution);
execution.setVariable("var2", new StringBuffer(value2).reverse().toString());
}
//Camunda官方建议提供Setter方法支持Filed注入(兼容CDI)
public void setText1(Expression text1) {
this.text1 = text1;
}
public void setText2(Expression text2) {
this.text2 = text2;
}
}
注:
在SpringBoot环境慎用Field注入,由于SpringBoot Bean默认Singleton(单例),
并发访问可能存在field覆盖冲突。
1.2.2 Business Rule Task - 业务规则任务(可绑定DMN)
如下图可通过Business Rule Task绑定单独的DMN文件中的Decision决策表格,
以此实现根据不同输入产生不同输出的效果,
DMN输出结果需通过Result Variable等设置进行映射,
映射之后才可供后续流程元素进行访问。
例如如上图中的Decision Ref中product-discount-rule即对应DMN模型中的Decision.id,
如下即为具体决策表格内容,关于DMN的介绍可参见本文第2章:2. DMN介绍
。
1.2.3 User Task - 委托给具体用户处理的任务,需要对应的用户进行处理
参考:https://docs.camunda.org/manual/latest/reference/bpmn20/tasks/user-task
- assignee - 受托人(处理任务的人)
- candidate users - 候选人(处理任务的多个人,由逗号分隔)
- candidate groups - 候选组织(处理任务的多个组织,由逗号分隔)
- 注: assignee、candidate users、candidate groups均支持表达式,例如:
${bizUser}
- 支持流程变量bizUser作为受托人${ldapService.findAllSales()}
- 支持Spring/CDI bean的方法调用,返回结果为String或Collection<String>,下同${ldapService.findManagerForEmployee(emp)}
- 支持Spring/CDI bean的方法调用,参数emp即对应流程变量emp
- due to date - 到期时间
- follow up date - 跟进时间
1.2.4 Input/Output Paramerters
参考:https://docs.camunda.org/manual/latest/user-guide/process-engine/variables/#input-output-variable-mapping
camunda提供输入、输出参数的转换映射,
即可通过表达式(解析之前已存在的流程变量并转换为新的变量)
来提取变量,
- Variable Assignment Type=String orExpression
- Variable Assignment Value=具体的表达式
表达式示例如下:
- ${myVar}
- ${myResultMap.key1}
并将提取的值作为当前元素(如task)的:
输入参数(Input Parameters)
- 作为输入变量供当前活动使用- Local Variable Name - 输入变量名
输出参数(Output Parameters)
- 作为当前活动的输出变量- Process Variable Name - 输出变量名
- Process Variable Name - 输出变量名
1.3 Gateway - 网关
网关,即根据条件产生不同流程分支。
1.3.1 Exclusive Gateway - 排他网关(1入多出,仅1出执行,出带条件)
仅执行第一个条件为真的分支,
若均不为真,则报异常(可添加默认流)
顺序流分为条件流、默认流。
设置默认流参考:https://forum.camunda.org/t/gateway-default-flow/447/5
1.3.2 Parallel Gateway - 并行网关(多入多出,无条件)
并发执行(不评估条件)Fork或Join,
- Fork - 分支
- Join - 汇合(在其他分支都执行成功后统一汇合)
1.3.3 Inclusive Gateway - 包含网关(多入多出,出带条件)
可看出Exclusive和Paralles网关的结合体,
- Fork - 排他网关带条件多分支,并行执行
- Join - 并行网关汇合
1.3.4 Event Based Gateway - 基于事件的网关(1入多出,仅一出执行,出带事件)
通过捕获事件来决定执行分支(区别于排他网关根据条件),
仅执行第一个发生事件对应的分支,
且每个序列流分支均需连接到一个中间捕获事件。
1.4 Pool/Lane - 组织/角色
Pool - 池,表示组织、公司、系统
Lane - 泳道,表示参与者、角色
1.5 子流程
1.5.1 嵌入式子流程
- 与主流程元素存在顺序流连接
- 仅以
唯一空白开始事件
开始 - 嵌入子流程为
实线边框
(区别于事件子流程为虚线)
1.5.2 事件子流程
由事件触发的子流程
- 与主流程元素不存在顺序流连接
- 支持
消息、定时器、信号、错误、补偿等
开始事件(不支持空白开始事件) - 事件子流程为
虚线边框
(区别于嵌入事件子流程为实线) - 支持
中断、非中断
事件子流程- 中断(开始事件为实线边框) - 取消当前任务执行,转而执行事件子流程
- 非中断(开始事件为虚线边框) - 继续执行当前任务,进而并发执行事件子流程
1.5.3 事务子流程
嵌入式子流程,对一组活动进行分组,作为一个整体的所有活动同时成功或失败(终止和取消)。
- 与主流程元素存在顺序流连接
- 事务子流程为
双实线边框
- 可由事务子流程内的
取消结束事件
触发事务子流程回滚(触发子流程内的补偿边界事件
的执行) - 亦可由事务子流程(成功执行)之后的
中间抛出补偿事件
触发事务子流程回滚(触发子流程内的补偿边界事件
的执行) - 可在事务子流程上添加
取消边界事件
来接管取消后的事务子流程的流程走向 - 事务子流程内的
错误事件
若未在事务子流程的作用域内被捕获,那么事务子流程被终止,且不会执行补偿。
1.5.4 调用活动
调用独立部署的流程(作为当前流程的子流程执行),
在调用活动执行完成后,再继续执行当前流程。
2. DMN介绍
定义规则模型,可与BPMN中Business Rule Task通过Decision.id进行绑定。
简单理解:类似可视化的Drools编辑器(规则引擎),
底层默认使用FEEL表达式。
2.1 Decision - 决策
2.1.1 Decision Table(id, name) - 决策表格
如下图新建Decision,默认类型即为Decision Table,可通过扳手图标🔧切换类型。
点击Decision左上角的蓝色图标,即可进入决策表编辑界面,决策表各组成部分如下图:
Input - 输入(1个或多个)
- ID - 唯一ID
- label - 显示标签
- Expression - 提取输入值的表达式(根据Process Variables生成input值)
- 变量名
- 包含变量名的表达式
- Expression Language - 表达式语言类型(默认FEEL)
- type - 提取出的输入值的类型(非必须,建议添加,可避免非法输入类型转换)
- Input Variable - 提取出的输入值的存储变量名(默认cellInput),可用于后续规则中inputEntry表达式中
Output - 输出(1个或多个)
- ID
- Label - 显示标签
- Ouput Name - 输出结果的存储变量名(后续流程中可通过变量名称引用此变量)
- Type - 输出结果的类型(非必须,建议添加,可避免非法输出类型转换)
Rule - 规则,一个决策表格由多行规则组成
- Input Entry(Condition) - 规则的输入条件
- 空的InputEntry表示永远为真(永远满足)
- Output Entry(Conclusion) - 规则的输出结果
- InputEntry和OutputEntry支持表达式
- 当同一行的(多个)InputEntry都满足时则输出对应的(多个)OutputEntry
Hit Policy - 规则匹配策略
Hit Policy | 说明 | 示例 |
---|---|---|
Unique | 仅有一条规则满足 或者 都不满足,若仅有一条满足则输出 | |
Any | 任何一条规则满足则输出(允许多条规则同时满足,但这些规则必须有相同的输出OutputEntry) | |
First | 第一条满足则输出(允许多条规则同时满足,但仅第一条会被输出) | |
Rule order | 允许多条规则满足,则按照规则顺序全部输出 | |
Collect | 允许多条规则满足,则按照任意(不确定)顺序全部输出 Collect策略允许指定聚合器Aggregators(仅输出一条)
|
注:
DMN中表达式(默认使用FEEL)支持范围:
- Input Expression: sets the input value for an input column of the decision table
- Input Entry: used to determine if a rule of the decision table is applicable
- Output Entry: returns a value which is added to the output of a matched rule of the decision table
- Literal Expression: used to determine the value of a decision literal expression
2.1.2 Literal Expression - 字面量表达式
如下图新建Literal Expression,
即根据表达式提取决策结果,如调用bean方法获取结果。
3. Form介绍
即自定义表单,用于与BPMN Task进行绑定。
User Task Forms类型
参考:
https://docs.camunda.org/manual/latest/user-guide/task-forms
注: BPMN2.0中仅 StartEvent和UserTask支持链接form
FormType:
- Embeeded Task Forms
内嵌HTML或JS表单到TaskList中 - Camunda Forms
集成Camunda Modeler创建的Form,支持Form Key和Form Ref格式引用 - External Task Forms
链接自定义应用,Form不内嵌在TaskList中展示
Form Key
FORM-TYPE:LOCATION:FORM.NAME
- FORM-TYPE
- camunda-forms
- embedded
- LOCATION
-
demployment
作为部署的一部分,在通过Modeler部署时需通过附件一起上传
-
app
form文件集成在war应用中,目录需放在 src/main/webapp/forms
-
- FORM.NAME
- FORM-TYPE
例如
- camunda-forms:deployment:payment.form
- embedded:app:forms/FORM_NAME.html
Form Ref
仅支持绑定Camunda Form(Modeler创建)
- camunda:formRef - key
- camunda:formRefBinding
- deployment - 位于同一deployment
- latested - 最新版本
- version - 指定版本,通过camunda:formRefVersion设置版本
- camunda:formRefVersion
更多: