文档章节

使用flowable构建一个命令行工作流示例

cleanCode
 cleanCode
发布于 2017/03/08 23:03
字数 4596
阅读 1283
收藏 10

创建一个工作流引擎

在开始之前稍作说明:flowable是activiti的原班核心人员从activiti分离出来的一套工作流引擎(这和activiti的诞生如出一辙),其核心是超快速、稳定的BPMN2流程引擎,易于与 Spring集成使用。简单一句话:Flowable是activiti的升级版.

在这个教程中,我们将构建一个简单的示例,演示如何创建一个Flowable流程引擎,介绍一些核心概念,并展示如何使用API。我们将使用Maven获取Flowable依赖项并管理构建,但同样,任何替代方法也可以工作(Gradle,Ivy等)。此教程中流程和工作流是一个意思。

我们将要构建请假的工作流示例:

  •      员工请假的天数
  •      经理将批准或拒绝请假
  •      我们将模拟在一些外部系统中注册请假,并把其结果发送给员工的电子邮件

首先,我们通过File→New→Other→Maven Project创建一个新的Maven项目

如下一个截图中,我们创建一个简单的项目(跳过原型选择)

并填写一些“Group Id”和“Artifact id”:

我们现在有一个空的Maven项目,我们将添加两个依赖项:

  •      Flowable流程引擎,它将允许我们创建一个ProcessEngine对象并访问Flowable API。
  •      作为Flowable引擎需要一个数据库来存储运行流程实例时的执行和历史数据。在这里我们使用内存数据库H2。 注意,H2依赖包括数据库和驱动程序。 如果使用另一个数据库(例如,PostgresQL,MySQL等),则需要添加特定的数据库驱动程序依赖关系。

将以下内容添加到pom.xml文件中:

<dependencies>
  <dependency>
    <groupId>org.flowable</groupId>
    <artifactId>flowable-engine</artifactId>
    <version>6.0.0</version>
  </dependency>
  <dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.3.176</version>
  </dependency>
</dependencies>

如果因为某些原因没有自动检索依赖JAR,您可以右键单击项目并选择Maven→更新项目以强制进行手动刷新(但通常不需要)。 在项目中,在“Maven依赖项”下,您现在应该看到流动引擎和各种其他(传递)依赖项。

创建一个新的Java类并添加一个常规的Java main方法:

package org.flowable;

public class HolidayRequest {

  public static void main(String[] args) {

  }

}

我们需要做的第一件事是实例化一个ProcessEngine实例。 这是一个线程安全的对象,你通常只需要在应用程序中实例化一次。 ProcessEngine是从ProcessEngineConfiguration实例创建的,它允许您配置和调整流程引擎。 通常,ProcessEngineConfiguration是通过XML文件创建的,但是(如我们这里所做的),您也可以通过编程方式创建它。 ProcessEngineConfiguration所需的最低配置是到数据库的JDBC连接:

package org.flowable;

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();
  }

}

在上面的代码中的第10行,创建了一个StandaloneProcessEngineConfiguration对象。 “Standalone”在这里指的是引擎被完全由其自身创建并使用(例如,在Spring环境中,使用SpringProcessEngineConfiguration类,而不是此类)。 在第11到14行,设置H2数据库实例的JDBC连接参数。 重要提示:此类数据库在JVM重新启动后数据会丢失。 如果希望数据持久化,则需要切换到持久性数据库并相应地切换连接参数。 在第15行,我们将一个标志设置为true,表明如果数据库实例不存在,则会自动创建数据库实例。当然我们也可以手动使用Flowable附带一组SQL文件,创建数据库实例。

然后使用此配置创建ProcessEngine对象(第17行)

 

你现在就可以运行: Eclipse中最简单的方法是右键单击类文件并选择Run As→Java Application:

此应用程序运行没有问题,但在控制台几乎没有有用的信息,只有指出日志记录没有正确配置:

Flowable在内部使用SLF4J作为其日志框架。 对于此示例,我们将使用log4j logger 实现 SLF4j,因此将以下依赖项添加到pom.xml文件中:

<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>1.7.21</version>
</dependency>
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-log4j12</artifactId>
  <version>1.7.21</version>
</dependency>

Log4j需要一个属性文件。 将log4j.properties文件添加到src / main / resources文件夹,内容如下:

log4j.rootLogger=DEBUG, CA

log4j.appender.CA=org.apache.log4j.ConsoleAppender
log4j.appender.CA.layout=org.apache.log4j.PatternLayout
log4j.appender.CA.layout.ConversionPattern= %d{hh:mm:ss,SSS} [%t] %-5p %c %x - %m%n

重新运行应用程序。 您现在应该看到关于引擎启动和创建的数据库日志:

我们现在有一个准备好的流程引擎了。下面我们部署一个流程定义吧

 

部署流程定义

我们将要构建一个非常简单的请假流程。 Flowable引擎以BPMN 2.0规范定义流程,BPMN 2.0规范具体使用XML表述。 在Flowable术语中,我们将此称为流程定义。 从流程定义,可以启动许多流程实例。 可以将流程定义视为流程实例的蓝图。 在该特定情况下,流程定义定义请假所涉及的不同步骤,而一个流程实例匹配一个特定雇员的请假。

BPMN 2.0存储为XML,但它也有一个可视化部分:它以标准方式定义每个不同的步骤类型(人工任务,自动服务调用等)的表示方式,以及如何连接这些不同的步骤 。 通过这一点,BPMN 2.0标准允许技术和商业人员以双方理解的方式来沟通业务流程。

我们将使用如下的流程定义:

该流程不言而喻,但为了清楚起见,让我们说明一下:

  • 此流程需要一些信息:例如员工姓名,请假的天数和请假原因。我们可以在流程的创建过程中要求输入这些信息,当然也可以先创建流程,流程启动后,请假人员再输入这些信息,输入这些信息过程中可以改变主意,甚至取消。在某些情况下这种方式更有价值(例如,请假流程已启动多少次,但未完成),具体取决于业务目标。
  •     左边的圆圈被称为开始事件。它是流程实例的起点。
  •     第一个矩形是用户任务。这是用户必须在流程中执行的一个步骤。在这种情况下,经理需要批准或拒绝请假。
  •     根据经理的决定,排他网关(具有十字形的菱形)将路由到流程实例的批准或拒绝分支。
  •     如果批准,我们必须在一些外部系统中注册该请假,然后再次为原始员工执行用户任务,通知他们该决定。这当然可以替换为电子邮件。
  •     如果拒绝,将向员工发送一封电子邮件,通知他们。

通常,使用可视化建模工具(诸如Flowable 设计器(Eclipse)或flowable建模器(web应用))对这样的流程定义建模。

然而,在这里,我们将直接编写XML以熟悉BPMN 2.0及其概念。

对应于上图的BPMN 2.0 XML如下所示。 注意,这只是流程部分。 如果您使用图形建模工具,底层XML文件还包含描述图形信息的可视化部分,例如流程定义的各种元素的坐标(所有图形信息都包含在XML中的BPMNDiagram标记中 ,它是定义标签的子元素)。

将以下XML保存在src / main / resources文件夹中,命名为holiday-request.bpmn20.xml。

<?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="holidayRequest" name="Holiday Request" isExecutable="true">

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

    <userTask id="approveTask" name="Approve or reject request"/>
    <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"
        flowable:class="org.flowable.CallExternalSystemDelegate"/>
    <sequenceFlow sourceRef="externalSystemCall" targetRef="holidayApprovedTask"/>

    <userTask id="holidayApprovedTask" name="Holiday approved"/>
    <sequenceFlow sourceRef="holidayApprovedTask" targetRef="approveEnd"/>

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

    <endEvent id="approveEnd"/>

    <endEvent id="rejectEnd"/>

  </process>

</definitions>

第2到第11行看起来有点令人生畏,但它几乎在每个流程定义中都会看到。 它是一种XML命名空间,完全兼容BPMN 2.0标准规范。

每一步(在BPMN 2.0术语中是活动)都有一个id属性,在XML文件中为其提供唯一标识符。 所有活动都可以有一个可选的名称,这增加了可读性。

活动通过顺序流连接,顺序流是图中的有向箭头。 当执行流程实例时,将遵循顺序流从开始事件流向下一个活动。

离开排他网关的顺序流(带有X的菱形)有明显的特征:两者都具有以表达式形式定义的条件(见第25和32行)。 当流程实例执行到达此网关时,将判断条件,执行解析为true的第一个条件。 这是排他网管的特征:只有一个被选中。 当然,如果需要不同的路由行为,则可以选择其他类型的网关。

表达式条件是$ {approved},这是$ {approved == true}的缩写。 这approved变量称为流程变量。 流程变量是与流程实例一起持久化,并且可以在流程实例的生存期内使用。因为它在流程实例启动是并不存在, 这意味着我们必须在流程实例中的某个点(在经理的审批任务提交时)中设置此流程变量。

现在我们有了BPMN 2.0 XML的流程定义,接下来我们需要将它部署到引擎。 部署流程定义意味着:

  •      流程引擎将XML文件存储在数据库中,因此可以在需要时获取它
  •      流程定义被解析为内部的可执行对象模型,以方便启动流程实例。

要将流程定义部署到Flowable引擎,需要使用RepositoryService,它可以从ProcessEngine对象中获取。 使用RepositoryService部署流程定义很简单,你只需把XML文件传递给 deploy()方法:

RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deployment = repositoryService.createDeployment()
  .addClasspathResource("holiday-request.bpmn20.xml")
  .deploy();

我们可以通过RepositoryService创建一个新的ProcessDefinitionQuery对象,来查询流程定义的名字,以此验证流程定义对引擎是否已知,并了解一点API。

ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
  .deploymentId(deployment.getId())
  .singleResult();
System.out.println("Found process definition : " + processDefinition.getName());

启动流程实例

我们已将流程定义部署到流程引擎中,可以以流程定义为蓝图随时启动流程实例。

要启动流程实例,我们需要提供一些初始的流程变量。 通常,当流程自动触发时,您将通过呈现给用户的表单或通过REST API来设置这些变量。 在这个例子中,我们将保持简单,并使用java.util.Scanner类在命令行中实现:

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启动一个流程实例。 收集的数据作为java.util.Map实例传递,其中key是将在稍后用于检索变量的标识符。 使用流程id启动流程实例。 此id与在BPMN 2.0 XML文件中设置的id属性匹配(holidayRequest)。

(注意:除了使用id,以后有很多方法可以启动一个流程实例)

<process id="holidayRequest" name="Holiday Request" isExecutable="true">
RuntimeService runtimeService = processEngine.getRuntimeService();

Map<String, Object> variables = new HashMap<String, Object>();
variables.put("employee", employee);
variables.put("nrOfHolidays", nrOfHolidays);
variables.put("description", description);
ProcessInstance processInstance =
  runtimeService.startProcessInstanceByKey("holidayRequest", variables);

当流程实例启动时,将创建一个实例,从启动事件开始。 执行到经理审批用户任务, 此行为将在数据库中创建一个可以在以后使用查询找到的任务。 用户任务是等待状态,引擎将停止执行任何进一步的操作,返回API调用。

 

事务性

在Flowable中,数据库事务对保证数据一致性和解决并发问题起着至关重要的作用。 当您进行Flowable API调用时,默认情况下,一切都是同步的,并且是同一事务。 意思是,当方法调用返回时,事务将被启动并提交。

当流程实例启动时,将有一个数据库事务从流程实例的开始到下一个等待状态。 在此示例中,这是第一个用户任务。 当引擎达到此用户任务时,状态将保留到数据库,并且事务提交并返回API调用。

在Flowable中,流程实例的执行过程,总是以数据库事务的形式从一个等待状态到下一个等待状态。并且 一旦持久化,数据可以在数据库中长时间,甚至几年一直存在,直到执行API调用,进一步处理实例。 注意,当进程实例处于这种等待状态,等待下一个API调用时,不消耗CPU和内存资源。

在这个示例中,当第一用户任务完成时,将使用一个数据库事务从用户任务通过排他网关(自动逻辑)直到第二用户任务。 或直接到另一条路径的尽头。

 

查询和完成任务

在真实的应用程序中,将有一个用户界面,员工和经理可以登录并查看他们的任务列表。 通过用户界面,他们可以查看以流程变量的形式存储在流程实例中的数据,并决定他们要对任务做什么。 在本例中,我们将直接调用流程API,来模拟任务列表的功能。

我们尚未配置用户任务的经办人。 我们希望第一个任务分配给经理,第二个用户任务将分配给请假的员工。 为此,将candidateGroups属性添加到第一个任务:

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

同理,配置第二个用户任务的经办人,如下所示。 注意,这里我们并没有想上面的那样给经办人配置一个静态值(manager),而是使用了流程变量${employee}(实例启动时传递的流程变量):

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

为了得到实际的任务列表,我们通过TaskService创建一个TaskQuery,用来查询经理组的用户任务:

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());
}

使用任务标识符,我们现在可以获得特定的流程实例变量,并在屏幕上显示请假详情:

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?");

如果你运行这个,应该看起来像这样:

经理现在可以完成任务。 在现实中,这通常意味着用户提交表单。 然后将表单中的数据作为流程变量传递。 这里,任务完成时我们将通过传递一个带有已批准变量的map(名称很重要,因为它在序列流的条件中被使用!):

boolean approved = scanner.nextLine().toLowerCase().equals("y");
variables = new HashMap<String, Object>();
variables.put("approved", approved);
taskService.complete(task.getId(), variables);

任务现在完成,并且基于批准的流程变量来选择离开排他网关的两个路径中的一个。

 

编写JavaDelegate

最后一块拼图仍然缺失:我们没有实现自动逻辑(在请假被批准时执行)。 在BPMN 2.0 XML中,这是一个服务任务,它看起来像:

<serviceTask id="externalSystemCall" name="Enter holidays in external system"
    flowable:class="org.flowable.CallExternalSystemDelegate"/>

实际上,这个逻辑可以是任何东西,从调用具有HTTP REST的服务到对公司已经使用了几十年的系统执行一些遗留代码调用。 我们不会在这里实现实际的逻辑,而只是记录处理。

创建一个新类(文件→新建→Eclipse中的类),填写org.flowable作为包名称和CallExternalSystemDelegate作为类名。 使该类实现org.flowable.engine.delegate.JavaDelegate接口并实现execute方法:

package org.flowable;

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

public class CallExternalSystemDelegate implements JavaDelegate {

    public void execute(DelegateExecution execution) {
        System.out.println("Calling the external system for employee "
            + execution.getVariable("employee"));
    }

}

当执行到达服务任务时,BPMN 2.0 XML中引用的类将被实例化并被调用。

现在运行示例时,将显示日志消息,说明自定义逻辑确实已执行:

 

历史数据

选择使用流程引擎(Flowable)的许多原因之一是因为它自动存储所有流程实例的审批数据或者称作历史数据。 根据这些数据可以创建丰富的报告,从而深入了解组织如何工作,瓶颈在哪里等等。

例如,假设我们要显示到目前为止执行流程实例的持续时间。 为此,我们从ProcessEngine获取HistoryService,并为历史活动创建一个查询。 在下面的代码段中,您可以看到我们添加了一些额外的过滤:

  • 一个特定流程实例的活动 
  • 已经完成的活动

结果是按结束时间排序的,这意味着我们将按执行顺序得到它们。

HistoryService historyService = processEngine.getHistoryService();
List<HistoricActivityInstance> activities =
  historyService.createHistoricActivityInstanceQuery()
   .processInstanceId(processInstance.getId())
   .finished()
   .orderByHistoricActivityInstanceEndTime().asc()
   .list();

for (HistoricActivityInstance activity : activities) {
  System.out.println(activity.getActivityId() + " took "
    + activity.getDurationInMillis() + " milliseconds");
}

再次运行示例,我们在控制台中看到类似下面的内容:

startEvent took 1 milliseconds
approveTask took 2638 milliseconds
decision took 3 milliseconds
externalSystemCall took 1 milliseconds

 

总结

本教程介绍了各种Flowable和BPMN 2.0概念和术语,同时还演示了如何以编程方式使用Flowable API。

当然,这只是旅程的开始。 后面的部分将更深入地介绍Flowable引擎支持的许多特性和功能。 其他部分介绍了Flowable引擎可以设置和使用的各种方式,并详细描述BPMN 2.0规范。

© 著作权归作者所有

cleanCode
粉丝 4
博文 1
码字总数 4596
作品 0
海淀
高级程序员
私信 提问
Guns 3.1,集成 flowable 工作流引擎及完善代码生成器

Guns 3.1更新日志: 集成flowable 6.2.0工作流框架,并提供一个报销流程示例 代码生成器单独分出一个模块,并提供完善的界面操作来进行代码生成操作 修改表名统一以sys_开头 整理部分代码结构 ...

stylefeng
2017/12/11
6.2K
5
业务流程管理 BPM 和工作流系统--flowable

flowable 是著名 Java 工作流引擎 Activiti 的原作者从 Activiti 分支创建的新工作流引擎。 flowable 是一个业务流程管理(BPM)和工作流系统,适用于开发人员和系统管理员。其核心是超快速,稳...

匿名
2016/10/22
12K
2
黑月ゞ/springboot-flowable

springboot-flowable 介绍 springboot-flowable 快速开发工作流 软件架构 springboot + flowable 使用说明 将flowable的依赖加入到POM中即可,flowable使用需要一个数据库,这里为了方便我选择...

黑月ゞ
05/21
0
0
工作流引擎 flowable 5.22.0,完全兼容 Activiti

2016年10月13日,从工作流引擎 Activiti 分支出的新项目 Flowable 发布了首个版本 flowable-5.22.0 ,可无缝替代activiti-5.21.0 。 亮点: 无缝替代 activiti-5.21.0 。除了修改 Maven 的 gr...

viruscamp
2016/10/28
10.8K
8
基于Scala构建分布式调度ETL系统Akkaflow

Akkaflow分布式调度系统 演示系统: 点击这里 用户/密码:admin/admin,项目托管地址:点击这里 简介 是一个基于架构上构建的分布式高可用ETL调度工具,可以把一个job中子任务按照拓扑关系在集...

kent7306
2017/04/02
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Spring Cloud 笔记之Spring cloud config client

观察者模式它的数据的变化是被动的。 观察者模式在java中的实现: package com.hxq.springcloud.springcloudconfigclient;import org.springframework.context.ApplicationListener;i...

xiaoxiao_go
28分钟前
2
0
CentOS7.6中安装使用fcitx框架

内容目录 一、为什么要使用fcitx?二、安装fcitx框架三、安装搜狗输入法 一、为什么要使用fcitx? Gnome3桌面自带的输入法框架为ibus,而在使用ibus时会时不时出现卡顿无法输入的现象。 搜狗和...

技术训练营
今天
4
0
《Designing.Data-Intensive.Applications》笔记 四

第九章 一致性与共识 分布式系统最重要的的抽象之一是共识(consensus):让所有的节点对某件事达成一致。 最终一致性(eventual consistency)只提供较弱的保证,需要探索更高的一致性保证(stro...

丰田破产标志
今天
7
0
docker 使用mysql

1, 进入容器 比如 myslq1 里面进行操作 docker exec -it mysql1 /bin/bash 2. 退出 容器 交互: exit 3. mysql 启动在容器里面,并且 可以本地连接mysql docker run --name mysql1 --env MY...

之渊
今天
7
0
python数据结构

1、字符串及其方法(案例来自Python-100-Days) def main(): str1 = 'hello, world!' # 通过len函数计算字符串的长度 print(len(str1)) # 13 # 获得字符串首字母大写的...

huijue
今天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部