鍍金池/ 教程/ Java/ 業(yè)務(wù)流程引擎設(shè)計(jì)
分布式鎖的簡(jiǎn)單實(shí)現(xiàn)
關(guān)于框架體系與戰(zhàn)術(shù)的思考
開源與中小型軟件公司的未來趨勢(shì)
生態(tài)圈的建立
用200行的DBF解析器來展示良好架構(gòu)設(shè)計(jì)
緣起
業(yè)務(wù)流程引擎設(shè)計(jì)
軟件開發(fā)雜談
高屋建瓴,理念先行
借船下海還是造船下海
Web界面快速開發(fā)實(shí)踐
教計(jì)算機(jī)程序解數(shù)學(xué)題
量身定制規(guī)則引擎,適應(yīng)多變業(yè)務(wù)場(chǎng)景
緩存相關(guān)代碼的演變
理想的開源框架與設(shè)計(jì)原則
框架2.0的設(shè)計(jì)梳理
與屈原對(duì)話及開源精神

業(yè)務(wù)流程引擎設(shè)計(jì)

一般的時(shí)候,我們都采用編程式開發(fā),編程式開發(fā)的好處非常明顯:直接、高效、自由,當(dāng)然其缺點(diǎn)也是有的,與其優(yōu)點(diǎn)剛好相對(duì),因?yàn)橹苯?,所以有些變化都要進(jìn)行代碼上的修改;因?yàn)楦咝?,所以一旦出問題,導(dǎo)致的結(jié)果也比較嚴(yán)重,因?yàn)樽杂桑詭淼男薷娘L(fēng)險(xiǎn)也比較大。 這也就是許多大的公司都在進(jìn)行流程化開發(fā)的重要原因之一,比如:上海普元,Livebos, Justep,還有許許多多知名不知名的公司都有類似的流程化開發(fā)引擎存在,通過流程化開發(fā),增強(qiáng)代碼的復(fù)用性,降低軟件開發(fā)成本及測(cè)試成本,提升軟件的可維護(hù)性及降低維護(hù)成本。

在設(shè)計(jì)Tiny框架時(shí),我們也考慮了自己的方案,主要包括以下幾個(gè)方面的問題:

a.組件擴(kuò)充的便捷性

組件的擴(kuò)充的便捷性是指,流程其實(shí)玩的就是組件,如果組件擴(kuò)充起來非常困難,會(huì)直接影響到流程引擎的可用性。所以Tiny框架的流程引擎的組件結(jié)構(gòu)非常之簡(jiǎn)單,僅有一個(gè)接口方法;流程組件的注冊(cè)與加載也是非常重要的,如果在擴(kuò)充流程組件的時(shí)候,需要復(fù)雜的注冊(cè)或配置過程,這個(gè)時(shí)候流程擴(kuò)充的便捷性也會(huì)大大降低。Tiny框架采用了引用即注冊(cè)的方案,只要把流程組件放入系統(tǒng)運(yùn)行環(huán)境之間,就完成了流程組件的注冊(cè),即可以在流程中使用,便得流程組件的擴(kuò)充的便捷性大大提高。

b.流程的面向?qū)ο筇匦灾С?/h3>

流程的面向特性支持是指在Tiny框架中流程是具有面向?qū)ο蟮奶匦缘?。流程可以進(jìn)行繼承,這樣帶來一個(gè)好處就是多個(gè)流程中重復(fù)的部分,可以定義在一個(gè)父流程中,然后子流程只要繼承父流程,即可;流程節(jié)點(diǎn)是可以被覆蓋的,也就是說,在父流程中可以定義一個(gè)空節(jié)點(diǎn),但是流程中定義了流轉(zhuǎn)關(guān)系,但是流程節(jié)點(diǎn)的實(shí)現(xiàn)留在子流程中實(shí)現(xiàn);

c.流程的易編輯性

流程的編輯必須方便、容易,有專門的流程編輯工具更好,沒有的時(shí)候,使用普通的Xml編輯器也可以方便的進(jìn)行編輯。

d.流程的可重入性

一般的流程引擎都是不可重入的,也就是只能從開始執(zhí)行,執(zhí)行到結(jié)束結(jié)點(diǎn)之后完成。Tiny流程引擎支持流程重入,也就是說,不一定是從開始結(jié)點(diǎn)執(zhí)行,可以從任意一個(gè)結(jié)點(diǎn)執(zhí)行。這個(gè)機(jī)制為程序的邏輯提供了非常大的自由度,可以利用此特性容易的構(gòu)建頁(yè)面流引擎或工作流引擎。即使是業(yè)務(wù)流程引擎,也會(huì)由此獲得更大的自由度。

由于支持流程的可重入性,在本流程處理當(dāng)中,不僅可以在當(dāng)前流程中進(jìn)行切換與轉(zhuǎn)接,還可以流轉(zhuǎn)到其他流程的節(jié)點(diǎn)當(dāng)中,這在業(yè)務(wù)處理及頁(yè)面處理,流程處理方面都提供了極大的使得,但是這也是一個(gè)雙刃劍,在提供了這么靈活的功能的同時(shí),也會(huì)導(dǎo)致業(yè)務(wù)流程看起來比較復(fù)雜,因此,控制方面最好由架構(gòu)師或核心開發(fā)人員來編寫,普通開發(fā)人員只開發(fā)具體的業(yè)務(wù)點(diǎn)即可。

呵呵,說了這么多,大家理解起來可能還是比較抽象,那就來個(gè)例子看看:

<flow id="1000" name="Hello">  
     <nodes>  
           <node id="begin">  
                 <component class-name="org.tinygroup.flow.HelloWorldComponent">  
                     <properties>  
                         <property name="name" value="world" />  
                     </properties>  
                 </component>  
           </node>  
     </nodes>  
< /flow>  

HelloWorldComponent的源碼如下:

public class HelloWorldComponent implements ComponentInterface {  
     String name;  
     public String getName() {  
         return name;  
     }  
     public void setName(String name) {  
         this.name = name;  
     }  
     public void execute(Context context) {  
         context.put("result", String.format("Hello, %s", name));  
     }  
 }  

可以看出,所有組件必須實(shí)現(xiàn)ComponentInterface 接口 從其實(shí)現(xiàn)邏輯可以看出,它就是把“Hello, ”加上輸入的名字,放在了環(huán)境變量的result當(dāng)中。 下面看看執(zhí)行結(jié)果: a.按默認(rèn)開始結(jié)點(diǎn)開始執(zhí)行

Context context = new ContextImpl();  
 flowExecutor.execute("1000",  context);  
 assertEquals("Hello, world", context.get("result"));  

b.從指定節(jié)點(diǎn)開始執(zhí)行

Context context = new ContextImpl();  
 flowExecutor.execute("1000","begin", context);  
 assertEquals("Hello, world", context.get("result"));  

可以看到確實(shí)是執(zhí)行并返回了結(jié)果,但是它的執(zhí)行機(jī)理是怎么樣的呢?? 實(shí)際上,上面的流程是一個(gè)簡(jiǎn)化的流程,就是說Tiny流程引擎的有些參數(shù)不輸入,也可以按照約定正確的執(zhí)行,實(shí)際上寫得完整的話,例子是下面這個(gè)樣子的:

<flow id="1000" version="1.0" privateContext="false" extend-flow-id="" name="Hello" title="你好示例" default-node-id="end" begin-node-id="begin" end-node-id="end" enable="true">  
   <description>some thing....</description>  
   <nodes>  
     <node id="begin">  
       <component class-name="org.tinygroup.flow.HelloWorldComponent">  
         <properties>  
           <property name="name" value="world"/> <span></span> </properties>  
       </component>  
       <next-nodes>  
         <next-node exception-type="java.lang.Exception" next-node-id="end"/>  
       </next-nodes>  
     </node>  
   </nodes>  
< /flow>  

其中flow節(jié)點(diǎn)的屬性含義為:

  • id,唯一確定一個(gè)流程
  • privateContext,如果是true,則在流程單獨(dú)申請(qǐng)一個(gè)context,否則共用調(diào)用者的context,這樣可以有效避免環(huán)境變量沖突問題
  • extend-flow-id,繼承的流程id,這個(gè)繼承id是一個(gè)非常強(qiáng)大的功能,后面詳細(xì)介紹
  • version版本號(hào),同一id的流程可以存在多個(gè)版本,訪問時(shí),如果不指定版本則默認(rèn)采用最新版本
  • name,title僅用于說明其英文,中文名稱,易于理解而已。
  • default-node-id表示,默認(rèn)執(zhí)行節(jié)點(diǎn),即如果一個(gè)組件執(zhí)行完畢,其項(xiàng)值沒有指定下一處理節(jié)點(diǎn)則執(zhí)行默認(rèn)節(jié)點(diǎn)
  • begin-node-id,開始節(jié)點(diǎn)
  • end-node-id,結(jié)束節(jié)點(diǎn)

如果不指定,則begin-node-id默認(rèn)為begin,end-node-id默認(rèn)為end

  • node節(jié)點(diǎn):id必須指定,在一個(gè)流程當(dāng)中id必須唯一。
  • component節(jié)點(diǎn)
  • class-name用于指定組織實(shí)現(xiàn)類名
  • properties是組件的屬性列表
  • property中的name與value是組件的屬性的值,value,這里傳入的是個(gè)字符串,但是實(shí)際當(dāng)中可以處理中可以非常靈活,后面再介紹。
  • next-nodes,是指根據(jù)執(zhí)行結(jié)果進(jìn)行后續(xù)處理的規(guī)則。
  • next-node,具體的一條規(guī)則,component-result,匹配項(xiàng),支持正則表達(dá)式,節(jié)點(diǎn)中的組件執(zhí)行結(jié)果進(jìn)行匹配,匹配成功則執(zhí)行此規(guī)則中的下一節(jié)點(diǎn)。
  • exception-type是異常的類名稱,如果出現(xiàn)異常且與這里定義的類型匹配,則執(zhí)行此規(guī)則中的下一節(jié)點(diǎn)。

上面說到繼承,流程繼承實(shí)現(xiàn)起來是非常簡(jiǎn)單的,只要在extend-flow-id屬性中指定即可。

繼承不支持多繼承,即流程只能繼承自一個(gè)流程,但是可以支持多層繼承,即

a>b>c>d.....

實(shí)際開發(fā)過程中,不要把繼承搞得太復(fù)雜,這樣會(huì)把程序邏輯搞得更難理解的。

繼承實(shí)際會(huì)起到什么作用呢?

首先,會(huì)繼承一些屬性,另外會(huì)把節(jié)點(diǎn)信息繼承過來。 簡(jiǎn)單來說就是:兩者都有,當(dāng)前流程說了算,當(dāng)前沒有,父流程說了算。

繼承應(yīng)用到什么場(chǎng)景呢??

繼承應(yīng)用于業(yè)務(wù)處理的模式非常相似,只有中間處理環(huán)境不同的時(shí)候。 比如:

A B C D ---O--- -D -C -B -A

類型的業(yè)務(wù)處理流程,只有O不同,其他處理模式完全相同,此時(shí)采用繼承方式都非常舒服了,只要定義父流程,在子流程中只用定義O一個(gè)流程節(jié)點(diǎn)即可。以后要統(tǒng)一進(jìn)行流程調(diào)整,只要在父流程中進(jìn)行調(diào)整就可以了。

比如:flow aa定義為

<flow id="aa" name="aa">  
   <nodes>  
     <node id="begin">  
       <next-nodes>  
         <next-node component-result="begin" next-node-id="hello"/>  
       </next-nodes>  
     </node>  
     <node id="hello">  
       <component class-name="org.tinygroup.flow.HelloWorldComponent">  
         <properties>  
           <property name="name" value="world"/>  
         </properties>  
       </component>  
       <next-nodes>  
         <next-node next-node-id="end"/>  
       </next-nodes>  
     </node>  
   </nodes>  
< /flow>  

flow bb定義為

<flow id="bb" name="bb" extend-flow-id="aa">  
< nodes>  
< node id="hello">  
< component class-name="org.tinygroup.flow.HelloWorldComponent">  
< properties>  
< property name="name" value="world" />  
< /properties>  
< /component>  
< /node>  
< /nodes>  
< /flow>  

則流程bb也可以順利執(zhí)行,且執(zhí)行結(jié)果是Hello, world
非常重要的一個(gè)亮點(diǎn)就是屬性賦值。
屬性賦值是否好用,決定了框架的易用性。
可以支持常量賦值"1"表示數(shù)字常量
aa 表示字符串常量可以支持,環(huán)境變量賦值
比如:xx表示從環(huán)境變量取xx鍵值的對(duì)象
可以支持屬性賦值
比如:xx.abc表示取環(huán)境變量xx的屬性abc
比如:xx.abc.def表示取環(huán)境變量xx的屬性abc的屬性def
可以支持組合賦值
比如:${in:aa.abc.def}-${in:bb.cc.dd}
表示把環(huán)境aa中的屬性abc的屬性def中間加"-"再加上環(huán)境變量bb中的cc的屬性的dd屬性
其中屬性的層次不受限制。
另外,取值方式,也支持自行擴(kuò)展:
比如:可以用${in:xmlkey.aa}也取在環(huán)境中xmlkey對(duì)應(yīng)的xml節(jié)點(diǎn)的aa屬性
所以,只有想不到的,沒有做不到的。
應(yīng)用開發(fā)與部署方式,比較典型的有B/S與B/A/S,C/A/S等。對(duì)于B/A/S和C/A/S方式,因?yàn)锳與B和C是分離部署的,所以,所有的內(nèi)容都需要是通過Context進(jìn)行傳遞的。
如果是通過分離式部署,那么就需要通過網(wǎng)絡(luò)來傳遞請(qǐng)求環(huán)境數(shù)據(jù)。
如果是想通過B/S環(huán)境來構(gòu)建系統(tǒng),此時(shí)就會(huì)期望通過HTTP處理線程來同布調(diào)用流程處理結(jié)果。
同時(shí),有時(shí)流程處理的數(shù)據(jù)可能是在Request,RequestAttribute,Session,Cookie中,如果把這些數(shù)據(jù)COPY到環(huán)境當(dāng)中去,其實(shí)是有較大的性能消耗的。

本流程引擎即支持通過服務(wù)方式調(diào)用,也可以通過短路方式進(jìn)行調(diào)用。
雖然我們推薦使用B/A/S體系架構(gòu),但是不能否認(rèn),目前我們的許多產(chǎn)品還是在B/S架構(gòu)下運(yùn)行的。

但是好在,這個(gè)對(duì)于流程引擎來說,他并不直接訪問Request和Session,Cookie等內(nèi)容,所以,即使是集成在一起部署,也不妨礙進(jìn)行分離式部署,依然可以保證服務(wù)的無(wú)狀態(tài)特性,前提就是需要實(shí)現(xiàn)一個(gè)Context的接口。

小結(jié):

Tiny的流程引擎,提供了相當(dāng)強(qiáng)悍的功能及擴(kuò)展性,上面只說了一部分,有些也沒有完全說清楚,實(shí)際上,還提供了包含EL表達(dá)式等許多高級(jí)功能,對(duì)于期望進(jìn)行流程式編排開發(fā)來說,有相當(dāng)好的支持。 從后期效果來看,在Tiny框架中,業(yè)務(wù)流程編排及頁(yè)面流程編排都是基于此引擎構(gòu)建,應(yīng)用效果非常良好。