从腾讯视频DDD重构案例,看看DDD极大价值

图片

本文目录

- 尼恩说在前面

- 携程订单系统重构的DDD实践

- 一、案例介绍

- 二、问题分析

  - 2.1 沟通困难

  - 2.2 业务边界不清晰

  - 2.3 面对业务变化修改困难

- 三、解决方案

  - 3.1 回归业务本质——挖掘愿景

  - 3.2 高效沟通——利用事件风暴统一语言

  - 3.3 自上而下细化边界——子域划分

  - 3.4 自下而上抽象概念——限界上下文

  - 3.5 挖掘业务变化的瓶颈——上下文依赖关系

- 四、收益总结

  - 4.1 业务逻辑耦合降低

  - 4.2 团队效率提高

  - 4.3 性能和稳定性提高

  - 4.4 数据一致性

  - 4.5 人力成本大幅下降

- 五、遇到的问题和方案探索

  - 5.1 领域专家难寻

  - 5.2 业务需求多且急

  - 5.3 团队对DDD理解不深

- 说在最后

- 部分历史案例

携程订单系统重构的DDD实践

作者:小白龙,携程资深后端开发工程师,关注架构落地、研发效能领域。

随着历史业务的不断演进和业务场景的日益复杂,携程用车和租车(简称两车)在面对历史技术债务和系统复杂度不断上升带来的理解、维护、迭代难题时,开始寻求更有效的方法来降低复杂度和提升效率。

本文描述了两车如何利用DDD(Domain-driven Design,领域驱动设计)方法论来减轻系统复杂度,以及在重构历史系统过程中所作出的取舍和思考。此案例对于复杂业务场景下的领域驱动设计具有借鉴意义。

一、案例介绍

携程用车订单业务包括接送机、包车、打车等产线,涉及订单状态管理、支付状态管理、供应商订单状态管理、履约状态管理等功能。履约状态关乎司机相关状态,完成订单需结清额外费用。

携程租车订单业务涵盖订单状态管理、支付状态管理、押金扣除记录、供应商订单状态管理、履约状态管理等功能。履约状态主要涉及取还车相关状态。

订单和相关实体如下图所示:

图片

二、问题分析

由于两车业务存在一些差异,为了读者更容易理解,因此将抽取共性问题来说明。

2.1 沟通困难

关于沟通困难,在整个开发过程中,沟通是一个耗时且复杂的过程。需求方需与产品沟通,产品再与研发沟通。研发在开发过程中发现问题需与产品确认,产品则需寻找需求方核实。跨团队沟通更加复杂。以下是一些常见场景:

  • 产品不关心研发的实现,但是觉得需求很简单或者很复杂。

  • 研发开发过程中发现一些忽略的细节需要产品确认,产品要找需求方确认。

  • 历史逻辑没人知道,需求评审的时候无法发现问题,做到最后发现有问题。

  • 跨团队之间不了解对方的业务,需要反复沟通确认。

  • 遇到同一个名词不同的理解导致无效沟通。

  • 一个需求到底该哪个域来实现是我们在实践中经常反复探讨的问题。

例如订单和供应商订单在不同的团队内都叫订单,在沟通中针对“订单”的讨论就会产生歧义。

2.2 业务边界不清晰

设计之初,订单被各调用方当作了对外输出的数据源头,数据需求方只要调订单详情即可获取全量数据,这为以后订单的迭代带来了相当大的隐患。订单在自己的业务模型中加入大量不涉及自身业务的冗余字段,在系统的演进过程中,由于无脑插入他方业务字段使得订单自己也要维护相关的逻辑(解释和修改),导致各方对订单的耦合日益加深,导致订单服务的发布变成高风险行为,甚至一个无关订单业务的相关字段修改也可能导致系统故障。

例如订单上关于供应商的相关数据,用户订单有一份,采购订单也有一份,当采购要修改供应商的相关逻辑时要用户订单也一起修改,而用户订单必须排查和推动相关使用到这个字段的业务方切换替代方案。

图片

2.3 面对业务变化修改困难

随着历史业务迭代,订单中集成了许多非订单关注的核心业务逻辑。例如,历史上发送用户通知是根据订单状态变化触发的。订单需提供所有相关参数,导致核心业务依赖非核心业务。在这种情况下,若需求变更,如重发发送失败的通知,逻辑似乎只能放在订单上,不够优雅。

图片

三、解决方案

3.1 回归业务本质——挖掘愿景

为了解决业务归属问题和明确系统发展方向,避免将资源浪费在非核心功能上,我们需要了解当前项目的本质和目标。因此,我们需要为系统设定一个愿景,这将引导我们在未来的迭代中保持正确的方向。愿景就像是我们的产品定位,它区别于其他系统,也确定了当前系统的边界。

图片

愿景就像是手电筒发出的光,黑暗中的边界就是我们的系统,而光的延伸方向则代表着系统的未来。

有许多方法可以阐述一个愿景,为了降低实施的难度,我们选择采用麦肯锡的“电梯演讲”形式,围绕机遇、挑战、优势和劣势来展开。这个过程中,领域专家和开发团队需要共同进行头脑风暴,这实际上也是领域驱动设计(DDD)统一语言的起点,我们必须在愿景的层面达成一致。

产品名称 用户订单管理系统
产品品类 一个查看和管理订单平台
描述目标客户或利益相关人的需求或机会 1、定后履约的查看和管理
2、流畅: 每个用户有操作需求的节点高度自动化,无需人工处理
3、贴心:想用户所想,在合适的时间告诉用户合适的事情
4、无忧: 减少用户的焦虑,让用户放心的使用我们的服务
阐释产品能够带来的关键价值(或者说购买的理由) 为用户提供订单流程管理。
与竞争产品的不同之处

友情提醒

Eric Evans 在他的书中曾提到过一种模式:领域愿景描述(Domain Vision Statement)

“由于一开始项目的模型通常不存在,但是需求是早已定下的重点,为了我们在后续阶段清楚了解系统的价值,以价值作为我们的导向。”

在研究领域愿景声明时,我们发现编写一份合格的文档并非易事。因为它没有明确的规范和套路,Eric 也只是提供了几个案例供我们参考。虽然撰写愿景声明并不复杂,但要达到合格标准仍有一定的门槛。因此,我们选择回归 Eric 的观点:“很多项目团队都会编写‘愿景说明’以便管理。最好的愿景说明会展示出应用程序为组织带来的具体价值。”

3.2 高效沟通——利用事件风暴统一语言

图片

关于统一语言,一个经典的例子便是传话游戏。一句话经过多人的转述,最后可能完全变了一个意思。

为了迅速实现统一语言,我们在订单重构过程中投入了大量时间进行事件风暴。事件风暴有以下几个优点:

  • 事件风暴以业务流程为核心进行讨论,使在场的每一个人都能通过多条流程深入了解业务实体的变化。

  • 事件风暴汇集了“领域专家”,包括产品、开发、测试等团队成员,本质上是一场集合集体智慧的头脑风暴。在事件风暴中,所有人都能达成业务共识。

  • 事件风暴整合了各人的领域知识,是一场领域知识的分享会。

图片

过去,事件风暴主要以线下工作坊的形式开展,以增强参与感。然而,由于成本和线上办公的兴起,我们现在更多地采用在线工作坊。在此推荐两个工具:行知蜂(BeeArt)和可画(Canva),它们都支持多人在线协作。

事件风暴实际上非常简单,就是业务流程+业务用例。将业务流程横向展开,通过用例将业务中的名词状态变化一一列举。色块的大小和颜色可以参考www.eventstorming.com,但只要能统一大家的认知,颜色并非关键。

在实践过程中,我们发现先列举业务中单据的状态变化,再补全触发状态变化的动作和角色,效率会更高。关键是要识别出大家认知中的不同事物相同名词、不同名词相同事物,以便后续建模。

通过事件风暴,我们主要关注以下几种情况:

  • 沟通中那些脱离当前领域就难以理解的词汇;

  • 相同名词,含义不同的;

  • 名词不同,含义相同的。

将以上三种情况涉及的名词动词总结成统一语言表,特别是第三种情况,是我们划分限界上下文的关键依据。例如,在讨论支付单时,我们发现存在两种支付单:一个是包含我们业务的支付单,它需要记录当前支付场景并包含一定的业务规则;另一个是支付平台的支付单,每次支付都会生成一个支付单,它可以认为是与更抽象的订单相关(如会员订单、优惠券订单)。

于是我们提取了费项记录这个概念,表示一笔订单可以有多个支付事件,用于区分我们的支付单与支付中台的支付单之间的差异。通过这种方式,我们逐步实现了统一语言,为后续的建模和协作奠定了基础。

3.3 自上而下细化边界——子域划分

传统的面向过程开发方法在面对复杂系统时,通常采用 DFD(数据流图)进行拆分。在 DDD(领域驱动设计)中,子域概念应运而生。我们经常听到领域和子域这两个词,无论是 Eric 的 DDD 还是 IDDD,都大量使用了这两个概念。但有时候,我们并未真正理解子域是如何划分而来的。

对于一个已有的系统而言,我们可以根据康威定律得出:团队边界=系统边界,因此,我们可以认为每个团队负责的部分就是天然的子域。以订单团队为例,我们可以初步划分为用户订单组和采购派发组。基于此,我们可以得出一个领域划分:

图片

此时我们根据愿景,可以明确两个子域各自的职责:

  • 用户订单子域:负责用户订单流程的查看和管理,并在需要时主动通知用户。

  • 采购订单子域:负责采购订单流程的流转,包括供应商和行前行中行后的状态更新。

最后支付使用的是携程金融的能力,由于支付平台的能力在携程内部是统一的,因此我们认为支付平台属于通用域。

3.4 自下而上抽象概念——限界上下文

领域的概念相对而言还是模糊的,因此Eric提出了DDD中最重要的概念:限界上下文。而限界上下文并非凭空而来,而是需要对我们在事件风暴中得到的名词进行归纳而来。

图片

首先,我们列举了用户订单域的各种用例,包括下订单、支付订单、修改订单、取消订单等。

通过建模法归纳模型,我们发现订单流程中存在多种支付场景,同时依赖支付平台的支付单。因此,我们得到了维护支付单状态的支付费项记录,它既维护了支付单相关的信息,也维护了订单系统内关于支付的业务逻辑。

最后我们根据业务相关性对得出的实体进行归纳,结合我们的愿景得出三个上下文,分别是:

  • 用户订单状态上下文:负责管理用户订单状态管理;

  • 支付费项上下文:负责订单支付相关状态管理;

  • 用户通知上下文:负责对用户进行多种方式的通知。

图片

3.5 挖掘业务变化的瓶颈——上下文依赖关系

实际上,限界上下文可以拆分为更细的粒度,但应遵循奥康姆剃刀原则,尽量设置合理的数量,有理有据地进行拆分。我们先来看看原系统的上下文依赖关系:

图片

  • 消息中心:作为携程的消息中台,不会为某个业务线做特殊逻辑,因此是遵奉者(Conformist)。此时,订单内部耦合了消息处理,如果发送消息没有业务逻辑,采用防腐层(ACL)方式较为常见。

  • 用户通知:由于用户通知存在业务逻辑,订单直接与消息中心交互显得不合理。订单作为核心域,应尽量不依赖其他域。为此,我们进行了如下设计:

  • 用户订单状态上下文:更加内聚,关注订单状态管理。

  • 用户通知上下文:独立于订单,便于迭代。

根据Eric对上下文关系的总结,我们可以得出消息中心作为携程的消息中台,不会为了某个业务线做特殊逻辑,因此是很明显的遵奉者(Conformist)。此时消息相关的处理耦合在订单内部,如果发送消息没有业务逻辑那么采取防腐层(ACL)的方式是比较常见的。

由于我们已经识别到用户通知存在业务逻辑,因此订单直接和消息中心交互显得奇怪,而且订单作为核心域,本来就应该尽量不依赖其它域,对此我们进行了如下设计:

图片

这样用户订单上下文更加内聚,而用户通知也更加易于迭代。

四、收益总结

4.1 业务逻辑耦合降低

通过划分上下文和明确职责,各个领域负责自己的数据和领域知识。这样一来,订单不再涉及这些字段,而是由数据写入的业务方负责维护。因此,在后续与订单无关的业务逻辑发生变化时,订单无需进行修改。这有助于降低业务逻辑耦合,提高系统的灵活性和可维护性。

4.2 团队效率提高

在上下文划分和康威定律应用的过程中,各个团队的职责与各自领域形成映射。这使得过去在功能分工上存在的争议得以解决,团队之间能够更加高效地协作。此外,通过优化资源分配和任务协调,进一步提高了团队协作的效率。

4.3 性能和稳定性提高

经过上下文划分,订单实体从原来的780多个字段简化为200多个字段,大大降低了订单的维护成本。同时,存储数据量减少,各个业务逻辑也由各自的写入方负责维护。接口性能得到优化,p95写入速度从68ms提升至12ms,读取速度从63ms降低至5ms。这些改进提高了系统性能和稳定性,为业务发展奠定了基础。

4.4 数据一致性

过去,业务方在将数据写入订单时,可能因网络抖动等原因导致写入失败或数据错误。如今,业务方将数据存储在自己的领域内,不再写入订单。这样一来,数据一致性得到增强,避免了因耦合导致的数据不一致和字段写错等问题。

4.5 人力成本大幅下降

产研沟通涉及的相关方数量减少,链路缩短。去除因业务逻辑耦合导致订单修改的人力成本,整体人力成本在小项目中下降70%,大项目甚至下降80%。通过降低人力成本,提高了项目效益和投资回报率。

五、遇到的问题和方案探索

实际应用领域驱动设计(DDD)的过程中,我们遇到诸如领域专家难寻、业务需求多变且紧急、团队对DDD理解不足等问题。针对这些问题,我们总结出以下几点经验:

5.1 领域专家难寻

在实际工作中,找到一位严格的领域专家往往是困难且成本高昂的。为了解决这个问题,我们可以借助资深研发人员、资深QA人员等,同时通过互相借鉴的方式,尽管业务不完全相同,但领域上仍有相通之处。此外,培养内部领域专家和建立知识体系也是非常重要的。

5.2 业务需求多且急

实际工作中,我们常常陷入各种业务项目的忙碌之中,很多项目都十分紧急。这种情况很容易导致我们无法专注于DDD改造。为应对这一问题,我们采取在有时间时确定方向、提前进行设计,然后在业务项目中逐步实现的策略。同时,通过敏捷开发方法和良好的需求管理,确保在紧急业务需求中仍能保持DDD改造的进度。

5.3 团队对DDD理解不深

为提高团队对DDD的认识,我们成立了专门的DDD培训小组,将实际落地经验整理成规范和最佳实践。在落地过程中,培训小组负责把关,确保大家不走弯路。此外,通过内部交流、分享和实践,提高团队成员对DDD的认知和实践能力。

说在最后

DDD架构如何落地,是是非常常见的面试题。

以上的内容,如果大家能对答如流,如数家珍,基本上 面试官会被你 震惊到、吸引到。

在面试之前,建议大家系统化的刷一波 5000页《尼恩Java面试宝典PDF》,并且在刷题过程中,如果有啥问题,大家可以来 找 40岁老架构师尼恩交流。

最终,让面试官爱到 “不能自已、口水直流”。offer, 也就来了。

当然,关于DDD,尼恩即将给大家发布一波视频  《第34章:DDD的学习圣经》, 帮助大家彻底穿透DDD。

图片

部分历史案例