Flowable是一個使用Java編寫的輕量級業務流程引擎。Flowable流程引擎可用於部署BPMN 2.0流程定義(用於定義流程的行業XML標準), 建立這些流程定義的流程範例,進行查詢,存取執行中或歷史的流程範例與相關數據,等等。這個章節將用一個可以在你自己的開發環境中使用的例子,逐步介紹各種概念與API。
Flowable可以十分靈活地加入你的應用/服務/構架。可以將JAR形式發佈的Flowable庫加入應用或服務,來嵌入引擎。 以JAR形式發佈使Flowable可以輕易加入任何Java環境:Java SE;Tomcat、Jetty或Spring之類的servlet容器;JBoss或WebSphere之類的Java EE伺服器,等等。 另外,也可以使用Flowable REST API進行HTTP呼叫。也有許多Flowable應用(Flowable Modeler, Flowable Admin, Flowable IDM 與 Flowable Task),提供了直接可用的UI範例,可以使用流程與任務。
所有使用Flowable方法的共同點是核心引擎。核心引擎是一組服務的集合,並提供管理與執行業務流程的API。 下面 下麪的教學從設定與使用核心引擎的介紹開始。後續章節都建立在之前章節中獲取的知識之上。
Flowable,2016年基於Activiti誕生。
我們將構建的例子是一個簡單的請假(holiday request)流程:
1.1.1、建立maven專案
建立一個名爲holiday-request的maven專案,新增依賴:
<!--Flowable流程引擎-->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-engine</artifactId>
<version>6.3.0</version>
</dependency>
<!--MySQL驅動,這裏採用MySQL數據庫,如果採用其它數據庫,需要引入對應的依賴。-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>
1.1.2、建立數據庫表格
建立一個數據庫flowable_demo。
建立一個普通的Java類:HolidayRequest
/**
* @Author 三分惡
* @Date 2020/5/2
* @Description 建立表格
*/
public class HolidayRequest {
public static void main(String[] args) {
//1、建立ProcessEngineConfiguration範例,該範例可以設定與調整流程引擎的設定
ProcessEngineConfiguration cfg=new StandaloneProcessEngineConfiguration()
//2、通常採用xml組態檔建立ProcessEngineConfiguration,這裏直接採用程式碼的方式
//3、設定數據庫相關參數
.setJdbcUrl("jdbc:mysql://localhost:3306/flowable_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2b8&nullCatalogMeansCurrent=true")
.setJdbcUsername("root")
.setJdbcPassword("root")
.setJdbcDriver("com.mysql.jdbc.Driver")
.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
//4、初始化ProcessEngine流程引擎範例
ProcessEngine processEngine=cfg.buildProcessEngine();
}
}
1.1.3、執行
執行該類,會發現在數據庫flowable_demo中建立了34個表:
1.1.4、建立的表格說明
flowable命名規則:
34張表說明:
表分類 | 表名 | 表說明 |
---|---|---|
一般數據(2) | ACT_GE_BYTEARRAY | 通用的流程定義和流程資源 |
ACT_GE_PROPERTY | 系統相關屬性 | |
流程歷史記錄(8) | ACT_HI_ACTINST | 歷史的流程範例 |
ACT_HI_ATTACHMENT | 歷史的流程附件 | |
ACT_HI_COMMENT | 歷史的說明性資訊 | |
ACT_HI_DETAIL | 歷史的流程執行中的細節資訊 | |
ACT_HI_IDENTITYLINK | 歷史的流程執行過程中使用者關係 | |
ACT_HI_PROCINST | 歷史的流程範例 | |
ACT_HI_TASKINST | 歷史的任務範例 | |
ACT_HI_VARINST | 歷史的流程執行中的變數資訊 | |
使用者使用者組表(9) | ACT_ID_BYTEARRAY | 二進制數據表 |
ACT_ID_GROUP | 使用者組資訊表 | |
ACT_ID_INFO | 使用者資訊詳情表 | |
ACT_ID_MEMBERSHIP | 人與組關係表 | |
ACT_ID_PRIV | 許可權表 | |
ACT_ID_PRIV_MAPPING | 使用者或組許可權關係表 | |
ACT_ID_PROPERTY | 屬性表 | |
ACT_ID_TOKEN | 系統登錄日誌表 | |
ACT_ID_USER | 使用者表 | |
流程定義表(3) | ACT_RE_DEPLOYMENT | 部署單元資訊 |
ACT_RE_MODEL | 模型資訊 | |
ACT_RE_PROCDEF | 已部署的流程定義 | |
執行範例表(10) | ACT_RU_DEADLETTER_JOB | 正在執行的任務表 |
ACT_RU_EVENT_SUBSCR | 執行時事件 | |
ACT_RU_EXECUTION | 執行時流程執行範例 | |
ACT_RU_HISTORY_JOB | 歷史作業表 | |
ACT_RU_IDENTITYLINK | 執行時使用者關係資訊 | |
ACT_RU_JOB | 執行時作業表 | |
ACT_RU_SUSPENDED_JOB | 暫停作業表 | |
ACT_RU_TASK | 執行時任務表 | |
ACT_RU_TIMER_JOB | 定時作業表 | |
ACT_RU_VARIABLE | 執行時變數表 | |
其他表(2) | ACT_EVT_LOG | 事件日誌表 |
ACT_PROCDEF_INFO | 流程定義資訊 |
1.1.4、日誌設定
在上面的執行中,同時可以看到,控制檯有報錯的資訊,這是日誌沒有正確地設定:
Flowable使用SLF4J作爲內部日誌框架。我們使用log4j作爲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>
在 src/resouce目錄下新建log4j的組態檔log4j.properties:
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格式,這是一個業界廣泛接受的XML標準。
在Flowable術語中,我們將其稱爲一個流程定義(process definition)。一個流程定義可以啓動多個流程範例(process instance)。流程定義可以看做是重複執行流程的藍圖。 在這個例子中,流程定義定義了請假的各個步驟,而一個流程範例對應某個僱員提出的一個請假申請。
BPMN 2.0儲存爲XML,幷包含視覺化的部分:使用標準方式定義了每個步驟型別(人工任務,自動服務呼叫,等等)如何呈現,以及如何互相連線。這樣BPMN 2.0標準使技術人員與業務人員能用雙方都能理解的方式交流業務流程。
我們要使用的流程定義爲:
流程定義的說明:
我們假定啓動流程需要提供一些資訊,例如僱員名字、請假時長以及說明。當然,這些可以單獨建模爲流程中的第一步。 但是如果將它們作爲流程的「輸入資訊」,就能保證只有在實際請求時纔會建立一個流程範例。否則(將提交作爲流程的第一步),使用者可能在提交之前改變主意並取消,但流程範例已經建立了。 在某些場景中,就可能影響重要的指標(例如啓動了多少申請,但還未完成),取決於業務目標。
左側的圓圈叫做啓動事件(start event)。這是一個流程範例的起點。
第一個矩形是一個使用者任務(user task)。這是流程中人類使用者操作的步驟。在這個例子中,經理需要批準或駁回申請。
取決於經理的決定,排他閘道器(exclusive gateway) (帶叉的菱形)會將流程範例路由至批準或駁回路徑。
如果批準,則需要將申請註冊至某個外部系統,並跟着另一個使用者任務,將經理的決定通知給申請人。當然也可以改爲發送郵件。
如果駁回,則爲僱員發送一封郵件通知他。
一般來說,這樣的流程定義使用視覺化建模工具建立,如Flowable Designer(Eclipse)或Flowable Web Modeler(Web應用)。
這裏我們直接撰寫XML,以熟悉BPMN 2.0及其概念。
以下是與上面展示的流程圖對應的BPMN 2.0 XML。這裏只包含了「流程部分」。如果使用圖形化建模工具,實際的XML檔案還將包含「視覺化部分」,用於描述圖形資訊,如流程定義中各個元素的座標(所有的圖形化資訊包含在XML的BPMNDiagram標籤中,作爲definitions標籤的子元素)。
在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="holiday-request" 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">
<!--順序流條件:以表達式(expression)的形式定義了條件(condition) -->
<conditionExpression xsi:type="tFormalExpression">
<!--條件表達式:是${approved == true}的簡寫-->
<![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="edu.hpu.process.CallExternalSystemDelegate"/>
<userTask id="holidayApprovedTask" name="Holiday Approve!"/>
<sequenceFlow sourceRef="holidayApprovedTask" targetRef="approveEnd"/>
<serviceTask id="sendRejectionMail" name="Send out rejection email" flowable:class="edu.hpu.process.SendRejectionMail"/>
<sequenceFlow sourceRef="sendRejectionMail" targetRef="rejectEnd"/>
<!--結束事件-->
<endEvent id="approveEnd"/>
<endEvent id="rejectEnd"/>
</process>
</definitions>
每一個步驟(在BPMN 2.0術語中稱作活動(activity))都有一個id屬性,爲其提供一個在XML檔案中唯一的識別符號。所有的活動都可以設定一個名字,以提高流程圖的可讀性。
活動之間通過順序流(sequence flow)連線,在流程圖中是一個有向箭頭。在執行流程範例時,執行(execution)會從啓動事件沿着順序流流向下一個活動。
離開排他閘道器(帶有X的菱形)的順序流很特別:都以表達式(expression)的形式定義了條件(condition) 。當流程範例的執行到達這個閘道器時,會計算條件,並使用第一個計算爲true的順序流。這就是排他的含義:只選擇一個。當然如果需要不同的路由策略,可以使用其他型別的閘道器。
這裏用作條件的表達式爲approved,這是approved,這是{approved == true}的簡寫。變數’approved’被稱作流程變數(process variable)。流程變數是持久化的數據,與流程範例儲存在一起,並可以在流程範例的生命週期中使用。在這個例子裡,我們需要在特定的地方(當經理使用者任務提交時,或者以Flowable的術語來說,完成(complete)時)設定這個流程變數,因爲這不是流程範例啓動時就能獲取的數據。
現在我們已經有了流程BPMN 2.0 XML檔案,下來需要將它部署(deploy)到引擎中。部署一個流程定義意味着:
流程引擎會將XML檔案儲存在數據庫中,這樣可以在需要的時候獲取它。
流程定義轉換爲內部的、可執行的物件模型,這樣使用它就可以啓動流程範例。
將流程定義部署至Flowable引擎,需要使用RepositoryService,其可以從ProcessEngine物件獲取。使用RepositoryService,可以通過XML檔案的路徑建立一個新的部署(Deployment),並呼叫deploy()方法實際執行:
//建立RepositoryService範例
RepositoryService repositoryService=processEngine.getRepositoryService();
//載入流程
Deployment deployment=repositoryService.createDeployment()
.addClasspathResource("holiday-request.bpmn20.xml")
.deploy();
我們現在可以通過API查詢驗證已經部署在引擎中的流程定義。通過RepositoryService建立的ProcessDefinitionQuery物件實現。
//查詢流程定義
ProcessDefinition processDefinition=repositoryService.createProcessDefinitionQuery()
.deploymentId(deployment.getId())
.singleResult();
System.out.println("Found process definition : "+processDefinition.getName());
執行:
xml檔案已經儲存進了數據庫:
現在已經在流程引擎中部署了流程定義,因此可以使用這個流程定義作爲「藍圖」啓動流程範例。
//1、獲取流程初始化變數
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();
<process id="holiday-request" name="Holiday Request" isExecutable="true">
//2、使用RuntimeService啓動一個流程範例
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("holiday-request",variables);
在流程範例啓動後,會建立一個執行(execution),並將其放在啓動事件上。從這裏開始,這個執行會沿着順序流移動到經理審批的使用者任務,並執行使用者任務行爲。這個行爲將在數據庫中建立一個任務,該任務可以之後使用查詢找到。使用者任務是一個等待狀態(wait state),引擎會停止執行,返回API呼叫處。
輸入流程初始化變數:
將數據插入數據庫中
向數據庫中插入了數據:
在Flowable中,數據庫事務扮演了關鍵角色,用於保證數據一致性,並解決併發問題。當呼叫Flowable API時,預設情況下,所有操作都是同步的,並處於同一個事務下。這意味着,當方法呼叫返回時,會啓動並提交一個事務。
流程啓動後,會有一個數據庫事務從流程範例啓動時持續到下一個等待狀態。在這個例子裡,指的是第一個使用者任務。當引擎到達這個使用者任務時,狀態會持久化至數據庫,提交事務,並返回API呼叫處。
在Flowable中,當一個流程範例執行時,總會有一個數據庫事務從前一個等待狀態持續到下一個等待狀態。數據持久化之後,可能在數據庫中儲存很長時間,甚至幾年,直到某個API呼叫使流程範例繼續執行。請注意當流程處在等待狀態時,不會消耗任何計算或記憶體資源,直到下一次APi呼叫。
在這個例子中,當第一個使用者任務完成時,會啓動一個數據庫事務,從使用者任務開始,經過排他閘道器(自動邏輯),直到第二個使用者任務。或通過另一條路徑直接到達結束。
在更實際的應用中,會爲僱員及經理提供用戶介面,讓他們可以登錄並檢視任務列表。其中可以看到作爲流程變數儲存的流程範例數據,並決定如何操作任務。在這個例子中,我們通過執行API呼叫來模擬任務列表,通常這些API都是由UI驅動的服務在後台呼叫的。
我們還沒有爲使用者任務設定辦理人。
<!--將任務指派給經理組-->
<userTask id="approveTask" name="Approve or reject request" flowable:candidateGroups="managers"/>
<!--指派給請假審批的審批人,${employee}使用流程變數動態指派,在流程範例啓動時傳遞-->
<userTask id="holidayApprovedTask" name="Holiday Approve!" flowable:assignee="${employee}"/>
//通過TaskService查詢 manager 組的任務
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?");
//經理完成任務
boolean approved=scanner.nextLine().toLowerCase().equals("y");
variables = new HashMap<String, Object>();
variables.put("approved", approved);
//任務完成,並會在離開排他閘道器的兩條路徑中,基於’approved ’流程變數選擇一條
taskService.complete(task.getId(),variables);
現在還缺最後一點,服務任務呼叫的服務沒有實現:
<!--服務任務,一個自動活動,它會呼叫一些服務-->
<serviceTask id="externalSystemCall" name="Enter holidays in external system" flowable:class="edu.hpu.process.CallExternalSystemDelegate"/>
/**
* @Author 三分惡
* @Date 2020/5/3
* @Description
*/
public class CallExternalSystemDelegate implements JavaDelegate {
public void execute(DelegateExecution delegateExecution) {
System.out.println("Calling the external system for employee "
+ delegateExecution.getVariable("employee"));
}
}
執行:
啓動流程:
檢視任務
完成任務
執行自動邏輯
至此,一個模擬請假流程就完成了。
選擇使用Flowable這樣的流程引擎的原因之一,是它可以自動儲存所有流程範例的審計數據或歷史數據。這些數據可以用於建立報告,深入展現組織執行的情況,瓶頸在哪裏,等等。
從ProcessEngine獲取HistoryService,並建立歷史活動(historical activities)的查詢。
//獲取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");
}
參考:
【1】:Flowable BPMN 使用者手冊 (v 6.3.0)
【2】:flowable學習(Ⅰ) --- 建立一個簡單的flowable流程例子
【3】:activiti與flowable的區別
【4】:Flowable數據庫表說明
【5】:基於BPMN2.0的工作流(Workflow)
【6】:BPMN 2.0 / Flowable
【7】:BPMN 2.0規範
【8】:第 3 章 BPMN 2.0