前言
最近有個新專案,需要實現類似工作流引擎的效果,如果不知道是啥,看完本文就懂了。
公司內其實也有些自研的,可能就是不像開源的這些那樣,還支援這個那個規範,都是基於需求定製開發的,擴充套件性稍微差點。
所以,這次其實幾個同事,分工調研了幾個開源的和公司內的,開源的包括activiti、flowable、camunda,我這邊主要調研了flowable、camunda,同事調研了activiti和公司內部的。
最終看下來,我們的需求,其實不需要用到這麼複雜的開源框架,公司內的一個框架更符合一些。開源的框架,會建很多表,表也不符合公司內的建表規範,所以還需要閱讀原始碼,去改造之類的,也比較麻煩。會引入很多jar包,總體來說,還是比較重。
文末有幾個引擎的對比,大家有興趣可以看看,也可以加我微信和我探討(只花了兩天時間,可能也瞭解得也比較粗略)。
最終來說,技術還是服務於需求的,不是因為框架牛逼就硬上,合適最重要。
先說說uml和omg
學過軟體工程的同學,肯定知道uml,全稱Unified Modeling Language,統一建模語言。建模,為啥要建模,因為軟體研發過程較為抽象,一個需求來了,肯定要先分析分析,建個模(通俗就是:畫個圖),但是每個人畫出來的圖都不一樣,比如uml裡用一個小人來表示使用者,有的人就不願意用小人。所以,為了業界內人士溝通交流更方便,就定義了一套標準,每種圖應該怎麼畫,包含了哪些部分。
比如uml包含了如下型別的圖,每種圖裡,都有固定的圖例來代表固定的意思(僅部分):
ok,大家明白了uml,我再說說omg是啥,omg是個標準化組織,致力於提出uml這樣類似的標準,和業界的公司進行討論交流,各公司的人、學術界的人、omg的人,共同討論,提出一個大家都能接受的方案,然後大家就按照這個標準來進行實現。
世界上的標準化機構很多,omg手裡拿出來的,現在廣為使用的,被iso採納的,有如下幾個。
主要就是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等持久化資料庫)
<!-- 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>
建立流程引擎例項
以下,先建立一個引擎例項出來:
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)。
<?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對應的流程圖如下:
接下來,我們就把這個檔案,傳給流程引擎,讓它基於該檔案,建立一個工作流。
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("holiday-request.bpmn20.xml")
.deploy();
建立後,實際就寫到記憶體資料庫h2了,此時,我們還可以把它查出來
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.deploymentId(deployment.getId())
.singleResult();
System.out.println("Found process definition : " + processDefinition.getName());
建立工作流例項
工作流例項,一開始需要一些輸入引數,員工不是需要請假嗎,我們就需要:員工姓名、請假天數、事由等。
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,引數準備好了,就準備傳給工作流了:
ProcessInstance processInstance =
runtimeService.startProcessInstanceByKey("holidayRequest", variables);
此時,就會根據流程定義裡的:
<userTask id="approveTask" name="Approve or reject request" activiti:candidateGroups="managers"/>
建立一個任務,任務有個標籤,就是candidate group,這裡是manager,可以猜得出,是給manager建了個審批任務。
manager查詢並審批任務
以下,基於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);
taskService.complete(task.getId(), variables);
這裡就是把全域性變數 approved,設為了true,然後提交給引擎。引擎就會根據這裡的變數為true還是false,走不同分支。對應了:
回撥使用者程式碼--使用者開始休假
上面審批後,就會進入下一個節點:休假。
<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