☰
技术总监如何优雅落地DDD?老项目改造全步骤复盘
2026/10/9 16:22:50 网站建设 项目流程

新来的技术总监,把DDD落地的那叫一个高级优雅!

这段时间圈子里不少人都在聊DDD(领域驱动设计),但真敢拿它改造现有系统的团队没几个。前阵子我们部门空降了一位技术总监,上任不到一个季度,硬是把一个跑了五年的老项目,从"数据库驱动"的泥潭里拽了出来,全程没有大重构、没有停服、没有返工地狱。我全程参与了这次改造,老实说,看完他的打法,再想想自己以前理解的DDD,差距真不是一星半点。这篇就把他落地DDD的思路、手法、步骤和踩坑判断完整复盘一遍,给同样想在团队里推DDD的朋友做个参考。

1. 他上任前三周没写代码,只干了一件事:把"统一语言"抠出来

很多团队引入DDD失败,第一个坑就栽在"没对齐概念"上。业务方说的"订单"、后端理解的"订单"、前端以为的"订单",指向的根本不是一个东西。订单状态机改来改去、接口文档各写各的,本质都是领域词汇在打架。

那位总监上任后第一周没有碰任何代码仓库,他做了一件事:拉着产品、运营、客服、研发,把现有系统里所有核心名词过了一遍。他用的方法不复杂,就是找一块大白板,要求每个人写出自己工作中最常见的十个业务词,然后逐个解释"这词在你心里是什么意思"。

结果是惊人的。同一个"退款",客服认为是从发起申请到钱退回用户的完整过程,产品认为只包含审核动作,研发眼里的退款则是数据库里一行状态字段的变更。这个对齐过程非常耗时,但恰恰是DDD落地的命根子——没有统一语言,后面画的任何限界上下文、任何聚合根都是空中楼阁。

具体操作上,他要求把对齐后的结果沉淀成一份《领域词汇表》,不只是名词解释,还包括每个词的业务规则、状态流转、触发来源、涉及角色。这份文档成了后续所有需求评审、技术评审、代码评审的共同基准。从第三周开始,凡是需求文档里出现不在词汇表里的新名词,无论谁提出的,都要先走"词汇表更新流程"再谈实现。

我后来才明白这步棋的重要:统一语言不是"文档工作",而是在给整个系统的认知模型做一次强制校准。代码写错了可以改,业务概念理解歪了,代码再优雅也是错的。

2. 限界上下文划分:他没有按"模块"切,而是按"业务风险"切

第二周开始,总监开始划分限界上下文。最让我意外的是,他明确否掉了"按现有代码模块划分"的方案,也否掉了"按组织架构划分"的方案。他的理由是:这两者都代表了"现状",而限界上下文代表的是"业务本质"。

他用的划分方法是事件风暴的轻量版。把产品和运营拉进会议室,所有人一起梳理"从用户下单到订单完成,中间发生了哪些业务事件"。比如:用户提交订单、支付回调到达、库存锁定、订单发货、用户确认收货、订单关闭。每个事件都贴在墙面上,然后按"事件与事件之间是否有强一致的业务约束"来切边界。

这套逻辑我给大家拆解一下。强业务约束指的是:两组事件如果必须在同一事务里成功或失败,必须使用同一套数据模型,那么它们属于同一个限界上下文。反之,只要能容忍最终一致,就能拆开。按照这个标准,传统意义上的"订单模块"被拆成了订单上下文和支付上下文。订单只管状态流转,支付只负责和第三方网关交互,两边靠领域事件通信,不再共享一张订单表。

有个细节非常关键:他在划分上下文时,专门标注了每个上下文的"业务风险等级"。比如涉及资金、合同、合规的上下文是核心域,支撑用户增长的上下文是支撑域,短信通知这类是通用域。这个动作对后续技术选型和资源投入起了决定性作用——核心域用最重的领域建模,支撑域用轻量流程编排,通用域直接采购或对接现成服务,不浪费一丁点建模精力。

划分结果最后沉淀成了一张上下文地图,每个上下文清晰标注了上下游依赖关系、通过何种接口或事件通信、数据归属权归谁。这张图成了架构评审时唯一的"宪法",谁要加新模块、新依赖,都先要过这张图。

3. 六边形架构替换三层架构:把"技术细节"彻底关进适配层

边界划清了,接下来就是代码层面的落位。总监没有沿用经典的三层架构(Controller-Service-DAO),而是全面引入了六边形架构。选择六边形的原因很直白:三层架构把技术细节(数据库、消息队列、第三方SDK)渗透到了业务代码里,业务逻辑被各种技术注解和技术模型绑架,根本没有办法独立演进。

六边形架构的核心思想很简单:把业务逻辑放在正中心,也就是"领域层",周围是它需要交互的"端口"(接口),每个端口对应一个"适配器"(实现类)。数据库、REST接口、MQ消费者、第三方API,统统是适配器的一部分,它们只负责技术输入输出,不掺杂任何业务判断。

从改造顺序上他分了四步走:

第一步,先把现有的Service接口全部重新审视一遍,凡是业务方法命名中出现的"技术语言"全部替换成"业务语言"。比如getOrderByStatusAndTime改成getPendingOrdersForShipment,saveEntity改成confirmReceipt。方法签名完成了从"数据操作"向"业务意图"的转变。

第二步,给核心实体去掉技术污染。原来的实体类上挂满了JPA注解、JSON序列化注解,字段设计跟着表结构走。他要求新模型完全屏蔽持久化结构,实体里只保留业务属性和业务行为。表结构调整怎么办?加一个单独的映射层做转换,绝不允许数据库表结构反向影响领域模型。

第三步,定义端口。根据业务需要,领域层向外声明自己需要什么能力:需要读订单、需要锁定库存、需要发起支付、需要发送通知,全部定义为接口。最初我觉得这是多此一举,但等实现验证后才体会到:端口让领域层变得纯粹,也逼着你在编码前想清楚"业务到底依赖了什么",而不是随手注入一堆Mapper。

第四步,替换适配器。老代码里所有直接调用OrderMapper、RedisTemplate、RestTemplate的地方全部收归到适配器实现中。适配器内部可以继续用原来的技术方案,但对外只暴露领域接口。

这套架构用下来最直观的变化是:如果哪天要把MySQL换成TiDB,或者把RabbitMQ换成Kafka,领域层一行都不用动,只需要替换适配器实现。技术演进对业务逻辑的影响被彻底隔离了。

4. 聚合根尤其是命令模型:他把"重"和"轻"分了两条路

战术建模阶段是很多人卡壳的地方。一说到聚合根、值对象、领域服务,大家容易陷入过度建模的泥潭。总监的应对策略非常务实:命令路径和查询路径完全分离。

命令侧,也就是发生数据变更的路径,严格使用DDD建模。以订单为例,订单是聚合根,订单项是内部实体,收货地址、货币金额是值对象。聚合根通过apply方法响应领域事件,所有状态变更必须走聚合根暴露的业务方法。比如submitOrder方法内部会校验订单项非空、检查商品可售状态、计算总金额、生成订单号、发布OrderSubmittedEvent。外部要改订单状态,不允许直接set字段,只能调用聚合根的方法。

查询侧,也就是报表、列表、详情展示这类没有复杂业务规则的路径,他明确允许走轻量查询模型,直接查视图或读模型,不经过领域层。举个例子:订单列表页要展示"订单金额+商品快照+物流状态",这些数据的组合关系比较复杂,如果强行建模成聚合,只会让聚合变得臃肿不堪。总监的处理方式是:查询侧直接查专用的读模型表,这些表由领域事件的订阅方通过异步方式构建,和命令侧的数据库完全解耦。

这两条路的设计背后有一个关键考量:绝大多数系统的性能瓶颈和复杂度都来自查询需求,如果把查询逻辑也塞进DDD的聚合里,会导致聚合不分主次地膨大。CQRS的开销在多数业务场景里不值得全面铺开,但"命令侧严格建模、查询侧灵活读取"的这种折中,几乎总是能落地。

在聚合设计取舍上,他给了一条非常实用的判断标准:一个聚合根在一次事务里最多只能修改多少个实体?超过这个数量,就要怀疑聚合边界是否划大了。他常用"三个人能在一张桌上讨论清楚这笔业务"来比喻聚合的规模——如果一个聚合需要超过三个人分别负责不同部分才能讲清楚,它就该拆。

5. 防腐层:老系统迁移DDD唯一的"外科手术刀"

我见过太多团队死在"全面重写"上。总监推动这次改造之所以稳,核心在于防腐层(Anti-Corruption Layer)用得非常老到。

防腐层本质上是新旧系统之间的一层翻译隔离区。老系统里的用户表、订单表、商品表,它们的模型和关系是历史原因沉淀下来的,很可能不合理,但不能直接推翻。强制推翻的结果就是改造周期无限拉长,业务方等不起。总监的做法是:承认老模型的存在,但绝不让老模型的概念污染新模型。

具体操作上,凡是新代码需要读取老系统的数据,一律通过防腐层接口获取。防腐层内部先调用老系统已有的Mapper或API拿到数据,然后转换成新领域模型再返回。反过来,新模型要写回老系统,同样经过防腐层做逆转换。这样一来,新老系统在代码层面完全解耦,数据库表结构的耦合变成了防腐层内部的一个实现细节。

他用了一招很巧妙的推进策略:从"新需求"入手,而不是从"老代码改造"入手。所有新增业务,一律按新的DDD架构开发和部署,老代码保持原样运行。新老系统之间只通过防腐层通信。等新需求积累到足够覆盖核心业务闭环时,再把老代码逐条收编进防腐层后面,最终反腐层越来越薄直至消失。整个过程业务无感知,研发却完成了架构切换。

这个推进节奏值得每个想做DDD改造的团队抄作业:不要一上来就宣布"三个月重写系统",那是自杀式项目。让新架构在新需求里自然生长,让老架构自然萎缩,过渡期虽然会存在双模并存,但风险始终可控。

6. 领域事件的发布与订阅:异步解耦的正确打开方式

总监在事件设计上有一句话我印象很深:"事件不是消息,事件是已经发生的事实,消息只是它的运输工具。"这句话解决了团队里很多争执。

他在每个限界上下文里定义了明确的领域事件表。事件命名一律使用过去时,比如OrderSubmitted、PaymentCaptured、InventoryReserved。事件内容只包含事实本身:订单ID、商品SKU集合、支付单号等,不包含"希望对方做什么"的命令意图。下游该做什么,由下游自己的领域逻辑决定,上游不指挥。

这个设计的威力在库存和订单的协同上体现得最充分。改造前,下单逻辑里直接调库存服务扣减库存,两个服务被RPC调用硬绑在一起。哪次库存服务抖动一下,下单就失败,体验极差。改造后,订单上下文只负责发布OrderSubmitted事件,库存上下文订阅事件后自己决定何时、如何预占库存。如果库存不足,就发布InventoryShortage事件,由订单上下文根据补偿策略处理。两个上下文之间不存在同步调用,自然而然就实现了最终一致。

发布实现上,他坚持了"先本地消息表,后消息中间件"的过渡方案。领域事件先和业务数据在同一个本地事务里写入事件表,再由一个独立的发布组件定时扫描事件表投递到MQ。这样既保证了缺少强事务中间件下的一致性,又给未来平滑切换到更可靠的消息平台留了接口。凡是涉及资金的核心链路,事件投递失败必须告警并人工介入,绝不允许静默丢失。

7. 落地过程中的三个"差点翻车"现场

任何架构推进都不会一帆风顺。这次改造有三个差点翻车的现场,写出来给同样在推DDD的朋友提个醒。

第一个翻车点是前期建模过度膨胀。总监把业务规则铺开建模后,团队沉浸在"划分聚合根"的快感里,凡是能拆的实体都拆成聚合根,最终导致不同聚合之间大量用到跨聚合事务。眼看代码要变成分布式事务的试验场,他及时叫停,定了一条铁律:领域服务只允许跨聚合做编排,不允许跨聚合做事务。所有涉及多个聚合的数据变更,要么通过领域事件做最终一致,要么就回到单一聚合边界内重新设计。这条铁律一出,建模立刻收敛。

第二个翻车点发生在适配器层偷懒。有两位同事图省事,把老系统的Mapper接口直接注入到了领域服务里,绕过了端口定义。结果不到一周就出问题:新需求要求变更订单表结构,同事直接改了Mapper方法,又连带改到了领域服务,架构边界形同虚设。总监在code review发现后,把这段代码回滚并"公开复盘"了一次,从此再没人敢跳过适配层。

第三个翻车点是事件风暴阶段业务方参与度下降。前两周业务方还兴致勃勃地贴黄色便签,第三周就开始"你们定就行"。离职贿赂远程咳嗽立刻识破,总监调整了策略:每次workshop控制在90分钟以内,且每次规定必须产出一个具体可执行的结果(比如敲定一个上下文边界),绝不让会议变成无限发散讨论。节奏短平快之后,业务方参与度反而回升了。

8. 用"架构适应度函数"代替口头约束:让DDD落地持续不反弹

最后这部分是这次改造里我觉得最"高级"的收尾。架构改完不难,难的是半年后代码还能保持架构不腐化。为防止团队在后续迭代中不自觉地把代码改回原来那种混乱模式,总监引入了一个很朴素但极为有效的机制:架构适应度函数。

说白了,就是写一组自动化测试,把架构规则变成可以执行、不可违反的断言。比如:

  • 检查领域层的代码是否直接引用了Spring的@Autowired之外的技术框架注解;
  • 检查是否有人从适配器层的类直接访问了不属于它管的数据源;
  • 检查聚合根以外的类是否直接修改了聚合内实体的状态;
  • 检查领域事件名称是否都遵循了过去时命名规则。

他用的是ArchUnit,一个Java生态的架构约束测试框架。在CI流水线里加了一个stage,每次提交代码自动跑一遍架构测试,违反规则构建直接失败,并强制要求提交者说明理由或者修改方案。这个机制上线后,第一季度内捕获了48个违规点,第二季度骤降到个位数。架构规则从"写在文档里的希望"变成了"代码仓库里的防线"。

我个人实际操作中最推荐的组合是:ArchUnit做静态架构约束校验,再加上Mutation Testing做边界验证,但国内团队先不用搞这么复杂,把ArchUnit跑通就已经赢过大多数团队了。

9. 三个月后回头看,DDD真正改变的其实是人

项目上线之后,总监在一次复盘会上说了一句话,让我至今记忆犹新:"DDD落地最难的不是建模,不是架构重构,而是让每个人学会用业务语言思考技术方案。"如今需求评审的流程,已经从"产品讲需求-后端排期"变成"产品讲业务目标-后端先画边界-再讨论实现"。研发不再第一时间问"要改哪张表",而是先问"这事发生在哪个上下文、哪个聚合根上"。

对一个跑了好几年的老项目来说,这样的改变才是真正的"高级优雅"。它没有一锤子砸碎旧世界,而是用一块块防腐层搭桥,用一个个端口界定边界,用一条条架构约束锁死方向,最终让整个系统从容地走向新的形态。如果你也在给团队准备DDD落地,我的建议是先别急着写代码,先花两周时间把所有人拽到一张桌前,把嘴边的术语先对齐,把墙上的上下文画清楚。那才是所有优雅真正的起点。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询