工作流可以实现业务流程的自动化,用户可以自己定义工作流程,通过流程来把常用的任务组织起来,而无需在程序中固化流程。这也符合当今微服务,低代码开发的趋势。
Camunda是目前主流的一个工作流平台,遵循业界的标准(BPMN, DMN...),国内的很多低代码工作流平台也是基于Camunda来做进一步的定制开发的。
Camunda目前有7和8两个版本,其中最新的8版本是采用SAAS的方式来提供服务,也可以基于Kubernets来部署在云上。但是8版本虽然可以免费使用和更改,但是如果用于商业用途是受限制的。7版本分为社区版和企业版,社区版可以用于商业用途,因此我也是基于7版本来进行研究。
Camunda的核心包括了工作流引擎和建模软件,整个流程可以参加下图:
从以上流程可以看到,工作流的开发人员定义好工作流之后,导出为文件,部署到工作流引擎。引擎负责管理整个工作流的执行,发布工作流中的子任务,任务节点查询引擎的任务列表,执行任务并返回结果给引擎。引擎会把工作流进程的状态持久化到数据库。用户可以通过引擎提供的API,查询工作流的相关状况,以及对用户角色进行管理等。
工作流引擎和任务节点支持几种部署模式,常见的一种分布式集群部署的架构如下:
根据业务量的需求,可以部署多个工作流引擎服务器,通过Nginx实现负载均衡。部署多个独立的任务节点,每个节点都会连接到引擎获取待处理的任务,并执行。这种方式可以灵活的进行水平扩展,满足业务扩展的需求。
Camunda提供了一个工作流建模的桌面版软件,这个软件也是基于bpmn.io开源软件基础上做定制的。我们可以根据自己的需要进行自己的额外定制开发,例如添加自定义的属性面板,汉化界面等等。
以下我将以一个简单的例子来介绍如何使用。
在docs.camunda.org官网上有介绍。具体有几种方式,既可以直接使用已经编译好的程序运行,或者嵌入到自己的JAVA应用或WEB容器中运行。
这里我选用最简单的方式,直接运行docker镜像
docker run -d --name camunda -p 8080:8080 camunda/camunda-bpm-platform:run-latest
运行之后,访问本地8080端口即可进入到Camunda的管理界面。
下载Camunda的Modeler软件,定义一个工作流,如以下:
可以看到这个工作流定义一个开始节点(这个节点可以配置不同的开始条件,例如API调用触发,定时触发,或者事件触发等,这里是由API调用触发),若干个任务节点(任务节点包括不同类型,如常见的Service Task, User Task等。User Task是需要用户干预的,这里只用到了Service Task),还有一个网关,用于根据上一个任务的输出数据进行判断,执行不同的路径。最后还有一个结束节点,表示整个流程结束。
这里面的第一个Service Task Check Alarm,点击设置属性,其中的Implementation里面的Type设置为External,Topic设置为check-alarm。表示这个任务由一个外部的任务节点执行,引擎会把任务发送到一个check-alarm的主题,任务节点通过订阅这个主题获取需要处理的任务。
网关有两个执行路径,一个是判断Check Alarm任务的alarmid是不是等于123,如果是就执行下一个任务Get Alarm,另一个路径则是直接到结束节点。
点击每个路径,在condition里面设置判断表达式,这里设置${alarmid="123"}或者${alarmid!="123"}
第二个Service Task Get Alarm,设置属性Tpe为Connector,Connector ID设置为http-connector,表示这个服务将调用一个http接口。在Connector inputs里面添加三个输入,分别是url, headers, method。其中url填入要调用的http接口地址,注意如果这个地址需要传入参数的话,需要加上${},例如这里我需要传入上一个任务返回的alarmid参数,因此需要这样写http://ip:port/getalarm?alarmid=${alarmid}。method设置为GET,headers的assignment type设置为Map,然后添加一个条目是Accept,值是application/json。
工作流定义完成之后,可以调用REST API来部署,或者直接点击Modeler软件下方的Deploy按钮,部署到工作流引擎。
首先我们实现一个任务程序,处理上面的第一个任务Check Alarm。这是一个external类型的业务,我们可以基于Java,python或者自己喜欢的语言来编写一个任务节点客户端代码。这里以Java为例。
package com.example;
import java.util.logging.Logger;
import org.camunda.bpm.engine.variable.Variables;
import org.camunda.bpm.engine.variable.value.ObjectValue;
import java.util.Collections;
import org.camunda.bpm.client.ExternalTaskClient;
public class CheckAlarmWorker {
private final static Logger LOGGER = Logger.getLogger(CheckAlarmWorker.class.getName());
public static void main(String[] args) {
ExternalTaskClient client = ExternalTaskClient.create()
.baseUrl("http://172.22.225.152:8080/engine-rest")
.asyncResponseTimeout(10000) // long polling timeout
.build();
// subscribe to an external task topic as specified in the process
client.subscribe("check-alarm")
.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 networktype = externalTask.getVariable("type");
LOGGER.info("Query alarm for the network type: '" + networktype);
ObjectValue alarmIdObject = Variables
.objectValue("123")
.create();
externalTaskService.complete(externalTask, Collections.singletonMap("alarmid", alarmIdObject));
})
.open();
}
}
对于第二个任务Get Alarm,我们只需要实现一个HTTP接口供调用即可,这里省略相关的代码。
之后我们运行这些任务程序,等待处理任务。
最后一步就是启动一个工作流实例,执行相应的任务。
这里采用调用REST接口的方式,例如访问以下的curl命令,这里key后面带入的是工作流的Processor ID,在body里面传入一个变量type,数值是amf
curl --location --request POST 'http://172.22.225.152:8080/engine-rest/process-definition/key/Process_1a3tj5v/start' \
--header 'Content-Type: application/json' \
--data-raw '{
"variables": {
"type": {
"value": "amf"
}
}
}'
启动实例之后,我们可以在Camunda管理界面查看实例运行情况,因为这个工作流很简单,实例一下子就运行完了。我们可以查看任务节点打印的日志,可以看到任务都已正常完成。
以上就是对于工作流的一个实际开发的例子,后续我将研究一下基于bpmn.io的代码,实现一个定制化的建模程序。