我的微型工作流引擎設(shè)計(jì)
一、前言
提到工作流很多人就會想到OA,的確OA就是典型的工作流的應(yīng)用,但是工作流并不僅僅局限于OA,工作流應(yīng)該算是基礎(chǔ)框架軟件,主要用于流程的重組和優(yōu) 化,它有廣闊的應(yīng)用領(lǐng)域。在java下有很多優(yōu)秀的開源工作流可以選擇比如activit5、jpbm4等,在.net下卻幾乎找不到令人滿意的工作流引 擎可用。當(dāng)然不是說.net下沒有開源的只是有些國產(chǎn)開源的但看了代碼后就一點(diǎn)興趣都沒有了,且不說代碼質(zhì)量如何,還引入了一大堆的東西,想在項(xiàng)目中應(yīng)用 也是非常困難。鑒于此我還是決定自己開發(fā)一款.NET微型工作流引擎。
二、基本說明
為什么叫微型工作流引擎?就是超輕量級,以方便在項(xiàng)目中輕便的使用,比如只有一個類庫dll,大小也就幾百k到1M左右,不過我們要先回過頭來看看工作流系統(tǒng),它實(shí)在是太大了,它應(yīng)該包括:
1、工作流引擎 2、工作流設(shè)計(jì)器 3、工作流管理系統(tǒng) 4、表單設(shè)計(jì)器
目前來說的我只實(shí)現(xiàn)了核心引擎,流程定義也只能先在xml中編輯然后讀取到引擎中或者直接定義到數(shù)據(jù)庫中,但整個流程是能夠正常流轉(zhuǎn)。至于流程設(shè)計(jì)器、表 單設(shè)計(jì)器、工作流管理系統(tǒng)這個我有精力了再慢慢開發(fā)。這里我完成的只是很小的一塊,但是是工作流的核心,可以很方便的嵌入到業(yè)務(wù)系統(tǒng)中應(yīng)用。
引擎主要提供了對于工作流定義的解析以及流程流轉(zhuǎn)的支持。工作流定義文件描述了業(yè)務(wù)的交互邏輯,工作流引擎通過解析此+工作流定義文件按照業(yè)務(wù)的交互邏輯 進(jìn)行業(yè)務(wù)的流轉(zhuǎn),工作流引擎通常通過參考某種模型來進(jìn)行設(shè)計(jì),通過調(diào)度算法來進(jìn)行流程的流轉(zhuǎn)(流程的啟動、終止、掛起、恢復(fù)等),通過各種環(huán)節(jié)調(diào)度算法來 實(shí)現(xiàn)對于環(huán)節(jié)的流轉(zhuǎn)(環(huán)節(jié)的合并、分叉、選擇、條件性的選擇等)。
三、初步印象
1、從概念開始解釋估計(jì)大家都會看不下去了。我們先拿一個簡單實(shí)例來看看,新建一個項(xiàng)目,引用我的工作流引擎類庫(Chitu.Bpm.dll,取名為赤兔)。
在項(xiàng)目啟動時配置流程引擎(Global.asax.cs中),如下:
- //初始化流程引擎
- BpmConfiguration
- .Instance()
- .Config(@"C:\Configration\BpmConfig.xml")
- .Start();
在項(xiàng)目中使用時,比如新建流程定義:
- //取得工作流上下文
- var bpm = new BpmContext()
- .UseTransaction(true)
- .SetActor("蕭秦");
- //新增流程定義
- bpm.NewProcessDefinition("請假流程")
- .SetXmlFile(@"C:\Definition\demo1.xml")
- .SetCategory("分類1")
- .SetEffectDate(DateTime.Now)
- .SetExpireDate(DateTime.Now.AddDays(180))
- .SetMemo("memo1")
- .Create() //創(chuàng)建流程定義,只生成bpm_definition_process表
- .Parse() //解析xml
- .Deploy(); //發(fā)布流程
啟動流程:
- //啟動流程
- var process = bpm.NewProcessIntance("請假流程ID", "蕭秦(業(yè)務(wù)ID)"); //創(chuàng)建流程實(shí)例
- process.SetVariable("流程變量1", "值1"); //設(shè)置流程變量
- process.Start();
人工任務(wù)節(jié)點(diǎn)轉(zhuǎn)交下一步:
- //任務(wù)完成
- var task = bpm.LoadTaskInstance("任務(wù)ID");
- task.SetVariable("任務(wù)變量2", "xx");
- task.Signal(); //觸發(fā)令牌流轉(zhuǎn)
2、接下來我們先看看流程定義的XML,以下是我捏造的一個流程,以便把各種節(jié)點(diǎn)都放進(jìn)去了。
- <?xml version="1.0" encoding="UTF-8"?>
- <process name="樣板房裝修流程">
- <start name="裝修申請">
- <transition to="裝修方案設(shè)計(jì)" >
- <action class="Namespace.MyActionHandler"></action>
- </transition>
- </start>
- <task name="裝修方案設(shè)計(jì)">
- <transition to="裝修方案審核">
- <action script="log.Debug('裝修方案審核 action test');"></action>
- </transition>
- </task>
- <decision name="裝修方案審核">
- <transitions>
- <transition to="裝修籌備" condition-expression="variable.pass == true"></transition>
- <transition to="裝修方案設(shè)計(jì)" condition-expression="variable.pass != true"></transition>
- </transitions>
- <events>
- <action event="enter" class="enterHandlerClass"></action>
- <action event="leave" class="leaveHandlerClass"></action>
- </events>
- <assignments>
- <assignment owner="{process.starter}"></assignment>
- </assignments>
- <variables>
- <variable type="boolean" name="IsPass" access="read,required"></variable>
- </variables>
- </decision>
- <fork name="裝修籌備">
- <transition to="裝修合同簽定"></transition>
- <transition to="等待裝修工人到位"></transition>
- <transition to="裝修材料預(yù)算"></transition>
- </fork>
- <sign name="裝修合同簽定" necessary="false" async="true">
- <transition to="裝修施工"></transition>
- </sign>
- <wait name="等待裝修工人到位">
- <transition to="裝修施工"></transition>
- </wait>
- <task name="裝修材料預(yù)算">
- <transition to="材料采購子流程"></transition>
- </task>
- <subflow name="材料采購子流程">
- <transition to="裝修施工"></transition>
- </subflow>
- <join name="裝修施工">
- <transition to="施工驗(yàn)收/付款"></transition>
- </join>
- <auto name="施工驗(yàn)收/付款">
- <transition to="裝修完成"></transition>
- </auto>
- <end name="裝修完成">
- </end>
- <events>
- <action event="process-start" class="TestStartHandler"></action>
- <action event="process-end" class="TestEndHandler"></action>
- </events>
- <variables>
- <variable type="string" name="start_id" access="readonly,required"></variable>
- <variable type="string" name="start_person"></variable>
- </variables>
- </process>
定義的根節(jié)點(diǎn)為流程(process),流程下為各個任務(wù)節(jié)點(diǎn)(node),任務(wù)節(jié)點(diǎn)分為:
start 開始節(jié)點(diǎn) auto 自動節(jié)點(diǎn) task 人工節(jié)點(diǎn) decisioin 決策節(jié)點(diǎn) fork 發(fā)散節(jié)點(diǎn) join 聚合節(jié)點(diǎn) sublfow 子流程節(jié)點(diǎn) sign 會簽節(jié)點(diǎn) wait 等待節(jié)點(diǎn) end 結(jié)束節(jié)點(diǎn)
任務(wù)節(jié)點(diǎn)下可以包括路由(transition)動作(action)及人員分配(assignment)變量定義(variable)
其中action包括幾種類型:1、class 2、script 3、sql 4、webservice 5、expression 在script或expression中可以直接訪問process.xxx屬性或task.xxx屬性或variable.xxx簡化了動態(tài)c#語句的使用。 當(dāng)然XML定義中還有很多其它的屬性定義我這里也沒有都列出來,以后用到了再仔細(xì)說。
3、關(guān)于數(shù)據(jù)庫設(shè)計(jì),這里僅僅是流程流轉(zhuǎn)核心所需要的表,表之間都沒有拉關(guān)系
(點(diǎn)擊圖片查看大圖)
#p#
四、部分功能剖析
a、我把它劃分為主要的幾大模塊: 引擎配置、流程定義、實(shí)例流轉(zhuǎn)、日志處理、計(jì)劃任務(wù)
引擎配置:配置引擎實(shí)例的數(shù)據(jù)庫連接、日志配置、參數(shù)設(shè)定等。 流程定義:利用xml來描述流程,主要定義任務(wù)節(jié)點(diǎn),路由、動作事件、變量、人員分配等 實(shí)例流轉(zhuǎn):根據(jù)定義運(yùn)行流程實(shí)例 日志處理:輸出日志 任務(wù)計(jì)劃:會啟動一個服務(wù),用于處理比如延時啟動,任務(wù)過期等
b、流轉(zhuǎn)中的關(guān)鍵性類設(shè)計(jì)包括:
1、流程對象(Process) 2、工作任務(wù)(Task) 3、路由(Trasition) 4、令牌(Token) 5、事件總線與動作處理 (EventBus、ActionHandler) 6、人員分配及委托機(jī)制(Assignment、Depute) 7、流程回退處理(RollbackService) 8、消息服務(wù)(NotifyService)
c、通常引擎控制流程調(diào)度流轉(zhuǎn)核心的調(diào)度算法主要有FSM以及PetriNet兩種,基于調(diào)度算法來完成流程的流轉(zhuǎn):
1、FSM(有限狀態(tài)機(jī)) FSM 的定義為包含一組狀態(tài)集(states)、一個起始狀態(tài)(start state)、一組輸入符號集(alphabet)、一個映射輸入符號和當(dāng)前狀態(tài)到下一狀態(tài)的轉(zhuǎn)換函數(shù)(transition function)的計(jì)算模型。當(dāng)輸入符號串,模型隨即進(jìn)入起始狀態(tài)。它要改變到新的狀態(tài),依賴于轉(zhuǎn)換函數(shù)。在有限狀態(tài)機(jī)中,會有有許多變量,例如,狀態(tài) 機(jī)有很多與動作(actions)轉(zhuǎn)換(Mealy機(jī))或狀態(tài)(摩爾機(jī))關(guān)聯(lián)的動作,多重起始狀態(tài),基于沒有輸入符號的轉(zhuǎn)換,或者指定符號和狀態(tài)(非定有 限狀態(tài)機(jī))的多個轉(zhuǎn)換,指派給接收狀態(tài)(識別者)的一個或多個狀態(tài),等等。遵循FSM流程引擎通過狀態(tài)的切換來完成流程的流轉(zhuǎn)。 2、PetriNet 信息流的一個抽象的、形式的模型。指出一系統(tǒng)的靜態(tài)和動態(tài)性質(zhì)。PetriNet通常表示成圖。遵循PetriNet流程引擎通過令牌來決定流程的流轉(zhuǎn)。 我采用的是第二種PetriNet算法。用Token來表示當(dāng)前實(shí)例運(yùn)行的位置,也利用token在流程各個點(diǎn)之間的轉(zhuǎn)移來表示流程的推進(jìn),如下圖所示:
令牌流轉(zhuǎn)邏輯,我把以下類方法都做一個簡化省略了路由選擇及節(jié)點(diǎn)處理細(xì)節(jié),好讓大家明白令牌的流轉(zhuǎn):
- //令牌Token類中Signal
- public void Signal()
- {
- fromTask.Leave(executeContext);
- }
- //任務(wù)Task類中的Leave
- public void Leave(ExecutionContext executionContext)
- {
- transition.Take(executionContext);
- }
- //路由Transition類中的Take
- public void Take(ExecutionContext executionContext)
- {
- toTask.Enter(executionContext);
- }
- //任務(wù)Task類中的Enter
- public void Enter(ExecutionContext executionContext)
- {
- Run(executionContext);
- }
至此令牌成功的從一個節(jié)點(diǎn)轉(zhuǎn)移到下一個節(jié)點(diǎn)了,令牌的流轉(zhuǎn)是工作流的關(guān)鍵,當(dāng)然不同的節(jié)點(diǎn)處理是有所不同的,其中最復(fù)雜的當(dāng)數(shù)發(fā)散節(jié)點(diǎn)及聚合節(jié)點(diǎn)了。這里就介紹到這里,不再給大家詳細(xì)介紹了。
d、目前我引擎中實(shí)現(xiàn)的主要包括以下功能:
1、解釋過程定義 2、控制過程實(shí)例—創(chuàng)建、激活、掛起、終止等 3、控制流程調(diào)度流轉(zhuǎn) 4、自定義動作及事件發(fā)布 5、流程變量及工作變量處理 6、任務(wù)計(jì)劃,比如延時啟動,任務(wù)過期等 7、委托服務(wù),委托代辦 8、回退服務(wù),回退到任意節(jié)點(diǎn)或召回 9、消息服務(wù),比如認(rèn)領(lǐng)通知、待辦提醒、催辦消息… 10、流程任務(wù)監(jiān)控服務(wù) 11、日志處理及歷史記錄 12、任務(wù)分配與認(rèn)領(lǐng) 13、參與者組織模型接口
五、總結(jié)
目前我的這款工作流引擎還在繼續(xù)完善當(dāng)中,我總結(jié)下它的優(yōu)缺點(diǎn):
優(yōu)點(diǎn): 1、它是一款超輕量極或者說是微型的工作流引擎,而且綠色無污染,它只有一個dll,大小僅1M左右。 2、它目前支持SQL Server、MySql、Oracle、SQLite、PostgreSql等多種數(shù)據(jù)庫 3、體型上來說它雖然是微型,但功能上并不算微型,它的設(shè)計(jì)結(jié)合了現(xiàn)代的OA及傳統(tǒng)工作流、基本上可以實(shí)現(xiàn)我們大多數(shù)的功能需要。 4、它其實(shí)是面向開發(fā)者設(shè)計(jì)的,從上面初始印象中的實(shí)例代碼中大家可以看到,它的接口是很集中、精簡、友好的,讓開發(fā)者容易理解而且使用起來更方便簡單。所以它更適合嵌入到項(xiàng)目中開發(fā)。 缺點(diǎn): 1、它現(xiàn)在沒有流程設(shè)計(jì)器、管理系統(tǒng)、表單設(shè)計(jì)器等,充其量只能算是一個類庫,并不是直接拿來就可以使用。 2、目前剛剛完成第一個內(nèi)部版本,而且目前只在我們內(nèi)部項(xiàng)目中使用,所以它不夠成熟,雖然我們會持續(xù)的改進(jìn)和完善。 3、缺乏成功應(yīng)用的案例。 對于我們自己來說,這些缺點(diǎn)都是我們需要繼續(xù)努力的地方,可能還需要大量的時間來完成。目前來說我們還不打算開源,等它慢慢穩(wěn)定成熟后我們會 考慮是不是開源出來。如果大家有好的建議或有哪方面的疑惑我很樂意給大家解答,或者你也在設(shè)計(jì)開發(fā)自己的工作流,我們可以相互交流下。