DDD · 领域驱动 / 战术设计

DDD速查

限界上下文与通用语言、实体值对象与聚合设计、仓储与工厂、四层架构与依赖倒置、领域事件与 CQRS、贫血充血与演进路径——领域驱动设计最常追问的 43 个核心考点一张表收齐,随查随用。

43条速查 7大主题 持续更新

📖 速查表

点击展开各小节

🗺️ 战略设计
名称说明要点 / 示例
限界上下文 一个明确的模型边界,边界内模型含义统一、术语唯一;同一个「商品」在交易上下文与库存上下文中是两个不同的模型 DDD 最大的价值在战略设计;限界上下文是微服务边界划分的直接依据 必背
通用语言 业务与研发共建的统一术语,直接用于沟通、文档与代码;代码中的类名、方法名应能对上业务黑话 翻译成本是模型腐化的信号:如果代码与业务沟通之间还需要「翻译」,说明通用语言没有落地
上下文映射总览 一张图描述限界上下文之间的集成关系(上游/下游、协作方式),是跨团队协作的契约视图 先画映射再定接口;集成点数量与关系类型决定了防腐的成本
合作关系 / 共享内核 合作关系:两个上下文协同演化、需求联动变更;共享内核:共享一小部分模型(如公共枚举),双方共同维护 共享内核改动影响双方,约定变更需双方评审,共享面越小心智负担越小
客户-供应商 / 防腐层 客户-供应商:上游顾忌下游需求,影响面可协商;防腐层(ACL):下游加一层翻译,隔离上游模型的污染 对接遗留系统与第三方服务首选防腐层 外部对接必备
OHS / PL / 遵奉者 开放主机服务(OHS):上游以稳定协议服务多个下游;发布语言(PL):与 OHS 搭配的公开数据格式;遵奉者:下游直接沿用上游模型以降低成本 平台化团队常用 OHS + PL;遵奉者是「放弃翻译换集成简单」的务实取舍
限界上下文协作
订单/库存/支付各自独立建模,跨上下文靠事件与防腐层
🧱 战术设计积木
名称说明要点 / 示例
实体 Entity 有唯一标识、生命周期内标识不变、状态可变的领域对象;相等性按 ID 比较 典型:订单、用户;用 ID 而非属性判等,属性可随时间变化
值对象 Value Object 无标识、不可变,通过全部属性判等的对象;可整体替换、不可原地修改 典型:金额、地址、时间段;优先用值对象——消掉 getter/setter 与判空,天然线程安全 推荐
实体 vs 值对象判别 三问:需要唯一标识吗?需要单独跟踪生命周期吗?相等性看 ID 还是看属性?看 ID 是实体,看属性是值对象 同一概念在不同上下文角色可不同:地址在收货上下文是值对象、在物流追踪上下文可能是实体 易混淆
聚合与聚合根 聚合 = 一组必须保持业务一致性的对象树,聚合根是唯一对外入口;外部只能引用聚合根,内部对象由根统一维护 聚合根负责守护不变量(Invariant):如「订单金额 = 明细之和」在根的方法内保证 面试高频
聚合设计原则 聚合尽量小;一次事务只修改一个聚合;聚合之间通过 ID 引用而非对象引用;边界内强一致、边界外最终一致 Vaughn Vernon 聚合设计四原则浓缩版,先保证小聚合再谈其他 面试高频
跨聚合事务边界 跨聚合一致性用领域事件 + 最终一致(本地消息表/Outbox/事务消息),而不是用分布式事务硬拉齐 例外:同上下文内确有强一致且并发低,可放宽为单事务多聚合,但要意识到并发冲突面变大
领域服务 vs 应用服务 领域服务:承载无状态的领域逻辑(放不进实体/值对象的业务规则);应用服务:薄编排层——鉴权、参数校验、事务、调仓储、发事件,不含业务规则 判别:把这段逻辑讲给业务听,业务能听懂的是领域逻辑(放领域层),听不懂的是流程编排(放应用层)
📦 仓储与工厂

仓储是 DDD 落地时最先被做错的一块:记住「一个聚合根一个仓储」,子对象永远随根整体存取,别把它退化成第二个 DAO。

名称说明要点 / 示例
Repository 职责 为聚合提供「像操作内存集合一样」的存取语义:按 ID 获取、增删、按规约查询;向领域层屏蔽 ORM 与表结构 仓储操作的是聚合整体,不是一张表;save 的对象图含子对象就整体持久化
仓储边界 一个聚合一个仓储(不是一张表一个 Repository);只针对聚合根建仓储,子对象随根整体存取 订单聚合里有明细、地址,只建 OrderRepository,不建 OrderItemRepository 易踩坑
避免泄漏持久化细节 领域层仓储接口只收发领域对象;接口签名不出现 SQL 片段、ORM 注解与持久化框架类型;DTO 转换放应用层或基础设施层 仓储返回被 ORM 污染的实体让上层随手改持久化字段,是最常见的泄漏方式
工厂使用时机 聚合创建过程复杂(多对象组装、规则校验、默认值推导)时用工厂封装创建逻辑;简单对象用构造器/静态工厂即可 区分新建与重建:从存储加载(reconstitution)不触发业务规则校验,与业务新建分开实现
仓储接口归属 接口定义在领域层,实现类放在基础设施层——依赖倒置在战术落地中最直观的一处 领域层只 import 自己定义的接口,换 ORM/换存储不动领域代码
🏛️ 架构分层
名称说明要点 / 示例
传统三层 UI → 业务逻辑层(Service)→ 数据访问层(DAO),请求自上而下直达数据库 业务逻辑逐渐被 DAO 与 Service 互相稀释,Service 膨胀、实体变数据袋,易滑向贫血模型
DDD 四层 用户接口层 → 应用层 → 领域层 → 基础设施层;领域层沉淀实体/值对象/领域服务/仓储接口,是系统核心资产 应用层薄、领域层厚是健康信号 必背
依赖方向 传统三层自上而下最终依赖数据库;DDD 领域层不依赖任何层,基础设施层反向实现领域层定义的接口(依赖倒置) 判断分层是否正确:领域层能否脱离数据库独立编译与单测
洋葱架构 核心 = 领域模型,向外依次是领域服务、应用服务、外部接口与基础设施;依赖只能指向圆心 数据库放在最外圈,只是被基础设施适配的技术细节 面试高频
六边形架构 应用核心(领域 + 端口 Port)+ 适配器(驱动侧:REST/定时任务;被驱动侧:DB/MQ/RPC 客户端) 端口即接口;核心对传输协议与存储一无所知,换框架不动核心 面试高频
整洁架构 同心圆:Entities → Use Cases → Interface Adapters → Frameworks & Drivers,依赖规则指向内圆 与洋葱/六边形共同思想一致:依赖倒置——让业务规则不依赖技术细节 一通三通
📡 领域事件与 CQRS
名称说明要点 / 示例
领域事件命名 用过去时态命名已发生的领域事实:OrderPaid、StockDeducted;事件不可协商、不可撤回 事件要素:事件名 + 聚合 ID + 发生时间 + 业务载荷;命令是将来时、事件是过去时 易混淆
事件发布时机 聚合方法内只收集事件(挂到聚合/上下文),事务提交成功后再发布——避免事务回滚但事件已外发的乌龙 可靠投递:本地消息表 / Outbox + 轮询或 CDC(如 Canal 订阅 binlog)
事件溯源 一句话:不存当前状态而存全部变更事件,状态由事件重放(replay)得出 天然完整审计日志;配合快照(snapshot)控制重放开销 面试常考
CQRS 命令与查询职责分离:写模型保证不变量与业务规则,读模型按查询形态定制(冗余表/物化视图/Elasticsearch) 写路径复杂化换读路径简单化;常与领域事件配合,事件驱动读模型更新 面试高频
CQRS 适用场景 读写模型差异大、读多写少需独立扩容、复杂多条件查询不想拖累写模型 CRUD 为主的简单场景上 CQRS 是自找复杂度;先评估同步延迟业务能否接受
最终一致性 引入事件后读写模型之间是最终一致,存在秒级延迟窗口 写后立读的体验问题:前端可返回写模型结果,或读请求按用户维度路由到主库
⚠️ 实践避坑
名称说明要点 / 示例
贫血 vs 充血对照 贫血:实体只有 getter/setter,逻辑全堆在 Service;充血:行为内聚在实体/聚合上,Service 只做编排 DDD 鼓励充血,但不是把所有逻辑硬塞进实体——无状态跨实体的规则放领域服务 必背
过度设计信号 小团队 CRUD 业务就上全套战术模式;为「未来可能的扩展」预建聚合与事件;一条简单规则绕三层 战术模式是手段不是 KPI,复杂度要付利息 警惕
从事务脚本演进 路径:事务脚本(过程式 Service)→ 表模块 → 领域模型 先把最复杂、变更最频繁的核心域换成领域模型,边缘支撑域保持脚本即可,不必一刀切
Simple 先行原则 先用最简单方案把业务跑通,待复杂度真实出现(规则重复、分支膨胀、多人协作冲突)再引入聚合/事件重构 重构的前提是有测试兜底;没有测试就大改模型等于裸奔
战略先行 先分清核心域/支撑域/通用域与限界上下文,再谈战术积木 上下文划错,战术再精致也是在错误边界上的精致;建模投入优先给核心域
团队协作成本 DDD 要求业务深度参与(事件风暴、通用语言共建、统一术语评审) 业务抽不出时间的团队,先从代码内部建模改善(值对象、聚合边界)做起,同样有收益
常见误用 把 DDD 当「分层框架」照搬目录结构;仓储退化成另一个 DAO;领域事件沦为跨模块调用的新通道 自检:目录叫 domain 但实体全是 getter/setter,就是换皮三层 易踩坑
🧾 聚合设计实操判断
聚合边界没有标准答案,只有可操作的判断问句:拿这六问对着你的模型过一遍。
判断场景判断方法实操要点
聚合边界问句 第一问 删掉聚合根,它还有独立意义吗?有 → 独立聚合;没有 → 收进聚合内部 订单明细离开订单无意义 → 随根生灭;收货地址可独立管理 → 拆分要慎重
跨聚合引用 聚合之间只存对方聚合根的 ID,不持对象引用 加载不级联、修改不连坐;需要联合展示时由查询服务跨仓组装,不走聚合
一次事务只改一个聚合 面试高频 一条业务规则能否在一个聚合内闭环?能 → 单事务;不能 → 拆事件 一个事务改多个聚合 = 并发冲突面扩大 + 互相等待,聚合边界立刻失守
一致性选型 不变量必须时刻成立的放边界内强一致;跨聚合默认最终一致 金额 = 明细之和在订单根方法内保证;扣库存与订单状态用领域事件拉齐
防腐层隔离 外部对接 第三方 / 遗留系统的模型是否直接渗入了领域层?渗入即加 ACL 翻译 领域层只认识自己的通用语言,外部字段映射全部关在防腐层内完成
仓储按聚合根一比一 易踩坑 数一数仓储数量:是否与聚合根一一对应? 出现 OrderItemRepository 就是聚合划错了;子对象随根整体存取