DDD与中台的敌人-扩展点业务

一、背景

DDD与中台的本质就是尽量复用,沉淀已有业务能力,为新业务赋能,但是大多情况下DDD或者中台无法承担一些超大规模的业务app,比如阿里系的一些中台应用,别的如字节跳动也在按中台的建设思路去搞,但是不管怎么样中台都跟企业本身的组织架构有关,中台做大或者做小控制其规模的仍然是企业高管以及其背后的组织力量。就前一阵的字节也在按事业群拆中台。但是不管怎么样中台代表着大量大型中心化的系统,因此都变得非常厚重。

聊了这么多,那我们探讨一下为啥中台会这么厚,中台厚了的一个重要的导火索–扩展点业务。那为啥又跟DDD有关呢,因为DDD本身跟中台有一点概念和理论的重合关系,DDD的战略模式也在重点关注组织上如何更好的管理大型业务系统。那么在战术模式上DDD如何解决扩展点业务带来的问题,这里其实也是值得探究的。

二、扩展点业务阐述

2.1 什么是扩展点

常见定义,扩展点这里我理解应该是出自设计模式的一个设计原则:开闭原则,总结起来就是:对修改关闭,对扩展开放。在C#和Java语言体系里的修改是对类和接口实现的修改,扩展是对抽象类和接口实现的扩展。扩展点的上线应该是模块级别的扩展,说白了就是我在模块内部做高内聚,与其他模块做低耦合,同时我模块内支持可扩展。

2.2 扩展点业务的复杂度

我们这里可以仔细品一下这句话在扩展点业务的意思。对修改关闭是指已有的能力和应用尽量不动,降低修改代码回归的成本。对扩展开放指我可以通过领域模型表达的领域能力来扩展新的业务应用能力。所以到这里扩展点已经从代码级别提升到了模块级别以及应用级别了。当然,扩展点从小到大其实也有些细微的变化就是越大越难理解,越小扩展也越清晰。

2.3 扩展点为什么会成为敌人

  1. 对领域模型和服务产生冲击

  2. 扩展点耦合了中台本身的能力

  3. 扩展点在链路调用中出现问题难以排查

  4. 扩展点众多不方便管理

  5. 扩展点的实现方案不够合理,代码更难维护

2.4 扩展点在设计层面的案例

这里我们举一些现实生活中的例子来体验一下扩展点的能力。

  1. 苹果电脑的扩展坞

  2. 网络协议的扩展

  3. 框架能力的扩展

  4. 开放主机服务模式

2.6 扩展点在架构层面的扩展

这里我们在架构层面来探讨两种层次的扩展,首先我们回顾一下DDD的经典分层架构,四层架构图:

DDD与中台的敌人-扩展点业务
DDD分层架构V2.png


这里的架构图是我简化的,没有遵从严格分层的模式,而是做了点松散分层,当然也不致于特别松散。
很容易想到的扩展就是在应用层扩展,那么另外一个可扩展的层面就是领域层。
所以如果按DDD总体分层的架构图来看扩展点,其实就类似于如下的架构图:

DDD与中台的敌人-扩展点业务
DDD分层架构V2 (1).png

2.7 应用层面的扩展

这里我们看一下应用层面的扩展,首先我们了解一下应用层面的扩展是怎么回事。当我们的领域模型或者领域能力能够完全支持新业务场景的时候,但是在应用层却没有显示的表达出来。所以我们需要对应用层的业务逻辑做迭代和更新。这里我们举一个我司交易核心项目的退款场景的案例。为了更清楚的表达应用层面的扩展场景我画了一下交易核心的简单架构图:

DDD与中台的敌人-扩展点业务
交易核心应用包 (1).png



在领域层退款能力已经覆盖了三个场景,但是对于应用层来说,由于不同的业务分包,所以每个业务线需要的能力需要在适配层或者应用层包里去表达,因此当C业务线需要接入其他退款场景的时候(如原路退回)则只需要在应用层包里去进行简单实现即可。

2.8 领域层面的扩展

很多情况下我们基本不会在领域层进行扩展,因为如果领域模型稳定的话,那么这种扩展理论上在应用层是可以解决的。那么为什么领域层面依然有一些人或者框架尝试在领域层进行扩展点的构建呢?我个人觉得可能有下面的几个方面原因:

  1. 构建超大型复杂平台项目,多维度收拢领域能力,对于应用层来说更多的是一些应用适配,也就是说应用层与适配层之间变得相对模糊了。

  2. 领域能力本身没有进行合适的管理导致使用不恰当的扩展方案。比如一个平台项目按DDD的方式进行演进的话,本身并没有问题,但是领域层随着业务的接入,场景的支持适配。领域层本身也变得复杂,在迭代过程中领域层变得不可控,领域能力与应用层能力编排变得职责不再清晰。

  3. 领域能力底层的模型在应对业务变化时无法跟上领域模型的变化,导致领域模型与数据库模型出现映射偏移,在后期的迭代中数据库模型可能很久没有更新,而新业务场景一直在老数据库模型上继续叠加,识别新上下文和统一语言时没有更新领域模型或者只是简单的去适配实现。最终复杂度会呈现在领域层,并向上扩散到应用层。

这里简单举一个例子大家感受一下,算是商品领域的一个简单案例,我们从最简单的领域能力看一下。
比如商品的sku,spu.stock.最简单的实现是spu-sku-stock都是一对一的。那么随着业务的变化要支持spu-sku是一对多的,数据库模型是支持的,领域层不一定支持,如果领域层不支持的话那么领域层需要进一步调整,来适配。现在我们进一步来看一下当我们要区分前后台spu,并且前后台spu是多对多的,但是不区分sku,领域层会很方便的可以识别并调整这种区分,但是在底层而言就需要做比较大的调整。比如增加关联表,或者加字段和类型做自关联,但是明显不符合数据库的设计范式。如果数据库模型不做更改或者少改去适应领域的变化的话,我们再增加一个区分前后台sku的需求的话,对于领域层的改动会比较大,但是对于底层改动可能更大。

三、扩展点的类型场景

3.1 平行式扩展

这里按照Java的语言特性分两种情况,我们继续按之前文章中讲述的KVPairBO对象建模来举例(文章链接在文末)将模型用PlantUML类图来表达一下,如下:

  1. 抽象类扩展

    DDD与中台的敌人-扩展点业务

  2. 接口扩展

    DDD与中台的敌人-扩展点业务

平行式扩展的一个典型案例就是SPI,当然直白的说也可以认为是插件式的,可拔插式的。

3.2 膨胀式扩展

这里我举几个膨胀式扩展的一些具体类型,当然也涉及到一些设计模式的内容,这里我们举另外一个例子,营销场景下的优惠规则匹配。
1.代码方法内扩展(违背开闭原则)
这里我用一个服务类来表示一下,如下代码片段(伪代码):

public class CouponCalculateService{
    //根据优惠券列表和订单详情判断是否可以使用优惠券
    public int calculate(List<Coupon> couponList,String orderCode){
        Object order = getDetail(orderCode);
        //小于100元的订单没有优惠券可以抵扣
        if(order.sumCount < 100){
            return 0;
        }
        //订单金额在100~200之间则找到一个小于20的优惠券
        else if(order.sumCount > 100 && order.sumCount < 200){

            return couponList.stream.filter(coupon -> coupon.amount<20 ).getFirst().amount;
        }
        //订单金额大于200找到一个全店铺通用的优惠券
        else if(order.sumCount > 200){

            return couponList.stream.filter(coupon -> coupon.isGlobal).getFirst().amount;
        }

        //todo list
        //a业务线需要支持小优惠金额的优惠计算?
        //b业务线需要用店铺给的优惠券和用户领取的优惠金额进行计算
        //a业务线需要通过积分换指定小额度的优惠卡进行优惠计算?

    }

}

以上通过代码方法内扩展的案例给大家展示一下这种扩展是如何变得更加复杂而且逐渐不可控的。
2.接口和类的方法扩展
这里我们用接口和类的方法来定义新的优惠计算能力,如下plantUML类图:

DDD与中台的敌人-扩展点业务

增加方法和类级别的扩展依然有点违反开闭原则的意思,但是总体来说比第一种方式稍微好一点。
3.包装式扩展(装饰器模式)
这里类似于多优惠券同时扣减,比如我的订单大于1000块的话,可能会有更多优惠,如果使用信用卡支付或者绑定某某支付渠道的话会有其他场景的优惠加入计算,可以对常规的优惠模型进行包装实现订单金额优惠扣减。


3.3 继承式扩展

继承式扩展算是一种常见的扩展能力,源自于Java面向对象的三大特性之一。很多情况下基于继承的机制我们可以对业务能力进行增强和扩展,但是由于继承理论上是没有限制的,所以继承式扩展在业务代码上并不建议使用。
这里我们用做菜来举一个例子。用牛肉做菜。这里我们定义牛肉菜谱。首先是红烧类-红烧牛肉。
现在需要对红烧牛肉做升级,加土豆。所以新特性就变成了红烧类-红烧牛肉-红烧牛肉+土豆。
现在呢,我们不想吃普通牛肉了,想继续升级一下,变成牛腩。追求更好的口感,所以新特性就变成了红烧类-红烧牛肉-红烧牛肉+土豆-红烧牛腩+土豆。现在呢,有个顾客说我要把土豆变成白萝卜,并且要求多加香菜点缀,多要酱汁拌饭。所以这个菜就从红烧牛肉变成了一个VIP版本的菜系了。

3.4 组合式扩展

组合式扩展这里也可以认为是混合式扩展,就是说对于需要扩展的地方存在随意修改主流程逻辑也增加了一些扩展层,导致最终的代码和执行流程变成组合式扩展。

3.5 增强式扩展

这里的增强式扩展我总结了下有以下几种情况,比如AOP切面,执行过滤器这类偏技术性实现的增强,比如在AOP里面为参数和返回值做动态控制,过滤特定字符串等等。由于这种扩展不会特别复杂大多情况也不会深度应用这些机制进行业务上的扩展,所以复杂性还稍微可控。

四、如何解决扩展点业务的问题

4.1 扩展点问题简述

从上面的理论分析来看,扩展点业务确实很复杂,稍微一不注意,核心系统就会出现大量if else等的逻辑。在阿里待过的都大概了解过TMF,星环等产品,当然还有其他业务框架。其本质都在解决中台如何构建支持旗下各个业务线各自在中台的扩展点问题。表象就是我中台提供各种业务能力,能力地图,将这些中台的能力显示的表达出来。其他则由底层的各种框架来支持。那么大厂的解决方案一般不会太深入的开放出来。作为中小厂的技术人员,如果是我们,我们怎么去解决这个问题。以下有几个维度可以供大家讨论:

4.2 框架维度

这里我提供了两个在框架维度解决扩展点问题的方向。

  1. 控制扩展能力

因为扩展点的实现以及扩展点在业务主流程中的侵入,导致扩展点不好管理。开放式的扩展或者没有约束的扩展相当于没有扩展,一旦应用起来依然无法解决扩展点导致的复杂性问题。

  1. 表达扩展场景

这个方向是因为扩展点肯定与核心流程或者标准实现不一样,对于业务方肯定是先表达个性化扩展的,因此维护方可能会站在标准实现的角度去做,二者的视角是相对的,那么关注点也是相对的。因此对于扩展场景而言,维护方可能会忽略或者不够重视扩展场景,导致扩展越多与标准实现区别越大。所以,框架层面要能良好的表达扩展点的场景。让双方的认知能聚焦到扩展场景里,不仅代码上可以直观看到扩展调用,扩展描述,可视化界面和调用链路都需要表达这种信息。所以框架层面要做的工作会比较多,对于提供扩展的维护方来说也会受益良多,调用方更想这样。

4.3 代码设计模式维度

  1. 策略模式的扩展

基于策略模式的扩展是比较明显的平行式扩展,大多情况下这种扩展策略非常好用,但是一旦策略或者规则场景太多策略实现会变得复杂,明显的缺陷就是会导致策略类的膨胀。如果不加以改造重构复杂度必然上升。

  1. 状态模式的扩展

很多时候我们用状态模式会做一些简单的状态流程控制,要不就是用基于状态机去驱动,基于封装的状态机组件可以适当控制复杂度,但是状态流转跳转等等依然是比较复杂的存在。更经典的例子是工作流引擎,很多企业会基于引擎做各种业务场景的扩展。因此对于这类起步就比较复杂的场景还是建议在设计之初就要做的相对灵活,通过分离技术复杂度,同时控制扩展能力是有可能控制系统复杂度的。

当然还有其他模式的扩展,这里不再深入。

4.4 能力编排维度

从这个维度思考问题的前提是应用能力和服务能力(这里先不谈领域)已经积累的足够多了。需要基于当前场景能力支持更多其他场景能力,实现能力的复制。一种极端的情况就是我应用模块已经足够大了,我不想扩展了,我把核心模型给你,你自己去扩吧。当然这就有点扯远了。那我们看一下正常情况下还是基于应用工程内的扩展,当我们的接口方法,业务流程足够多的时候,我们可以把这些能力具象化,比如整个可视化界面展示我支付领域的能力等等。
那么当中台开发人员不够或者需要类似于开放平台的API的时候,我们可以通过某些工具平台如plantUML调用时序图或者说流程控制图activity来显示的表达我扩展场景的逻辑。落实到代码层面就是说这种业务场景对我来说只是一次调用或者说只是一层facade,或者也叫适配。对我应用本身来说我依然是可扩展的,我依然不需要编写扩展代码。

4.5 领域设计维度

从领域设计的角度来说,扩展点就像病毒(逆子)一样,他总想站在不同的角度去冲击领域模型,领域能力。再不然会渗透到应用层和基础设施层导致整个应用系统工程复杂度上升。这里我觉得有一些方面尝试

  1. 在领域层中严格区分核心领域能力,标准领域能力和扩展领域能力

  2. 及识别扩展点背后的业务上下文是否没有被显示表达,领域模型+数据库模型是否能够及时支持扩展,并不影响已有系统的领域逻辑。

  3. 在应用层可以灵活运用领域层暴露的各种能力(比如冗余代码,适当编排,通用抽象等等策略)

  4. 从团队协作对接来看,可以详细分析扩展点是否需要系统维护方去支持,或者能否共建扩展点逻辑,相当于在应用层和适配层中增加一层共同维护的扩展层。当然这需要更多东西去支持构建。

4.6 落地的维度

我们从软件需求分析和技术设计的角度去看如何解决扩展点落地的问题。从需求的角度来说相关人员一定要提出哪些是需要扩展的哪些是未来可能扩展的,这类似于插件机制。所以当软件设计者或者系统维护方去实现需求的时候必然会考虑哪些扩展点,并在领域模型和数据库模型上预留适当的扩展元素或者扩展点。比如定义字段枚举,基于接口和抽象类定义核心能力,一开始就设计SPI支持的机制等等。当然扩展点过多之后就要考虑用其他策略去控制扩展点导致的软件复杂度。比如脚本化,动态化,配置化等等。

4.7 各种维度的本质

以上对扩展点的分析以及从不同维度来控制扩展点的本质就是需要控制核心流程或者业务主流程之外的分支流程,将这些分支流程的复杂度控制在一定的水平使得整个服务之间的调用平稳正常。

五、结论

本文从扩展点的业务出发阐述了扩展点业务为什么会成为中台和DDD的敌人,以及分析了扩展点有哪些类型并可能会造成对应的软件复杂度的上升,最后从不同维度给出一些控制扩展点复杂度的解决思路。总体来说扩展点并不可怕,各种分支业务,扩展节点也不可怕。可怕的是我们没有更好的方法策略去观察它,评估它,解决它。
总体来说要解决扩展点的复杂度问题,就是要将扩展点业务显示的表达出来,并通过一定机制进行简化管理,从不同维度监控业务流程在扩展点的行为。

六、参考资料

6.1 KV数据结构场景建模

https://mp.weixin.qq.com/s/IqoeNmmfHJOkco4oYIBLnw

6.2 百度百科定义

https://baike.baidu.com/item/%E6%89%A9%E5%B1%95%E7%82%B9/56097152?fr=aladdin

6.3 COLA2.0扩展点实现

https://blog.csdn.net/significantfrank/article/details/100074716?spm=1001.2014.3001.5501

6.4 TMF2.0

https://developer.aliyun.com/article/291244

6.5 扩展点设计案例

https://www.jianshu.com/p/a20e1793f6d9

6.6 其他扩展点相关博文

http://blog.sina.com.cn/s/blog_493a84550102xg6i.html
https://blog.csdn.net/q6233855/article/details/118945430

七、参会通知

文末最后,打个广告哈,可以来支持一下:

DDD与中台的敌人-扩展点业务


原文始发于微信公众号(神帅的架构实战):DDD与中台的敌人-扩展点业务

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/241628.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!