揭祕jbpm流程引擎內核設計思想及構架

1     前言
2.1      概念的基礎
2.2      環境的基礎
4.1      模型與定義對象
4.2      調度機制與算法
4.3      執行機制與狀態
4.4      實例對象與執行環境
6.1      首先解決如何形式化描述一個流程的問題
6.2      抽象的節點(Node)和轉移(Transition)
6.3      流程:節點與轉移的組合
6.4      節點的類型和擴展
7.1      吸納自Petri Net思想
7.2      Token的推進
7.3      非常簡單的調度機制
8.1      執行機制
8.2      分支處理
 

1       前言

 

       流程引擎內核僅是“滿足Process基本運行”的最微小結構,而整個引擎則要複雜很多,包括“狀態存儲”、“事件處理”、“組織適配”、“時間調度”、“消息服務”等等外圍的服務性功能。引擎內核,僅包含最基本的對象和服務,以及用於解決流程運行問題的調度機制和執行機制。
       如果,你掌握了一個流程引擎的靈魂,你纔有能力理解它的全部。否則,一個引擎對你來說,可能只是一個複雜的結構,豐富多彩API、令人眼花繚亂的“功能”和“服務”而已。
 
       本身工作流這個領域就是一個很“狹窄”的領域,國內的廠商也不是很多,其中有部分實現技術並不弱。但可能涉於安全等因素,並沒有多少技術人員探討“深度的工作流技術實現問題”。而廣大的開發愛好者卻還在花費大量的時間在摸索“如何理解工作流、如何應用工作流”。 所以在此之前,國內尚未有一篇技術文章探討工作流引擎內核的實現,當然也沒有探討jBpm引擎內核的文章了。在www.javaeye.com 技術站點和我的blog(http://blog.csdn.net/james999)上有幾篇專門探討jbpm應用的文章,對於初步想了解如何使用jbpm的讀者來說,值得看看。
 
       對於這方面的技術分享,開源是個不錯的突破口。
       本篇就是以jBpm爲實例,來詮釋工作流引擎的內核設計思路和結構。但是這僅僅是從jBpm的實現角度來輔助大家理解,因爲工作流引擎內核的設計、實現是有很多方式:這會因所選的模型、調度算法、推進機制、狀態變遷機制、執行機制等多方面的不一樣,而會差別很大。比如基於Activity Diagram模型的jBpm和基於FSM模型的OSWorkflow引擎內核之間就有很大的差別。
       相比較而言,jBpm的模型比較複雜,而引擎內核實現的比較“精簡”,非常便於大家“由淺入深的理解”。

2       閱讀本篇的基礎準備

       本文的讀者羣主要是面向有一定工作流基本概念的開發人員。所以本文認爲你已經具備瞭如下基本工作流知識:
(1)       初步瞭解工作流系統結構。比如理解工作流引擎在工作流系統中所處的位置和作用
(2)       對流程定義(Process Definition)和流程實例(Process Instance)相關對象有所瞭解。比如理解Process Instance代表什麼,工作項(WorkItem)代表什麼。
       在閱讀本篇的時候,如果你已經搭建了一套jbpm的開發環境,那麼將有助於你更容易理解本篇的很多內容,也便於實際體驗代碼。從www.jbpm.org官方網站下載jbpm-starters-kit開發包,按照其參考手冊,可以很容易在eclipse開發環境中建立項目,效果圖類似如下:

 

3       什麼是流程引擎內核?

       我比較推崇“微內核的流程引擎構架”,並在最近兩三年內寫了兩篇探討此方面的文章:第一篇是寫於05年7月份的《微內核流程引擎架構體系》,第二篇是07年7月份的《微內核過程引擎的設計思路和構架》(受普元《銀彈》雜誌約稿所寫,尚未對外公開)。
       但至今對外闡述引擎內核到底是什麼。
   
 
       正如上面的兩張圖所示,我們可以通過“微內核”的構架來使得流程引擎的結構更加“清晰”。而能否實現“微內核”的根本,則是看你是否能夠設計並抽象出“良好的引擎內核結構”。
 
       很顯然,要想設計出一套結構優良的引擎內核,首要條件就是:明白什麼是引擎內核。
 
       首先我們需要明白引擎是什麼,引擎可以做什麼。這在WfMC的《工作流參考模型》中已經有很詳細的解答,本文不再重複。知道這個僅僅是不夠的,你還需要很清晰的明白如何去“爲流程建模”,而這則在Aalst大師所著的《工作流管理——模型、方法、系統》一書有細緻闡述,本文也不再重複。
       但很可惜,至今尚未有一本專門的書籍來論述“過程建模方法”的,或者說如何利用這些既有的“過程建模方法(諸如FSM、PetriNet、EPC、Activity Diagram等等)”來解決流程問題。這個只能分別查閱相關資料,此處也不敘述。
       因爲文本只講“引擎內核”。
 
       如果我們暫且把那複雜的流程業務性問題,諸如“組織模型分配”、“分支條件計算”、“事件處理”、“消息調度”、“工作項處理”、“存儲”、“應用處理”、以及那些“變態的諸如會籤、回退之類的模型”都統統的拋棄,只留下“最單純的過程性問題”,也就是“解決一個過程運行問題,按秩序的從一個節點到另一個節點的執行”。——這就是引擎內核所關注的根本問題。
       上面這句話,估計會引起很多人“拍磚”。在很多人看來,工作流之所以看起來很“難”,就是因爲這些複雜多變的“業務性問題”都統統綁在一個“引擎”上造成的。
       其實,這是兩個“維度”的問題,也就是“引擎的抽象”和“引擎的應用”這兩個不同維度,不同層面的問題。但這絕不是兩個獨立的問題,“引擎的抽象”的好與壞,直接影響到“引擎的應用”的可複雜度和可支持度,當然我們也不能否認,“引擎的應用”問題也是一個很複雜的問題。但本文是站在“引擎的抽象”這個維度來闡述問題的。對於“引擎的應用”問題,可參考我的前作:2003年11月份的《工作流模型分析》、2003年12月份的《工作流授權控制模型》、2004年7月份的《工作流系統中組織模型應用解決方案》。
       也就是說,本文不是指導大家如何去“使用jbpm”,而是闡述“jbpm的引擎的內核部分是如何構建的”。但本文的主旨不是告訴大家“jBpm是如何設計引擎內核的”,而是以jBpm爲例,來介紹“引擎內核”。
 
       引擎內核所關注的是一個非常“抽象”層面的問題,而不同引擎關注的“一套完整的執行環境”。或者我們可以這麼來說,引擎內核的職責是非常“精簡”的:確保流程按照既有的定義,從一個節點運行到另一個節點,並正確執行當前節點。
       總的來說,引擎內核主要關注四個方面的問題:
(1)       流程定義問題:不是說如何圖形化的定義流程,而是如何用一套定義對象,來詮釋所定義的流程。
(2)       流程調度問題:提供什麼的機制,可以確保流程能夠處理複雜的“流程圖結構”,諸如串行、並行、分支、聚合等等,並在這複雜結構中確保流程從一個節點運行到另一個節點。
(3)       流程執行問題:當流程運行到某個節點的時候,需要一套機制來解決:是否執行此節點,並如何執行此節點的問題,並維持節點狀態生命週期。
(4)       流程實例對象:需要一整套流程實例對象來描述流程實例運行的狀態和結果。

       工作流引擎本身就是一種“base on model”的組件,流程實例的執行都是依賴於所定義的“流程定義”,而工作流引擎則是提供了這樣一種環境,來維持流程實例的運行。
       所以引擎內核,必須提供一套定義對象來描述“流程定義”,並且這些定義對象必須反映出一種“模型”。
       比如jBpm的定義對象,是與其所基於的Activity Diagram模型相對應的。

       引擎內核的另一個重要功能,就是保證流程實例準確的從一個節點運行到另一個節點,而這則需要依賴於一套調度機制。
       引擎的調度機制有很多種實現方法,有的甚至是與“所依賴的模型有關”。但普遍來講,很多引擎都受到Petri Net的影響,而採用token來調度。
       jBpm本身就吸納的token這套機制,當然,與Petri Net的調度機制還是有所區別。我們將在下面的章節詳細介紹。

       經過引擎的調度,實例運行到某個節點了,此時必須必須提供一套機制,來判斷當前節點是否可執行,如果可執行,那麼需要提供一套runtime envrioment來執行節點——這就是引擎的執行機制。
       複雜的流程引擎會依賴於“流程實例狀態”或“活動實例狀態”的約束和變遷來進行處理。之所有有時候我們會把一個流程引擎也叫做“狀態機”,很大程度上也是這個原因。

       每個一個流程實例,必須維護一套屬於自己的“運行環境和數據”,而這則是實例對象的責任了。基本上實例對象會包含如下信息:
(1)       與流程實例的狀態或控制信息
(2)       與活動實例的狀態或控制信息。如果某些引擎不支持活動實例,那麼必然會有某些其他實例信息,可以當前節點的狀或控制信息。
(3)       一些臨時的“執行”信息,便於引擎針對某種情況進行處理
 

 5       jbpm,“精簡”的開源流程引擎

       好的開源工作流引擎不多,jbpm和osworkflow算是其中兩個有特色而且比較容易實際應用的。目前一些國內的中小型流程應用項目,就是在jbpm或osworkflow的基礎上擴展實現。jBpm採用了Activity Diagram的模型,而osworkflow則是FSM的模型。
       當然,這僅僅是jbpm3之後的事情。自從被Jboss收購之後,jbpm對早先的2.0構架進行了重組,整個結構完全本着“微內核”的思想進行設計。
       現在這裏從技術角度來分析jbpm3的優點,簡單羅列幾個大家都容易看見的:
(1)       jbpm的模型是採用UML Activity Diagram的語義,所以便於開發人員理解流程。
(2)       jbpm提供了可擴展的Event-Action機制,來輔助活動的擴展處理。
(3)       jbpm提供了靈活的條件表達式機制,來輔助條件解析、腳本計算的處理。
(4)       jbpm提供了可擴展的Task及分配機制,來滿足複雜人工活動的處理。
(5)       藉助hibernate的ORM的優勢,jbpm能夠很容易支持多種數據庫。
 
當然,還有一些優點,是很多開發人員並不太注意的,比如:
(1)       jbpm的Node機制非常靈活,開發人員可以很容易定製“業務化語義的節點”,並滿足運行時候處理的需要。
 
有很多靈活的優點,當然也少不了存在一些“侷限”。
(1)       很顯然,只能有一個start-state。
(2)       jbpm依靠Token來調度和計算,在同一個時刻中,一個ProcessInstance只允許一個Token對象只存在一個Node中(分支當然用Child Token對象處理)。所以本質上就不支持“multi-instance”模式。
(3)       jbpm作爲一款開源的工作流引擎,其更多的是關注“如何輔助你更容易的讓流程運行完成”,但是並不記錄“流程運行的歷史和軌跡”。這一點可能是東西方文化的差異性所在,因爲國內的流程應用,比較關注“運行軌跡”。
 
       至於其他的一些侷限,比如不支持“回退”、“跳轉”等操作,這也是因爲東西方文化的差異所在。西方人認爲“往回流轉的情況肯定也是一種業務規則所定義,那麼肯定可以通過分支或條件來解決”,而東方則把“回退作爲一個人性化管理和處理的潛在特點”。所以諸如此類的一些“特定需求”,估計只能通過擴展jbpm來實現了,甚至有時候,簡單的擴展是無法解決問題的——正如上一節所說的那樣,“引擎的抽象”會影響“引擎的應用”的複雜度支持。
       但是,當你試圖修改jbpm代碼的時候,你會顧慮jbpm的LGPL協議嗎?(很多國內企業從來不考慮這個協議問題,寒)。
 

 6       jBpm流程模型與定義對象

       這裏說的“定義流程”並不是說jbpm3中那個基於eclipse plugin的圖形化建模工具。而是如何去解決“形式化的描述一個流程”的問題。
       形式化的描述流程並不是一個簡單的問題,從上世紀七十開始,人們就在探索用各種各樣多的模型來描繪流程:Petri Net, FSM, EPC, Activity Diagram, 以及近來的XPDL MetaModel等等,延伸到如今的BPEL,BPMN,BPMD等等。
        jBpm採用了Activity Diagram的模型語義:其將用Start State、State、Action State(Task Node)、End State、Fork、Join、Decision、Merge、Process State這幾個“元素”的組合來描述任何一個流程。其中Action State是Activity Diagram中的標準語義,在jBpm爲了便於大家理解和使用,jBpm採用了TaskNode這個語義。
 
       在WfMC的Workflow Reference Model中,對流程引擎的功能描述,其中就包含一項:解析流程定義。如果想滿足這這功能,前提條件就必須有最基本的兩個:
(1)       有一套形式化的描述語言(通常爲xml格式)。利用這個描述語言可以描述一個流程的定義。比如WfMC所提出的XPDL這個描述語言。當然,jBpm也有自己的一套,名爲jPDL,也是一個xml格式的。
(2)       有一套對象集可以反映流程的定義模型和結果,一般叫做定義對象。流程引擎就需要把“xml格式的流程定義”解析爲一套對象,而這套對象的結構則反映了流程的結構。
      
       我們暫且不去探討jPDL那個形式化的xml語言,而把重心放在jBpm那套定義對象中。因爲這個定義對象是屬於Engine Kernel的一部分。
6.2    抽象的節點(Node)和轉移(Transition)
       面向對象的繼承性、多態性可以讓我們從最抽象的部分來描述對象。那麼這套定義對象也需要從最基礎的“抽象”說起。
       process的本質就是“節點”和“有向弧”當然你也可以說是Node和Link,或者Node和Transition,或者Activity和Transition等等之類的。jBpm採用的是Node和Transition來表示“節點”和“有向弧”。
       於是乎,在jbpm中你可以看到這樣的結構關係:
       對於一個節點來說,從定義角度,其只關心幾個事情:
(1)       這是個什麼類型的節點。這個節點可能是start state,也可能是一個task node,或者是一個fork。
(2)       這個節點的轉入Transition和轉出Transition。
       可能有的人會說,還需要關心節點的轉入轉出的類型,比如And Splite或者Xor Join之類。這個並沒有錯,因爲很多流程模型的節點元素需要考慮這個,比如WfMC的XPDL模型。但是jBpm的節點是沒有這樣的屬性的,或者說的更準確些,是Activity Diagram模型的節點沒有這樣的特性。活動圖是採用“Fork”、“Join”這樣的節點來解決“分支”問題。
       僅利用節點和轉移的組合,就可以表達一個“過程(Process)”。當然這個流程只能告訴人們“大概的業務過程”,當然不包括很複雜的信息。如下圖所示:
       這是一張非常標準的“活動圖”,如果我們用jbpm的設計器,看看這樣一張“流程圖”:
 
       不論你如何繪畫,改變不了這張圖的本質:它就只有兩個基本元素:節點和轉移。只是有的節點是start-state,有的是task-node,有的是join,有的是end state而已。
       我們可以通過定義自己的Node節點對象,來補充jbpm自定的節點對象。只需要extends Node,並重寫讀寫xml的read和write方法,重寫負責執行的execute方法,在org/jbpm/graph/node/node.types.xml中配置即可,當然,你可以寫的更加複雜,更加業務化的節點。

7       jBpm的過程調度機制

       jBpm的過程調度機制是吸納了Petri Net的一些思想。
       jBpm採用Token來表示當前實例運行的位置,也利用token在流程各個點之間的轉移來表示流程的推進,如下圖所示:
 
       當jbpm試圖去啓動一個流程的時候,首先是構造一個流程實例,併爲此流程實例創建一個Root Token,並把這個Root Token放置在Start Node上。
       以下截取部分代碼實現,僅供參考。手頭有jbpm3相應開發環境的朋友,可以打開ProcessInstance和Token這兩個類。(注:以下所有參考代碼,爲了突出主題,都已經將實際代碼中的event,log等處理刪除)
public ProcessInstance( ProcessDefinition processDefinition ) {
    this.processDefinition = processDefinition;
    this.rootToken = new Token(this);
 
public Token(ProcessInstance processInstance) {
    this.processInstance = processInstance;
    this.node = processInstance.getProcessDefinition().getStartState();
       jbpm是允許在start-state執行Task的,也允許在start-state創建工人任務。不過此處我們不予討論。
       當Token已經在Start-State節點了,我們可以開始往前推進,來促使流程實例往前運行。對於外部操作來說,觸發流程實例往下運行的操作有兩個:
(1)       強制執行ProcessInstance的signal操作
(2)       執行TaskInstance的end操作。
但是,這兩個操作,都是通過“當前token的signal操作”來內部實現的,如下圖所示:
 
       TokenSignal操作表示:實例需要離開當前token所在的節點,轉移到下一個節點上。因爲NodeNode之間是“Transition”這個橋樑,所以,在轉移過程中,會首先把Token放入相關連的Transtion對象中,再由Transition對象把Token交給下一個節點。

       讓我們來看看Token類中signal方法的部分代碼實現,僅供參考:
public void signal() {
    //注意ExecutionContext對象
    signal(node.getDefaultLeavingTransition(), new ExecutionContext(this));
}

void signal(Transition transition, ExecutionContext executionContext) {
    // start calculating the next state
    node.leave(executionContext, transition);
}
 
       接下來,請注意node.leave()這個操作。這是一個很有意思的語義轉換:我們是採用token的signal操作來表示往下一個節點推進,但是實際確實執行的node.leave ()操作。
 
       如果這地方讓你自己來實現,代碼會不會就是這樣子呢?不妨此處想一想。
//假設代碼,僅供思考
void signal(Transition transition, ExecutionContext executionContext) {
    transition.take(executionContext);
}
 
       前面說過,jbpm的調度機制吸納的Petri Net的思想。在Petri Net中,並沒有transition中駐留token這個語義,token只駐留在庫所(Place)中。所以,jbpm此處的設計思路,是於此有一定關係的。所以只是把一個ExecutionContext對象放在了transition中,而不是一個token對象。
       讓我們來看看node對象的leave方法:
public void leave(ExecutionContext executionContext, Transition transition) {
    Token token = executionContext.getToken();
    token.setNode(this);
    executionContext.setTransition(transition);
    executionContext.setTransitionSource(this);
    transition.take(executionContext);
}
              我們直接跟蹤進Transition的take操作:
public void take(ExecutionContext executionContext) {
    executionContext.getToken().setNode(null);
    // pass the token to the destinationNode node
    to.enter(executionContext);
}
             
       經過這麼多的中間步驟,我們終於把ExecutionContext對象從一個node轉移到下一個node了。讓我們來看看Node對象的enter操作:
public void enter(ExecutionContext executionContext) {
    Token token = executionContext.getToken();
    token.setNode(this);
    // remove the transition references from the runtime context
    executionContext.setTransition(null);
    executionContext.setTransitionSource(null);
 
    // execute the node
    if (isAsync) {
     
    } else {
      execute(executionContext);
    }
}
 
       至此,jBpm成功的從一個節點轉移到下一個節點了。—— 這就是jbpm的調度機制。
       怎麼樣,是不是非常的簡單?
       讓我們把整個過程,用一張更清晰的“思維圖”來展示一下:


8       jBpm的過程執行機制

       前面我們的“過程調度機制”是爲了讓流程可以正確的從“一個節點轉移到下一個節點”,而本節所要講解的jbpm“執行機制”,則是爲提供一個運行機制,來保證“節點的正確執行”。
       首先我們需要明確如下的概念:
(1)       節點有很多中,每種節點的執行方式肯定是不一樣的
(2)       節點有自己的生命週期,不同的生命週期階段,所處的狀態不同。
 
       在WfMC的《工作流參考模型》文檔中,爲活動實例歸納了幾個可參考的生命週期。(僅供參考,實際很多工作流引擎的節點的生命週期要比這複雜)
 
       但是,jbpm並沒有突出“節點生命週期”這個理念,僅僅只是在“Event”中體現出出來。在我看來,可能的原因有兩個:
(1)       jBpm沒有NodeInstance這個概念。利用Token和TaskInstance,jBpm足以持久化足夠的信息,能夠讓流程實例迅速定位到當前運行的狀態。
(2)       jBpm的Event已經很豐富,並且這個Event是圍繞“Token的轉移”而設置的,並不是圍繞Node的生命週期設置的。
(3)       通常我們需要在Active和Completed的生命週期內所要操作的分支與聚合,在jBpm模型中分別由Fork、Join之類的節點替代。所以jBpm過分關注Node生命週期的管理意義不是非常大。
 
       作爲個人,我並不行賞jBpm這樣拋棄“節點生命週期管理”的實現方式,更行賞OBE(最早的基於XPDL模型的java工作流引擎之一)的生命週期約束和管理。但是,也不得不承認,jBpm規避了“繁瑣的狀態維護”,反而讓處理變得“簡易”,也更容易被大家所理解和接受,而這也正是OBE逐漸消失的一個原因:過於複雜和臃腫。
       
       讓我們在前面那張jBpm的“調度機制思維圖”上,再稍稍補充一點(爲了突出顯示,與上圖有所改動)。

       這張圖應該可以很好的詮釋出,jBpm是如何執行各種節點的,這也是得益於OO的“多態與繼承”特性。
 
       jBpm的執行機制非常簡單,但還是需要稍微補充一下有關“分支”方面的處理。
       jBpm採用sub token的機制來解決分支方面的處理:當遇到有分支的時候,會爲每個分支節點創建一個child token。在聚合節點(Join或Merge),則依賴其同步或異步的聚合方式,來分別處理。
       比如我們參看Fork節點的執行代碼(爲了突出重點,省略部分代碼):
public void execute(ExecutionContext executionContext) {
    Token token = executionContext.getToken();
    Iterator iter = transitionNames.iterator();
    while (iter.hasNext()) {
      String transitionName = (String) iter.next();
      forkedTokens.add(createForkedToken(token, transitionName));
    }
    iter = forkedTokens.iterator();
    while( iter.hasNext() ) {
      //省略部分代碼
      ExecutionContext childExecutionContext = new ExecutionContext(childToken);
      leave(childExecutionContext, leavingTransitionName);
    }
}
 
protected ForkedToken createForkedToken(Token parent, String transitionName) {
    Token childToken = new Token(parent, getTokenName(parent, transitionName));
    forkedToken = new ForkedToken(childToken, transitionName);
    return forkedToken;

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
       至於Merge節點,我想此處不用在累贅的展示,有興趣的,可以參看Merge類的execute方法,即可。
 

9       jBpm內核結構與實例對象

       Jbpm引擎內核的結構非常“精簡”。除了我們上面所說的那些定義對象(各種Node節點和Transtion),還有幾個與“運行實例”相關的對象。如下圖所示,jbpm引擎內核對象主要是在org.jbpm.graph.def和org.jbpm.graph.exe包。
(1)       我們需要描述一個流程實例,所以需要一個ProcessInstance對象。
(2)       每個流程實例,都會維護一套屬於其自己的“執行環境”,也就是ExecutionContext對象。注意,這裏是一套,而不是一個。
 

10   後記

       上半年寫了些bpm和SOA的文章,也被csdn的好友拉着忽悠了不少這方面的概念,弄的好像我開始搞這方面的工作似的。其實不然,本質工作與這有“天壤之別”,完全是非常底層的java技術應用。而workflow,也有兩三年沒有從事這方面的開發了,所以寫此篇文章,着實費了點功夫。
       想痛痛快快寫篇有關“引擎內核”的文章,這個想法由來以及了,卻擔心自己不足以詮釋清楚,反而容易誤導他人,遂中途多次放棄。
       正如前面所說的那樣,引擎內核的實現,並沒有一套“固定的模式”或者“固定的實現體系”,會因爲很多因素而造成實現不同。如果想把“引擎內核”的實現真正詮釋清楚,必須把這些相關因素都詮釋明朗——但這依然是一個浩大的工程。
       前些日子,受朋友所託,爲他們的公司學員講了幾節工作流的課程,期間嘗試jBpm來詮釋了一下引擎的實現思路,發現效果不錯。——受此引發,遂萌發了以jBpm爲實例,來簡單詮釋“流程引擎內核”想法。
       耗時一週的業餘時間,雖然還很難詮釋自己的全部想法,但“點出幾個要點”,還是應該有了。
 
發佈了196 篇原創文章 · 獲贊 110 · 訪問量 98萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章