domain-driven-design.md 13 KB

领域驱动设计(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,作为后续变更的基准

架构规则

  1. 一个事务只修改一个聚合根;跨聚合一致性用领域事件 + 最终一致,禁止跨聚合大事务。
  2. 聚合内业务逻辑内聚到领域对象(充血模型),禁止贫血模型(只有 getter/setter 的实体)。
  3. 外部只能持有聚合根的引用,聚合内部对象不可直接被外部访问。
  4. 跨聚合引用只存 ID,禁止对象直接持有。
  5. 领域事件在领域层产生、在应用层发布,禁止领域层直接依赖消息中间件实现。
  6. 防腐层是必选项而非可选项,当对接遗留/外部系统时必须建立,禁止领域模型直接被外部模型渗透。
  7. 统一语言是硬约束:代码命名与 Glossary 不一致即视为缺陷,须在 code-review 阶段拦截。
  8. 新建聚合/上下文必须有真实业务驱动,禁止 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 标注未隔离测试项