如何形成统一设计风格-实践篇

339次阅读  |  发布于2年以前

一 背景

在上一篇[《业务团队如何统一架构设计风格?》] 中,探讨了一种业务架构的设计规范,以期达到这些目标:用标准约束技术细节;用技术工具而非文档推行标准;持续重构而非造新轮子;重视业务建模。但通篇表述较为抽象。本篇将总结团队近来的架构演进工作,以更具体的技术细节,详细阐释该理念,作为“统一业务设计风格”的实践篇。文中详述了多个层面的设计规约和基于规约的搭建方式,并在末尾回答了上一篇的诸多疑问。

二 总览

上图以电商产品为例,展示了一套标准框架的各层设计单元。先简单了解下概念,下一章节会详细解释各层的设计规约和搭建方式:

以产品合约描述完整的功能列表;以签署人身份来定位产品功能的适用场景;以合约分组来描述一个独立完备的功能域,分组的集合就是产品功能的范围和边界。通过对合约分组进行组装,可以快速搭建商业产品。

为了减少不同技术同学对领域进行建模的风格差异,我们对业务模型的使用场景做了诸多约定,串联起仓储管理/业务流程/业务组件等基础模块。所有人更关注于业务在模型上的表达,而大大减少了对实现细节的关注。基于对领域的分析,可以快速搭建业务模型。

用一套标准的业务流程框架,描述业务模型的完整执行流程:业务组件是一套高内聚的业务功能集合,基于组件配置将业务模型的信息适配为标准参数,交由基础设施执行具体功能;流程引擎负责创建和管理流程实例,接收指令来触发组件动作的执行,并实现状态推进/条件跳转和异常处理等分支管控的需求。通过对业务组件/基础设施的抽象和沉淀,可以快速搭建业务流程。

用一套标准的数据流机制,来满足视图层的定制化需求:数据流订阅器用于采集数据,物理来源包含区块链跨链数据/业务DB数据/文件系统数据/离线任务数据等;数据流消费器用来加工原始数据,生成展示层数据/待核对数据/数据指标等等。订阅器确保了数据来源的稳定和低成本的快速接入,消费器则交由技术同学自行定制业务逻辑。在不干扰领域建模的基础上,可以快速搭建数据视图。

三 规约详解

1 产品模式

产品合约

1)规约

  1. 产品合约以全局视角,描述完整的业务模式,包括:服务的目标客户,依赖的业务领域,输出的服务等等
  2. 产品合约的内容是一份静态描述文件,需要由签署身份列表来界定使用场景

2)实例

以电商产品为例,商家单独签署的产品合约被作为商家合约,描述了商品的上架要求;商家+平台+买家共同签署的产品合约,则适用于交易下单场景。

3)搭建

合约分组

1)规约

  1. 合约分组以局部视角,描述某个高度内聚的业务领域所提供的功能和依赖的配置信息,包括:业务模型,业务服务,业务流程,业务组件等等
  2. 多个合约分组共同组成一个可交付业务的产品合约

2)实例

电商产品合约下,商品分组描述了商品上架的流程和配置,下单分组约束了订单创建的流程和服务信息,退货分组则说明了退货流程和买家能够享受的客户服务。

3)搭建

2 业务领域

模型

1)规约

  1. 业务模型描述一个领域内的核心业务实体,是唯一贯通业务流程和业务组件的业务实例
  2. 一个业务模型内可以关联其他模型,但应避免出现循环依赖
  3. 一个完备的业务模型描述需要包含:数据模型,视图模型,业务模型/数据模型/视图模型的三者转换,业务模型仓储等

2)实例

退货业务,基于退货单推进业务流程,各业务组件从退货单获取必要的业务信息,执行退货/退款/通知等业务功能;退货单关联自一个正向订单,但正向订单不可反向依赖退货单;一个退货单模型对应一张主单据表和多张退货明细表,仓储需要负责完成业务模型<->数据模型的双向读写

3)搭建

服务

1)规约

1、业务服务是一套以业务领域为单位(interface)作聚合,开放给内外所有使用方的最小业务功能单元(method) 2、业务服务需要一套定义规范(annotation/aop等),对每一个功能单元有清晰直观的元数据描述,用以实现服务发现/文档生成/权限管控/稳定性保障等等。元数据包括:业务域,业务动作,读/写,错误码范围,返回值模型等等 3、业务服务的入参,限制为一个sysParam和一个bizParam,前者为调用来源/幂等ID/产品码/租户ID等系统参数,后者为各业务自行定义的模型参数,建议为可全链路透传(rpc->api->flow->component)的POJO

4、业务服务以Result形式返回,错误码尽量控制在元数据描述的范围内,不泄漏任何exception给调用方。返回的业务信息,建议为POJO或VO 5、业务服务不局限于调用方的物理来源,只需要在对接层增加简单的转换逻辑,做授权管控即可 6、写服务的实现,需要有事务管理机制

2)实例


public interface DemoOrderService {
    /**
     * 下单申请
     * @param sysParam sysParam
     * @param bizParam bizParam
     * @return result
     */
    @ApiFunction(apiType = ApiType.SUBMIT, funcBiz = "ORDER",funcAction = "APPLY",
            returnType = OrderApplyResponse.class, errorCodeType = CommonErrorCodeEnum.class)
    CommonResult<OrderApplyResponse> apply(ApiReqSysParam sysParam, OrderApplyInfo bizParam);
}

3)搭建

流程

1)规约

1、Flow用于描述一个完整的业务流程,基于单个业务模型,推进一个或多个业务子环节

2、对于单个业务模型的同一类型业务流程,可以有多个Flow定义,以满足不同业务模式的定制需求

3、Flow包含迁转 (transition) ,组件 (component) 和动作 (action) 三级结构,运作原理如下:每次触发 (operate) 对应于组件的一次action,所有action都成功的component会完结,而所有component都成功的transition将会触发Flow和业务模型的状态迁转。

4、Flow的目标是将复杂流程拆解成多个原子化的业务动作,相互解耦

5、Flow需要结合业务服务/消息/调度等调用入口的触发,才能实现完备的流程推进

6、Flow需要依赖外部调用方提供事务管理机制(通常是业务服务),需要依赖业务模型仓储来控制模型的加载和存储

2)实例


<?xml version="1.0" encoding="UTF-8"?>

<flow id="OrderApply" version="001" desc="标准下单流程">
    <config>
        <model class="xxx.xxx.Order"/>
    </config>
    <init type="INIT" desc="初始化">
        <action operate="INIT.INIT"/>
    </init>
    <transitions>
        <transition from="INIT" to="ITEM_OCCUPIED">
            <component type="ITEM" desc="扣减库存">
                <action operate="ITEM.OCCUPY"/>
            </component>
        </transition>
        <transition from="ITEM_OCCUPIED" to="DISCOUNT_OCCUPIED">
            <component type="DISCOUNT" desc="扣减优惠">
                <action operate="DISCOUNT.OCCUPY"/>
            </component>
        </transition>
        <transition from="DISCOUNT_OCCUPIED" to="SUCCESS">
            <component type="NOTIFY" desc="下单成功通知">
                <action operate="NOTIFY.SELLER"/>
                <action operate="NOTIFY.BUYER"/>              
            </component>
        </transition>
    </transitions>
</flow>

3)搭建

组件

1)规约

1、业务组件是某一类业务动作的聚合,面向业务功能设计,不局限于任何一个业务模型

2、业务组件的业务动作,是原子化的最小业务单元,粒度暂无强制要求,但以解耦和复用程度为衡量依据;建议其依赖一个到多个基础设施/业务服务,以模板化的方式提供标准的业务动作实现

3、对于某个业务模型,业务组件通过开放适配器(详见【基础设施-适配】)的方式支持受控定制,或以完全复写的方式实现排他定制(不允许其他业务复用)

4、所有的核心业务逻辑,都应收归到业务组件层及其以下(无流程的简单业务服务除外),包括但不限于:参数校验,业务校验,重入/幂等控制,业务模型变更,合约分组变更,计算规则,外部服务交互等等

5、业务组件需要一套定义规范(xml/annotation等),对其支持的业务动作和业务模型有清晰直观的元数据描述,用以搭建业务流程。元数据包括:业务动作列表和对应的触发点(operate),支持的业务模型列表

2)实例

public interface BizModelDiscountComponent<T extends BizModel> extends BizModelComponent<T> {

    /**
     * 占用优惠
     * @param context
     */
    void occupy(FlowContext context);

    /**
     * 退回优惠
     * @param context
     */
    void refund(FlowContext context);
}
<componentTemplate type="DISCOUNT" desc="优惠">
    <interface name="xxx.xxx.BizModelDiscountComponent"/>
    <bizModelMappings>
        <bizModelMapping>
            <bizModel class="xxx.xxx.Order"/>
            <componentEntry name="orderDiscountComponent"/>
        </bizModelMapping>
        <bizModelMapping>
            <bizModel class="xxx.xxx.RefundOrder"/>
            <componentEntry name="refundOrderDiscountComponent"/>
        </bizModelMapping>
    </bizModelMappings>
    <triggerMappings>
        <triggerMapping>
            <triggerTemplate operatePostfix="OCCUPY"/>
            <methodEntry name="occupy"/>
        </triggerMapping>
        <triggerMapping>
            <triggerTemplate operatePostfix="REFUND"/>
            <methodEntry name="refund"/>
        </triggerMapping>
    </triggerMappings>
</componentTemplate>

适配器Adapter的解释,详见【模型适配】小节

public abstract class AbstractBizModelDiscountComponent<T extends BizModel> implements BizModelDiscountComponent<T> {
    @Resource
    private DiscountApiService discountApiService;

    @Override
    public void occupy(FlowContext context) {
        // TODO AdapterConfigInfo根据context从当前合约中获取
        T bizModel = (T) context.getBizModel();
        getDiscountAdapter().processOnOccupyResult(
                bizModel,
                discountApiService.occupy(getDiscountAdapter().toOccupyInfo(bizModel, new AdapterConfigInfo()))
        );
    }

    @Override
    public void refund(FlowContext context) {
        // TODO AdapterConfigInfo根据context从当前合约中获取
        T bizModel = (T) context.getBizModel();
        getDiscountAdapter().processOnRefundResult(
                bizModel,
                discountApiService.refund(getDiscountAdapter().toRefundInfo(bizModel, new AdapterConfigInfo()))
        );
    }

    @SuppressWarnings("unchecked")
    protected BizModelToDiscountAdapter<T> getDiscountAdapter(){
        return (BizModelToDiscountAdapter<T>) FlowInstanceFactory.instanceBizAdapter(
                "DISCOUNT", (Class<? extends BizModel>) TypeUtils.getRealClassOfParameterizedType(this));
    }
}

3)搭建

3 基础设施

注:此处的基础设施与DDD中的概念有很大差异,请勿混淆

规约

  1. 基础设施是一套以高复用高内聚低变化的外部服务能力为单位(interface)作聚合,开放给业务服务/业务组件使用的最小功能单元(method)
  2. 基础设施可以是对渠道能力的封装,如外部商家渠道服务/跨境渠道服务等;也可以是对通用技术能力的封装,如优惠服务/商品服务/客户服务等
  3. 基础设施和业务服务的差异在于:前者的核心功能通常由外部服务提供,在当前系统内的核心职责是参数组装/场景识别/返回解析和异常处理
  4. 基础设施的定义不依赖于外部服务,入参为自行定义的标准POJO,返回值同样以Result封装,屏蔽外部服务的exception和业务异常,业务返回同样是标准POJO

实例


public interface NotifyGateway {

    /**
     * 通知(邮件/短信/站内信)
     * @param notifyInfo
     * @return
     */
    CommonResult<NotifyResponse> notify(NotifyInfo notifyInfo);
}

搭建

4 模型适配

规约

  1. 模型适配用于衔接业务模型和基础设施/业务服务,实现模型->入参和返回->模型的双向处理
  2. 在模板化的业务组件中,适配器和基础设施/业务服务的调用链已经固化,各业务模型的组件实例只需要实现对应的适配器,即可完成业务定制
  3. 适配器通常与产品合约配置结合,描述业务模型->基础设施/业务服务入参的映射关系

实例

public abstract class BizModelToDiscountAdapter<U extends BizModel> implements BizModelAdapter<U> {
    @Override
    final public String getType(){
        return "DISCOUNT";
    }
    /**
     * 生成扣减申请
     * @param bizModel
     * @return
     */
    abstract public OccupyInfo toOccupyInfo(U bizModel, AdapterConfigInfo configInfo);

    /**
     * 处理扣减结果
     * @param bizModel
     * @param result
     */
    abstract public void processOnOccupyResult(U bizModel, CommonResult<OccupyResponse> result);

    //...
}

@BizAdapter
public class OrderToDiscountAdapter extends BizModelToDiscountAdapter<Order> {
    @Override
    public List<ConfigDef> getConfigDefs() {
        return Lists.newArrayList(
                ConfigEnum.DISCOUNT_TYPE,
                ConfigEnum.DISCOUNT_TERM
        );
    }

    @Override
    public OccupyInfo toOccupyInfo(Order bizModel, AdapterConfigInfo configInfo) {
        // 解析出客户选择的优惠类型
        return new OccupyInfo();
    }

    @Override
    public void processOnOccupyResult(Order bizModel, CommonResult<OccupyResponse> result) {
        // TODO 根据扣减成功的优惠,重新计算订单金额
    }

    // ...
}

搭建

四 总结

啰嗦了这么多,为避免被过度细节冲淡主题。最后以几个问题做个小结:

1 业务设计规范体现在哪里?

架构层面,从产品合约->业务领域->基础设施,我们对应用做了模块拆解,在不同层面设计了业务规约,约束了各模块的职责;技术层面,通过多个底座组件,一定程度上实现了平台和业务定制的隔离,限制了业务细节的无序散布。

2 业务设计只有合适没有标准,为何要强制规范?

规范的目的不是标准本身,本文提出的标准也未必适合所有问题域。想传达的是,团队内需要有业务设计的某种共识和沉淀,在每次迭代需求和每次项目产出的基础上,持续积累持续重构持续优化,这对新人融入/个人成长和团队协作都很有帮助。

3 如何快速支撑业务,研发效能提升体现在哪里?

需要明确的是,对于全新的业务需求,不会带来明显的效能提升,甚至会为了满足设计规范,带来一定程度的额外成本。但当多人协作,工作交接,或是现有功能部分可复用的场景下,会减少很多不必要的沟通和维护成本。举例来说,当一个业务需求出现时,研发人员需要做如下判断:

  1. 业务模型:是否需要新的业务模型,是否需要调整现有模型

2 . 业务服务:xxxxxxxxxxxx业务服务,xxxxxxxxxxxx现有服务

3 . 业务流程:xxxxxxxxxxxx业务流程,xxxxxxxxxxxx现有流程

4 . 业务组件:xxxxxxxxxxxx业务组件,xxxxxxxxxxxx现有组件

5 . 基础设施:xxxxxxxxxxxx基础设施,xxxxxxxxxxxx现有设施

6 . 产品合约/合约分组:基于上述判断,评估产品合约和合约分组的组装

带来的效能提升有这样几点:业务领域的每个模块互相解耦,研发过程并行化,投入人员1+1可以=2;改造范围更易于定位,资源评估更为准确,进度把控更加清晰;针对频繁变动且成本过高的模块,进行针对性的重构,影响范围可控;上文中的很多处规约,都有潜在的低代码化可能,能进一步提升搭建效率。

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8