天天看點

jBPM最佳實踐 (jBPM Best Practices)

在我曾經見過的正式的第一個JBPM項目中,我注意到開發人員有時使用 executionContext 來放置很多流程的變量。為了控制執行流,向 executionContext 中增加變量是基本的法則,但是不管如何,不要被它誘惑的往裡面放任何東西!例如:假設你正在設計一個複雜的售票系統:你可能需要存儲一些關于執行者的附加資訊,例如:名、姓、郵件。是以在執行上下文中你混合了業務變量和流程變量!

一個好的解決思路是:在一個EJB中構造這些字段,并且在你的 executionContext 中僅保持一個ticketid。

下面是一個使用Seam與業務流程進行互動的EJB的例子:

view plaincopy to clipboardprint?

@Stateful     

@Name("TicketingSystem")   

public class TicketBean implements TicketBeanItf {   

@PersistenceContext(type=PersistenceContextType.EXTENDED)   

EntityManager em;   

@In(create = true)     

@Out     

private Ticket ticket;   

// We make this available to the business process     

@Out(scope=ScopeType.BUSINESS_PROCESS, required=false)     

long ticketId;   

@CreateProcess(definition="TroubleTicketing")     

public void createTicket() {   

em.persist(ticket);     

// Keep a reference to the ticketId in your biz process     

ticketId = ticket.getTicketId();     

}   

}    

@Stateful  

@Name("TicketingSystem")

public class TicketBean implements TicketBeanItf {

@PersistenceContext(type=PersistenceContextType.EXTENDED)

EntityManager em;

@In(create = true)  

@Out  

private Ticket ticket;

// We make this available to the business process  

@Out(scope=ScopeType.BUSINESS_PROCESS, required=false)  

long ticketId;

@CreateProcess(definition="TroubleTicketing")  

public void createTicket() {

em.persist(ticket);  

// Keep a reference to the ticketId in your biz process  

ticketId = ticket.getTicketId();  

}

@Name("TicketingSystem")  

public class TicketBean implements TicketBeanItf {  

@PersistenceContext(type=PersistenceContextType.EXTENDED)  

EntityManager em;  

private Ticket ticket;  

long ticketId;  

 public void createTicket() {  

    em.persist(ticket);  

    // Keep a reference to the ticketId in your biz process  

    ticketId = ticket.getTicketId();  

 }  

}  

随意在執行上下文(executionContext)中添加領域變量,除了被當作一個不好的設計選擇外,也會在很大程度上降低你流程運作的性能。

使用jBPM異常處理器,僅用來設定變量或者做錯誤通知 

JBPM有一個内建的異常處理器,能夠被應用在單一節點或者整個流程。

<exception-handler exception-class = "java.lang.Exception">  

<action class = "com.sample.handlers.BPMExceptionHandler">  

</action>  

</exception-handler>      

<exception-handler exception-class = "java.lang.Exception">

<action class = "com.sample.handlers.BPMExceptionHandler">

</action>

</exception-handler>    

<exception-handler exception-class="java.lang.Exception">  

    <action class="com.sample.handlers.BPMExceptionHandler"></action>        

</exception-handler>  你或許曾經被誘惑在JBPM中使用異常處理機制來決策執行流,不要這樣做!JBPM的機制并不完全與java的異常處理相似,在java中,一個捕獲的異常能夠對控制流産生影響。而在jBPM的案例中,控制流不能被jBPM的異常處理機制改變。異常可以被捕獲或不捕獲。不捕獲的異常被扔給用戶端(例如,調用 token.signal()方法的用戶端)或者這個異常被jBPM異常處理器捕獲。對于捕獲的異常,graph execution 會當作沒有異常發生繼續執行。

使用異常處理器的最佳實踐是,執行那些相關的action(設定變量,發生郵件,JMS消息等),然後或者繼續graph的執行(你期望的行為)或者因為事務失敗而重新扔出異常,并且結束目前節點回到開始的狀态。

依賴于被扔出的異常,在你的Action中捕獲業務異常并且設定一些流程變量也是一個好的設計。然後你可以在流程中構造一個決策節點,用來選擇一個特定的執行路徑。

需要JBPM做失敗轉發?在一個叢集的SLSB(stateless session bean)中封裝JBPM的調用 

jBPM是一個狀态機:流程描述和運作時的狀态被持久化到資料庫中,但是在一個叢集環境中,他們不會自動失敗轉發。如果一個叢集節點失敗了,這時正在執行的一些觸發器(UI,JMS,重複執行的計時器)需要停止執行并且不得不重新啟動。依賴于你所處的事務上下文,這些動作能夠被自動執行(重新傳遞JMS消息,重新執行計時器)或者請求UI互動(如果叢集節點當機需要重新開機時,顯示錯誤消息給使用者)。

是以,當流程描述是持久化時,工作流的失敗轉發必須被手動執行。jBPM能夠被用來建構一個完整的失敗保護,可叢集的支援失敗轉發的工作流解決方案,但是它不支援開箱即用。

那麼,在你的工作流中增加這些特性的最簡單的方式是什麼?對于大多數的案例,最佳的解決方案是在你的應用中用一個無狀态的會話bean來封裝jBPM API的調用,并且在最後建構叢集。

給你的EJB增加叢集能力一點也不複雜,參考下面的方案:

<a href="http://www.mastertheboss.com/en/jboss-application-server/49-jboss-ejb3-in-a-cluster.html">http://www.mastertheboss.com/en/jboss-application-server/49-jboss-ejb3-in-a-cluster.html</a>

在盡可能的地方使用super states 

一個Superstate是一組節點:它是一種将節點組合成相關集合的友善的方式,是在流程中執行個體階段的表示。例如,一個應用程式能夠分階段組合流程中的所有節點。動作(Action)可以被關聯到superstate事件上。一個重要的意義就是一個令牌(token)能夠在給定的時間上被多層嵌套。這可以友善地來檢查一個流程是否被執行了,例如在開始階段。

是以,當每個狀态都代表着一個流程的階段時,将流程拆分出super states是一種好的設計,但是為什麼我提倡對super states的使用還有更多的理由:

JBPM是一個狀态機并且本身沒有傳遞一個内建的圖形化環境。官方的Eclipse插件,可以讓你來圖形化的流程模組化,但是它不是在市場上最好的BPM前端工具:圖示太大,太醜陋,并且你不能夠自己為不同的節點類型來定制圖示。如果你曾經用JBPM畫一個有100個節點的流程,你可能也做我所做的事情了:因為流程的圖檔很大并且很雜亂,我從JBPM中自己設計了一個前端展現層。

如果你不希望為JBPM設計一個新的前端,那麼盡可能廣泛地使用superstates,它将使你的流程(圖)更可讀,并且當你展現一個5頁的流程圖時你老闆不會暈。

嘗試擴充JBPM的API而不是搞亂複雜的流程模組化 

有時候開發人員(我也是)不去尋找更簡單的解決方案,或許用jBPM内建的節點來模組化流程而導緻一個不必要的複雜流程。

想更簡單……

jBPM是非常易擴充的(action handlers,定制節點類型)并且有時比用存在的節點做簡單的模組化還要容易!這可以使得事情沒有那麼複雜。

例如:假設你有這樣一個需求,在一個依賴于地理定位的特定泳道内指派一個任務。(如果一個任務發生在紐約,它指派給使用者A,如果它發生在芝加哥那麼指派給使用者B)

拿到 org.jbpm.taskmgmt.def.Task.java 這個類并且增加下述的字段和方法:

private String taskLocation;     

public String getTaskLocation() {     

return  taskLocation;     

}     

public void setTaskLocation(String taskLocation){     

this.taskLocation = taskLocation;     

private String taskLocation;  

public String getTaskLocation() {  

return  taskLocation;  

public void setTaskLocation(String taskLocation){  

this.taskLocation = taskLocation;  

private String taskLocation;

public String getTaskLocation() {

   return taskLocation;

public void setTaskLocation(String taskLocation){

   this.taskLocation = taskLocation;

現在為 Task.java 修改hibernate配置檔案,在 Task.hbm.xml 中:

&lt;property name="taskLocation" column= "TASKLOCATION_"&gt;&lt;/property&gt;  

&lt;property name="taskLocation" column= "TASKLOCATION_"&gt;&lt;/property&gt;

最後修改 JpdlXmlReader.java 以使得當流程從資料庫中讀入時新的屬性被注入到Task類中。

String taskLocation = taskElement.attributeValue("taskLocation");   

if (taskLocation==null) {   

         taskLocation = taskElement.attributeValue("taskLocation");   

if (taskLocation != null){   

          task.setLocation(taskLocation);   

String taskLocation = taskElement.attributeValue("taskLocation");

if (taskLocation==null) {

         taskLocation = taskElement.attributeValue("taskLocation");

if (taskLocation != null){

          task.setLocation(taskLocation);

另一個定制的例子可以應用到你的查詢中。假設你想用很多規則來過濾你的任務:考慮一個用JSF來做渲染的任務清單和一個任務過濾視窗,這個視窗中你可以按照優先級、日期、執行人、任務名稱等來過濾任務。

一個不會使得事情更複雜的、實作它的方式是,給任務執行個體增加過濾器:僅僅是打開任務執行個體的hibernate檔案并且添加一些hibernate過濾器:

&lt;filter name= "filterPriority" condition= ":paramPriority = PRIORITY_" /&gt;         

&lt;filter name="filterDesc" condition= ":paramDesc = DESCRIPTION_" /&gt;             

&lt;filter name="filterId" condition= "str(ID_) LIKE (:paramId) " /&gt;         

&lt;filter name="filterCreate" condition= "CREATE_ BETWEEN :paramFrom and :paramTo" /&gt;   

&lt;filter name= "filterPriority" condition= ":paramPriority = PRIORITY_" /&gt;      

&lt;filter name="filterDesc" condition= ":paramDesc = DESCRIPTION_" /&gt;          

&lt;filter name="filterId" condition= "str(ID_) LIKE (:paramId) " /&gt;      

&lt;filter name="filterCreate" condition= "CREATE_ BETWEEN :paramFrom and :paramTo" /&gt; 

注意,傳遞給過濾器的參數以“:”開始,而其它的字段(像_ID)屬于TaskInstance。然後當你填充你的資料庫時,使被選擇的過濾器生效:

String sql  = "from org.jbpm.taskmgmt.exe.TaskInstance";   

session.enableFilter("filterPriority").setParameter("paramPriority","2");   

Query queryParent = session.createQuery(sql);      

List list = queryParent.list();   

Iterator iter = list.iterator();   

 while (iter.hasNext()) {      

  TaskInstance taskInstance = (TaskInstance)iter.next();   

String sql  = "from org.jbpm.taskmgmt.exe.TaskInstance";

session.enableFilter("filterPriority").setParameter("paramPriority","2");

Query queryParent = session.createQuery(sql);   

List list = queryParent.list();

Iterator iter = list.iterator();

 while (iter.hasNext()) {   

  TaskInstance taskInstance = (TaskInstance)iter.next();

 }

&lt;filter name="filterPriority"  condition=":paramPriority = PRIORITY_"/&gt;    

    &lt;filter name="filterDesc"      condition=":paramDesc = DESCRIPTION_"/&gt;        

    &lt;filter name="filterId"        condition="str(ID_) LIKE (:paramId) "/&gt;    

    &lt;filter name="filterCreate"    condition="CREATE_ BETWEEN :paramFrom and :paramTo"/&gt;  

如果你有複雜的規則,雇用一個Drools開發人員 

首先要了解:

一個工作流引擎(或者傳統的面向圖的程式設計)是關于制定一個代表執行的圖。節點可以展現為等待狀态。

一個規則引擎是關于指定一套規則然後為指定的一套事實庫應用一個推理算法。

Drools怎樣同JBPM相配合呢?一個最佳的實踐是用JBPM來使一個規則引擎中的Handlers中的全部或部分邏輯具體化。換句話說,JBPM引擎能夠由Drools規則引擎來驅動。

結合其它觀點,這一做法也并非适用于所有情況,是以問問自己幾件事:

我的java Action Handlers 有多複雜?如果你僅僅需要從資料庫中讀取一些資料,而不需要更多的,這可能不适合用一個規則引擎。然而,在使用java來實作一個處理大量業務規則的場合,當使用JBPM引擎做你的流程管理時是值得考慮用Drools的。這是因為大多數的應用開發随着時間會越來越複雜,而Drools會讓你輕松地應付這些,特别是如果你的應用的生命周期比較長。更進一步,Drools通過在一個或多個很容易配置的XML檔案中指定業務規則可以幫助你對付将來的業務規則變化。

Drools的另一個得分點是,Drools“指引”開發人員正确地編寫代碼來“做正确的事情”。同時,規則比編碼更容易閱讀,是以你的員工在“閱讀業務”時會更舒服。

更進一步,在正确使用的情況下,Drools能記住的不僅僅是資訊,而且還有以前使用這些資訊的測試結果,進而給整個應用一個快速的提升。

用還是不用BPEL? 

BPEL是一個XML語言,用來描述長期運作的web服務的互動。它主要被用來集中地編排消息交換,是以在SOA中是一個關鍵的要素。

BPEL與JPDL有什麼共同點呢?

兩個語言都可以描述過程 

同外部代理互動 

對于活動的排程 

異常處理 

錯誤恢複 

即使它們有一些共同點,但這些元素的具體表達式導緻不同的閱聽人,讨論它們對于過程的描述:

JPDL用更加簡單的語義來描述組織過程 

BPEL用複雜的場景來描述結構化的組成 

而與外部代理的互動也被不同地實作:

BPEL是基于面向文檔的,是以可以在整個公司範圍上使用 

JPDL是面向對象的,是以它可以作為公司應用元件的主幹 

BPEL将人之間的互動代理給 Partner Service 實作 

jPDL提供內建的任務管理 

那麼我應該什麼時候使用BPEL?

當你需要你的流程很輕便的伸展到java平台之外——BPEL流程能夠在基于java平台的伺服器或任何其它的軟體平台(例如.NET)上被執行。這一點在使用不同平台的合作夥伴之間進行商業互動的場景中是特别重要的。 

當沒有直接的人員涉及(即無人工幹預的流程),同時你更需要對長時間運作的業務流程很好支援的情況下。 

當你需要以一個相對簡單的方式取得事務補償時。在已經完全成功的業務流程中的補償,或者取消步驟是業務流程中最重要的概念。補償的目标是撤銷,即消除一個業務流程中已被放棄的一部分已經被執行的節點所造成的影響。 

當這些條件不滿足時使用JPDL。

原文:http://www.mastertheboss.com/en/jbpm/106-jbpm-best-practices.html

     本文轉自胡奇 51CTO部落格,原文連結:http://blog.51cto.com/huqicto/280665,如需轉載請自行聯系原作者