领域驱动设计(Domain-Driven Design)
本文件是 dev-expert 的领域建模专项补充参考,不作为独立子技能计数,也不提供 @ 显式调用入口。命中「领域建模/限界上下文/聚合根/领域事件/统一语言/防腐层/复杂业务规则建模/遗留系统隔离」等信号,且任务为复杂业务系统(多子域/术语多义/跨上下文协作)时,按需与 software-project、spec-driven-development、architecture-decision、mysql-database、code-generation 协同加载。CRUD 为主的脚本/营销页/小工具不加载本 reference,避免过度设计(违反 Karpathy 简洁优先)。
适用边界
- 适用:业务规则复杂、术语多义、跨子域协作的中大型系统;遗留系统渐进重构需要隔离新旧模型;需求频繁变更需要稳定领域内核抵御变化。
- 不适用:CRUD 为主、表即业务的小工具/脚本/营销页/简单后台——直接
mysql-database + code-generation 更高效。
- 判定信号:业务对象 ≥ 5 且存在同名异义、跨模块业务规则联动 ≥ 2、需要对接遗留系统且不想被其模型污染。
核心概念速查
| 概念 |
定义 |
与现有 reference 的衔接 |
| 统一语言(Ubiquitous Language) |
需求→代码全程使用同一术语,消除翻译损耗 |
贯穿 project-memory-management Glossary,强制约束而非仅记录 |
| 实体(Entity) |
有唯一身份、生命周期内身份不变的对象 |
mysql-database 表设计前先识别实体 |
| 值对象(Value Object) |
无身份、由属性值定义、不可变 |
金额/地址/坐标等用值对象,减少表与 ORM 复杂度 |
| 聚合根(Aggregate Root) |
一致性边界入口,外部只能持有根的引用 |
mysql-database 事务边界:一个事务只改一个聚合 |
| 领域事件(Domain Event) |
表示领域中已发生的事实,用于解耦上下文 |
spec-driven-development Scenario 的业务侧表达 |
| 限界上下文(Bounded Context) |
模型的语义边界,同一术语在不同上下文可有不同含义 |
补 software-project 模块边界审查的语义维度 |
| 上下文映射(Context Map) |
描述上下文间关系(ACL/共享内核/客户-供应商等) |
补 architecture-decision 的集成模式选择 |
执行流程
第一步:建立统一语言
- 从需求描述提取领域名词,与业务方确认含义,消除同名异义/异名同义
- 术语写入
project-memory-management Glossary,标注所属限界上下文
- 代码命名必须与统一语言一致(
Order 在销售上下文 = 订单,在物流上下文 = 运单,须区分命名如 SalesOrder/ShipmentOrder)
第二步:事件风暴识别领域事件
- 用领域事件倒推业务流程:先列"什么发生了"(OrderPlaced/PaymentReceived/OrderShipped),再连成流程
- 每个事件标注:触发者、相关实体、后续影响
- 事件命名用过去式,表示已完成的事实
第三步:识别聚合与聚合根
- 按"一致性边界"分组:必须强一致的对象归入同一聚合,根为外部访问入口
- 聚合设计原则:
- 小聚合优先:一个聚合根 + 少量值对象/内部实体,避免大聚合导致大事务
- 跨聚合用引用(存 ID)而非直接对象持有
- 聚合内业务逻辑内聚到根或内部实体(充血模型),禁止贫血模型
- 一个事务只修改一个聚合;跨聚合一致性用领域事件 + 最终一致
第四步:划定限界上下文
- 按语义边界切分,而非技术分层:同一术语在不同上下文可有不同模型
- 上下文边界对齐团队/子系统/部署单元,便于独立演进
- 输出上下文清单:上下文名 / 核心领域语言 / 对外接口 / 依赖的其他上下文
第五步:上下文映射与集成模式选择
对每对交互的上下文,选择集成模式(权衡走 architecture-decision 流程并附反选论证):
| 模式 |
适用场景 |
代价 |
| 防腐层(ACL) |
接入遗留系统/外部不可控 API,避免被其模型污染 |
多一层翻译层,增加复杂度 |
| 共享内核 |
多上下文必须共享同一子模型 |
共享部分变更需多方协调 |
| 客户-供应商 |
上游(供应商)服务下游(客户),双方可协商 |
需协商机制,强势上游会压制下游 |
| 遵奉者(Conformist) |
下游被迫遵循上游模型 |
下游丧失模型自主权 |
| 各行其道(Separate Ways) |
上下文无实际耦合,独立演进 |
可能重复建模,但解耦彻底 |
第六步:领域模型落地实现
- 聚合根作为事务与一致性入口,外部通过根的方法操作内部对象
- 领域事件在聚合根方法内产生,由应用服务/事件分发器发布
- 应用服务编排聚合、事务、事件发布;领域服务承载跨聚合的无状态领域逻辑
- 仓储(Repository)按聚合根粒度提供持久化,禁止跨聚合 JOIN 式仓储
- 与
code-generation 协同:生成代码须体现充血模型与聚合边界,禁止产出贫血模型 + 巨型 Service
第七步:验证与沉淀
- 领域规则用单元测试覆盖聚合根方法(不依赖数据库)
- 跨上下文协作用集成测试覆盖事件流
- 领域模型与术语沉淀到
project-memory-management,作为后续变更的基准
架构规则
- 一个事务只修改一个聚合根;跨聚合一致性用领域事件 + 最终一致,禁止跨聚合大事务。
- 聚合内业务逻辑内聚到领域对象(充血模型),禁止贫血模型(只有 getter/setter 的实体)。
- 外部只能持有聚合根的引用,聚合内部对象不可直接被外部访问。
- 跨聚合引用只存 ID,禁止对象直接持有。
- 领域事件在领域层产生、在应用层发布,禁止领域层直接依赖消息中间件实现。
- 防腐层是必选项而非可选项,当对接遗留/外部系统时必须建立,禁止领域模型直接被外部模型渗透。
- 统一语言是硬约束:代码命名与 Glossary 不一致即视为缺陷,须在
code-review 阶段拦截。
- 新建聚合/上下文必须有真实业务驱动,禁止 speculative 建模(呼应 Karpathy 简洁优先)。
常见坑与反模式
| 场景 |
风险 |
处理方式 |
| 贫血模型 + 巨型 Service |
业务逻辑散落、领域对象沦为 DTO |
行为内聚到聚合根,Service 只做编排 |
| 跨聚合事务 |
大事务、锁竞争、一致性边界模糊 |
拆为聚合内事务 + 领域事件最终一致 |
| 大聚合(一个根聚合数十实体) |
事务过大、加载性能差 |
拆小聚合,跨聚合用 ID 引用 |
| 直接持有外部上下文对象 |
模型耦合、上下文边界失效 |
只存 ID + 防腐层翻译 |
| 领域事件同步阻塞发布 |
事务回滚但事件已发出、或事件失败拖垮主流程 |
事务提交后再发布(如 DB::afterCommit / 事务后派发) |
| 为 CRUD 场景强加聚合/事件 |
过度设计、违反简洁优先 |
适用边界外场景直接走 mysql-database + code-generation |
| 共享内核无节制扩张 |
多上下文被绑定、变更困难 |
共享内核限制在极小稳定子集,其余走 ACL |
| 统一语言仅记录不约束 |
术语漂移、模型与需求脱节 |
code-review 阶段强制核对命名与 Glossary 一致 |
关联 reference
| 协同点 |
说明 |
software-project 第三步点五模块边界审查 |
本 reference 提供语义边界维度(限界上下文),与工程边界(依赖/接口/数据持有)互补;审查时先语义后工程 |
mysql-database |
表设计前先识别实体/值对象/聚合根;事务边界对齐聚合边界 |
spec-driven-development |
Scenario 可用领域事件表达;领域模型作为 spec Design 层的核心产物 |
architecture-decision |
上下文映射集成模式选择走架构决策流程,附反选论证 |
code-generation |
生成代码须体现充血模型与聚合边界;应用服务/领域服务/仓储分层 |
code-review |
审查贫血模型、跨聚合事务、统一语言一致性、防腐层缺失 |
refactoring |
遗留系统重构优先建立防腐层再逐步抽取领域模型 |
project-memory-management |
统一语言写入 Glossary 并标注所属上下文;领域模型作为决策记录沉淀 |
质量标准
- 限界上下文必须有明确的语义边界和核心领域语言,不能按技术分层切分
- 聚合根必须是一致性边界入口,外部不可绕过根访问内部对象
- 跨聚合一致性必须显式选择事务或最终一致,禁止隐式跨聚合事务
- 统一语言必须贯穿需求→代码,Glossary 与代码命名一致
- 对接遗留/外部系统必须有防腐层,领域模型不可被外部模型直接渗透
- 领域规则必须有单元测试覆盖(不依赖数据库)
- 适用边界外的简单场景不得强加 DDD 模式(呼应 Karpathy 简洁优先)
失败回退机制
| 步骤 |
失败条件 |
回退目标 |
最大重试 |
不可恢复时升级路径 |
| 第一步:建立统一语言 |
业务方术语含义不清或存在分歧 |
列出 2-3 种理解,请业务方确认 |
2 |
标注待确认术语,暂用占位命名 |
| 第二步:事件风暴 |
业务流程无法连成事件链 |
退回第一步补全统一语言 |
1 |
输出流程断点清单交业务方补充 |
| 第三步:识别聚合 |
聚合边界难以判定(强一致 vs 最终一致不明) |
按变更频率与一致性要求分组,标注假设 |
2 |
走 architecture-decision 决策聚合边界 |
| 第四步:划定限界上下文 |
上下文边界与现有模块/团队结构冲突 |
输出语义边界 vs 工程边界冲突清单 |
1 |
交架构师/业务方拍板 |
| 第五步:上下文映射 |
集成模式难以取舍 |
走 architecture-decision 反选论证 |
1 |
POC 验证后由用户拍板 |
| 第六步:领域模型落地 |
与现有框架/ORM 冲突(如 Eloquent 倾向贫血模型) |
输出冲突点 + 妥协方案 |
1 |
标注已知限制,部分内聚部分编排 |
| 第七步:验证与沉淀 |
领域规则测试难以隔离数据库 |
用内存仓储/Factory 隔离 |
2 |
标注未隔离测试项 |