微服務業務生命週期流程管控引擎

  如題,這裏介紹我最近開源的進行業務生命週期管控的流程引擎(OSS.EventFlow),當然現在開源的工作流框架很多,無心比較異同,僅表達個人感受:

  典型的OA工作流程引擎,這也是當前很多工作流引擎的重要功能,只是OA場景對於系統來說,其業務特點比較突出,抽象歸納相對容易很多。但是這個對於很多業務場景特別是複雜場景能夠觸達的有限,大多複雜業務流程進度的推進是糅合在業務的具體操作代碼之中,不能有效的把動作執行邏輯和業務的流動邏輯切割,如果開發人員沒有較高的技術能力和一定的業務深度,新人員需要遍覽系統角角落落才能拼湊出產品的概覽圖,甚至可能是個夾雜很多廢棄邏輯的畸形流程。同樣,對於新的產品業務流程開發人員又將其拆解粘合在系統中各處。

   在另一方面,在實際的代碼開發過程中,當一個模塊分給開發人員之後,不管其水平高低,這個模塊的質量主管基本很難有效控制,好點的團隊有代碼評審,但終究只能算是事後補救。這些基本帶來以下三個問題,也是我在這個框架編寫過程中一直思考的:

  1. 微服務的劃分,在單個業務點上已經做到了獨立,可是站在整個產品的流程上看,多個服務可能的交叉依賴調用,特別是複雜邏輯,產品流程如何落實到代碼中進行清晰管控

  2. 業務單元的邊界和銜接處理,常見的是在當前功能方法的底部,直接調用下一步的方法,又如業務之間添加消息隊列的處理,都直接侵入業務代碼中,當前方法的的單元化和可複用性,直接受限於實施工程師水平的高低。同時這個又直接影響開發負責人或開發主管的管控能力。

  3. 隨着產品的迭代,業務流程的快速變化,如何能夠不動已有業務單元代碼本身的情況下,快速在當前業務流程中排除或新增業務操作單元。

   要減少以上的問題的發生,關鍵在於如何解決業務操作單元之間的關聯性。回到現實,對於任何產品,它一定存在一個業務生命週期,在這個週期內,圍繞某個對象,隨着時間推進,執行一系列的動作,最終得到某個業務結果。既然存在時序性,那麼就可以嘗試抽象一個時序輪廓的路徑圖,在這個輪廓之下,再去填充具體動作實現,進度由這個時序輪廓路徑圖根據具體的動作結果決定如何推進,這個路徑圖則可以和產品流程圖進行映射,形成系統裏的流程管控中樞,在系統編碼層級將事件內調用的”隱式導向”轉變爲事件外的“顯式導向”。

   OSS.EventFlow是以BPMN 2.0 流程管理爲思路,設計的輕量級業務生命週期流程引擎基礎框架,將業務領域對象的流程管控和事件功能抽象剝離,切斷事件功能方法內的鏈式調用,提權至流程引擎統一協調管控,事件功能作爲獨立處理單元嵌入業務流程之中,由流程引擎處理事件的觸發與消息傳遞,達成事件處理單元的有效隔離。由此流程的銜接變成可獨立編程的部分,同時向上層提供業務動作的獨立擴展,保證業務單元的絕對獨立和可複用性, 目的是可以像搭積木一樣來完成不同功能代碼的集成,系統向真正的低代碼平臺過渡。

   OSS.EventFlow引擎的原理是將整個業務流當做一個流程管道,結合流程流轉的特性,此引擎抽象了三類核心流程的管道組件:

  1. 事件活動組件

   這類組件主要是處理任務的具體內容,如發送短信,執行下單,扣減庫存等實際業務操作

  2. 網關組件

  這類組件主要負責業務流程方向性的邏輯規則處理,如分支,合併流程

  3. 連接器組件

  這類組件主要負責其他組件之間的消息傳遞與轉化

 

一. 事件活動組件

  這個組件就是業務的動作本身,根據任務觸發的特性,如關聯自動執行,中斷觸發(如用戶觸發,或消息隊列等),根據這兩種情形,提供了兩個抽象基類:

  1. BaseActivity - 直接執行活動組件

  常見如自動審覈功能,或者支付成功後自動觸發郵件發送等,最簡單也是最基本的一種動作處理。 繼承此基類,重寫Executing方法實現活動內容,同一個流體下實現自動關聯執行。 如果Executing方法返回False,則觸發Block,業務流不再向後續管道傳遞 返回True,則流體自動流入後續管道

  2.BaseActionActivity<TContext, TResult> - 用戶觸發活動組件

  繼承此基類 ,重寫Executing方法(自定義返回結果類型)實現活動內容。 當業務流流入當前組件時,觸發調用Notice(虛方法可重寫),之後業務流動停止, 當用戶觸發時,顯式調用 Action 方法(內部調用Executing返回自定義結果類型),流程繼續向後流動執行。

二. 網關組件

  此組件主要負責邏輯的規則處理,業務的走向邏輯無非分與合,這裏給出兩個基類:

  1.BaseAggregateGateway - 聚合業務分支流程活動組件

   將多條業務分支聚合到當前網關組件下,由當前網關統一控制是否將業務流程向後傳遞,只需要繼承此基類重寫IfMatchCondition 方法即可

  2.BaseBranchGateway - 分支網關組件

   此組件將業務分流處理,定義流體時通過AddBranchPipe添加多個分支,至於如何分流,只需要繼承此基類重寫FilterNextPipes方法即可,你也可以在此之上實現BPMN中的幾種網關類型(並行,排他,和包含)。

三. 連接器組件

  此組件主要負責消息的傳遞和轉化處理,在消息的傳遞過程中又支持直接傳遞和異步緩衝(IBufferTunnel)傳遞,根據是否需要轉化,或者異步定義三個基類如下:

  1.BaseConnector<InContext, OutContext> - 轉化連接組件

  業務流經過此組件,直接執行Convert方法(需重寫),轉化成對應的下個組件執行參數,自動進入下個組件。

  2.BaseBufferConnector<TContext> - 異步緩衝連接組件

  繼承IBufferTunnel接口 繼承此組件後,必須重寫Push方法,實現異步緩衝保存的處理,業務流進入此組件後,調用Push方法保存,之後業務流動停止, 消息喚醒時,需顯式調用Pop方法,業務流繼續向後執行

  3.BaseBufferConnector<InContext, OutContext> - 異步緩衝+轉化 連接組件

   繼承此組件後,必須重寫Push,Convert方法,實現異步緩衝保存和轉化的處理,業務流進入此組件後,同樣調用Push方法保存,之後業務流動停止, 消息喚醒時,需顯式調用Pop方法(內部調用Convert方法,完成參數轉化),業務流繼續向後執行

四. 簡單示例場景

  首先我們先假設當前有一個進貨管理的場景,,需經歷 進貨申請,申請審批,購買支付,入庫(同時郵件通知申請人) 幾個環節,流程圖如下: 

  

 根據此流程圖,每個環節對應一個事件活動,這裏以申請活動我們定義如下:

    public class ApplyActivity : BaseActivity<ApplyContext>
    {
        protected override Task<bool> Executing(ApplyContext data)
        {
            // ......
            LogHelper.Info("這裏剛纔發生了一個採購申請"); 
            return Task.FromResult(true);
        }
    }

這裏爲了方便觀察,直接繼承 BaseActivity,即多個活動連接後,自動運行。 相同的處理方式我們定義剩下幾個環節事件,列表如下:

    ApplyActivity      - 申請事件    (參數:ApplyContext)
    AutoAuditActivity  - 審覈事件    (參數:ApplyContext)
    PayActivity        - 購買事件    (參數:PayContext)
    StockActivity      - 入庫事件    (參數:StockContext)
    EmailActivity      - 發送郵件事件    (參數:SendEmailContext)

以上五個事件活動,其具體實現和參數完全獨立,同時因爲購買支付後郵件和入庫是相互獨立的事件,定義分支網關做分流(規則)處理,代碼如下:

    public class PayGateway:BaseBranchGateway<PayContext>
    {
        protected override IEnumerable<BasePipe<PayContext>> FilterNextPipes(List<BasePipe<PayContext>> branchItems, PayContext context)
        {
            // ......
            LogHelper.Info("這裏進行支付通過後的分流");
            return branchItems;
        }
    }

這裏的意思相對簡單,即傳入的所有的分支不用過濾,直接全部分發。

同樣因爲五個事件的方法參數不盡相同,中間的我們添加消息連接器,作爲消息的中轉和轉化處理(也可以在創建流體時表達式處理),以支付參數到郵件的參數轉化示例:

    public class PayEmailConnector : BaseConnector<PayContext, SendEmailContext>
    {
        protected override SendEmailContext Convert(PayContext inContextData)
        {
            // ......
            return new SendEmailContext() { id = inContextData.id };
        }
    }

通過以上,申購流程的組件定義完畢,串聯使用如下(這裏是單元測試類,實際業務我們可以創建一個Service處理):

        public readonly ApplyActivity ApplyActivity = new ApplyActivity();
        public readonly AuditActivity AuditActivity = new AuditActivity();

        public readonly PayActivity PayActivity = new PayActivity();

        public readonly PayGateway PayGateway = new PayGateway();

        public readonly StockConnector StockConnector = new StockConnector();
        public readonly StockActivity  StockActivity  = new StockActivity();

        public readonly PayEmailConnector EmailConnector = new PayEmailConnector();
        public readonly SendEmailActivity EmailActivity  = new SendEmailActivity();

          //  構造函數內定義流體關聯
        public BuyFlowTests()
        {
            ApplyActivity
            .Append(AuditActivity)
            .AppendConvert(applyContext => new PayContext() {id = applyContext.id})// 表達式方式的轉化器
            .Append(PayActivity)
            .Append(PayGateway);

            // 網關分支 - 發送郵件分支
            PayGateway.AddBranchPipe(EmailConnector)
            .Append(EmailActivity);

            // 網關分支- 入庫分支
            PayGateway.AddBranchPipe(StockConnector)
            .Append(StockActivity);
            //.Append(後續事件)
        }


        [TestMethod]
        public async Task FlowTest()
        {
            await ApplyActivity.Start(new ApplyContext()
            {
                id = "test_business_id"
            });
        }

運行單元測試,結果如下:

xxx Detail:這裏剛纔發生了一個採購申請

xxx Detail:管理員審覈通過

xxx Detail:發起支付處理

xxx Detail:這裏進行支付通過後的分流

xxx Detail:分流-1.郵件發送

xxx Detail:分流-2.庫存保存

 

如果你已經看到這裏,並且感覺還行的話可以在下方點個贊,或者也可以關注我的公衆號(見二維碼) 

_________________________________________

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章