最近做了一次對(duì)企業(yè)/云平臺(tái)級(jí)工作流引擎Activiti的調(diào)查:
- TA,系出名門——由JBoss公司jBPM4引擎的原作者創(chuàng)立(JBoss公司無奈地已在jBPM5中改用了Drools內(nèi)核、走了其他路線);
- TA,植根于Java開源社區(qū),擁有SpringSource、MuleSoft、Signavio等公司以及全球大量擁護(hù)者基于最新開發(fā)實(shí)踐的養(yǎng)分支持;
- TA,“出場(chǎng)費(fèi)”要比IBM、Oracle等大廠的同類低很多,卻同樣支持BPMN 2.0國際標(biāo)準(zhǔn)、各大流行數(shù)據(jù)庫、集群擴(kuò)展等,包括對(duì)云平臺(tái)所需的多租戶特性也已有原生支持。
InfoQ上2012年4月就已有過一篇推薦Activiti的文章,本文則是筆者近2個(gè)月實(shí)戰(zhàn)Activiti的一篇簡(jiǎn)書&共享。
背景概述
目標(biāo) ------------------------------------
- 用戶能方便地修改流程定義的細(xì)節(jié)(例如改分歧條件、復(fù)制添加已有的類似節(jié)點(diǎn)),只要不涉及新的表單/服務(wù)/編程、就無需委托開發(fā)人員出馬。
- 能以REST服務(wù)方式調(diào)用,與現(xiàn)有業(yè)務(wù)系統(tǒng)銜接。比如這次的業(yè)務(wù)系統(tǒng)是.Net架構(gòu),而Activiti則是Java開源引擎,但因?yàn)樘峁┝薘EST API接口(且易于擴(kuò)展)、能以JSON/XML方式完成輕便的數(shù)據(jù)交互。
- 支持BPMN 2.0國際標(biāo)準(zhǔn)(具體后述)。標(biāo)準(zhǔn)統(tǒng)一之爭(zhēng)雖然往往夾雜著企業(yè)各自的便利/利益,但畢竟有其行業(yè)合理性與權(quán)威性(例如繁雜的醫(yī)療/金融/物流領(lǐng)域),在行業(yè)出現(xiàn)新的需求而升級(jí)標(biāo)準(zhǔn)時(shí)、只需升級(jí)符合新標(biāo)準(zhǔn)的引擎就能適應(yīng)需求。
成果 ------------------------------------
-
打通基于REST API接口的 工作流生命周期(不足部分做定制補(bǔ)充)
- 流程設(shè)計(jì)模型:一覽、編輯(有模型編輯器)、部署(+允許啟動(dòng)者)-->流程定義
- 流程定義:一覽、掛起/激活、啟動(dòng)-->流程實(shí)例
- 流程實(shí)例:一覽、掛起/激活、查看(當(dāng)前狀態(tài))
- 任務(wù):一覽(我的任務(wù))、簽收/完成(表單變量-->流程變量)
- 監(jiān)聽器:觸發(fā)、處理(e.g.發(fā)送任務(wù)通知郵件)
-
不包括
- 表單編輯器:Activiti并不帶表單編輯器(收費(fèi)版中有),內(nèi)置+外置兩種表單支持方式能滿足入門級(jí)業(yè)務(wù)需求,但多數(shù)需集成開發(fā)。
- 郵件模板編輯器:郵件模板的可視化編輯、也不在工作流引擎的負(fù)責(zé)范圍之內(nèi),需根據(jù)業(yè)務(wù)需求定制開發(fā)。
資料 ------------------------------------
<a id="reference" name="reference"></a>
調(diào)研、定制中以下一些資料較為有用,(感謝相關(guān)作者&)已有相關(guān)基礎(chǔ)的讀者可據(jù)此推斷本文的參考價(jià)值。
- 官方文檔(含REST API):http://www.activiti.org/userguide/
漢化文檔:http://www.mossle.com/docs/activiti - 中文社區(qū)::咖啡兔:http://www.kafeitu.me/activiti.html
- 引擎應(yīng)用項(xiàng)目范例:
官方:activiti-explorer(一分鐘入門), activiti-rest(REST API)
咖啡兔:kft-activiti-demo - 開發(fā)環(huán)境構(gòu)筑:
使用Eclipse構(gòu)建Maven項(xiàng)目 (step-by-step)
Maven + Eclipse + Tomcat - 開啟項(xiàng)目調(diào)試之旅 - 引擎源碼:https://github.com/Activiti/Activiti
引擎類庫API:http://www.activiti.org/javadocs/index.html
工作流概念+BPMN標(biāo)準(zhǔn)
如果希望直接將Demo跑起來邊看邊理解的話,可以跳到下一節(jié),否則就先隨我把概念部分混個(gè)眼熟。
工作流概念
制作了一張工作流在生命周期中不同階段/環(huán)節(jié)間切換的關(guān)系圖:
- REST API:在此特指Activiti引擎的RESTful接口、以及另行擴(kuò)展的接口,提供所有工作流解析/處理的核心功能
- 模型編輯器:在此特指Activiti Modeler,也可選用其他來做定制
- 流程設(shè)計(jì)模型:工作流在此處于設(shè)計(jì)環(huán)節(jié),可被可視化地編輯調(diào)整(如圖1)。其中重要的元素,包括用戶任務(wù)(與表單畫面銜接,接受用戶的選擇/承認(rèn)/否決之類的輸入;是活動(dòng)節(jié)點(diǎn)中最主要的一種,參見BPMN2.0標(biāo)準(zhǔn))、監(jiān)聽器(響應(yīng)各種任務(wù)/活動(dòng)節(jié)點(diǎn)上的事件(e.g.已被簽收/已被辦理)、進(jìn)行不同處理(e.g.按照特定模板發(fā)送通知郵件))。
- 流程定義:工作流在此處于“已部署”環(huán)節(jié),以一個(gè)“請(qǐng)假流程”為例的話、一旦被部署、有權(quán)限的用戶就可以啟動(dòng)/發(fā)起該流程申請(qǐng)請(qǐng)假了(誕生一個(gè)“流程實(shí)例”、見后),流程定義的內(nèi)容已不可被編輯(只可被掛起/激活),如需做修改、只能對(duì)流程設(shè)計(jì)模型做編輯、并再次部署。換句話說,流程定義、是處于流程從設(shè)計(jì)環(huán)節(jié)到運(yùn)行階段之間、相對(duì)穩(wěn)定的中間環(huán)節(jié)。
- 流程實(shí)例:工作流至此已進(jìn)入“運(yùn)行中”階段。一個(gè)已經(jīng)被啟動(dòng)、進(jìn)入“運(yùn)行中”階段的工作流,就好比一個(gè)“狀態(tài)機(jī)”,可能處在不同活動(dòng)節(jié)點(diǎn)的不同狀態(tài)上(還可能并發(fā))、直至結(jié)束。當(dāng)工作流運(yùn)行到一個(gè)用戶任務(wù)節(jié)點(diǎn)(比如“總經(jīng)理/副總經(jīng)理承認(rèn)”)、而該任務(wù)的候選辦理者有多人時(shí),其中之一(比如副總經(jīng)理)就可以“簽收”,然后“辦理”該任務(wù)(也可以在界面上隱匿“簽收”、一律只顯示成“辦理”)。
- 流程狀態(tài)跟蹤器:對(duì)“運(yùn)行中”階段的工作流當(dāng)前所處的活動(dòng)節(jié)點(diǎn)、狀態(tài)做出可視化標(biāo)識(shí)。使用官方組件Diagram Viewer會(huì)很方便,也可使用其他方法。
- 歷史流程實(shí)例:已經(jīng)結(jié)束的流程,仍可被查詢到(包括其中的用戶任務(wù)、選項(xiàng)輸入),這會(huì)有助于事后分析統(tǒng)計(jì)。
注意:5.16版本開始還新增了“事件日志機(jī)制”,可采用大數(shù)據(jù)存儲(chǔ)分析(參見官方文檔)。
BPMN 2.0標(biāo)準(zhǔn)
<a id="bpmn2" name="bpmn2"></a>
先上一張諾貝爾頒獎(jiǎng)盛典籌備工作的設(shè)計(jì)模型圖:
基于工業(yè)標(biāo)準(zhǔn)的工作流設(shè)計(jì),是滿足不同領(lǐng)域客戶復(fù)雜業(yè)務(wù)需求的有力保障。
BPMN2.0標(biāo)準(zhǔn)工作流的模型編輯與保存,可以在瀏覽器App形式的Activiti Modeler中進(jìn)行(并可以嵌入你的業(yè)務(wù)系統(tǒng)中),在此直接對(duì)照著Modeler的圖標(biāo)對(duì)主要的BPMN元素做簡(jiǎn)單介紹:
-
開始事件(Start Event)
<提醒>- 啟動(dòng)者變量(initiator):一個(gè)開始事件的
initiator
屬性中所保存的、只是一個(gè)“用于記錄啟動(dòng)者user_id的變量名稱”,可以算是個(gè)“變量聲明”屬性。當(dāng)一個(gè)用戶、例如"kermit"(官方范例用戶)、啟動(dòng)了這一工作流時(shí)(在流程設(shè)計(jì)模型被部署、成為一個(gè)“流程定義”后、它就能被某一用戶啟動(dòng)成為一個(gè)“流程實(shí)例”),"kermit"就會(huì)被記錄到在initiator
中聲明的變量、比如"starter"中,那樣就會(huì)有一個(gè)流程變量starter被賦值為"kermit",并能在后續(xù)的活動(dòng)節(jié)點(diǎn)中(e.g.用戶任務(wù))被用于任務(wù)辦理人(assignee)屬性的賦值表達(dá)式(e.g.${starter}
)、或是其他的例如分歧條件表達(dá)式中(${starter=="kermit"}
)。詳見官方文檔。
開始事件的initiator
屬性是唯一用于保存“變量名稱”的,在整個(gè)流程級(jí)別、或是任務(wù)級(jí)別、都存在著類似于candidateUsers
/candidateStarterUsers
的屬性,它們保存的直接就是允許啟動(dòng)流程/辦理任務(wù)的用戶ID(允許多個(gè))。詳見官方文檔。
注意:Activiti Modeler的當(dāng)前版本還不支持設(shè)置流程級(jí)別的candidateStarterUsers
/candidateStarterGroups
(允許啟動(dòng)者),Eclipse插件Activiti Designer中可以,但僅供開發(fā)者使用;因此需要通過擴(kuò)展REST API、在“流程設(shè)計(jì)模型::部署”、或是“流程定義::設(shè)置”環(huán)節(jié)進(jìn)行設(shè)置。
- 啟動(dòng)者變量(initiator):一個(gè)開始事件的
-
活動(dòng)節(jié)點(diǎn)(Activity)
<提醒>- 用語:Activity通常被翻譯成“節(jié)點(diǎn)”、包括了各種任務(wù)(Task)、順序流(Sequence Flow)、子流程(Sub-Process)等,一切具備生命周期狀態(tài)的元素。國內(nèi)也有使用“步驟”(Step)的,但其實(shí)Step的定義似乎應(yīng)是“節(jié)點(diǎn)(Activity)+網(wǎng)關(guān)(Gateway)”(@ultimus.com)。
-
humanPerformer
vsactiviti:candidateUsers
:這里將引入BPMN標(biāo)準(zhǔn)屬性與Activiti擴(kuò)展屬性的對(duì)比與互換的話題。
1.語法上的不同:Activiti擴(kuò)展確有其化繁為簡(jiǎn)的優(yōu)勢(shì),請(qǐng)比較兩段定義("activiti:"打頭的即為擴(kuò)展),
a. BPMN標(biāo)準(zhǔn)屬性
<process> ... <userTask id='theTask' name='important task' > <potentialOwner> <resourceAssignmentExpression> <formalExpression>user(kermit), group(management)</formalExpression> </resourceAssignmentExpression> </potentialOwner> </userTask> </process>
b. Activiti擴(kuò)展屬性
<process> ... <userTask id="theTask1" name="my task" activiti:assignee="kermit" /> <userTask id="theTask2" name="my task" activiti:candidateUsers="kermit, gonzo" /> <userTask id="theTask3" name="my task" activiti:candidateGroups="management, accountancy" /> </process>
2.標(biāo)準(zhǔn)與擴(kuò)展沖突的解決:詳見官方文檔
a.Activiti擴(kuò)展的前提是:總有簡(jiǎn)單的方法、轉(zhuǎn)換成標(biāo)準(zhǔn)方法
b.Activiti擴(kuò)展的目標(biāo)是:最終把它們加入到下一版本的BPMN規(guī)范中, 或者至少可以引起對(duì)特定BPMN結(jié)構(gòu)的討論 結(jié)構(gòu)(Structural)
<非重點(diǎn)>網(wǎng)關(guān)(Gateway)
<非重點(diǎn)>結(jié)束事件(End Event)
<非重點(diǎn)>-
連接(Connecting Object)
- 順序流(sequence flow):用于連接各種事件、活動(dòng)節(jié)點(diǎn)、網(wǎng)關(guān)的連線。除了網(wǎng)關(guān)會(huì)對(duì)流出的順序流有特殊限制之外(例如排他網(wǎng)關(guān))、BPMN缺省的流出順序流(若有復(fù)數(shù)個(gè)的話)是“并發(fā)”的、即同時(shí)發(fā)出。可以對(duì)順序流做條件式限制,即添加一個(gè)conditionExpression元素:
<sequenceFlow id="flow" sourceRef="theStart" targetRef="theTask"> <conditionExpression xsi:type="tFormalExpression"> <![CDATA[${order.price > 100 && order.price < 250}]]> </conditionExpression> </sequenceFlow>
注意:目前條件表達(dá)式只能使用UEL(統(tǒng)一表達(dá)式語言),詳見官方文檔。
開發(fā)環(huán)境構(gòu)筑:Eclipse + Maven + Tomcat + Activiti
<a id="env" name="env"></a>
我所使用的開發(fā)環(huán)境:
- Eclipse Java EE IDE for Web Developers, Luna
- Apache Maven 3.2.3
- Apache Tomcat 7
- Activiti 5.16.3(5.16.4起設(shè)置方法有變化且官方文檔未同步更新):請(qǐng)下載開源代碼,而非其運(yùn)行版
<提醒>
-
Maven的基本配置對(duì)初學(xué)者來說會(huì)多有挫折
Maven本身是個(gè)好東西,開源項(xiàng)目很多對(duì)其他開源項(xiàng)目有大量的依賴引用,難免存在交叉引用、版本覆蓋、重復(fù)下載等繁瑣問題,而Maven工具&標(biāo)準(zhǔn)就是為了應(yīng)對(duì)這個(gè)而產(chǎn)生的(并成為Apache頂級(jí)項(xiàng)目)。
可是,主要陷阱在于:- 國內(nèi)糟糕的網(wǎng)絡(luò)訪問情況...很多中央倉庫鏡像會(huì)出現(xiàn)超時(shí)
- 部分依賴庫會(huì)不存在于中央倉庫中,需手動(dòng)下載后放入本地倉庫中
- Maven可以用命令行方式運(yùn)行、也可用Eclipse插件方式來使用,但有時(shí)兩者的結(jié)果會(huì)不盡相同...
解決方法:
- 找到好文檔,從新建/導(dǎo)入一個(gè)Maven項(xiàng)目、編譯&運(yùn)行它開始
使用Eclipse構(gòu)建Maven項(xiàng)目 (step-by-step)
注意: 其中有些章節(jié)為【必須讀】有些【不必讀】
2.Maven的安裝和配置
2.2.配置
2.2.1.修改默認(rèn)的本地倉庫位置:必須讀
重新設(shè)置本地倉庫位置,使得你可以更方便地 a.將需要手動(dòng)下載的依賴庫放置其中 b.備份已經(jīng)成功構(gòu)建的本地倉庫、用于環(huán)境移植
2.2.2.修改默認(rèn)的中央倉庫鏡像:不必讀
通常是根據(jù)項(xiàng)目不同,修改項(xiàng)目pom.xml文件中的<repository>配置
4.使用Maven來構(gòu)建Web項(xiàng)目
4.1.創(chuàng)建Maven的web項(xiàng)目:不必讀
我們需要的是導(dǎo)入Activiti官方項(xiàng)目、而非新建,具體方法見后“導(dǎo)入已有項(xiàng)目”。
4.2.使用Maven添加項(xiàng)目依賴包:不必讀
導(dǎo)入項(xiàng)目的pom.xml中已經(jīng)配置好了,除非你有新的依賴包需求。 - 使用國內(nèi)經(jīng)驗(yàn)者已有的“私服”:比如咖啡兔的Maven私服,具體POM設(shè)置見后“導(dǎo)入已有項(xiàng)目”。
- 小組開發(fā)時(shí),可考慮在局域網(wǎng)里搭一個(gè)“私服”(自己的中央倉庫):參考Nexus。
- <a id="import" name="import"></a>導(dǎo)入已有項(xiàng)目:Activiti-activiti-5.16.3\modules\activiti-webapp-explorer2項(xiàng)目
Eclipse中導(dǎo)入的方法:File > Import... > Maven::Existing Maven Projects > Next > 選中項(xiàng)目根目錄下的pom.xml文件即可。
-
Maven配置修改(pom.xml文件編輯):m2eclipse插件已經(jīng)裝好的情況下,pom.xml會(huì)自動(dòng)被在表單編輯器中打開(不過通常只需在文本方式下編輯、表單方式下輔助閱讀)
a. 添加中央倉庫鏡像,比如咖啡兔的Maven私服(不過有時(shí)也會(huì)斷線、需添加其他鏡像)<repositories> ...... <repository> <id>kafeitu</id> <url>http://maven.kafeitu.me/nexus/content/groups/public</url> </repository> </repositories>
b. 添加依賴庫,比如數(shù)據(jù)庫驅(qū)動(dòng)(這里不需要修改,使用默認(rèn)h2數(shù)據(jù)庫即可、且無需下載安裝h2(已自帶,否則反而要改版本號(hào)))
<dependencies> ...... <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> </dependencies>
-
數(shù)據(jù)庫連接修改:src/main/resources/db.properties
(可選)給出一個(gè)改用Microsoft SQL Server的例子:db=mssql jdbc.driver=com.microsoft.sqlserver.jdbc.SQLServerDriver jdbc.url=jdbc:sqlserver://localhost:1433;databaseName=activiti jdbc.username=sa jdbc.password=
不過相應(yīng)的,pom.xml中的數(shù)據(jù)庫驅(qū)動(dòng)依賴庫就也需要添改:
<dependencies> ...... <dependency> <groupId>com.microsoft.sqlserver</groupId> <artifactId>sqljdbc4</artifactId> </dependency> </dependencies>
-
工作流引擎配置修改:src/main/webapp/WEB-INF/activiti-standalone-context.xml
<bean id="demoDataGenerator" class="org.activiti.explorer.demo.DemoDataGenerator" init-method="init"> ...... <property name="createDemoUsersAndGroups" value="true" /> <property name="createDemoProcessDefinitions" value="true" /> <property name="createDemoModels" value="true" /> </bean> <bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration"> ...... <property name="databaseSchemaUpdate" value="true" /> </bean>
Activiti工作流引擎配置、目前(5.16.3)采用Spring框架方式加載,以實(shí)現(xiàn)不同擴(kuò)展的靈活配置。上手階段所需做的修改,主要就是如上屬性(分別用于自動(dòng)生成Demo數(shù)據(jù),以及比對(duì)引擎與數(shù)據(jù)庫之間的版本、自動(dòng)新建/升級(jí)數(shù)據(jù)庫),更多可參考官方文檔。
Maven項(xiàng)目刷新:Project右鍵菜單 > Maven > Update Project...,對(duì)于Maven項(xiàng)目來說,單單F5在文件系統(tǒng)層面刷新同步是不夠的。
-
Maven項(xiàng)目編譯:
- 命令行方式:較為簡(jiǎn)便,在你設(shè)置了PATH(包含
%MAVEN_HOME%\bin
)之后,便可在命令行窗口中、進(jìn)入到Maven項(xiàng)目的根目錄(pom.xml所在地)、然后執(zhí)行Maven的系列命令了,如:
mvn compile - m2eclipse插件方式:更為簡(jiǎn)便,不過建議先搭建起調(diào)試環(huán)境(不同于.Net中attach到進(jìn)程的方式,見后),然后直接在運(yùn)行/調(diào)試前自動(dòng)編譯即可。
- 命令行方式:較為簡(jiǎn)便,在你設(shè)置了PATH(包含
- 導(dǎo)入已有項(xiàng)目:咖啡兔Demo,有了導(dǎo)入官方范例的基礎(chǔ),導(dǎo)入其他例子就很簡(jiǎn)單。
- 完整步驟(推薦用Maven方式):咖啡兔Demo Wiki
-
基于Maven的Tomcat Web項(xiàng)目的斷點(diǎn)調(diào)試也會(huì)有一番周折
解決方法:- 找到好文檔,比如:Maven + Eclipse + Tomcat - 開啟項(xiàng)目調(diào)試之旅,這篇是可行的。網(wǎng)上其他還有很多走彎路的方法。
定制補(bǔ)充REST API接口打通生命周天(周期)
經(jīng)歷過之前的準(zhǔn)備,便可以導(dǎo)入這1.5個(gè)人月調(diào)研開發(fā)的成果項(xiàng)目了,上圖,
如果說activiti-explorer和kft-activiti-demo都是界面友好、前端與后臺(tái)并重的話,這次項(xiàng)目則純粹集中在后臺(tái)(封裝Activiti的引擎功能),因?yàn)榍岸私缑妫òū韱尉庉嬈鳌⑧]件末班編輯器)將由.Net頁面(一個(gè)企業(yè)內(nèi)部門戶網(wǎng)站)完成,銜接關(guān)系可參見圖2(生命周期)。
項(xiàng)目的完成可劃分為6個(gè)步驟:
-
Step1. 在官方范例activiti-explorer中嵌入activiti-rest
<a id="combine_explorer_rest" name="combine_explorer_rest"></a>
前文推薦了導(dǎo)入官方范例項(xiàng)目activiti-explorer作為入門(一分鐘入門+導(dǎo)入已有項(xiàng)目),在此第一步,我們就是在其基礎(chǔ)上、加上對(duì)REST API的支持,也就是嵌入另一個(gè)官方范例activiti-rest。
對(duì)于缺少J2EE/Spring項(xiàng)目經(jīng)驗(yàn)的人可能會(huì)稍有障礙,其實(shí)兩個(gè)官方范例項(xiàng)目合并的主要破解點(diǎn)如下:- web.xml 中的Listener配置:
src\main\webapp\WEB-INF\web.xml
activiti-webapp-explorer2(activiti-explorer的項(xiàng)目名)中的Listener配置,分別是Spring框架的ContextLoaderListener和RequestContextListener;而當(dāng)你打開activiti-webapp-rest2(activiti-rest的項(xiàng)目名)則會(huì)發(fā)現(xiàn),Listener只有org.activiti.rest.common.servlet.ActivitiServletContextListener
,這是一個(gè)位于activiti-common-rest依賴包中的類、只用來啟動(dòng)和回收一個(gè)ProcessEngine類的實(shí)例。結(jié)論:我們無需合并該Listener,因?yàn)閍ctiviti-explorer已經(jīng)采用Spring的Bean方式來加載ProcessEngine實(shí)例。
- web.xml 中的Servlet配置:
src\main\webapp\WEB-INF\web.xml
官方范例(5.16.3為止)采用的是Restlet方式的REST實(shí)現(xiàn),因此用到的servlet是org.restlet.ext.servlet.ServerServlet
。我們將來擴(kuò)展REST API將采用Spring MVC方式(后續(xù)),但對(duì)已有的REST API(5.16.3為止)、我們只能保留現(xiàn)有的Restlet方式入口,不過需注意,activiti-explorer范例與activiti-rest范例所用的org.restlet.application
(Restlet方式下的入口類)并不相同,前者用的是一個(gè)只做了簡(jiǎn)單Router綁定的自定義類org.activiti.rest.explorer.application.ExplorerRestApplication
、僅供modeler和diagram使用,后者(activiti-rest)用的則是包含較嚴(yán)格認(rèn)證機(jī)制的org.activiti.rest.service.application.ActivitiRestServicesApplication
,也因此如官方文檔所述、標(biāo)準(zhǔn)REST API的調(diào)用都是需要經(jīng)過Basic認(rèn)證的。結(jié)論:合并兩個(gè)官方范例的
org.restlet.application
類,即使得activiti-explorer中的Restlet Application類、繼承org.activiti.rest.service.application.ActivitiRestServicesApplication
,覆蓋其createInboundRoot()
方法、補(bǔ)充入activiti-rest所需的RestServicesInit.attachResources(router);
。
- web.xml 中的Listener配置:
-
Step2. 再加入kft-activiti-demo的配置
如從“資料”中文章可以看到,咖啡兔的范例,貢獻(xiàn)了很多國內(nèi)項(xiàng)目級(jí)別的成熟經(jīng)驗(yàn),比如:- 中文化/國際化UTF-8
- 對(duì)官方標(biāo)準(zhǔn)REST API的調(diào)用、和不足之處的補(bǔ)充范例
- Spring MVC方式的REST API擴(kuò)展(盡管其結(jié)果也多以Spring MVC的ModelView方式返回、但入口與內(nèi)部處理都足以借鑒)
這一步,我們所需做的就是把咖啡兔Demo中可借鑒的配置部分嵌入自己的項(xiàng)目中:
- pom.xml
最有價(jià)值的一段,就是借用咖啡兔的Maven私服(參見“導(dǎo)入已有項(xiàng)目”),否則在國內(nèi)網(wǎng)絡(luò)現(xiàn)狀下甚至可能寸步難行。
其次便是對(duì)于Spring框架、持續(xù)層的支持等等,值得注意的是,activiti-explorer作為activiti官方范例、是繼承了其“上層”activiti-root
的Maven配置的,而我們自己的項(xiàng)目、應(yīng)該并不會(huì)希望建立這種繼承,這種情況下,參照kft-activiti-demo的POM便是個(gè)safe的選擇。 - web.xml
咖啡兔Demo同樣使用Spring Bean方式加載,因此不需修改;同時(shí)因?yàn)榭Х韧肈emo啟用了Spring MVC架構(gòu)、而這是我們將來擴(kuò)展REST API時(shí)同樣可以用的,因此需要搬運(yùn)如下這一段:
其中<servlet> <servlet-name>springServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
spring-mvc.xml
將負(fù)責(zé)具體的Spring MVC配置(后述)。 - activiti-context.xml
Spring框架缺省調(diào)用的上下文配置文件是applicationContext.xml
,咖啡兔Demo是直接把Activiti引擎所需要的配置寫在其中的,activiti-explorer范例則是使用<import>
把后臺(tái)、前端的配置分在兩個(gè)不同的文件中(activiti-standalone-context.xml
和activiti-ui-context.xml
)。作為我們自己的項(xiàng)目,只需合并在一個(gè)activiti-context.xml
中即可。 - spring-mvc.xml
不長(zhǎng),直接留用,只需把<context:component-scan>
中的base-package
改成自己的項(xiàng)目包就可以;定制擴(kuò)展的REST API將以一個(gè)個(gè)Spring MVC Controller的形式實(shí)現(xiàn),這里的配置將實(shí)現(xiàn)對(duì)項(xiàng)目包下帶有@Controller
標(biāo)簽的類的自動(dòng)掃描、實(shí)例化。
-
Step3. REST API學(xué)習(xí)
RESTful服務(wù)的好處,如前所述、主要是能夠使得業(yè)務(wù)系統(tǒng)所依賴的技術(shù)與工作流引擎所依賴的技術(shù)(Java)之間解耦。當(dāng)Activiti引擎作為一個(gè)RESTful接口的Web服務(wù)運(yùn)行時(shí)(輸入輸出可以都是JSON數(shù)據(jù)),瀏覽器、桌面、移動(dòng)客戶端、基于Java、.Net、Python都可與之交互。- URL風(fēng)格
僅以獲取“流程設(shè)計(jì)模型一覽”為例,這是一個(gè)標(biāo)準(zhǔn)REST API中就有的功能(官方文檔),如果加上Spring Servlet的映射({servlet_mapping}
,在web.xml中已配置)等就形成完整的HTTP請(qǐng)求路徑:
配置時(shí)則只需GET http://{host:port}/{app_name}/{servlet_mapping}/repository/models
{servlet_mapping}
之后的部分,例如GET repository/models //表示獲取“一覽” GET repository/models/{modelId} //表示獲取“某個(gè)個(gè)體” POST repository/models //表示“新建”一個(gè)個(gè)體 PUT repository/models/{modelId} //表示“修改”某個(gè)個(gè)體
- 官方的Restlet方式REST實(shí)現(xiàn)
官方的REST API實(shí)現(xiàn)(后臺(tái))就在activiti-rest范例中。URL模式與實(shí)現(xiàn)方法之間的映射,位于src\main\java\org\activiti\rest\service\application\RestServicesInit.java
中:
進(jìn)一步打開router.attach("/repository/models", ModelCollectionResource.class); router.attach("/repository/models/{modelId}", ModelResource.class); ......
ModelCollectionResource
類,可見Restlet方式的注釋(Annotation)聲明:
Restlet本身我們將不再沿用,但從映射所關(guān)聯(lián)到的各個(gè)方法體中我們可以查找到各個(gè)功能的幕后實(shí)現(xiàn)。@Get("json") public DataResponse getModels() {......} @Post public ModelResponse createModel(ModelRequest request) {......}
- URL風(fēng)格
-
Step4. REST API擴(kuò)展
對(duì)于標(biāo)準(zhǔn)REST API無法滿足的需求,就需要自行擴(kuò)展來實(shí)現(xiàn)了。擴(kuò)展會(huì)涉及到將來Activiti引擎升級(jí)時(shí)的兼容問題,不過實(shí)際開發(fā)中發(fā)現(xiàn)、REST接口層的代碼量相對(duì)來說是很薄的、即便其所用到的表層API發(fā)生了變化修改量也不會(huì)很大。- URL風(fēng)格
擴(kuò)展API的{servlet-mapping}
部分是與標(biāo)準(zhǔn)API有所區(qū)別的(比如一個(gè)是service
另一個(gè)就是service-ext
);其他URL風(fēng)格方面則盡量與官方REST API保持一致。 - Spring MVC方式REST實(shí)現(xiàn)
在Spring MVC方式中,REST URL模式與實(shí)現(xiàn)方法之間的映射,直接位于各個(gè)實(shí)現(xiàn)方法所在的類中,包括位于類的聲明、方法的聲明之上的注釋標(biāo)簽(@RequestMapping(...)
)。
以“流程設(shè)計(jì)模型部署”為例,
可以看到,deploy()方法所對(duì)應(yīng)的URL模式是@Controller @RequestMapping(value = "/service-ext/repository/models") public class ModelController { ...... @RequestMapping(value = "deploy/{modelId}") public String deploy(@PathVariable("modelId") String modelId, @RequestParam(value="starterUser", required=false) String starterUserId, @RequestParam(value="starterGroup", required=false) String starterGroupId, RedirectAttributes redirectAttributes) {......} }
/service-ext/repository/models/deploy/{modelId}
,其中{modelId}
部分將被直接解析成方法參數(shù)modelId
,另外有兩個(gè)方法參數(shù)starterUserId
、starterUserGroup
則對(duì)應(yīng)的是URL中的QueryString部分(且是可選的),一個(gè)有效的HTTP請(qǐng)求可能是:
更完整的Spring MVC語法文檔可參考這個(gè)鏈接,包括GET http://localhost:8080/app/service-ext/repository/models/deploy/1111?starterUser=kermit
@ResponseBody
的使用(方便的將對(duì)象返回值轉(zhuǎn)成JSON等格式)、produces="application/json"
、consumes="application/json"
等等。 - 官方代碼借鑒
(略述)可根據(jù)與需求近似的REST API、找到與相關(guān)URL模式相映射的實(shí)現(xiàn)方法。activiti-engine核心包下的org.activiti.engine.impl.RepositoryServiceImpl
等類是關(guān)鍵類,org.activiti.engine.impl.interceptor.CommandExecutor
則是引擎采用的“Command設(shè)計(jì)模式”的執(zhí)行者,各種工作流操作都會(huì)被歸結(jié)為相應(yīng)的某種Command<Object>
、在設(shè)置好特定的操作上下文CommandContext
后、就由CommandExecutor
負(fù)責(zé)執(zhí)行。一般我們并不需要修改到Command設(shè)計(jì)模式的領(lǐng)域,而只需參考activiti-rest包中是如何調(diào)用其他包的代碼就夠。
- URL風(fēng)格
-
Step5. 測(cè)試界面(客戶端)
如上,我們?cè)诤笈_(tái)每定制擴(kuò)展一個(gè)REST API(e.g.“流程設(shè)計(jì)模型部署”),相應(yīng)都需要在前端(Web)測(cè)試頁面(如圖5)中增加一組測(cè)試。
注意:測(cè)試頁面其實(shí)相當(dāng)于是“業(yè)務(wù)系統(tǒng)”的角色,是可以與后臺(tái)(工作流引擎)分隔為兩個(gè)WebApp項(xiàng)目的。
測(cè)試界面并未使用jquery-ui、blueprint等前端技術(shù),只是簡(jiǎn)單的JSP(其實(shí)純HTML/JS都完全可以,因?yàn)橹恍鐰jax通信+JSON包裝/解析)。給出一段調(diào)用“流程設(shè)計(jì)模型部署”API的HTML鏈接:<a id="deploy_link" href="${ctx}/service-ext/repository/models/deploy/<%=deploy_model_id%>?starterUser=<%=starter_user_id%>"> 流程設(shè)計(jì)模型部署 </a>
再給出一段部署后、已進(jìn)入工作流運(yùn)行時(shí)階段(runtime)時(shí)、模擬用戶提交某份表單完成某個(gè)用戶任務(wù)的JS代碼:
<script type="text/javascript">
function onhandle(taskId){
var request = {
action: "complete",
assignee: "${userId}",
variables: [
{name: "complete_key_001", value: "complete_value_001"},
{name: "complete_key_002", value: "complete_value_002"},
{name: "complete_key_003", value: "complete_value_003"}
]
}
var request_json = JSON.stringify(request);
ajaxRequest("POST", "${ctx}/service-ext/runtime/tasks/complete/" + taskId, request_json, null);
}
function ajaxRequest(method, url, request, callback) {
var xhr = new XMLHttpRequest();
xhr.open(method, url, true);
if(url.indexOf("/service/")>0){
//standard activiti REST api requires 1.Auth(base64) 2.POST 3.JSON header
xhr.setRequestHeader("Authorization", "Basic " + base64.encode("kermit"+":"+""));
if("POST" === method.toUpperCase() ){
xhr.setRequestHeader("Content-Type","application/json");
}
}
else if(url.indexOf("/service-ext/runtime/process-instances")>0)
xhr.setRequestHeader("Content-Type","application/json");
else if(url.indexOf("/service-ext/runtime/tasks")>0)
xhr.setRequestHeader("Accept", "application/json");
xhr.onload = (callback!=null) ? callback : function() {
debugger;
};
xhr.send(request);
}
</script>
- Step6. 貫通生命周期
根據(jù)你所服務(wù)的“業(yè)務(wù)系統(tǒng)”的不同,會(huì)對(duì)REST服務(wù)的接口提出不同的需求,甚至?xí)绊懙焦ぷ髁魃芷诘?strong>責(zé)任人劃分:例如,流程設(shè)計(jì)模型究竟是能從官方Modeler直接兼容地保存到數(shù)據(jù)庫、只需一個(gè)id即可部署,還是需要采用其他的Modeler編輯、然后以BAR/ZIP包方式直接部署、因此參數(shù)也就變成了文件流;或者說流程實(shí)例被啟動(dòng)后,某個(gè)用戶名下的“我的(用戶)任務(wù)”、究竟是點(diǎn)擊后就“簽收+辦理”并彈出表單、還是分成兩步進(jìn)行。
當(dāng)我們逐一完成如圖5的若干項(xiàng) 擴(kuò)展或標(biāo)準(zhǔn)REST API調(diào)用,圖2中展示的工作流生命周期也就能被打通了。工作流運(yùn)行階段、根據(jù)用戶輸入的不同、會(huì)進(jìn)行不同的流轉(zhuǎn),可以通過官方范例中已有的流程狀態(tài)跟蹤器“Diagram Viewer”查看進(jìn)度如下:
圖6:運(yùn)行時(shí)階段的工作流狀態(tài)查看
上圖紅線部分顯示了,“填寫申請(qǐng)”確認(rèn)提交后經(jīng)過了“直屬上級(jí)審批”、但在“人事審批”環(huán)節(jié)被否決,又重新進(jìn)入了“填寫申請(qǐng)”環(huán)節(jié)。
小結(jié)
本文記錄了近2個(gè)月期間對(duì)開源工作流引擎Activiti做調(diào)研、貫通整個(gè)工作流生命周期(如圖2)的過程,重點(diǎn)在于標(biāo)識(shí)出同樣沒有相關(guān)基礎(chǔ)的技術(shù)人員所可能碰到的陷阱、疑惑,希望能節(jié)省你的時(shí)間(少加幾晚上班陪陪家人~);如有不正確/并非最優(yōu)解的地方、也請(qǐng)有經(jīng)驗(yàn)者指正。
因?yàn)樯婕肮椭骼妫酥R(shí)點(diǎn)之外、完整源碼就不能共享了。同時(shí),因?yàn)闃I(yè)務(wù)系統(tǒng)中已包含自主開發(fā)的“表單編輯器”與“郵件模板編輯器”,這兩部分內(nèi)容(包括與引擎之間的具體銜接)不涵蓋在本文范圍之內(nèi)。