在动手写这篇总结之前,我先说句实话:DDD(领域驱动设计)不是银弹,但你如果被“业务规则越来越多,代码越写越乱,改一个需求恨不得牵连七八个 Service”这种情况折磨过,那它值得你静下心研究一次。我在两个中大型项目里完整落地过 DDD,第一次踩坑踩到怀疑人生,第二次才慢慢摸到门道。今天这篇不谈教科书上的概念搬运,只聊我在实战中的思考、选型理由、落地步骤,以及那些文档里根本不会写的坑。
这篇文章适合谁?不是刚入行的新人,而是已经在 CRUD 里泡了很久、手里捏着几个复杂业务模块、隐约觉得“现在这种写法不可持续”的后端开发。也适合那些正准备用 DDD 重构老系统、或者要把单体按业务能力拆成微服务的团队。我会从“为什么需要 DDD”讲起,到战略设计、战术设计、代码落地,最后聊一聊那些让人头大的实际问题和我的应对办法。
1. 为什么要做 DDD:从代码坏味道说起
1.1 我遇到的真实业务复杂度困境
事情要从 2019 年我接手的一个营销系统说起。这个系统负责整个公司的优惠券、满减活动、积分兑换、用户标签等一堆业务。刚接手的时候,代码已经跑了三年,按传统三层架构写的:Controller → Service → DAO。乍一看结构挺清晰,但真的动手改需求就发现,所有业务逻辑全堆在 Service 里,一个订单结算的 Service 方法有六百多行,里面混杂了优惠计算、库存扣减、积分累加、短信通知,甚至还有折扣码校验。
最要命的是,产品经理提需求从来不按“模块”提,而是按“场景”提。比如“大促期间,用户下单时如果使用了优惠券,且商品在秒杀活动里,则积分按原价计算”。这种需求落到现有代码里,你根本不知道该改哪个 Service,因为优惠逻辑既在订单 Service 里出现,又在优惠券 Service 里有,还和商品模块耦合在一起。改一个地方,另一个地方就出 bug,线上事故接连不断。
这就是典型的业务复杂度失控。业务规则本身并不算天文数字级的复杂,但代码结构没有跟着业务边界走,导致复杂度被无序地扩散到所有层。我当时做过一次统计:优惠相关逻辑散落在 23 个 Service 方法里,没有任何一个类是完整的“优惠上下文”。
1.2 DDD 不是一套 API 写法,而是思维方式切换
很多团队对 DDD 有个误解,以为“按包结构分成 controller/service/domain/infrastructure 四层就算 DDD 了”。这种认知害了不少人。真正的 DDD,第一步不是写代码,而是切换思考方式:从“数据库表怎么设计”转向“业务领域怎么建模”。
传统开发流程是:先看需求 → 设计数据库表 → 写增删改查。这种流程下,数据库成了系统的核心,业务逻辑反而被表结构牵着鼻子走。表设计得好,代码就少踩坑;表设计得烂,业务代码就是戴着镣铐跳舞。而 DDD 的核心思维是:领域模型才是系统的核心,数据库、缓存、消息队列、RPC 协议这些东西统统是基础设施,应当为领域模型服务,而不是反过来。
我打一个生活化比方。传统开发像是按房间功能买家具,先定了每个房间放什么,然后家具只能迁就房间格局;DDD 则是先弄清楚一家人怎么生活,再决定客厅、厨房、书房怎么划分,最后才挑合适的家具。家具(表结构、缓存、接口协议)不合适可以换,但生活动线(业务逻辑)不能被家具绑定。
这个思维一旦转过弯来,你会发现很多之前“改不动”的代码,根源根本不是技术选型问题,而是模型和业务脱节了。贫血模型(实体只有 getter/setter,逻辑全在 Service)之所以如此普遍,就是因为开发者脑子里先有“表”再有“对象”,对象只是表的映射,自然存不住业务规则。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 战略设计:先画边界,再写代码
2.1 限界上下文怎么划分:我的一套实操方法
战略设计是 DDD 的第一大块,核心产出物就是限界上下文的划分。限界上下文说白了,就是“模型的有效边界”。同一个“用户”概念,在订单上下文里关注的是收件人和支付信息,在风控上下文里关注的是历史行为和信用分,在营销上下文里关注的是标签和等级。没有边界,所有人共用一套 User 类,结果就是每个需求都要兼容所有场景,类越来越臃肿,谁都不敢动。
我划分限界上下文最常用的方法,是找“业务动词”而不是“业务名词”。名词是容易误导人的,比如“订单”,订单管理是一个上下文,订单履约又是另一个上下文,订单售后还是另一个。但如果你去看业务动词——下单、支付、发货、退款——每个动词背后都对应一套独立的业务规则、独立的生命周期、独立的干系人。
实操时我建议从这三个角度切:
- 看变化频率:一段业务规则如果经常变(比如促销策略),而另一段很少变(比如订单状态流转),它们大概率是两个上下文。
- 看团队组织:康威定律在这里特别灵验——通常一个稳定的团队只会维护一个核心上下文,跨团队的调用会自然形成 API 边界。
- 看数据归属:问一个问题,“这个数据到底该由谁负责修改?”如果两个模块都要改同一份数据,往往意味着边界画错了。
拿我做的营销系统举例,我最终把“优惠计算”从“订单处理”里拆了出来,形成独立的优惠上下文。订单上下文只管订单本身的创建、状态流转,优惠上下文负责算价和核销。两个上下文之间通过“请求优惠计算并返回结果”这样的方式交互,订单不再依赖任何优惠的表结构。
2.2 通用语言:每个上下文都要有自己的黑话
通用语言这个概念,听起来简单,做起来最难。它是领域专家和开发团队之间共享的一套术语,每个术语的含义在同一个上下文内有且只有一个。
我见过最典型的问题就是“客户”这个词。市场部说客户是指“留过手机号的潜在用户”,销售部说客户是指“已经签过合同的正式客户”,客服部说客户是指“所有提交过工单的人”。如果开发的模型里只有一个 Customer 表,那必然顾此失彼。DDD 的做法是,在“售前线索上下文”里叫 Prospect,在“合同管理上下文”里叫 Account,各管各的,通过防腐层做翻译和转换。
创建通用语言不能光靠开会。我的经验是,每次需求评审会上,凡是大家
