版權聲明:
本文爲博主原創文章,未經博主容許不得轉載。關注公衆號 技術匯(ID: jishuhui_2015) 可聯繫到做者。
『工單系統』從宏觀上看,是一些狀態流的轉換,筆者認爲,工單系統的實現便是對工做流(workflow)的實現,典型的應用有企業OA系統,各種CRM,ERP等。html
對於工單系統的實現,其實能夠結合實際業務去編寫相應的業務代碼,這樣作的最大的好處是定製化程度高,運行業務流程高度自定義化。然而,物極必反,高度定製的業務流程將會失去必定的靈活性。spring
那麼問題來了:如何權衡?編程
答曰:基於業內標準實現。架構
標準=權威app
當咱們發現設計出來的業務流程不符合標準,就不得不懷疑了。框架
是時候要祭出工做流的「殺手鐗」了:BPMN2.0編程語言
至於BPMN2.0的基礎介紹,我在此再也不贅述,用一句話歸納:BPMN2.0是IBM制定的一套完善的工做流開發標準,它包含了諸如事件,網關,順序流,任務等各種基本元素。工具
BPMN2.0畢竟只是一套標準,還須要付諸實現,業內知名的工做流開發框架當屬Activiti了,使用的編程語言是受衆較多的Java,若是實際工做中有用到工做流,可使用此開發框架,若是主流開發框架不是Java,只能自造輪子了。ui
若是準備進入工做流的開發,不管是使用框架,仍是自研,建議閱讀一下這份文檔,對實際開發工做很是有幫助。spa
再回到文章開頭提到的「工單系統」,說說筆者啓動此項目前的一些我的想法。
工單的流程推進自己就是工做流的一種體現,因此理所固然是使用了BPMN2.0的標準。大概花了一個工做日去研讀了上述提到的參考文檔,愈發以爲BPMN2.0標準的精妙之處,要囊括現有的業務需求,那是綽綽有餘。
固然,在作系統架構的時候,一定是要結合實際業務需求的。仔細分析了自身的業務需求,發現都是一些「短流程」,工單任務不會超過2個,工單任務類型也只會有一種(即爲userTask),流程分支也很少,總而言之,筆者面臨的都是一些較爲簡單的工單流程。
若是直接使用Acviti這類龐大複雜的框架,一方面是實際的工單流程不算複雜,二來有大材小用之嫌,再者團隊成員都不熟悉此標準,理解並對接起來有難度。
所以,筆者走上了基於BPMN2.0標準自研的道路。
倒不是有重造一個Activiti的雄心壯志,而其實質是對Activiti進行功能裁剪,只實現了一些必要的標準。
關於此工單系統的架構設計將會分三篇文章講解,此篇文章將着重介紹我用到了哪些BPMN2.0標準元素。
Workflow Definition Language(如下簡稱WDL),意思是工做流定義語言,屬於我的自創,並不是官方術語,只是想在團隊內統一語言而已。
沿襲Activiti的設計實現,WDL也是基於XML的。
在工單系統裏面,筆者實現瞭如下8種基本元素:
下面對WDL的組成部分進行介紹:
一、根節點,由一對definitions標籤組成。
<definitions id="def" name="工做流程配置"> </definitions>
二、流程定義節點,由一對process標籤表示,與其下屬子節點組成一個完整的流程。
值得一提的是,BPMN2.0標準中是容許subProcess(子流程)存在的,這個feature在此工單系統裏並未實現。
<process id="verify_work" name="用戶審覈流程"> </process>
三、空開始事件節點,一般表示一個流程的開始。
<startEvent id="start" name="開始事件"/>
四、定時事件定義,不可單獨存在,其效果是在其餘事件的基礎上加了一個定時器,典型的應用是下面將提到的定時邊界事件。
<timerEventDefinition> <!-- 時間點 或者 cron表達式 --> <timeDuration>${duration}</timeDuration> </timerEventDefinition>
五、定時邊界事件。
邊界事件都是捕獲事件,它會附在一個節點上,當節點運行時,事件會監聽對應的觸發類型。 當邊界事件被捕獲,節點就會中斷運行,同時執行事件的後續流程。
定時邊界事件能夠理解爲一個暫停等待警告的時鐘。當流程執行到綁定了邊界事件的環節, 會啓動一個定時器。
當定時器觸發時,環節就會中斷,並沿着定時邊界事件的外出連線繼續執行。
<boundaryEvent id="escalationTimer" cancelActivity="true" attachedToRef="userTask"> <!-- cancelActivity表示是否會中斷邊界事件所依附的任務 --> <timerEventDefinition> <timeDuration>0 15 10 * * ? *</timeDuration> </timerEventDefinition> </boundaryEvent>
六、消息(邊界)事件,消息的接收和發送要在應用或架構的一層實現的,流程引擎則內嵌其中。
這個元素相較於標準,仍是有改動的。消息事件在工單系統中被界定爲是一種回調通知的手段,通知的類型有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>
七、順序流程節點,在表現形式上是一個單向箭頭,所以須要定義兩端的元素。起始元素用sourceRef屬性定義,指向元素用targetRef屬性定義,其值都是元素的id屬性值。由此也能夠看出,順序流上的基本元素一般都須要有id屬性進行標識的,並且最好不要重複,避免混淆。
<sequenceFlow id="flow1" sourceRef="ss" targetRef="tt" />
八、 條件流程節點,意思知足某種條件才通向對應的順序流。
<sequenceFlow id="flow1" sourceRef="exclusiveGw" targetRef="task1"> <conditionExpression>${condition}</conditionExpression> </sequenceFlow>
九、用戶任務,表示須要工做人員實際操做推進的任務節點。
BPMN2.0標準中有很豐富的任務類型,諸如腳本任務,Java服務任務,郵件任務等等,Activiti也擴展出來了Mule任務,Camel任務等。
而在工單系統中,只須要用到用戶任務,搭配其餘事件,便可很好的知足業務需求。
<userTask id="task" name="verify_task"/>
十、排他性網關節點,就像程序的if-else的判斷,鏈接排他性網關的衆多分支,最終只會走向其中一個分支。
<exclusiveGateway id="xgid" name="Request approved" default="sf"/> <!-- default表示默認流程 -->
十一、並行性網關節點,和排他性網關是對立的,鏈接並行性網關的衆多分支將會同時執行。
<parallelGateway id="pgid" name="gname"/>
十二、空結束事件節點,一般做爲一個流程的結束。
<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仍是簡單易懂的,而且能覆蓋到絕大多數工單流程,其能成爲業內標準,也是自有一番道理的。