最近进行的一次技术选型(工作流引擎)及相关知识介绍

2022-03-28 19:00:24 浏览数 (1)

前言

最近有个新项目,需要实现类似工作流引擎的效果,如果不知道是啥,看完本文就懂了。公司内其实也有些自研的,可能就是不像开源的这些那样,还支持这个那个规范,都是基于需求定制开发的,扩展性稍微差点。所以,这次其实几个同事,分工调研了几个开源的和公司内的,开源的包括activiti、flowable、camunda,我这边主要调研了flowable、camunda,同事调研了activiti和公司内部的。

最终看下来,我们的需求,其实不需要用到这么复杂的开源框架,公司内的一个框架更符合一些。开源的框架,会建很多表,表也不符合公司内的建表规范,所以还需要阅读源码,去改造之类的,也比较麻烦。会引入很多jar包,总体来说,还是比较重。

文末有几个引擎的对比,大家有兴趣可以看看,也可以加我微信和我探讨(只花了两天时间,可能也了解得也比较粗略)。

最终来说,技术还是服务于需求的,不是因为框架牛逼就硬上,合适最重要。

先说说uml和omg

学过软件工程的同学,肯定知道uml,全称Unified Modeling Language,统一建模语言。建模,为啥要建模,因为软件研发过程较为抽象,一个需求来了,肯定要先分析分析,建个模(通俗就是:画个图),但是每个人画出来的图都不一样,比如uml里用一个小人来表示用户,有的人就不愿意用小人。所以,为了业界内人士沟通交流更方便,就定义了一套标准,每种图应该怎么画,包含了哪些部分。

比如uml包含了如下类型的图,每种图里,都有固定的图例来代表固定的意思(仅部分):

ok,大家明白了uml,我再说说omg是啥,omg是个标准化组织,致力于提出uml这样类似的标准,和业界的公司进行讨论交流,各公司的人、学术界的人、omg的人,共同讨论,提出一个大家都能接受的方案,然后大家就按照这个标准来进行实现。

世界上的标准化机构很多,omg手里拿出来的,现在广为使用的,被ios采纳的,有如下几个。

主要就是uml和bpmn,注意,没有xml(图里右上角那个是xmi)。

bpmn

图形规范

bpmn(Business Process Model And Notation)是啥,也是一种建模语言,和uml类似,就是规定:xxx,用这个来表示,yyy,用那个来表示。

bpmn主要是对工作流进行建模,大家公司里的oa系统,基本就是工作流的典范,比如下面这样一个用户请假流程,就是遵循bpmn规范的。

这个图里,哪些地方用空心圆,哪些地方是矩形,哪些地方用菱形,都是有讲究,这就是bpmn规范里定义的。

xml规范

bpmn对图形有规范,对图形背后的存储格式也有定义,这个图,最终会转化为一份xml,这份xml也是遵循特定的schema的。

上图这个xml,就是遵循omg官方的schema,里面最外层是一个process元素,里面的元素则只能是startEvent、sequenceFlow等。

这样标准化了之后,业界各个厂商,就可以各自开发一套实现,只要这套实现,最终能生成上面这样的xml,那就是符合bpmn的,拿这份bpmn文件到其他厂商那里,其他厂商的程序也能正确解析该文件,因此就实现了互联互通,这就是标准的力量。

bpmn版本历史

主要是4个版本,现在业界基本都是基于2010年的最新版本2.0进行实现,也就是bpmn2.0

VERSION

ADOPTION DATE

URL

2.0

十二月 2010

https://www.omg.org/spec/BPMN/2.0/

1.2

一月 2009

https://www.omg.org/spec/BPMN/1.2/

1.1

一月 2008

https://www.omg.org/spec/BPMN/1.1/

1.0

三月 2007

https://www.omg.org/spec/BPMN/1.0/

bpmn 2.0的业界实现

实现还是挺多的,近10多个。现在大家比较用得多的,还是红框的几个,Activiti、Camunda、Flowable、jBPM。

这些实现,互相有些关系,就像log4j的维护人后来又创建了logback一样。

目前主要就是在 Camunda/flowable 6/ activiti里面去选择。

flowable 内嵌模式快速了解

创建maven工程(文末有代码)

如果一上来,直接就开始比较各框架的差异,大家由于对其中任意一个都不了解,所以也没法参照。这里先讲一下flowable框架(目前最先了解这个框架)。

flowable 引擎,支持两种运行模式,一种是内嵌到业务服务中,咱们先讲这种。

先建一个普通的maven工程,加入flowable引擎的依赖以及h2内嵌数据库的依赖(正式项目会换成mysql等持久化数据库)

代码语言:javascript复制
    <!-- https://mvnrepository.com/artifact/org.flowable/flowable-engine -->
    <dependency>
      <groupId>org.flowable</groupId>
      <artifactId>flowable-engine</artifactId>
      <version>6.7.2</version>
    </dependency>
    <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
      <version>1.4.192</version>
    </dependency>

创建流程引擎实例

以下,先创建一个引擎实例出来:

代码语言:javascript复制

import org.flowable.engine.ProcessEngine;
import org.flowable.engine.ProcessEngineConfiguration;
import org.flowable.engine.impl.cfg.StandaloneProcessEngineConfiguration;

public class HolidayRequest {

  public static void main(String[] args) {
    ProcessEngineConfiguration cfg = new StandaloneProcessEngineConfiguration()
      .setJdbcUrl("jdbc:h2:mem:flowable;DB_CLOSE_DELAY=-1")
      .setJdbcUsername("sa")
      .setJdbcPassword("")
      .setJdbcDriver("org.h2.Driver")
      .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);

    ProcessEngine processEngine = cfg.buildProcessEngine();
  }

}

接下来,我们就可以往这个引擎实例上部署一个流程xml。比如,假设我们最终想建立一个员工请假流程,那么,我们可以通过各种办法(如flowable自带的web-ui拖拽的方式或手动创建xml等),来建立一个下面这样的,符合bpmn2.0规范的流程定义xml(holiday-request.bpmn20.xml)。

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:activiti="http://activiti.org/bpmn"
             typeLanguage="http://www.w3.org/2001/XMLSchema"
             expressionLanguage="http://www.w3.org/1999/XPath"
             targetNamespace="http://www.flowable.org/processdef">

    <process id="holidayRequest" name="Holiday Request" isExecutable="true">

        <startEvent id="startEvent"/>
        <sequenceFlow sourceRef="startEvent" targetRef="approveTask"/>

<!--        <userTask id="approveTask" name="Approve or reject request"/>-->
        <userTask id="approveTask" name="Approve or reject request" activiti:candidateGroups="managers"/>

        <sequenceFlow sourceRef="approveTask" targetRef="decision"/>

        <exclusiveGateway id="decision"/>
        <sequenceFlow sourceRef="decision" targetRef="externalSystemCall">
            <conditionExpression xsi:type="tFormalExpression">
                <![CDATA[
          ${approved}
        ]]>
            </conditionExpression>
        </sequenceFlow>
        <sequenceFlow sourceRef="decision" targetRef="sendRejectionMail">
            <conditionExpression xsi:type="tFormalExpression">
                <![CDATA[
          ${!approved}
        ]]>
            </conditionExpression>
        </sequenceFlow>

        <serviceTask id="externalSystemCall" name="Enter holidays in external system"
                     activiti:class="org.example.CallExternalSystemDelegate"/>
        <sequenceFlow sourceRef="externalSystemCall" targetRef="holidayApprovedTask"/>

<!--        <userTask id="holidayApprovedTask" name="Holiday approved"/>-->
        <userTask id="holidayApprovedTask" name="Holiday approved" activiti:assignee="${employee}"/>

        <sequenceFlow sourceRef="holidayApprovedTask" targetRef="approveEnd"/>

        <serviceTask id="sendRejectionMail" name="Send out rejection email"
                     activiti:class="org.flowable.SendRejectionMail"/>
        <sequenceFlow sourceRef="sendRejectionMail" targetRef="rejectEnd"/>

        <endEvent id="approveEnd"/>

        <endEvent id="rejectEnd"/>

    </process>

</definitions>

这个xml是不是比较抽象?这个xml就类似于一种标准格式,就像java的class文件一样,实现跨平台的效果。

该xml对应的流程图如下:

接下来,我们就把这个文件,传给流程引擎,让它基于该文件,创建一个工作流。

代码语言:javascript复制
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deployment = repositoryService.createDeployment()
  .addClasspathResource("holiday-request.bpmn20.xml")
  .deploy();

创建后,实际就写到内存数据库h2了,此时,我们还可以把它查出来

代码语言:javascript复制
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
  .deploymentId(deployment.getId())
  .singleResult();
System.out.println("Found process definition : "   processDefinition.getName());

创建工作流实例

工作流实例,一开始需要一些输入参数,员工不是需要请假吗,我们就需要:员工姓名、请假天数、事由等。

代码语言:javascript复制
Scanner scanner= new Scanner(System.in);

System.out.println("Who are you?");
String employee = scanner.nextLine();

System.out.println("How many holidays do you want to request?");
Integer nrOfHolidays = Integer.valueOf(scanner.nextLine());

System.out.println("Why do you need them?");
String description = scanner.nextLine();


RuntimeService runtimeService = processEngine.getRuntimeService();

Map<String, Object> variables = new HashMap<String, Object>();
variables.put("employee", employee);
variables.put("nrOfHolidays", nrOfHolidays);
variables.put("description", description);

ok,参数准备好了,就准备传给工作流了:

代码语言:javascript复制
ProcessInstance processInstance =
    runtimeService.startProcessInstanceByKey("holidayRequest", variables);

此时,就会根据流程定义里的:

代码语言:javascript复制
<userTask id="approveTask" name="Approve or reject request" activiti:candidateGroups="managers"/>

创建一个任务,任务有个标签,就是candidate group,这里是manager,可以猜得出,是给manager建了个审批任务。

manager查询并审批任务

以下,基于manager查询任务:

代码语言:javascript复制
TaskService taskService = processEngine.getTaskService();
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("managers").list();
System.out.println("You have "   tasks.size()   " tasks:");
for (int i=0; i<tasks.size(); i  ) {
  System.out.println((i 1)   ") "   tasks.get(i).getName());
}

我们可把任务打印出来看看:

代码语言:javascript复制
System.out.println("Which task would you like to complete?");
int taskIndex = Integer.valueOf(scanner.nextLine());
Task task = tasks.get(taskIndex - 1);
Map<String, Object> processVariables = taskService.getVariables(task.getId());
System.out.println(processVariables.get("employee")   " wants "  
    processVariables.get("nrOfHolidays")   " of holidays. Do you approve this?");

审批任务:

代码语言:javascript复制
boolean approved = scanner.nextLine().toLowerCase().equals("y");
variables = new HashMap<String, Object>();
variables.put("approved", approved);
taskService.complete(task.getId(), variables);

这里就是把全局变量 approved,设为了true,然后提交给引擎。引擎就会根据这里的变量为true还是false,走不同分支。对应了:

回调用户代码--用户开始休假

上面审批后,就会进入下一个节点:休假。

代码语言:javascript复制
<serviceTask id="externalSystemCall" name="Enter holidays in external system"
             activiti:class="org.example.CallExternalSystemDelegate"/>

这里有个class,就是需要我们自己实现的。

然后,基本流程就自己走完了。

flowable rest-api模式

简介

上面那种,是其作为一个jar,内嵌到我们的程序里,创建引擎对下。由我们业务程序去驱动引擎的运行。引擎和业务代码在同一个进程。

其实,flowable也可以作为一个独立服务运行,提供rest-api出来,这样的话,非java语言的开发者也可以使用该引擎了。

这个只需要我们下载官方的zip包,里面有个rest的war包,我们丢到tomcat里运行。

上传工作流定义xml文件,部署工作流

如果要实现上面java-api那样的功能,我们就需要调接口来实现

下面就开始启动工作流:

其他接口就不一一展示了。可以参考文档。

flowable-ui,通过web ui进行流程xml建模

上面手工建立xml,还是比较累的,我们可以通过其提供的web ui来建模,省点力气。(不过也不是很好用,各种名词比较费解,大家可能还是要自己做一套前端界面,调用自己的接口,来生成一个xml文件)

上面的rest那一节,tomcat里就部署了一个flowable-ui的。

就可以通过下面这样的方式来建模。

其他方面

活跃程度:activiti是最活跃的,activiti (非常活跃,一天一个alpha版本)> camunda(一个月一个alpha版本) > flowable(几个月或半年一个版本) 依赖:会引入37个jar包,当前最新的6.7.2版本 mysql:核心建表语句为7张,历史表5张;程序运行后,结果有47张表,具体原因暂时没去研究 持久层框架:写mysql表时,使用mybatis

引擎对比

总结

demo代码:https://gitee.com/ckl111/all-simple-demo-in-blog/tree/master/flowable-test

0 人点赞