Domain-Driven Design 领域驱动设计

1. 概述

Domain-Driven Design(DDD,领域驱动设计)是 Eric Evans 在其 2003 年著作 Domain-Driven Design: Tackling Complexity in the Heart of Software 中提出的软件开发方法论。其核心思想是:软件系统的设计应以业务领域模型为中心,开发团队与领域专家共建统一的领域语言(Ubiquitous Language),将复杂的业务逻辑直接表达在代码中。

DDD 解决的核心问题:当业务复杂度高(非简单 CRUD)时,如何组织代码使模型精确反映业务,且能随业务演进持续演化。DDD 尤其适合微服务架构下对复杂限界上下文(Bounded Context)的内部建模。

DDD 分为两大层面:

2. 战略设计

2.1 统一语言(Ubiquitous Language)

开发团队与领域专家使用同一套术语来描述业务,这些术语直接出现在代码中(类名、方法名、变量名)。代码即文档。

2.2 限界上下文(Bounded Context)

一个大系统被划分为多个限界上下文,每个上下文内拥有自洽的领域模型。同一个词(如 "客户"、"产品")在不同上下文中可能有不同的含义和模型结构。这是 DDD 最核心的战略模式——用边界来控制模型的复杂度。

每个限界上下文通常对应一个微服务或一个子系统。上下文内部保持高度内聚,上下文之间通过明确的接口(API、事件、消息)通信。

2.3 上下文映射(Context Map)

描述不同限界上下文之间的关系模式。详见 第 6 节。

3. 战术设计

3.1 实体(Entity)

有唯一标识(ID)的对象,其标识在生命周期内不变,但属性可变。例如:Order(id=1001) 是一个实体,它的状态从 "NEW" 变为 "CONFIRMED",但 id 始终是 1001。

3.2 值对象(Value Object)

无唯一标识、不可变的对象,通过属性值来定义相等性。例如:Money(amount=100, currency="CNY") 和 Address(city, street, zip) 是值对象。值对象可以替换整个实例。

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

聚合是一组相关对象(Entity + Value Object)的集合,有且仅有一个根实体(聚合根)作为外部访问的入口。聚合内部的业务不变量(invariants)由聚合根统一维护。事务边界 = 聚合边界。

设计原则:

3.4 领域服务(Domain Service)

当某个业务操作不属于任何单一 Entity 或 Value Object 时,将其放在一个无状态的 Domain Service 中。例如:转账(TransferService)不属于单个 Account 实体。

3.5 仓库(Repository)

为聚合提供持久化访问的抽象。一个聚合一个仓库。对外隐藏了 ORM、SQL、NoSQL 等技术细节。

3.6 领域事件(Domain Event)

领域专家关心的 "发生的事实"。例如:OrderPlaced、PaymentCompleted。用于解耦上下文之间的通信,驱动流程、触发副作用,以及实现 Event Sourcing。

3.7 工厂(Factory)

当聚合的创建逻辑复杂时,使用工厂封装创建过程,确保创建出来的聚合始终处于合法状态。

3.8 应用服务(Application Service)

薄薄一层,负责编排:从 Repository 加载聚合 → 调用聚合/领域服务执行业务逻辑 → 持久化。不包含业务逻辑。通常对应一个用例(Use Case)。

3.9 分层依赖

最经典的 DDD 分层架构为四层(或简化为三层):

层职责依赖方向
接口层 (Interface/API)接收外部请求(HTTP/消息),适配 DTO→ 应用层
应用层 (Application)编排用例,事务管理,不包含领域逻辑→ 领域层 + 基础设施层
领域层 (Domain)核心业务逻辑,Entity/ValueObject/Aggregate/DomainService不依赖任何层
基础设施层 (Infrastructure)持久化、消息、外部系统适配→ 领域层(实现接口)

4. 行为图(系统"做什么")

4.1 用例图——DDD 设计过程

展示 DDD 设计活动中各角色的参与和协作。领域专家与开发者共建统一语言,架构师划定限界上下文,开发者实现战术模式。

DDD Design Process Use Case Diagram
DDD 设计过程中各角色的用例关系

4.2 活动图——战略设计流程

展示从领域分析到上下文划分、再到战术设计的完整 DDD 工作流。

Strategic Design Activity Diagram
DDD 战略设计→战术设计流程活动图(带泳道)

4.3 序列图——聚合持久化流程

展示应用服务通过仓库加载聚合、调用领域方法、持久化变更的典型流程。

Aggregate Persistence Sequence Diagram
订单聚合的加载-操作-持久化序列图

4.4 状态机图——聚合根生命周期

展示订单聚合根在电子商务领域中的典型状态流转。

Order Aggregate Lifecycle State Machine
订单聚合根的生命周期状态机图

5. 结构图(系统"由什么组成")

5.1 包图——DDD 分层架构

展示 DDD 经典四层架构中包(模块)之间的依赖关系。领域层处于核心,不依赖任何其他层。

DDD Layered Architecture Package Diagram
DDD 四层架构包图(依赖指向领域核心)

5.2 组件图——战术模式关系

展示 DDD 战术设计各组件(Entity、Aggregate、Repository、Service、Event 等)之间的协作关系。

DDD Tactical Patterns Component Diagram
DDD 战术模式组件关系图

5.3 类图——DDD 核心构建块

展示 Entity、Value Object、Aggregate Root、Repository、Domain Service、Domain Event、Factory 的典型定义与关系。

DDD Core Building Blocks Class Diagram
DDD 核心构建块类图(订单领域示例)

6. 限界上下文关系模式

在上下文映射(Context Map)中,以下 8 种模式描述了限界上下文之间的组织与集成关系:

模式缩写含义
PartnershipP两个上下文团队紧密协作,共同演化接口
Shared KernelSK共享部分领域模型(如公共值对象),需严格控制共享范围
Customer-SupplierC/S上游(Supplier)满足下游(Customer)需求,下游参与上游规划
ConformistCF下游完全跟随上游模型,不做转换。适用于上游有绝对话语权时
Anti-Corruption LayerACL通过适配层隔离上游模型,保护下游领域不被污染。是最常用的集成模式
Open Host ServiceOHS上游通过标准协议(REST API / gRPC / 消息)为所有下游提供统一接口
Published LanguagePL通过共享的标准化文档/消息格式(XML Schema、Avro、Protobuf)实现集成
Separate WaysSW两个上下文不集成,各自独立。当集成收益低于成本时采用
Context Map Patterns Diagram
DDD 上下文映射——限界上下文之间的 8 种组织关系模式

7. 实战速查手册

7.1 何时使用 DDD?

场景是否适合 DDD
复杂业务规则和流程✅ 适合
简单 CRUD 应用❌ 过度设计
数据密集型 / BI / 报表❌ 不适合
微服务架构下的核心服务✅ 适合
开发团队能直接接触领域专家✅ 适合(否则难以建立统一语言)

7.2 聚合设计原则

  1. 在聚合边界内保证业务不变量——跨聚合的不变量通过最终一致性保证
  2. 小聚合设计——尽可能让聚合只包含根实体和少量值对象
  3. 通过 ID 引用其他聚合——不要持有其他聚合的对象引用
  4. 一次事务只修改一个聚合
  5. 领域事件实现跨聚合协作——聚合发布事件,其他聚合订阅

7.3 Entity vs Value Object 速查

特性Entity(实体)Value Object(值对象)
唯一标识有(ID)无
可变性可变的不可变的
相等性基于 ID基于所有属性
生命周期有,可追踪无,随时可创建/丢弃
持久化独立表/文档嵌入所属实体
示例Order, User, ProductMoney, Address, Quantity

7.4 Aggregate Root vs Domain Service 选择

场景放哪里
操作属于单个聚合的业务逻辑聚合根的方法
操作涉及多个聚合或无主对象Domain Service
操作需要外部系统数据Application Service 编排,Domain Service 执行业务逻辑

7.5 Repository 使用规范

// ✅ 好的 Repository:面向聚合,返回完整聚合
interface OrderRepository {
  findBy(id: OrderId): Order;
  save(order: Order): void;
  findByCustomerId(customerId: CustomerId): Order[];
}

// ❌ 坏的 Repository:直接暴露底层 ORM
interface OrderRepository {
  findBySQL(sql: string): any[];
}

7.6 领域事件命名与结构

// ✅ 使用过去式,表达"已发生的事实"
OrderPlaced { orderId, customerId, timestamp, items }
PaymentAuthorized { paymentId, amount, timestamp }
ShipmentDispatched { shipmentId, trackingNumber, timestamp }

// ❌ 命令式命名(这应该放在 Commands 里)
PlaceOrder { ... }        // 这是 Command,不是 Event
AuthorizePayment { ... }  // 这是 Command,不是 Event

7.7 常见反模式

反模式说明解决
贫血领域模型Entity 只有 getter/setter,无业务方法。业务逻辑散落在 Service 中将业务逻辑沉淀到聚合根/实体上
大聚合一个聚合包含过多对象,导致性能和并发问题拆分为小聚合,通过领域事件协作
直接引用其他聚合在聚合 A 中持有聚合 B 的对象引用改为持有聚合 B 的 ID
Application Service 过胖业务逻辑写在 Application Service 里只做编排,逻辑下沉到 Domain
领域层依赖基础设施领域层直接 import ORM 或框架类依赖倒置:Domain 定义接口,Infrastructure 实现

7.8 推荐的 Java / Kotlin / TypeScript 项目结构

order-service/
├── domain/                    # 领域层(零外部依赖)
│   ├── model/                 # Entity, ValueObject, Aggregate
│   │   ├── Order.java
│   │   ├── OrderId.java       # 值对象
│   │   ├── OrderLine.java     # 实体
│   │   ├── Money.java         # 值对象
│   │   └── OrderStatus.java   # 枚举
│   ├── service/               # Domain Service
│   │   └── PricingService.java
│   ├── event/                 # Domain Event
│   │   └── OrderPlaced.java
│   ├── repository/            # Repository 接口
│   │   └── OrderRepository.java
│   └── factory/               # Factory
│       └── OrderFactory.java
├── application/               # 应用层
│   ├── service/               # Application Service(用例编排)
│   │   └── OrderApplicationService.java
│   └── dto/                   # 数据传输对象
│       └── CreateOrderDTO.java
├── infrastructure/            # 基础设施层
│   ├── persistence/           # Repository 实现,ORM 映射
│   │   └── OrderRepositoryImpl.java
│   ├── messaging/             # 消息队列适配
│   │   └── OrderEventPublisher.java
│   └── external/              # 外部服务调用
│       └── PaymentGatewayAdapter.java
└── interfaces/                # 接口层
    ├── rest/                  # REST API Controller
    │   └── OrderController.java
    └── event/                 # 消息监听器
        └── OrderEventListener.java

7.9 示例代码:一个完整的聚合

// =============== 值对象 ===============
// Money.java —— 不可变的值对象
public final class Money {
    private final BigDecimal amount;
    private final Currency currency;
    public Money(BigDecimal amount, Currency currency) {
        this.amount = amount; this.currency = currency;
    }
    public Money add(Money other) {
        if (!this.currency.equals(other.currency))
            throw new IllegalArgumentException("货币不匹配");
        return new Money(this.amount.add(other.amount), this.currency);
    }
    // equals() 和 hashCode() 基于 amount + currency
}

// =============== 聚合根 ===============
// Order.java —— 聚合根,封装业务不变量
public class Order {
    private OrderId id;
    private CustomerId customerId;
    private List lines;
    private OrderStatus status;
    private Money totalAmount;

    // 工厂方法 —— 确保创建的 Order 始终合法
    public static Order create(CustomerId customerId) {
        return new Order(OrderId.next(), customerId,
            new ArrayList<>(), OrderStatus.NEW, Money.zero(CNY));
    }

    public void addItem(ProductId productId, Quantity qty, Money price) {
        if (status != OrderStatus.NEW)
            throw new IllegalStateException("仅新订单可添加产品");
        lines.add(new OrderLine(productId, qty, price));
        recalculateTotal();
    }

    public void confirm() {
        if (lines.isEmpty()) throw new IllegalStateException("空订单不能确认");
        status = OrderStatus.CONFIRMED;
        registerEvent(new OrderPlaced(this.id, this.customerId,
            this.totalAmount, Instant.now()));
    }

    private void recalculateTotal() {
        totalAmount = lines.stream()
            .map(line -> line.price.multiply(line.qty))
            .reduce(Money.zero(CNY), Money::add);
    }

    // getters for persistence...
}

// =============== Repository 接口(领域层) ===============
public interface OrderRepository {
    Optional findById(OrderId id);
    void save(Order order);
}

// =============== Application Service ===============
public class OrderApplicationService {
    private final OrderRepository orderRepo;
    private final EventPublisher eventPublisher;

    @Transactional
    public OrderId placeOrder(PlaceOrderCommand cmd) {
        Order order = Order.create(cmd.getCustomerId());
        for (ItemRequest item : cmd.getItems()) {
            order.addItem(item.getProductId(), item.getQuantity(), item.getPrice());
        }
        order.confirm();
        orderRepo.save(order);
        order.getEvents().forEach(eventPublisher::publish);
        return order.getId();
    }
}