基於BPMN2.0的工單系統架構設計(上)

裝逼未遂的程式設計師發表於2018-05-26

版權宣告:本文為博主原創文章,未經博主允許不得轉載。關注公眾號技術匯(ID: jishuhui_2015) 可聯絡到作者。

一、前言

『工單系統』從巨集觀上看,是一些狀態流的轉換,筆者認為,工單系統的實現即是對工作流(workflow)的實現,典型的應用有企業OA系統,各類CRM,ERP等。

對於工單系統的實現,其實可以結合實際業務去編寫相應的業務程式碼,這樣做的最大的好處是定製化程度高,執行業務流程高度自定義化。然而,物極必反,高度定製的業務流程將會失去一定的靈活性。

那麼問題來了:如何權衡?

答曰:基於業內標準實現。

標準=權威

當我們發現設計出來的業務流程不符合標準,就不得不懷疑了。

標準

是時候要祭出工作流的“殺手鐗”了:BPMN2.0

至於BPMN2.0的基礎介紹,我在此不再贅述,用一句話概括:BPMN2.0是IBM制定的一套完善的工作流開發標準,它包含了諸如事件,閘道器,順序流,任務等各類基本元素。

BPMN2.0畢竟只是一套標準,還需要付諸實現,業內知名的工作流開發框架當屬Activiti了,使用的程式語言是受眾較多的Java,如果實際工作中有用到工作流,可以使用此開發框架,如果主流開發框架不是Java,只能自造輪子了。

如果準備進入工作流的開發,無論是使用框架,還是自研,建議閱讀一下這份文件,對實際開發工作非常有幫助。

再回到文章開頭提到的“工單系統”,說說筆者啟動此專案前的一些個人想法。

工單的流程推動本身就是工作流的一種體現,所以理所當然是使用了BPMN2.0的標準。大概花了一個工作日去研讀了上述提到的參考文件,愈發覺得BPMN2.0標準的精妙之處,要囊括現有的業務需求,那是綽綽有餘。

當然,在做系統架構的時候,必定是要結合實際業務需求的。仔細分析了自身的業務需求,發現都是一些“短流程”,工單任務不會超過2個,工單任務型別也只會有一種(即為userTask),流程分支也不多,總而言之,筆者面臨的都是一些較為簡單的工單流程。

如果直接使用Acviti這類龐大複雜的框架,一方面是實際的工單流程不算複雜,二來有大材小用之嫌,再者團隊成員都不熟悉此標準,理解並對接起來有難度。

因此,筆者走上了基於BPMN2.0標準自研的道路。

倒不是有重造一個Activiti的雄心壯志,而其實質是對Activiti進行功能裁剪,只實現了一些必要的標準。

關於此工單系統的架構設計將會分三篇文章講解,此篇文章將著重介紹我用到了哪些BPMN2.0標準元素。

二、工作流定義語言(WDL)

Workflow Definition Language(以下簡稱WDL),意思是工作流定義語言,屬於個人自創,並非官方術語,只是想在團隊內統一語言而已。

沿襲Activiti的設計實現,WDL也是基於XML的。

在工單系統裡面,筆者實現了以下8種基本元素:

BPMN2.0基本元素
下面對WDL的組成部分進行介紹:

1、根節點,由一對definitions標籤組成

<definitions id="def" name="工作流程配置"> </definitions>
複製程式碼

2、流程定義節點,由一對process標籤表示,與其下屬子節點組成一個完整的流程。 值得一提的是,BPMN2.0標準中是允許subProcess(子流程)存在的,這個feature在此工單系統裡並未實現。

<process id="verify_work" name="使用者稽核流程"> </process>
複製程式碼

3、空開始事件節點,通常表示一個流程的開始

<startEvent id="start" name="開始事件"/> 
複製程式碼

4、定時事件定義,不可單獨存在,其效果是在其他事件的基礎上加了一個定時器,典型的應用是下面將提到的定時邊界事件

<timerEventDefinition>     
	<!-- 時間點 或者 cron表示式 -->
    <timeDuration>${duration}</timeDuration>   
</timerEventDefinition> 
複製程式碼

5、定時邊界事件

邊界事件都是捕獲事件,它會附在一個節點上,當節點執行時,事件會監聽對應的觸發型別。 當邊界事件被捕獲,節點就會中斷執行,同時執行事件的後續流程。

定時邊界事件可以理解為一個暫停等待警告的時鐘。當流程執行到繫結了邊界事件的環節, 會啟動一個定時器。

當定時器觸發時,環節就會中斷,並沿著定時邊界事件的外出連線繼續執行。

<boundaryEvent id="escalationTimer" cancelActivity="true" attachedToRef="userTask">
	<!-- cancelActivity表示是否會中斷邊界事件所依附的任務 -->   
    <timerEventDefinition>       
        <timeDuration>0 15 10 * * ? *</timeDuration>   
    </timerEventDefinition> 
</boundaryEvent>
複製程式碼

6、訊息(邊界)事件,訊息的接收和傳送要在應用或架構的一層實現的,流程引擎則內嵌其中

這個元素相較於標準,還是有改動的。訊息事件在工單系統中被界定為是一種回撥通知的手段,通知的型別有REST和MQ兩種方式,通知所攜帶的引數在params中可被定義,name是引數名。

message標籤是唯一一個與process標籤同級的標籤,message就好比全域性變數,可以被WDL中多個元素引用。

以下定義了一個訊息體,並在訊息邊界事件中引用該訊息體。

<message id="newInvoice" name="newInvoiceMessage" type="REST | MQ">
   <params>
       <param name="target">${target}</param>
       <param name="a">${a}</param>
       <param name="x">${x}</param>
   </params>
</message>
<boundaryEvent id="boundary" attachedToRef="task" cancelActivity="true">
    <messageEventDefinition messageRef="newInvoice"/>
</boundaryEvent>
複製程式碼

7、順序流程節點,在表現形式上是一個單向箭頭,因此需要定義兩端的元素。起始元素用sourceRef屬性定義,指向元素用targetRef屬性定義,其值都是元素的id屬性值。由此也可以看出,順序流上的基本元素通常都需要有id屬性進行標識的,而且最好不要重複,避免混淆。

<sequenceFlow id="flow1" sourceRef="ss" targetRef="tt" />
複製程式碼

8、 條件流程節點,意思滿足某種條件才通向對應的順序流

<sequenceFlow id="flow1" sourceRef="exclusiveGw" targetRef="task1">
    <conditionExpression>${condition}</conditionExpression>
</sequenceFlow>
複製程式碼

9、使用者任務,表示需要工作人員實際操作推動的任務節點

BPMN2.0標準中有很豐富的任務型別,諸如指令碼任務,Java服務任務,郵件任務等等,Activiti也擴充套件出來了Mule任務,Camel任務等。

而在工單系統中,只需要用到使用者任務,搭配其他事件,即可很好的滿足業務需求。

<userTask id="task" name="verify_task"/>
複製程式碼

10、排他性閘道器節點,就像程式的if-else的判斷,連線排他性閘道器的眾多分支,最終只會走向其中一個分支

<exclusiveGateway id="xgid" name="Request approved" default="sf"/>  <!-- default表示預設流程 -->
複製程式碼

11、並行性閘道器節點,和排他性閘道器是對立的,連線並行性閘道器的眾多分支將會同時執行

<parallelGateway id="pgid" name="gname"/>
複製程式碼

12、空結束事件節點,通常作為一個流程的結束

<endEvent id="end" name="結束事件"/>
複製程式碼

補充說明

在上述的定時事件,訊息定義,順序流等元素均用到了同一種取值方式,即我們常見的${value}形式,並非用到了spring相關的解析手段,而是受到Activiti的啟發,使用的是JUEL工具對錶達式進行解析和執行。

在WDL中,不需要太複雜的表示式,支援簡單的取值和邏輯運算即可,如:${name},${approved==true}。

三、例項講解

為了加深對上述元素的理解,筆者挑了5個具有代表性的例項,展示其WDL的內容,例項配圖均來自這份文件

需要注意的是,definitions下面的子節點不講究先後順序,不一定要按流程走向書寫WDL。個人習慣是先定義節點元素,然後用順序流(sequenceFlow)進行連線。

這裡寫圖片描述

這種情況比較簡單,只有一個開始節點和一個任務節點。

<definitions id="def" name="工作流程配置"> 
    <process id="verifyCredit" name="verify credit"> 
        <startEvent id="start" name="開始"/> 
        <userTask id="unkown" name=""/> 
        <sequenceFlow id="flow1" sourceRef="start" targetRef="unkown"> 
            <conditionExpression>${condition}</conditionExpression> 
        </sequenceFlow> 
    </process> 
</definitions>
複製程式碼

這裡寫圖片描述

這是一個典型的單支順序流程。

<definitions id="def" name="工作流程配置"> 
    <process id="pid" name="my process"> 
        <startEvent id="start" name="開始"/> 
        <userTask id="write" name="Write monthly financial report"/> 
        <userTask id="verify" name="Verify monthly financial report"/> 
        <sequenceFlow id="flow1" sourceRef="start" targetRef="write"/> 
        <sequenceFlow id="flow2" sourceRef="write" targetRef="verify"/> 
        <sequenceFlow id="flow2" sourceRef="verify" targetRef="end"/> 
        <endEvent id="end"/> 
    </process> 
</definitions>
複製程式碼

這裡寫圖片描述

此處出現了多分支情況,並且定義了一個defaultFlow,類似於:

if (conditionA) {
    doTask1
} else if (conditionB) {
    doTask3
} else {
    doTask2
}
複製程式碼

不難看出,排他性閘道器一般都會伴隨一個defaultFlow,以下是WDL內容。

<definitions id="def" name="工作流程配置"> 
    <process id="pid" name="my process"> 
        <startEvent id="start" name="開始"/> 
        <userTask id="t1" name="Task1"/> 
        <userTask id="t2" name="Task2"/> 
        <userTask id="t3" name="Task3"/> 
        <exclusiveGateway id="xgid" name="Exclusive Gateway" default="t2"/> 
        <sequenceFlow id="flow1" sourceRef="xgid" targetRef="t1"> 
            <conditionExpression>${conditionA}</conditionExpression> 
        </sequenceFlow> 
        <sequenceFlow id="flow2" sourceRef="xgid" targetRef="t2"/> 
        <sequenceFlow id="flow1" sourceRef="xgid" targetRef="t3"> 
            <conditionExpression>${conditionB}</conditionExpression> 
        </sequenceFlow> 
    </process> 
</definitions>
複製程式碼

這裡寫圖片描述

到此處為止,算是可以看到一個完整的流程定義了,有開始和結束,各個任務節點,以及分支。

<definitions id="def" name="工作流程配置"> 
    <process id="verifyCredit" name="verify credit"> 
        <startEvent id="start" name="開始"/> 
        <userTask id="verifyCreditHistory" name="Verify credit history"/> 
        <sequenceFlow id="verify_flow" sourceRef="start" targetRef="verifyCreditHistory"/> 
        <exclusiveGateway id="approve" name="approve or not"/> 
        <sequenceFlow id="end_flow" sourceRef="verifyCreditHistory" targetRef="approve">
        <userTask id="contact" name="Contact customer for further information"/> 
        <sequenceFlow id="disapprove_flow" sourceRef="approve" targetRef="contact"> 
            <conditionExpress>${approve==false}</conditionExpress> 
        </sequenceFlow> 
        <sequenceFlow id="end_flow" sourceRef="contact" targetRef="end1"> 
        <endEvent id="end1"/> 
        <sequenceFlow id="approve_flow" sourceRef="contact" targetRef="end2"> 
            <conditionExpress>${approve==true}</conditionExpress> 
        </sequenceFlow> 
        <endEvent id="end2"/> 
    </process> 
</definitions>
複製程式碼

這裡寫圖片描述

這種情況是包含了一個定時邊界事件,如果cancelActivity="false",那麼情況就變得較為複雜了,因為有兩處結束節點,cancelActivity="true"的時候,則只在一處結束。

<definitions id="def" name="工作流程配置"> 
    <process id="verifyCredit" name="verify credit"> 
        <startEvent id="start" name="開始"/> 
        <userTask id="firstLineSupport" name="First line support"/> 
        <boundaryEvent id="escalationTimer" cancelActivity="true" attachedToRef="firstLineSupport"> 
            <timerEventDefinition> 
                <timeDuration>2017-02-12 12:00:00</timeDuration> 
            </timerEventDefinition> 
        </boundaryEvent> 
        <sequenceFlow id="flow1" sourceRef="start" targetRef="firstLineSupport"/> 
        <sequenceFlow id="flow2" sourceRef="firstLineSupport" targetRef="end1"/> 
        <endEvent id="end1"/> 
        <userTask id="secondLineSupport" name="Second line support"/> 
        <sequenceFlow id="flow3" sourceRef="firstLineSupport" targetRef="secondLineSupport"/> 
        <sequenceFlow id="flow4" sourceRef="secondLineSupport" targetRef="end2"/> 
        <endEvent id="end2"/>
    </process>
</definitions>
複製程式碼

到此,工單系統所需的基礎知識就講解完畢了。整體感覺,BPMN2.0還是簡單易懂的,並且能覆蓋到絕大多數工單流程,其能成為業內標準,也是自有一番道理的。

關注我們

相關文章