Flowable 初体验

最近需要交接同事手上的一些活,其中就涉及到了 Flowable 工作流引擎,那么本篇文章将记录我学习工作流引擎相关概念和知识。

背景

在工作中,我们会遇到各种各样的流程,如:报销流程、转正流程、请假流程、入职流程等等,一般实现整个流程就是引入状态机维护流程状态,这种实现方式简单、快,但长远来看,存在以下问题:

  • 流程与业务代码耦合,耦合性高。
  • 复用性极差,如果某个流程需要更改,需要重新开发代码,成本高。
  • 健壮性差,可能某个流程的审批人的流动,可能导致意外的错误,需要修改代码。

在这个背景下,我们顺势引出咱们要学习的东西,那就是工作流,接下来将介绍一些概念。

概念

BPM

Business Process Management,即业务流程管理,是一套达成企业各种业务环节整合的全面管理模式。

BPMN

业务流程模型和标记法(Business Process Model and Notation)是一套图形化表示法,用于以业务流程模型详细说明各种业务流程。它最初由业务流程管理倡议组织(BPMI, Business Process Management Initiative)开发。BPMI 于 2005 年与对象管理组织(OMG, Object Management Group)合并。2011 年 1 月 OMG 发布 2.0 版本,BPMN 2.0 为新的业务流程模型和标记法建立单一规范,对标记法、元模型和交换格式做出界定。目前主流的工作流引擎如:Activiti、Flowable、jBPM 都是基于 BPMN 2.0 规范开发流程。

Flowable

Flowable 是一个用 Java 编写的轻量级业务流程引擎。Flowable 流程引擎允许您部署 BPMN 2.0 流程定义(定义流程的行业 XML 标准)、为这些流程定义创建流程实例、运行查询、访问活动或历史流程实例及相关数据,以及更多其他功能。

Quick Start

创建一个流程引擎实例

接下将演示一个简单的请假流程来快速体验一下 Flowable 引擎。首先,我们先创建一个 Maven 工程,并在 pom .xml 文件中引入以下依赖:

xml 复制代码
<dependency>
  <groupId>org.flowable</groupId>
  <artifactId>flowable-engine</artifactId>
  <version>6.8.0</version>
</dependency>
<dependency>
  <groupId>com.h2database</groupId>
  <artifactId>h2</artifactId>
  <version>2.1.214</version>
</dependency>
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>2.0.7</version>
</dependency>
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-log4j12</artifactId>
  <version>2.0.7</version>
</dependency>

接着创建我们的主程序 LeaveProcess:

typescript 复制代码
package com.benjaminfan.flowable;

public class LeaveProcess {

    public static void main(String[] args) {
        
    }
}

我们要干的第一件事是获取一个 ProcessEngine 实例,这是一个线程安全对象,通常只需在应用程序中实例化一次(可以运用单例模式)。ProcessEngine 由 ProcessEngineConfiguration 实例创建,你可以对进程引擎进行配置和调整。通常情况下,ProcessEngineConfiguration 是通过配置 XML 文件创建的,也可以通过代码创建,接下来我们就使用代码进行创建:

java 复制代码
public class LeaveProcess {

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

        ProcessEngine processEngine = cfg.buildProcessEngine();
    }
}

为了快速体验,在这里我们使用的是内存数据库 H2,如果你想要持久化数据,请使用持久化数据库,如 MySQL,并切换对应的连接参数。

部署流程定义

接着我们需要构建一个员工的请假申请流程,那么 Flowable 引擎是希望我们使用 BPMN 2.0 的格式来定义的,这也是业界广泛接受和使用的标准。在 Flowable 术语中,称之为流程定义,流程定义定义了员工申请假期所涉及的多个步骤。

BPMN 2.0 是以 XML 的形式存储的,但也有可视化的部分:它定义了如何表示每种不同的步骤类型,以及如何将这些不同的步骤相互连接起来。因此,BPMN 2.0 标准,无论是对技术人员或是业务人员都很友好,便于双方理解,促进双方业务流程交流。

流程定义如下:

如上图,将整个请假流程已经清晰的描绘出来了,那我们详细说说里面的一些组成部分:

  • 请假肯定是需要请假人是谁、请假多久和请假事由这样的信息的,那么这些信息的输入可以作为流程中单独的第一步进行建模。最左边的圆圈称为起始时间,它是实例的起点。
  • 我们可以看到第一个矩形上有一个小人的标识,这是用户任务,是用户必须执行的流程步骤。在这种情况下,经理需要批准或拒绝请求。
  • 这个带 X 的菱形,称之为网关,那么根据经理的的决定,这个专属网关将会把流程实例路由到同意路径或不同意路径。
  • 如果同意请假,将记录一条员工请假记录到系统,然后再为原员工创建一个用户任务,通知他们。如果不同意请假,则会向员工发送一封电子邮件说明拒绝的理由。

通常,这种流程定义是通过可视化建模工具建模的,如 Flowable Designer 或 Flowable Modeler。在这里,我们使用 XML 的形式编写,与上面的流程定义图相对应的 BPMN 2.0 XML 如下所示:

ini 复制代码
<?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:xsd="http://www.w3.org/2001/XMLSchema"
             xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
             xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"
             xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI"
             xmlns:flowable="http://flowable.org/bpmn"
             typeLanguage="http://www.w3.org/2001/XMLSchema"
             expressionLanguage="http://www.w3.org/1999/XPath"
             targetNamespace="http://www.flowable.org/processdef">

    <process id="LeaveProcess" name="Leave Process" isExecutable="true">

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

        <userTask id="approveTask" name="批准或拒绝申请" flowable:candidateGroups="managers"/>
        <sequenceFlow sourceRef="approveTask" targetRef="decision"/>

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

        <serviceTask id="AddLeaveRecord" name="记录一条请假记录到系统"
                     flowable:class="com.benjaminfan.flowable.AddLeaveRecordDelegate"/>
        <sequenceFlow sourceRef="AddLeaveRecord" targetRef="leaveApprovedTask"/>

        <userTask id="leaveApprovedTask" name="请假批准" flowable:assignee="${employee}"/>
        <sequenceFlow sourceRef="leaveApprovedTask" targetRef="approveEnd"/>

        <serviceTask id="sendRejectionMail" name="发送拒绝请假邮件"
                     flowable:class="com.benjaminfan.flowable.SendRejectionMail"/>
        <sequenceFlow sourceRef="sendRejectionMail" targetRef="rejectEnd"/>

        <endEvent id="approveEnd"/>

        <endEvent id="rejectEnd"/>

    </process>

</definitions>

我们将这个 XML 文件命名为 leave-process.bpmn20.xml 保存到项目下的 src/main/resources 文件夹下。我们再来分析分析这个 XML 文件:

  • 所有图形信息都包含在 XML 中的 标签中。第 2 行至第 11 行中的内容,几乎所有流程定义中都会有这些内容,这是为了与 BPMN 2.0 标准规范完全兼容,这些都是模板式的东西。
ini 复制代码
<?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:xsd="http://www.w3.org/2001/XMLSchema"
             xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
             xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"
             xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI"
             xmlns:flowable="http://flowable.org/bpmn"
             typeLanguage="http://www.w3.org/2001/XMLSchema"
             expressionLanguage="http://www.w3.org/1999/XPath"
             targetNamespace="http://www.flowable.org/processdef">

</definitions>
  • 每个步骤(用 BPMN 2.0 术语来说,即 "活动")都有一个 id 属性,为其在 XML 文件中提供唯一的标识符。所有活动都可以设置一个可选的名称(name 属性),这可以增加可视化图表的可读性。
ini 复制代码
<startEvent id="startEvent"/>
<userTask id="approveTask" name="批准或拒绝申请" flowable:candidateGroups="managers"/>
  • 这些活动通过顺序流(标签)连接起来,顺序流在图表中是一个有方向的箭头。执行流程实例时,将按照顺序流从开始事件流向下一个活动。
ini 复制代码
<sequenceFlow sourceRef="approveTask" targetRef="decision"/>
  • 带 X 的菱形叫 exclusive gateway,当进程实例执行到该网关时,将对条件进行判断,选择第一个解析为 true 的条件,XML 里写成表达式的条件是 <math xmlns="http://www.w3.org/1998/Math/MathML"> a p p r o v e d ,是 {approved},是 </math>approved,是{approved == true} 的简写。变量 "approved" 被称为流程变量,流程变量是与流程实例一起存储的持久性数据,可以在流程实例的生命周期内使用。
xml 复制代码
<exclusiveGateway id="decision"/>
<sequenceFlow sourceRef="decision" targetRef="AddLeaveRecord">
    <conditionExpression xsi:type="tFormalExpression">
        <![CDATA[${approved}]]>
    </conditionExpression>
</sequenceFlow>
<sequenceFlow sourceRef="decision" targetRef="sendRejectionMail">
    <conditionExpression xsi:type="tFormalExpression">
        <![CDATA[${!approved}]]>
    </conditionExpression>
</sequenceFlow>
  • 我们需要将第一个任务分配给经理组,第二个用户任务分配给请假人,使用 candidateGroups 属性,对于请假人,我们并没有使用 "managers" 这样的静态值,而是基于进程实例启动时传递的进程变量的动态赋值。
ini 复制代码
<userTask id="approveTask" name="批准或拒绝申请" flowable:candidateGroups="managers"/>
<userTask id="leaveApprovedTask" name="请假批准" flowable:assignee="${employee}"/>

现在我们定义好了 BPMN 2.0 XML 文件,接下来就需要将它部署到引擎中。我们需要使用 RepositoryService 将流程定义部署到 Flowable 引擎,它可以从 ProcessEngine 对象中获取。使用 RepositoryService,通过传递 XML 文件的位置并调用 deploy() 方法来执行:

ini 复制代码
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deployment = repositoryService.createDeployment()
        .addClasspathResource("leave-process.bpmn20.xml")
        .deploy();

启动进程实例

我们已经将流程定义部署到流程引擎中,现在可以来启动流程实例了,上面我们提到了需要一些起始的输入数据来启动流程实例,这里我们就用最简单的方法是获取:

ini 复制代码
Scanner scanner= new Scanner(System.in);

System.out.println("请假人:");
String employee = scanner.nextLine();

System.out.println("请多少天假:");
Integer days = Integer.valueOf(scanner.nextLine());

System.out.println("请假事由:");
String description = scanner.nextLine();

我们可以通过 RuntimeService 启动一个进程实例,初始输入数据用 Map 保存并传递,其中 key 是标识符,稍后将用于检索变量。进程实例使用 key 启动,该 key 与 BPMN 2.0 XML 文件中设置的 id 属性相匹配。

arduino 复制代码
<process id="leaveProcess" name="Leave Process" isExecutable="true"></process>

当进程实例启动时,会创建一个执行程序并将其置于启动事件中。从这里开始,该执行将按照顺序来到用户任务,然后等待管理者的批准,并执行用户任务行为。该行为将在数据库中创建一个任务,可以通过查询找到该任务。用户任务处于等待状态,引擎将停止执行任何进一步的操作,并返回 API 调用。

查询并完成任务

我们通过 TaskService 创建一个 TaskQuery,并只查询经理组的任务列表:

ini 复制代码
TaskService taskService = processEngine.getTaskService();
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("managers").list();
System.out.println("你有 " + tasks.size() + " 个待办事项");
for (int i=0; i<tasks.size(); i++) {
    System.out.println((i+1) + ") " + tasks.get(i).getName());
}

利用任务标识符,我们可以获取具体的进程实例变量,并在显示出来:

dart 复制代码
System.out.println("请选择要处理的事项:");
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") + ",想要请 " +
        processVariables.get("days") + " 天假,你同意吗?同意(Y),不同意(N)");

接着经理就可以选择是否同意请假,选择完以后,意味着经理完成了任务,任务完成时会传递一个变量:

ini 复制代码
boolean approved = scanner.nextLine().toLowerCase().equals("y");
variables = new HashMap<String, Object>();
variables.put("approved", approved);
if (!approved) {
System.out.println("拒绝的理由:");
    String rejectReason = scanner.nextLine();
    variables.put("rejectReason", rejectReason);
}
taskService.complete(task.getId(), variables);

现在任务已完成,并根据 "approved" 流程变量选择网关的两条路径之一。

编写 JavaDelegate

接下来,我们来实现请假同意或不同意后将执行的自动逻辑。在 BPMN 2.0 XML 中,这是一项服务任务:

ini 复制代码
<serviceTask id="addLeaveRecord" name="记录一条请假记录到系统"
                     flowable:class="com.benjaminfan.flowable.AddLeaveRecordDelegate"/>

<serviceTask id="sendRejectionMail" name="发送拒绝请假邮件"
                     flowable:class="com.benjaminfan.flowable.SendRejectionMail"/>

创建两个 Java 类,分别命名为 AddLeaveRecordDelegate 和 SendRejectionMail,并实现 org.flowable.engine.delegate.JavaDelegate 接口:

java 复制代码
package com.benjaminfan.flowable;

import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;

public class AddLeaveRecordDelegate implements JavaDelegate {

    @Override
    public void execute(DelegateExecution delegateExecution) {
        System.out.println("新增一条请假记录,请假人:"
                + delegateExecution.getVariable("employee"));
    }

}
java 复制代码
package com.benjaminfan.flowable;

import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;

public class SendRejectionMail implements JavaDelegate {

    @Override
    public void execute(DelegateExecution delegateExecution) {
        System.out.println("发送拒绝请假邮件... 收信人:" + delegateExecution.getVariable("employee")
                + ",拒绝理由:" + delegateExecution.getVariable("rejectReason"));
    }
}

当执行到达服务任务时,BPMN 2.0 XML 中引用的类将被实例化并调用。我们可以运行项目,在控制台输入信息试试看:

作为经理,选择你要完成的事项,并处理:

当我们回车完成任务以后,我们可以看到控制台的输出:

至此,我们的一个快速入门就已经完成啦。完整的 demo 代码放在这里:demos/flowable-quick-start-demo at master · fan-zibang/demos

总结

我们可以来简单罗列一下不使用工作流引擎实现这个请假审批流程的步骤:

  1. 创建一个请假表,字段有 id、请假人、审批人、请假多久、请假理由、状态、是否同意、拒绝理由......
  2. 员工填写在线表单,发送申请请求,创建一条数据库记录,状态为员工已提交,组长待审批
  3. 组长查看任务列表,对请假进行审批,发生审批请求,更新数据库记录的状态为组长已审批,如果是同意就将字段同意更新为同意否则不同意,不同意还需要将拒绝理由填上

看上去整个过程实现起来很简单,但是如果我们的流程复杂起来就不一样了,后面领导说,如果三天以上的请假,组长审批完之后,还需要部门经理职位来进行二轮批准,这样你就不得不需要修改你的代码,还需要再维护多一个"部门经理已审批"的状态,甚至你可能还需要修改之前定义的"组长已审批"的状态为"组长已审批,部门经理待审批",而且之前在数据库已经存在的三天以上但缺少部门经理审批的数据,你还需要另外处理,不然可能会导致你的程序有意想不到的错误,如果后面请假一个月,还要增加一个审批流程,那可想而知,随着流程不断的增加,你的流程代码和要维护的状态可能变得难以维护。但是在使用工作流引擎下,我们只需要去修改流程定义(如下图),然后新增少量的部门经理处理的代码就能完成改造,可能我觉得例子不能很好说明工作流引擎的优点,大家可以自己动手实现几个小例子感受一下。

相关推荐
LunarCod11 天前
WorkFlow源码剖析——Communicator之TCPServer(下)
开源·workflow·c/c++·网络框架·异步编程·高性能高并发
LunarCod15 天前
WorkFlow源码剖析——Communicator之TCPServer(中)
后端·workflow·c/c++·网络框架·源码剖析·高性能高并发
昵称为空C25 天前
表达式引擎aviatorscript简单案例
后端·工作流引擎
MiyueFE1 个月前
02-源码篇1:Injector 依赖注入模式的实现
工作流引擎·bpmn-js
NLP工程化4 个月前
dify/api/models/workflow.py文件中的数据表
workflow·dify
小崔爱读书5 个月前
普元EOS学习笔记-低开实现图书的增删改查
java·workflow·工作流·low-code·eos·高低开
此人未设置昵称5 个月前
【ai_agent】从零写一个agent框架(二)如何让一个workflow/agent跑起来,runtime模块设计
后端·llm·workflow
此人未设置昵称5 个月前
【ai_agent】从零写一个agent框架(一)打造最强开放agent编辑框架,拳打dify,脚踩coze
后端·openai·workflow
YBCarry_段松啓5 个月前
Coze工作流:打通AI应用的最后一公里
人工智能·llm·工作流引擎