流程控制引擎组件化

简介: 流程控制引擎组件化  在较大规模的业务系统中经常会有这样的模块,它按照一定的业务流程调用其它模块来实现一定的业务逻辑,我们姑且称之为流程引擎。这里称之为引擎有两层含义,一、突显其在业务系统的核心重要位置。

                 流程控制引擎组件化

  在较大规模的业务系统中经常会有这样的模块,它按照一定的业务流程调用其它模块来实现一定的业务逻辑,我们姑且称之为流程引擎。这里称之为引擎有两层含义,一、突显其在业务系统的核心重要位置。二、它又是复杂不好维护的,通常由资深程序员把持。这样的引擎不仅代码繁多,与各个模块的接口复杂,并且一定程度对外是不透明的,就像一个黑盒模块。当一个新手想对其进行哪怕是一点点修改的时候都将会是一个灾难。本文就来讨论如何将这样一个庞大的引擎进行组件化改造,使其拥有代码级的流程图,使代码的维护难度降低一个量级,让新手也可以很快知道要如何修改代码,给引擎增加新的功能。

  我们来看两张图,分别是数控机床和机器人流水线。他们都是自动化的,就像我们的业务流程引擎一样。数控机床简洁、高效。但维护的时候需要专业人员,想增加新功能几乎不可能。机器人流水线,透明,看着有点复杂。但可以随时对流水线上任意部分进行升级,改造,替换。回到我们的业务流程引擎,我们到底希望是上面的哪一种?不难想到,由于业务需求的易变性,我们更倾向于第二种方案。但往往我们的代码是第一种情况,并且代码的组织是混乱的,并不是数控机床那种优雅。

 

 

数控机床

机器人流水线

   

  当我们面对一个不熟悉的引擎的时候,我们看到的是下面这样子。

  当我们花了一个月时间,仔细研读了几万行的代码后,我们看到的是下面这样子。

 

 

  虽然我们已经了解了引擎内部的情况,但就像图中所描绘的样子,当要给引擎增加一个新的功能的时候,新增的功能和代码很容易影响原来正确的系统,一个小局部(零件)的问题,都会影响到整个系统。那么,我们如何来改变现有的流程引擎,以实现方便地增加新的功能?

   

   

 

 

  如上图所示,如果我们的流程是一个流水线的话,我们就可以轻易地在流水线中增加新的组件,以实现新的功能。流程组件具有这样的特征:每个组件都是一个独立的个体,不受其它组件的影响。组件具有统一的接口,新增的组件可以方便和原有的组件一起工作。可以根据业务需要挑选合适的组件,组成一个新的业务流程。当把这些组件组合到一起,就形成了组件链表的概念。一个个独立的组件,就像链表里面的一个个节点,组件节点通过组件指针连接起来。一个流程的执行过程就像是对这个链表的遍历。并且这个链表不仅可以只是个单链表,而且可以是多分支链表,以应对流程中多条件分支的情况。

 

  

  下面我们来看看代码是如何来实现的。

组件节点:

 

 例子:

 

  这里的Execute()函数就是实现每个业务组件具体功能的地方,在这个函数里面可能调用其它模块。

空组件节点:

 

 

 

业务组件节点:

 

   开闭原则:每个组件都是一个独立体(类),组件之间是一种松耦合的关系。流程中某个组件的改动,或者新增加一个组件,并不会影响到其它组件。支持以一种“对扩展开放,对修改封闭”的方式来为流程增加新的功能。从而不会将错误引入原有的稳定、正确的代码中。组件就像流水线上的每个节点,独立可替换。

 

  流程组装:每个组件都有一个统一的编号ID。初始化时,通过按一定顺序读入这些ID,来进行流程的组装。流程中每个功能模块的调用顺序不再是固定写死的,而是在初始化时动态配置生成的。从而使流程功能的调整(增加,删除)变得很容易,不再用修改代码,而是调整流程组装配置列表的参数。

  由于流程可能是复杂的,具有复杂的分支情况,这里我们设计了一套编码方法来应对这个情况。

  流程编码方法:

  

 

 

  当我们有如下流程图需要实现的时候,下面图片可另外窗口打开。

 

 

  就可以按上面的编码方案生成相应的流程代码,注意这里面说的是代码。这里的流程组装配置列表与流程图是一一对应的。从流程组装配置列表就可以轻松看到计费控制的整个流程图,而不用再发很多时间去了解代码,进而再画流程图。代码化的流程图是自维护的,即跟实际的情况是同步的,不再需要另外地去维护。想像一下,当你接到一个需要维护的复杂引擎的时候,这个引擎的流程图就在你面前,而不再需要你花费一个月时间,阅读数万行代码,自己一点点去画出来。这是何等的进步——The source code is the design

流程组装配置列表

  组件化的优点:

  a)将流程和被流程调用的子模块进行解耦。例如:批价函数,m_pPriceGuiding->ExecuteRating();在现有的在线流程中共有38处地方进行了调用,而按新的设计方案只会在一个组件(ExecuteRating)中进行调用,要用的批价的地方,只要将其加到流程组装配置列表即可。可以想象,如果ExecuteRating()函数接口改变了,对于原有的在线流程将会是一个灾难,而新的设计将可以轻松应对。注意:这并不是一个特例,在流程中几乎所有对子模块的调用都存在这种情况。

  b)组件的复用和移植。新的设计改变了原来以整个流程为最小单位的方式,而是一个组件为系统的最小单位。每个组件都是功能独立的,互不影响的方法集。不仅可以在不同流程间进行复用,甚至于可以跨版本地复用。包括组件移植时,成本也是很低的,只要将组件的代码文件进行移植即可,移植的是文件而不是代码。

  c)一些组件会趋于稳定。原来以流程为最小单位设计,任何的改变都会影响到整个流程,导致每次升版时流程的代码总是有很大的变化。而新的设计以组件为系统最小的单位,有一些组件会趋于稳定,一定程度上保持不变。从而分隔了系统中变化和不变的部分。使系统从整体上更加健壮。

 

  下面介绍一些代码的细节。

RatingCtrlComponent.h
#ifndef RATINGCTRLCOMPONENT_H_
#define RATINGCTRLCOMPONENT_H_

class RatingCtrlComponent
{
public:
    virtual ~RatingCtrlComponent()
    {
    }
    /**
    * @brief 组件流程执行接口
    *
    * @param pRatableEvent [in/out]
    * @return bool
    */
    virtual bool Execute(TRatableEvent& pRatableEvent) = 0;
};

#endif
RatingCtrlDectorator .h
#ifndef RATINGCTRLDECTORATOR_H_
#define RATINGCTRLDECTORATOR_H_

#include "RatingCtrlComponent.h"

class RatingCtrlDectorator : public RatingCtrlComponent
{
public:
    RatingCtrlDectorator(RatingCtrlComponent* pComponentA) : m_pComponentA(pComponentA)
    {
    }
    virtual ~RatingCtrlDectorator()
    {
    }
    /**
    * @brief 组件流程执行接口
    *
    * @param pRatableEvent [in/out]
    * @return bool
    */
    virtual bool Execute(TRatableEvent& pRatableEvent) = 0;

protected:
    RatingCtrlComponent* m_pComponentA; ///< 该组件的下一个组件(A分支)
};

#endif
ComponentFactory.h
#ifndef COMPONENTFACTORY_H_
#define COMPONENTFACTORY_H_


#include <list>
#include <vector>
#include "ComponentDefine.h"
#include "RatingCtrlComponent.h"


class ComponentFactory
{
public:
    ComponentFactory()
    {
    }
    virtual ~ComponentFactory()
    {
        ClearComponent();
    }

    /**
     * @brief 装配组件
     *
     * 根据编码[一个数组]装配组件。
     * 将各个组件构成一个流程分支链表。
     *
     * @param viComponentID [in]
     * @return RatingCtrlComponent 流程的头结点
     */
    RatingCtrlComponent* BuildComponent(vector<int>& viComponentID);
    void ClearComponent();

protected:
    virtual RatingCtrlComponent* CreateComponent(int iComponentID, RatingCtrlComponent* pComponent,
                                                 list<RatingCtrlComponent*>& lpComponent) = 0;

private:
    list<RatingCtrlComponent*> m_lpComponent;
    vector<RatingCtrlComponent*> m_vpComponent;
};

#endif

 

 

ComponentFactory.cpp
#include "ComponentFactory.h"

void ComponentFactory::ClearComponent()
{
    vector<RatingCtrlComponent*>::iterator itr;

    for (itr = m_vpComponent.begin(); itr != m_vpComponent.end(); ++itr)
    {
        delete * itr;
        *itr = NULL;
    }
}


/*
下面的代码很短,却完了一件精妙的工作.组件以一种近似网状的结构组织起来,
就像所有的网络流算法一样,程序的执行过程就是选择一条路径遍历的过程.
不同的是这边的分支是根据业务不同的而进行选择的。如此复杂的想法却以
这么简单的方式来实现,甚为精妙!-----by liang.shishi
*/
RatingCtrlComponent* ComponentFactory::BuildComponent(vector<int>& viComponentID)
{
    if (viComponentID.empty())
        return NULL;

    m_lpComponent.clear();

    RatingCtrlComponent* pComponent = NULL;

    vector<int>::iterator itr = viComponentID.end() - 1;
    // 增加默认基础类
    if (*itr != NONE_COMPONENT)
    {
        viComponentID.push_back(NONE_COMPONENT);
    }

    for (itr = viComponentID.end() - 1; itr >= viComponentID.begin(); --itr)
    {
        if (*itr < 0)
        {
            if (*itr == -1)
            {
                // 正常分支结束后,放在容器后面
                m_lpComponent.push_back(pComponent);
            }
            else
            {
                for (int i = 0; i < (*itr)*(-1); i++)
                {
                    // 多分支情况,放容器前面
                    m_lpComponent.push_front(pComponent);
                }
            }

            // 处理以0结束的分支
            if ((itr - 1) >= viComponentID.begin() && *(itr - 1) == 0)
            {
                pComponent = NULL;
            }
            else
            {
                pComponent = m_lpComponent.front();
                m_lpComponent.pop_front();
            }

            continue;
        }

        pComponent = CreateComponent(*itr, pComponent, m_lpComponent);

        // 组装失败
        if (pComponent == NULL)
        {
            ClearComponent();
            // LOG
            return NULL;
        }

        m_vpComponent.push_back(pComponent);
    }

    return pComponent;
}
RatingCtrlFactory .h
#ifndef RATINGCTRLFACTORY_H_
#define RATINGCTRLFACTORY_H_

#include "ComponentFactory.h"


class RatingCtrlFactory : public ComponentFactory
{
public:
    /**
     * @brief 创建组件
     *
     * 根据iComponentID创建对应的组件,用pComponent初始化新组件,最后返回新组件给pComponent。
     * 对于分支组件,需要根据lpComponent做特殊处理~
     *
     * @param iComponentID [in]
     * @param pComponent [in/out]
     * @param lpComponent [in/out]
     */
    virtual RatingCtrlComponent* CreateComponent(int iComponentID, RatingCtrlComponent* pComponent,
                                                 list<RatingCtrlComponent*>& lpComponent);

private:
    RatingCtrlComponent* Back(list<RatingCtrlComponent*>& lpComponent);
};

#endif

 

#include "RatingCtrlFactory.h"
#include "NoneComponent.h"

RatingCtrlComponent* RatingCtrlFactory::Back(list<RatingCtrlComponent*>& lpComponent)
{
    RatingCtrlComponent* pComponent = lpComponent.back();
    lpComponent.pop_back();

    return pComponent;
}

// 当遇到多分支的时候,应该先将pComponent放回链表首,再从链表尾取出相应数量的组件指针进行组装.
RatingCtrlComponent* RatingCtrlFactory::CreateComponent(int iComponentID, RatingCtrlComponent* pComponent,
                                                        list<RatingCtrlComponent*>& lpComponent)
{
    ADD_TRACE("%s RatingCtrlFactory::CreateComponent IN: iComponentID: %d, pComponent: %p.\n", LOG_PREFIX, iComponentID,
              pComponent);

    if (iComponentID != NONE_COMPONENT && pComponent == NULL)
    {
        ADD_TRACE("%s RatingCtrlFactory::CreateComponent OUT: iComponentID: %d, pComponent: %p.\n", LOG_PREFIX, iComponentID,
                  pComponent);
        return NULL;
    }


    switch (iComponentID)
    {
    case NONE_COMPONENT:
        {
            if (pComponent != NULL)
            {
                return NULL;
            }

            pComponent = new NoneComponent();
            break;
        }
    case EXECUTE_INITIALIZE:
        {
            pComponent = new ExecuteInitialize(pComponent);
            break;
        }
    case EXECUTE_PROCESS_IN:
        {
            lpComponent.push_front(pComponent);

            if (lpComponent.size() < 4)
            {
                return NULL;
            }

            pComponent = new ExecuteProcessIN(Back(lpComponent), Back(lpComponent), Back(lpComponent), Back(lpComponent));

            break;
        }
        default:
        {
            return NULL;
        }
    }

    return pComponent;
}

复杂分机的代码实现:

 

#ifndef EXECUTEPROCESSIN_H_
#define EXECUTEPROCESSIN_H_

#include "RatingCtrlDectorator.h"

class ExecuteProcessIN : public RatingCtrlDectorator
{
public:
    ExecuteProcessIN(RatingCtrlComponent* pComponentA, RatingCtrlComponent* pComponentB, RatingCtrlComponent* pComponentC,
                     RatingCtrlComponent* pComponentD) : RatingCtrlDectorator(pComponentA), m_pComponentB(pComponentB),
                                                         m_pComponentC(pComponentC), m_pComponentD(pComponentD)
    {
    }
    virtual ~ExecuteProcessIN()
    {
    }

    /**
     * @brief IN业务流程选择
     *
     * 根据 ExecuteId 选择流程
     * A:初始包流程; B:更新包流程; C:结束包鉴权; D:异常流程
     *
     * @param pRatableEvent [in]
     * @return bool
     */
    virtual bool Execute(TRatableEvent& pRatableEvent);

private:
    RatingCtrlComponent* m_pComponentB;
    RatingCtrlComponent* m_pComponentC;
    RatingCtrlComponent* m_pComponentD;
};

#endif
#include "ExecuteProcessIN.h"

bool ExecuteProcessIN::Execute(TRatableEvent& pRatableEvent)
{
    ADD_TRACE("%s ExecuteProcessIN::Execute() Begin.\n", LOG_PREFIX);

    int iExecuteId = pRatableEvent.GetAttrEx(EA::EXECUTE_ID)->AsInteger();

    switch (iExecuteId)
    {
    case EXECUTE_ID_INIT:
        {
            ADD_TRACE("%s ExecuteProcessIN::Execute() End Goto EXECUTE_ID_INIT Process.\n", LOG_PREFIX);
            return m_pComponentA->Execute(pRatableEvent);
        }
    case EXECUTE_ID_UPDATE:
        {
            ADD_TRACE("%s ExecuteProcessIN::Execute() End Goto EXECUTE_ID_UPDATE Process.\n", LOG_PREFIX);
            return m_pComponentB->Execute(pRatableEvent);
        }
    case EXECUTE_ID_FINISH:
        {
            ADD_TRACE("%s ExecuteProcessIN::Execute() End Goto EXECUTE_ID_FINISH Process.\n", LOG_PREFIX);
            return m_pComponentC->Execute(pRatableEvent);
        }
    case EXECUTE_ID_EMCDR:
        {
            ADD_TRACE("%s ExecuteProcessIN::Execute() End Goto EXECUTE_ID_EMCDR Process.\n", LOG_PREFIX);
            return m_pComponentD->Execute(pRatableEvent);
        }
    default:
        {
            pRatableEvent.SetAttr(EA::OCS_RESULT_CODE, CCA::DIAMETER_RATING_FAILED);
            ADD_ERROR("ZSmart-Charging-Online-1015",
                      "%s ExecuteProcessIN::ExecuteRatableEvent() GetExecuteId Failed. iExecuteId = [%d], ReturnCode = [%d]\n",
                      LOG_PREFIX, iExecuteId, pRatableEvent.GetAttrEx(EA::OCS_RESULT_CODE)->AsInteger());
        }
    }

    ADD_TRACE("%s ExecuteProcessIN::Execute() Fail.\n", LOG_PREFIX);
    return false;
}

最终复杂的流程图:

 

目录
相关文章
|
7月前
|
缓存 安全 Java
|
6月前
|
JavaScript 前端开发 API
Vue核心指令解析:探索MVVM与数据操作之美
Vue核心指令解析:探索MVVM与数据操作之美
|
7月前
|
JavaScript
带你开启组件化编程
带你开启组件化编程
|
7月前
|
API 开发工具 数据库
简洁应用框架VSEF - 业务解构模版
简洁应用框架VSEF - 业务解构模版
80 0
|
7月前
|
存储 JavaScript 前端开发
第四章 模块和组件、模块化和组件化的理解
第四章 模块和组件、模块化和组件化的理解
|
移动开发 小程序 开发工具
Donut 多端框架是支持使用小程序原生语法开发移动应用的框架
Donut 多端框架是支持使用小程序原生语法开发移动应用的框架,开发者可以一次编码,分别编译为小程序和 Android 以及 iOS 应用,实现多端开发
433 0
Donut 多端框架是支持使用小程序原生语法开发移动应用的框架
|
小程序 开发者
小程序组件化开发
随着小程序的普及,越来越多的开发者开始使用小程序进行开发,而小程序的组件化开发已经成为了一种标配的开发模式。本文将深入介绍小程序组件化开发的相关知识,包括组件的定义、生命周期、通信和使用等方面,帮助大家更好地理解和使用小程序的组件化开发模式。组件是小程序中的一个重要概念,它能够将页面分解成独立的、可复用的部分,以便于开发和维护,组件可以包含一些特定的功能和样式,同时也能够接受外部的数据和事件。在小程序中,组件是通过Component构造函数来定义的。propertiesdata和methods。
149 0
|
存储 Java 开发者
我选择使用Lambda,就是因为其简洁、灵活、高效!
我选择使用Lambda,就是因为其简洁、灵活、高效!
81 0
|
存储 自然语言处理 算法
GaiaX开源解读 | 表达式作为逻辑动态化的基础,我们是如何设计的
GaiaX跨端模板引擎,是在阿里优酷、淘票票、大麦内广泛使用的Native动态化方案,其核心优势是性能、稳定和易用。本系列文章《GaiaX开源解读》,带大家看看过去三年GaiaX的发展过程。
356 0
|
开发框架 JavaScript 前端开发
前端工程化组件化开发框架之Vue的最基础的条件/循环
Vue是一款前端工程化组件化开发框架,可以让开发者更快速、高效地构建出各种复杂的Web应用程序。作为一名Vue开发者,了解Vue的最基础的条件/循环语句是非常必要的。
91 0