Fork me on GitHub

微服务业务生命周期流程管控引擎

  如题,这里介绍我最近开源的进行业务生命周期管控的流程引擎(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.库存保存

 

如果你已经看到这里,并且感觉还行的话可以在下方点个赞,或者也可以关注我的公众号(见二维码) 

_________________________________________

posted @ 2021-02-08 08:21  KevinCC  阅读(878)  评论(0编辑  收藏  举报