DDD早在2004年就由埃里克·埃文斯提出,但一直处于不温不火的状态,直到微服务盛行之后,DDD再次回到人们视野之中。京东、美团、华为等巨头也都在自家架构中实践了DDD。
DDD究竟有什么优势?它是如何落地的?实施难点又在哪里?结合惟客数据实践DDD的经验,今天就来一起探讨下。
咱们先从一个场景说起。
通常在研发流程中,需求评审完毕后就交由某个研发做设计,方案的设计完全取决于某个人的经验和对依赖模块的熟悉程度,可能很难识别出模块设计上的不合理。即使可以识别出,往往都会以技术的语言和产品经理进行沟通,产品经理可能很难理解,最终难达到预期的目的。
以惟客服务过的某地产集团案例为例。客户的诉求是实现“转赠功能”,比如用户A下单5件商品,将其中2件转赠给用户B,此时用户B的“我的订单”栏会出现一笔含有2件商品的订单,且不可退款的同时,用户B还可以再次实行转赠。
这意味着需要在订单领域生成一种“特殊的订单”和“特殊的拆单”,该订单仅能使用且不能退款。
需求评审完后,产品、研发一起开始做“领域建模”,由于涉及订单领域,也对照之前的“订单模型图”做了方案的设计,发现订单域承载更多的是交易相关,把转赠业务耦合进来,十分困难。
但研发在模型图里发现了专门承载履约的“核销聚合根”,只要核销权限能转出去,就可以满足业务场景,产品经理也十分认可,最终将产品做了修改,将原先的订单转送,变为了履约权限的转赠。
在上面的案例中,无论从产品设计角度,还是转赠场景和交易场景,划分都更清晰;从代码设计角度,减少冲击到订单域,避免现实中独立的两块业务在系统中耦合成一个模块,从而造成订单的核心代码被冲击,后续难维护的后果。最终实现了“多赢”的局面。
这种“多赢”的局面,就取决于DDD过程中带给我们的几点改变:
1、领域模型设计一定程度上拉齐了团队的设计水平,不再取决于某几个人的“经验”。
2、产品、研发建模的过程中,深究业务场景,推导出技术设计的同时也回补了需求漏洞。
3、团队内统一语言,尤其是产品和研发之间,沟通成本低,减少了由于理解差异导致的风险。
4、原先依赖的模块,也采用了DDD的设计,有完整的模型文档,并且有效识别出来了“订单聚合”、“核销聚合”,才使得转赠模块在做设计有了很好的基础。
基于对以上场景的了解,我们再回归定义,从定义出发,一步步拆解。
概念理解:DDD是什么?
DDD全称是领域驱动设计,这里的“领域”可以理解为业务边界,以及指定范围内待解决的业务问题。因此DDD它不是一种架构,而是一套思想,是一种拆解业务、划分业务、确定业务边界的方法。
DDD可以通过合理运用面向对象的封装、继承、多态等设计要素,降低或隐藏整个系统的业务复杂性,帮助我们设计出清晰的领域和边界,并使得系统具有更好的扩展性,很好的实现技术架构的演进,应对纷繁多变的现实业务问题。
DDD的优势在哪里?
程序员擅长从技术角度来解决项目问题。但一个软件系统是否真正可用,需要通过其提供的业务价值来体现。因此在关注技术的同时,DDD更强调将关注点转向软件系统所提供的业务价值。
我们先回顾下产研团队的协作模式,通常是将一个产品开发上线的各个过程拆出来,由市场/运营、产品、UI交互、前端、后端、测试等角色来做分工,每个过程专人负责。
这么做的好处是标准流水线作业,个体生产效率比较高,但坏处是大家都盯着眼前的一亩三分地,自然很容易忽略整体,更难从整体业务价值出发。同时也容易出现信息不对称的问题,比如产品在讨论A时,前端以为是说B。
那么,根据惟客数据在核心产品“惟客云”中实践DDD的经验总结,其优势可以包含以下几点:
统一语言:领域专家、产品、技术、测试等人员都在一起进行事件风暴(整理出相关联的业务指令和事件),在同一个场景下使用统一语言进行领域模型构建,信息传递不会丢失。
提高效率:设计就是代码、代码就是设计,可以根据领域模型图对编辑进行翻译,即使是新人接手也能快速理解整个业务和代码,上手就能写。
边界清晰:战略设计帮助产品决策人理解哪些投入是最重要的、哪些既有软件资产是可以重新拿来使用的、哪些人应该被加入团队中?战术设计则帮助产品研发人员具体实施。
降低成本:先划分业务边界,确认核心业务,避免伴随功能迭代后的代码不断叠加,导致代码耦合,降低维护成本。
以降低成本为例,假设我们要做一个电商订单下单需求,涉及到用户选定商品,下订单、支付订单、对用户下单时的订单发货:
常见做法是在分析好业务需求之后,开始设计表结构,订单表、支付表、商品表等等,然后编写业务逻辑当功能迭代,订单支付后要支持取消,下单商品要支持退换货,就又需要加表,并针对实现的逻辑不断进行修改。这也意味着当功能不断迭代,代码也会层层上叠。
而DDD要求先划分业务边界。该场景下的核心是订单,那么订单就是业务领域里的聚合逻辑体现。支付、商品信息、地址等等都是围绕订单实体。订单本身的属性决定之后,类似于地址只是一个属性的体现。当你将订单的领域模型构建好之后,衍生了仓库的上下文。
总结来说,DDD优势在质量、效率、成本3方面都得到了体现。
惟客是如何实践DDD的?
从时间线来说,可以分为3个阶段:
2020年7月,引入DDD,在小项目和模块进行试点;
2021年6月,完成在大会员模块进行实践和应用;
2022年3月,完成公司交易、营销、会员、家装产品线的改造和重构。
重点以3月份的项目为例。惟客投入70人参与本次项目,目的是解决老旧项目之间耦合严重、业务边界不清晰、代码重复率高、维护成本高等问题。从落地步骤来拆,包括5个部分:
需求评审
1、过需求、了解相关联业务的用户故事
2、产品输出用户故事地图、原型图、流程图,开发理解相关资料
拆用户故事
1、对照用户故事地图将用户故事进行细分
2、整理出相关联的业务指令和事件(事件风暴)
3、用户故事评审,确保需求没遗漏、业务指令和事件划分正确
领域模型图设计
1、识别应用上下文,划分领域界限(核心、通用、支领域),拆分成多个子域
2、根据用户故事及业务指令区分出查询模型和命令模型
3、在领域内绘制出相关联业务的领域模型图(包含查询模型、聚合、聚合内聚合根、实体、值对象的关系、实体具有的能力、领域服务、领域事件)
4、领域模型图评审
代码实现
1、对照领域模型图建业务指令、事件、实体、值对象、实体能力及领域服务
2、完成领域模块开发,建db入库
3、代码评审,对照领域模型图
4、完成所有业务开发
单元测试
1、对于聚合能力的单元测试
2、领域服务,指令的单元测试
实践过程中遇到哪些问题
为创建通用语言腾出来的时间和精力
通用语言是指非技术岗位也能很容易理解的语言,在目标拉齐的基础上,需要拉齐各个角色对术语都使用统一名词,对业务价值以及规则的描述使用统一的描述方式和规则,并且需要使得每一个人都能听的懂。
以商品条形码为例:
领域专家:从场景角度分析,它主要是供小程序扫描这个条形码进行购买商品
产品:从规则角度分析,商品编码,长度是多少位,组成规则是什么。
开发:按照功能事项的角度去分析,barcode,商品的sku级别的唯一标识,存储方式是什么。
很明显,大家分析问题的角度不一样,给出的语言不同,但本质上商品条形码=商品编码=barCode,因此需要花时间拉齐不同角色间的语言。
领域专家对于实践DDD很重要,但这个角色通常是业务专家,他们工作繁忙、时间难约,较难持续在一个项目中消耗太多时间。
因此在落地中,建议尽可能一次性把业务相关的疑问和问题准备好,减少对接次数、提高效率。
由过程编码 -> 面向对象编码
由数据模型 -> 领域模型
领域模型关注的是领域本身,是业务领域的核心实体,体现在问题域关键概念和之间的联系。构建领域模型的目标是看模型能否清晰表达业务语义、模型能否显性化,扩展性是其次。
数据模型关注的是数据存储,在我们所有的业务中都离不开数据,离不开对数据的CRUD。构建数据模型目标是看扩展性、性能等非功能属性,业务语义的表达则是其次。
人最难转变的是思维惯性,而不是技术和策略本身。因此在实践前,对以上3点有心理预期,并提前做好应对方案,可以更快地落地DDD。
总之,如果企业希望能更好地发挥软件系统的业务价值,从长期价值主义出发,提高代码的可维护性、可扩展性,那么DDD绝对是一套值得尝试的方法