创新方案
当代码与业务不匹配、要拆分大型系统或定义服务边界时…
适用于领域建模、限界上下文划分、聚合设计、领域事件、服务边界定义、单体拆分以及让代码结构与业务流程对齐的场景。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
当代码与业务不匹配、要拆分大型系统或定义通用
解决思路
- 让模型即代码、代码即模型:软件应体现对业务域的深入共享理解,领域专家和开发者使用同一种语言,并让该语言直接表达在代码库中。
- 按 Quick Diagnostic 评分:7 行每满足一行得 1 分,再加最多 3 分深度分;Core Domain 有真正丰富模型而非仅 CRUD +1,不变量位于聚合内而非服务中 +1,通用语言在对话、代码和测试中一致 +1;报告分数和具体失败行。
- 通过领域专家与开发者的深度协作让通用语言自然涌现,不要事后拼接词汇表;语言变化时同步修改代码。
- 如果某个概念难以命名,把它视为模型可能错误的设计信号,回到协作和建模中修正模型。
- 用领域概念命名类和方法,避免技术术语隐藏领域逻辑;例如用 ClaimAdjudicator 而不是 DataProcessor。
- 允许不同限界上下文用同一个词表达不同含义;不要为整个系统强求单一含义。
- 类/方法按领域概念和动词命名:如 LoanApplication、policy.underwrite(),而不是 RequestHandler、process()。
- 模块按领域概念组织:如 shipping/、billing/,而不是 controllers/、services/。
- 代码评审拒绝纯技术命名;把 Manager、Helper、Processor、Utils 标为命名坏味道并重命名。
- 运行建模会话并维护术语表;参考 references/ubiquitous-language.md 了解语言如何演进并反馈到代码。
- 把限界上下文定义为特定领域模型适用的显式边界;同一词如 Customer 在不同上下文中可有不同含义。
- 用上下文映射定义上下文之间的关系和翻译策略。
- 不要把限界上下文等同于微服务;它是语言和模型边界,可以包含多个服务。
- 让上下文边界与团队边界对齐,考虑 Conway's Law。
- 使用九种上下文映射模式描述团队之间的政治和技术关系;参考 references/bounded-contexts.md。
- 在核心域边界上使用 Anti-Corruption Layer 作为防御模式,绝不让外部模型泄漏进核心域。
- 使用 Shared Kernel 时保持其很小并明确治理,因为它会耦合两个团队。
- 先映射现状(包括 Big Ball of Mud),再定义目标上下文边界。
- 服务集成时使用 Anti-Corruption Layer,在边界把外部 API 响应翻译成自己的领域对象。
- 遗留迁移时使用 Conformist / ACL,把遗留系统包在适配器后,让适配器说你的领域语言。
- API 设计时使用 Open Host Service + Published Language,暴露有良好文档的 REST API 和规范 schema。
- 区分 Entity、Value Object 和 Aggregate:Entity 有跨状态变化持续存在的身份;Value Object 完全由属性定义且不可变;Aggregate 是以单一 root 强制一致性边界的实体和值对象集群。
- 用 Entity 测试:即使所有属性都改变,我是否仍是同一个东西?例如一个人改名和地址后仍是同一个人。
- 用 Value Object 测试:我是否只由属性定义?例如任意 $10 钞票可互换。
- 优先让大多数概念成为 Value Object 而非 Entity,并偏好不可变性。
- 保持聚合小:一个 root 加最小集群;引用其他聚合时使用 ID,不要用对象引用。
- 聚合内部使用立即一致性;聚合之间设计为最终一致性。
- 身份跟踪时使用带 ID 的 Entity,例如 Order 由 orderId 标识,并在状态变化后继续存在。
- 不可变属性使用 Value Object,例如 Address(street, city, zip),只替换、绝不修改。
- 定义一致性边界时使用 Aggregate Root,例如 Order 是 root,OrderLine 项只通过它存在。
- 并发控制时在 root 上使用乐观锁,例如 Order 上放 version 字段,两个编辑竞争时产生冲突。
- 参考 references/building-blocks.md 了解聚合设计规则和一致性边界。
- 用领域事件捕获领域专家关心的已发生事实,使用过去时命名,如 OrderPlaced、PaymentReceived。
- 把事件视为不可变事实;一旦发布就不能更改或撤回。
- 区分领域事件和集成事件:领域事件在限界上下文内部,集成事件跨边界。
- 利用事件实现时间解耦:生产者不等待消费者。
- 需要时用事件溯源把完整事件历史作为真理源,通过重放推导当前状态。
- 只为领域关心的状态变化发布事件,不是每个状态变化都值得事件。
- 状态转换时在领域动作中触发事件,例如 order.place() 触发 OrderPlaced。
- 跨上下文集成时发布集成事件,例如 OrderPlaced 在 shipping 上下文触发 ShippingLabelRequested。
- 实现最终一致性时使用异步事件处理器,例如库存处理器在 OrderPlaced 后异步更新库存。
- 参考 references/domain-events.md 了解事件命名、事件溯源和集成事件。
- 用 Repository 提供领域对象的内存集合幻象并隐藏持久化;用 Factory 封装复杂创建逻辑,保证聚合出生即有效。
- 把 Repository 接口放在领域层,把实现放在基础设施层。
- Repository 方法使用通用语言:如 findPendingOrders(),而不是 getByStatusCode(3)。
- 面向集合的 Repository 模拟 add/remove;面向持久化的 Repository 使用 save。
- 仅在复杂规则或多部分装配时才引入 Factory;两个字段的 Value Object 只需要构造函数。
- 用 Specification 模式把查询条件封装成领域对象,例如 OverdueInvoiceSpecification。
- 数据访问抽象使用 Repository 接口:领域层放 OrderRepository.findByCustomer(customerId),基础设施层放 PostgresOrderRepository。
- 复杂创建使用 Factory 方法:如 Order.createFromQuote(quote) 从 Quote 聚合验证并装配。
- 查询封装使用 Specification:如 spec = OverdueBy(days=30); repo.findMatching(spec)。
- 参考 references/repositories-factories.md 了解 Repository、Factory 和 Specification 模式。
- 用战略设计识别 Core Domain:竞争优势所在,并与 Supporting Subdomain(必要但不差异化)和 Generic Subdomain(商品化)区分。
- Core Domain 投入最好的人和最深建模;Supporting 该构建但不要过度工程;Generic(auth、email、payments)购买或使用开源。
- 用 Distillation 从周围复杂性中提取并突出 Core Domain。
- 用一页 Domain Vision Statement 描述 Core Domain 的价值主张。
- 随业务演变重新审视什么才是核心;今天的差异化可能明天变成商品。
- 构建 vs 购买时分类子域类型:自定义定价引擎属于核心;支付使用 Stripe 属于通用。
- 团队分配时把最好开发者放在 Core Domain:资深人员建模承保规则,初级人员集成邮件服务。
- 代码组织时分离核心与通用:domain/pricing/ 放深模型,infrastructure/email/ 放薄适配器。
- 参考 references/strategic-design.md 决定把工程投入放在哪里:子域分类和 distillation 技术。
- 避免技术名称:重命名为领域术语如 ClaimAdjudicator;如果不存在领域术语,概念可能错了。
- 避免一个模型统治一切:为 billing、shipping、marketing 等限界上下文各自建立只含所需属性的 Customer。
- 避免巨大聚合:拆分聚合,用 ID 引用,并接受聚合间最终一致性。
- 避免贫血领域模型:把行为移入实体和值对象,服务只做编排。
- 避免缺少 Anti-Corruption Layer:把每个外部系统包在翻译层后。
- 避免把限界上下文等同微服务:上下文是模型边界不是部署单元;先从单体中的模块开始。
- 不要跳过领域专家:进行定期建模会议,直到专家确认“是的,就是这样工作”。
- 诊断类名可读性:若领域专家不能读懂类名,把类、方法和事件重命名为通用语言。
- 诊断上下文边界:若未明确定义,画上下文映射并定义边界和翻译。
- 诊断聚合大小:若聚合不较小,拆分聚合,用 ID 引用,并接受最终一致性。
- 诊断领域对象行为:若只有数据,把业务规则移入实体和值对象。
- 诊断跨聚合通信:若未使用领域事件,引入事件并让聚合异步反应。
- 诊断外部集成:若缺少 Anti-Corruption Layer,在每个边界添加翻译层。
- 诊断核心子域:若未识别核心,分类子域并将深度建模聚焦在 Core Domain。
适用边界 · 注意事项
- 业务简单、以 CRUD 为主且缺少复杂领域规则时,不值得投入完整 DDD 建模。
- 没有领域专家参与、无法用业务语言验证模型时,不要仅靠开发团队闭门造模。
- 尚未厘清模型边界时,不要把每个限界上下文直接拆成微服务或分布式服务。