本篇要搞懂的 3 个问题:① 子域 vs 限界上下文到底什么关系、核心/通用/支撑域怎么分、边界划分有没有量化标准?② 聚合设计为什么要用工厂和仓储,领域层和基础层为什么要"依赖倒置"?③ 领域事件用异步消息,发布方和订阅方的数据怎么保证一致?微服务内部用不用上事件总线?
⚠️ 这是答疑篇,不引入新概念,而是把前面基础篇/进阶篇里大家踩坑最多的三处揉碎再讲一遍——读的时候建议你先回忆一下自己学到那一讲时心里的疑问。
承接前文:基础篇和进阶篇到此告一段落。前面我们一路学了领域/子域划分(02 讲)、事件风暴、聚合、DDD 分层架构与依赖倒置(07 讲)、领域事件等。作者在进实战篇之前,挑出读者提问里最集中的 3 个点回头补讲。这三个问题分别卡在三个层面:怎么划边界(子域/限界上下文)、怎么搭内部结构(聚合/工厂/仓储/依赖倒置)、怎么跨边界通信(领域事件的一致性)。
问题 1:子域和限界上下文什么关系?核心/通用/支撑怎么分?边界能不能量化?
先用大白话复述问题:DDD 里又是"子域"又是"限界上下文",又把领域分成"核心域、通用域、支撑域"。这些名词听着像同义反复——它们到底各管什么?而且实操时最焦虑的一点是:划到多大算合适?有没有一把尺子能量出来?
作者的解答:
划领域,本质就是把一个大问题不断切小,直到切成你熟悉、能快速处理的小问题,再给这些小问题排优先级。子域和限界上下文,是这个切分过程里前后两个阶段——粗切和细切。
| 子域 | 限界上下文 | |
|---|---|---|
| 本质 | 一块比较粗的领域边界 | 本质也是子域,但是在明确子域内细分出来的 |
| 切的依据 | 按业务阶段 / 功能模块粗分 | 在子域里用事件风暴详细设计出来 |
| 管不管内部 | 不考虑内部领域对象、对象关系和结构 | 明确了领域对象及其依赖关系,产出领域模型 |
| 目的 | 把问题缩小到一个方便做事件风暴的小空间 | 有了领域模型,就能直接做微服务设计 |
而核心域 / 通用域 / 支撑域是另一个维度的划分——不是切大小,而是分优先级、定 IT 投入:
- 它们都是业务领域,DDD 的设计方法和过程完全一样,只是重要性和功能属性不同。
- 分类的唯一目的:把好钢用在刀刃上,重要资源砸到核心域。
- 每个企业商业模式不同,核心域因企业而异,别拿一套固定眼光套所有公司(同一个能力在 A 公司是核心域,在 B 公司可能只是支撑域)。
关于"量化标准"——没有。目前没有可量化的领域/限界上下文划分标准,主要靠领域专家经验 + 团队在事件风暴里反复权衡。别指望一次迭代就建出完美模型,领域模型往往要多次迭代、持续演进。但只要边界(领域模型边界、聚合边界)划得清晰,后续演进的成本就会很低。
🪞 帮助消化:把"子域 → 限界上下文"理解成写代码前先分模块、再设计类。子域是"我先把系统粗略拆成订单、用户、库存几大块"(不管块里有什么);限界上下文是"针对订单这块,我用事件风暴把里面有哪些对象、谁依赖谁画清楚",画完就是领域模型。至于"量化标准"——它更像架构设计而非单元测试,没有红绿灯,只有反复评审。对你这种"会设计架构但代码靠 AI 写"的人反而是好消息:划边界这件最吃判断力的事,恰恰是 AI 替代不了、你最该亲自下场的地方。
问题 2:聚合为什么要用工厂和仓储?领域层和基础层为什么要依赖倒置(DIP)?
先用大白话复述问题:聚合里塞了一堆实体和值对象,凭什么非要搞"工厂模式"“仓储模式"这些花活?还有那个一直绕不过去的"依赖倒置(DIP)”——明明基础层(数据库这些)是底座,为什么要反过来让它去依赖上层?
作者的解答:
聚合的职责是实现核心业务逻辑,里面的领域对象由聚合根统一管理,以保证数据一致性。围绕它有两个关键设计模式:
工厂(Factory)模式——管"创建":
- 一个聚合里可能有非常多实体和值对象,且必须保证聚合根和它依赖的所有对象实例同时被创建。
- 如果都靠聚合根去一个个构造,会非常复杂——所以用工厂封装复杂对象的创建过程。
- 注意:不是所有对象都要走工厂。构造不复杂、只是单个对象,普通构造方法就够了,别过度设计。
仓储(Repository)模式 + 依赖倒置——管"持久化"和"解耦"(这俩是一个问题的两面):
先看传统 DDD 四层架构的毛病——所有层都依赖基础层。后果是:应用逻辑对基础层依赖太重,基础层里和资源(数据库/缓存/MQ)相关的代码会渗透进应用逻辑。而技术组件更新极快,一旦底层组件要换,这些渗进来的代码就会对上层应用逻辑造成"致命影响"。
解法:在基础层和上层应用逻辑之间插一层仓储层。
| 做法 | 效果 |
|---|---|
| 一个聚合对应一个仓储 | 仓储负责该聚合内数据的持久化 |
| 应用逻辑通过接口访问基础资源 | 应用逻辑不直接碰数据库代码 |
| 仓储的实现放在基础层 | 应用逻辑与基础资源实现彻底分离 |
| 换基础组件时只替换仓储实现 | 不影响应用逻辑——这就实现了依赖倒置 |
另外两条作者顺带强调的原则,很重要:
- 找不到聚合根也没关系:数据计算、统计、批处理这类场景,实体之间彼此独立、找不到聚合根、建不了领域模型,但业务上高内聚——照样可以当作一个聚合处理,除了不做聚合根设计,其他 DDD 分层方法都能用。
- 别为做 DDD 而做 DDD:业务复杂度不高时,硬上 DDD 反而徒增复杂度,某些原则可以突破,用传统方式也行,一切以解决实际问题为准。但有一条底线:哪怕用传统方式,也要保证领域模型边界和聚合的逻辑边界清晰,这样以后演进才不会太痛。
💡 帮助消化:把"依赖倒置"翻译成一句人话——让业务代码只认接口,不认具体实现。业务层说"我要一个能存订单的东西(接口)",至于背后是 MySQL 还是 Redis,由基础层去实现这个接口。这样换数据库 = 换一个实现类,业务代码一行不动。仓储就是那个"接口在上层、实现在下层"的具体落地。对你写论文/做项目特别实用:先让 AI 帮你定义清楚仓储接口,未来换存储方案时改动面会非常小。
问题 3:领域事件用异步消息,发布方和订阅方数据怎么保证一致?微服务内部一定要用事件总线吗?
先用大白话复述问题:领域事件靠消息总线异步发——发布方扔出消息就不管了,既不关心送没送到、也不关心对方处没处理。那万一消息丢了、对方处理失败,两边数据不就对不上了吗? 还有:微服务内部聚合之间传事件,也有必要上"事件总线"这套重家伙吗?
作者的解答:
先要接受一个前提:微服务之间为了解耦,数据走的是最终一致性,不是强一致。担心的"瞬间不一致"在设计上是被允许的,关键是最终能对上账。
对一致性要求高的场景,作者给出的标准设计是两边都落库 + 对账补偿:
| 环节 | 做法 |
|---|---|
| 发送方 | 保存业务数据时,在往消息中间件发消息之前,先把要发的消息写入本地库 |
| 接收方 | 处理消息之前,先把收到的消息写入本地库 |
| 对账 | 定期比对发送方和接收方的事件数据,识别出不一致 |
| 补偿 | 发现异常/不一致:用定时程序再次发送,必要时转人工处理 |
这套"消息先落本地库再发/再处理 + 定期对账 + 失败重发",就是在不要求强一致的前提下,给最终一致性兜底。
再看微服务内部要不要事件总线——作者的态度是"看你权衡,别盲目上":
- 微服务内部逻辑都在同一个进程、同一个数据库里,事务比跨微服务好控制得多。
- 内部领域事件如果引入事件总线,会增加开发复杂度。
- 经验法则:如果你的场景不会出现聚合之间数据不一致,就可以不用事件总线。
- 替代方案:用应用服务就能实现聚合之间的服务和数据协调。
💡 帮助消化:把"发送方先写本地库再发消息"理解成发快递前先自己留一张底单——这样哪怕快递(消息)半路丢了,对账时一比底单就知道哪单没到,补发即可。这招在工程上叫"本地消息表",是绕开分布式事务的经典做法。而"内部要不要事件总线"则是另一类判断:跨进程才值得上重武器,进程内能用方法调用(应用服务)解决的,就别为了’看起来很 DDD’而硬加中间件。 每多一个中间件,就多一处故障点和维护成本。
总结
这三个问题对应 DDD 落地的三个关卡:划边界、搭内部、跨边界通信。
- 划边界:子域是粗切(按业务/功能,不管内部),限界上下文是在子域内用事件风暴细切(产出领域模型);核心/通用/支撑是另一维度的优先级分类,目的是把资源砸向核心域;边界没有量化标准,靠经验 + 反复迭代,但边界清晰能让演进变便宜。
- 搭内部:工厂封装复杂对象的创建,仓储 + 依赖倒置让业务只认接口、不认数据库实现,换组件只改仓储;找不到聚合根(如批处理)也能当聚合处理;别为做 DDD 而做 DDD,但边界必须清晰。
- 跨边界通信:微服务间走最终一致性,靠"两边消息落库 + 定期对账 + 失败重发/人工"兜底;微服务内部不强求事件总线,能用应用服务协调就别盲目上中间件。
一句话速记
划边界靠经验没尺子——子域粗切、限界上下文细切出模型,核心域定投入;搭内部靠工厂建对象、仓储 + 依赖倒置只认接口,别为做 DDD 而做 DDD;跨边界靠最终一致性——两头落库加对账补偿兜底,进程内能用应用服务就别硬上事件总线。
思考题
作为一个会设计架构、但代码主要靠 AI 代写的开发者,这三个答案里有两件事其实是"你必须亲自扛、AI 替不了"的:
- 划边界这件最吃判断力的事,AI 给不出可靠答案(连作者都说没有量化标准)。你打算怎么把"领域专家经验"这部分补上——是找业务方对齐,还是自己先用事件风暴跑一遍?
- 依赖倒置/仓储接口让你可以"先定接口、再让 AI 填实现"。你能不能反过来利用这点:把接口边界亲自定死、把实现细节交给 AL 代写,从而既保住架构掌控权、又不被代码细节拖累?
- 看问题 3——本地消息表、对账、重发,每一环都可能出错。如果让你来评审,你会先盯哪个环节的可靠性(本地消息表会不会漏写?重发的幂等做了吗?人工处理会不会重复操作?)?
