领域驱动设计:从战略设计到战术实现

FreeGuideOnline 最新 2026-06-12

引言:为什么需要领域驱动设计?

在复杂的业务系统中,传统的以数据为中心的设计方式往往会导致业务逻辑散落在各处,代码难以维护,团队沟通成本剧增。领域驱动设计(Domain-Driven Design,DDD)提供了一套系统的方法,让开发团队专注于核心业务领域,通过通用语言、限界上下文、聚合等技术,构建出高内聚、可演化、业务表达清晰的软件模型。

本教程将从 DDD 的核心思想出发,带你完成战略设计战术实现的全流程实战。


第一部分:核心思想与战略设计

1.1 通用语言(Ubiquitous Language)

DDD 的基石是建立团队成员(领域专家、产品、开发、测试)共同使用的一套语言,并直接映射到代码中的类名、方法名。

实战要点:

  • 从领域专家那里挖掘术语,而不是自行创造。
  • 所有需求文档、白板讨论、代码模型必须使用同一套词汇。
  • 如果发现不同团队成员对同一术语理解不同,立刻召开“术语对齐会”,必要时引入新词或划分限界上下文。

1.2 限界上下文(Bounded Context)

一个大型系统不可能只用一个模型覆盖所有功能。限界上下文明确了某个模型在特定范围内的含义和职责。

如何识别限界上下文:

  • 找出现有系统中的语言冲突:例如“订单”在交易上下文和物流上下文中,行为与属性完全不同。
  • 按业务能力划分:如“用户管理”“商品管理”“支付”“物流”通常是不同的上下文。
  • 每个上下文拥有独立的模型、数据库、团队,本质上是一个微服务候选项。

上下文映射(Context Map):

  • 确定上下文之间的关系:共享内核、客户-供应商、防腐败层、开放主机服务等。
  • 为每个集成点选择正确的模式,防止模型污染。

1.3 子域划分

将业务系统分解为核心域、支撑域、通用域,合理分配技术投入。

  • 核心域: 公司真正的竞争力所在,需要投入最精心的设计。
  • 支撑域: 辅助完成核心业务,但不产生差异化优势,可以适度标准化。
  • 通用域: 现成的系统或外包即可,如认证授权、消息推送。

第二部分:战术设计——将模型落实到代码

2.1 实体(Entity)

拥有唯一标识、连续生命周期的对象。即使属性全部改变,标识也能保持不变。

建模原则:

  • 标识生成策略:UUID、数据库自增ID、业务编号(如订单号)。
  • 尽量保持一致性和不变条件检查在实体内部,避免“贫血模型”。
  • 示例(Java风格伪代码):
public class Order {
    private OrderId id;
    private CustomerId customerId;
    private Money total;

    public void applyDiscount(Discount discount) {
        // 业务规则
        this.total = this.total.subtract(discount.amount());
    }
}

2.2 值对象(Value Object)

没有唯一标识,通过属性值来定义的不可变对象。它们描述事物的特征,可以安全共享。

常见值对象: Money、Address、Email、日期范围。

好处:

  • 自带业务行为,如货币相加时校验币种。
  • 提升代码可读性和安全性。
  • 实现值对象的相等性基于属性比较。

2.3 聚合(Aggregate)与聚合根(Aggregate Root)

聚合是一组相关对象的集合,对外通过聚合根来保证一致性边界。

黄金法则:

  • 一次事务只能修改一个聚合实例,通过聚合根操作内部实体。
  • 小聚合优于大聚合,设计时考虑最终一致性而非全局强一致。
  • 根据业务不变量确定聚合边界:例如“订单总额必须等于订单项金额之和”这个不变量就需要 Order 作为聚合根,OrderItem 只能通过 Order 访问。

2.4 领域服务(Domain Service)

当某个业务行为无法自然归属于某个实体或值对象时,使用无状态的领域服务。

适用场景:

  • 操作涉及多个聚合。
  • 需要复杂的计算或本地外部系统交互。
  • 命名必须出自通用语言,如:FundsTransferService。

2.5 资源库(Repository)

资源库封装了聚合的存储与检索逻辑,模拟为“内存中的集合”。

设计要点:

  • 只对聚合根提供资源库,例如 OrderRepository。
  • 接口定义在领域层,实现在基础层。
  • 常用方法:save、load、findByCriteria。

2.6 领域事件(Domain Event)

描述领域中已经发生的有意义的事情,其他上下文可以订阅响应。

实践方式:

  • 事件命名采用过去式,如 OrderPlaced、PaymentReceived。
  • 在实体方法内部生成事件,通过事件发布器发送。
  • 用于最终一致性、审计日志、解耦上下文。

2.7 模块与包结构

将领域模型组织为清晰的模块,尽量让限界上下文和包结构对应。

建议分层:

com.example.ordercontext
├── domain          // 实体、值对象、领域服务、资源库接口
├── application     // 应用服务、用例编排、DTO
├── infrastructure  // 资源库实现、消息发送、外部接口适配
└── interfaces      // REST API、消息监听器、定时任务

第三部分:实战流程与模式

3.1 业务需求分析到模型落地流程

  1. 事件风暴工作坊: 领域专家和开发团队一起梳理业务流程,用橙色便利贴表示事件,用蓝色表示命令,识别出聚合和上下文边界。
  2. 识别核心模型: 提取出实体、值对象、聚合,画出 UML 草图(可在白板上)。
  3. 编写模型代码: 先写领域层的测试,再写实现,遵循“测试先行”。
  4. 与基础设施对接: 实现资源库,重点关注业务逻辑与存储逻辑分离。
  5. 持续重构: 随着对领域理解加深,不断调整聚合边界和表达。

3.2 常见反模式与解决方案

  • 贫血领域模型: 实体只有 getter/setter,业务逻辑全在 service 中。应把相关行为移到实体内部。
  • 过度聚合: 一个聚合包含过多对象导致性能低下。使用最终一致性和小聚合,按事务边界拆分。
  • ACID 思维僵化: 跨聚合强一致性会破坏独立性。善用领域事件和 Saga 实现最终一致。
  • 无上下文映射: 上下文之间直接数据耦合,导致“大泥球”。必须通过防腐层或共享语言隔离变化。

3.3 与微服务架构的关系

DDD 的限界上下文是划分微服务的天然边界。一个上下文通常对应一个微服务(或多个微服务如果上下文内部进一步解耦)。战术设计中的聚合则成为服务内部的模块边界。遵循 DDD 设计出的微服务天然具有高内聚和独立演化的能力。


结语

领域驱动设计并非复杂的教条,而是一套将业务复杂性转化为可维护软件的有效方法论。从战略层面的通用语言和限界上下文,到战术层面的聚合、实体、值对象、事件,它的核心在于:让代码说出业务的语言,让模型抓住不变的本质

建议你先在一个小型核心域中实践上述技术,逐步体会 DDD 带来的设计清晰度和长期价值。