最近進行的一次技術(shù)選型(工作流引擎)及相關(guān)知識介紹
前言
最近有個新項目,需要實現(xiàn)類似工作流引擎的效果,如果不知道是啥,看完本文就懂了。
公司內(nèi)其實也有些自研的,可能就是不像開源的這些那樣,還支持這個那個規(guī)范,都是基于需求定制開發(fā)的,擴展性稍微差點。
所以,這次其實幾個同事,分工調(diào)研了幾個開源的和公司內(nèi)的,開源的包括activiti、flowable、camunda,我這邊主要調(diào)研了flowable、camunda,同事調(diào)研了activiti和公司內(nèi)部的。
最終看下來,我們的需求,其實不需要用到這么復雜的開源框架,公司內(nèi)的一個框架更符合一些。開源的框架,會建很多表,表也不符合公司內(nèi)的建表規(guī)范,所以還需要閱讀源碼,去改造之類的,也比較麻煩。會引入很多jar包,總體來說,還是比較重。
文末有幾個引擎的對比,大家有興趣可以看看,也可以加我微信和我探討(只花了兩天時間,可能也了解得也比較粗略)。
最終來說,技術(shù)還是服務(wù)于需求的,不是因為框架牛逼就硬上,合適最重要。
先說說uml和omg
學過軟件工程的同學,肯定知道uml,全稱Unified Modeling Language,統(tǒng)一建模語言。建模,為啥要建模,因為軟件研發(fā)過程較為抽象,一個需求來了,肯定要先分析分析,建個模(通俗就是:畫個圖),但是每個人畫出來的圖都不一樣,比如uml里用一個小人來表示用戶,有的人就不愿意用小人。所以,為了業(yè)界內(nèi)人士溝通交流更方便,就定義了一套標準,每種圖應該怎么畫,包含了哪些部分。
比如uml包含了如下類型的圖,每種圖里,都有固定的圖例來代表固定的意思(僅部分):

ok,大家明白了uml,我再說說omg是啥,omg是個標準化組織,致力于提出uml這樣類似的標準,和業(yè)界的公司進行討論交流,各公司的人、學術(shù)界的人、omg的人,共同討論,提出一個大家都能接受的方案,然后大家就按照這個標準來進行實現(xiàn)。
世界上的標準化機構(gòu)很多,omg手里拿出來的,現(xiàn)在廣為使用的,被iso采納的,有如下幾個。

主要就是uml和bpmn,注意,沒有xml(圖里右上角那個是xmi)。
bpmn
圖形規(guī)范
bpmn(Business Process Model And Notation)是啥,也是一種建模語言,和uml類似,就是規(guī)定:xxx,用這個來表示,yyy,用那個來表示。
bpmn主要是對工作流進行建模,大家公司里的oa系統(tǒng),基本就是工作流的典范,比如下面這樣一個用戶請假流程,就是遵循bpmn規(guī)范的。

這個圖里,哪些地方用空心圓,哪些地方是矩形,哪些地方用菱形,都是有講究,這就是bpmn規(guī)范里定義的。

xml規(guī)范
bpmn對圖形有規(guī)范,對圖形背后的存儲格式也有定義,這個圖,最終會轉(zhuǎn)化為一份xml,這份xml也是遵循特定的schema的。

上圖這個xml,就是遵循omg官方的schema,里面最外層是一個process元素,里面的元素則只能是startEvent、sequenceFlow等。
這樣標準化了之后,業(yè)界各個廠商,就可以各自開發(fā)一套實現(xiàn),只要這套實現(xiàn),最終能生成上面這樣的xml,那就是符合bpmn的,拿這份bpmn文件到其他廠商那里,其他廠商的程序也能正確解析該文件,因此就實現(xiàn)了互聯(lián)互通,這就是標準的力量。
bpmn版本歷史
主要是4個版本,現(xiàn)在業(yè)界基本都是基于2010年的最新版本2.0進行實現(xiàn),也就是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的業(yè)界實現(xiàn)
實現(xiàn)還是挺多的,近10多個。現(xiàn)在大家比較用得多的,還是紅框的幾個,Activiti、Camunda、Flowable、jBPM。

這些實現(xiàn),互相有些關(guān)系,就像log4j的維護人后來又創(chuàng)建了logback一樣。

目前主要就是在 Camunda/flowable 6/ activiti里面去選擇。
flowable 內(nèi)嵌模式快速了解
創(chuàng)建maven工程(文末有代碼)
如果一上來,直接就開始比較各框架的差異,大家由于對其中任意一個都不了解,所以也沒法參照。這里先講一下flowable框架(目前最先了解這個框架)。
flowable 引擎,支持兩種運行模式,一種是內(nèi)嵌到業(yè)務(wù)服務(wù)中,咱們先講這種。
先建一個普通的maven工程,加入flowable引擎的依賴以及h2內(nèi)嵌數(shù)據(jù)庫的依賴(正式項目會換成mysql等持久化數(shù)據(jù)庫)
<!-- 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>
創(chuàng)建流程引擎實例
以下,先創(chuàng)建一個引擎實例出來:
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。比如,假設(shè)我們最終想建立一個員工請假流程,那么,我們可以通過各種辦法(如flowable自帶的web-ui拖拽的方式或手動創(chuàng)建xml等),來建立一個下面這樣的,符合bpmn2.0規(guī)范的流程定義xml(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: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文件一樣,實現(xiàn)跨平臺的效果。
該xml對應的流程圖如下:

接下來,我們就把這個文件,傳給流程引擎,讓它基于該文件,創(chuàng)建一個工作流。
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("holiday-request.bpmn20.xml")
.deploy();
創(chuàng)建后,實際就寫到內(nèi)存數(shù)據(jù)庫h2了,此時,我們還可以把它查出來
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.deploymentId(deployment.getId())
.singleResult();
System.out.println("Found process definition : " + processDefinition.getName());
創(chuàng)建工作流實例
工作流實例,一開始需要一些輸入?yún)?shù),員工不是需要請假嗎,我們就需要:員工姓名、請假天數(shù)、事由等。
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,參數(shù)準備好了,就準備傳給工作流了:
ProcessInstance processInstance =
runtimeService.startProcessInstanceByKey("holidayRequest", variables);
此時,就會根據(jù)流程定義里的:
<userTask id="approveTask" name="Approve or reject request" activiti:candidateGroups="managers"/>
創(chuàng)建一個任務(wù),任務(wù)有個標簽,就是candidate group,這里是manager,可以猜得出,是給manager建了個審批任務(wù)。
manager查詢并審批任務(wù)
以下,基于manager查詢?nèi)蝿?wù):
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());
}
我們可把任務(wù)打印出來看看:
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?");
審批任務(wù):
boolean approved = scanner.nextLine().toLowerCase().equals("y");
variables = new HashMap<String, Object>();
variables.put("approved", approved);
taskService.complete(task.getId(), variables);
這里就是把全局變量 approved,設(shè)為了true,然后提交給引擎。引擎就會根據(jù)這里的變量為true還是false,走不同分支。對應了:

回調(diào)用戶代碼--用戶開始休假
上面審批后,就會進入下一個節(jié)點:休假。
<serviceTask id="externalSystemCall" name="Enter holidays in external system"
activiti:class="org.example.CallExternalSystemDelegate"/>
這里有個class,就是需要我們自己實現(xiàn)的。

然后,基本流程就自己走完了。
flowable rest-api模式
簡介
上面那種,是其作為一個jar,內(nèi)嵌到我們的程序里,創(chuàng)建引擎對下。由我們業(yè)務(wù)程序去驅(qū)動引擎的運行。引擎和業(yè)務(wù)代碼在同一個進程。
其實,flowable也可以作為一個獨立服務(wù)運行,提供rest-api出來,這樣的話,非java語言的開發(fā)者也可以使用該引擎了。
這個只需要我們下載官方的zip包,里面有個rest的war包,我們丟到tomcat里運行。

上傳工作流定義xml文件,部署工作流
如果要實現(xiàn)上面java-api那樣的功能,我們就需要調(diào)接口來實現(xiàn)

下面就開始啟動工作流:

其他接口就不一一展示了。可以參考文檔。
flowable-ui,通過web ui進行流程xml建模
上面手工建立xml,還是比較累的,我們可以通過其提供的web ui來建模,省點力氣。(不過也不是很好用,各種名詞比較費解,大家可能還是要自己做一套前端界面,調(diào)用自己的接口,來生成一個xml文件)
上面的rest那一節(jié),tomcat里就部署了一個flowable-ui的。
就可以通過下面這樣的方式來建模。

其他方面
活躍程度:activiti是最活躍的,activiti (非常活躍,一天一個alpha版本)> camunda(一個月一個alpha版本) > flowable(幾個月或半年一個版本)
依賴:會引入37個jar包,當前最新的6.7.2版本
mysql:核心建表語句為7張,歷史表5張;程序運行后,結(jié)果有47張表,具體原因暫時沒去研究
持久層框架:寫mysql表時,使用mybatis
引擎對比

總結(jié)
demo代碼:
https://gitee.com/ckl111/all-simple-demo-in-blog/tree/master/flowable-test

浙公網(wǎng)安備 33010602011771號