跳转至

第16章:领域驱动设计(DDD)

领域驱动设计

领域驱动设计(Domain-Driven Design)是一种以业务领域为核心的软件设计方法论,通过将复杂业务逻辑显式建模来驾驭软件复杂性。

目录


1. DDD概述与核心思想

1.1 什么是DDD

DDD由Eric Evans在2003年《Domain-Driven Design: Tackling Complexity in the Heart of Software》一书中提出,核心主张:

  • 业务复杂性才是软件的根本挑战——技术复杂性是次要的
  • 领域模型是团队沟通和代码实现的统一语言
  • 持续精炼模型:模型不是一次设计完成的,需要随业务理解加深而演化

1.2 核心原则

原则 说明
统一语言(Ubiquitous Language) 开发者与领域专家使用同一套术语
模型驱动设计 代码直接反映领域模型
限界上下文 明确模型适用的边界
持续集成 模型与代码保持同步
知识消化 从领域专家处不断提炼知识

1.3 DDD vs 传统三层架构

Text Only
传统三层架构:
┌─────────────────┐
│  表示层 (UI)     │
├─────────────────┤
│  业务逻辑层      │  ← 贫血模型,Service承载所有逻辑
├─────────────────┤
│  数据访问层      │
└─────────────────┘

DDD分层架构:
┌─────────────────┐
│  用户接口层      │
├─────────────────┤
│  应用层          │  ← 编排用例,不含业务规则
├─────────────────┤
│  领域层          │  ← 充血模型,核心业务逻辑
├─────────────────┤
│  基础设施层      │  ← 技术实现细节
└─────────────────┘

2. 战略设计

2.1 限界上下文(Bounded Context)

限界上下文是DDD战略设计的核心概念,定义了模型的适用范围和语义边界

Text Only
电商系统限界上下文划分:

┌──────────┐  ┌──────────┐  ┌──────────┐
│ 商品上下文 │  │ 订单上下文 │  │ 支付上下文 │
│          │  │          │  │          │
│ Product  │  │ Order    │  │ Payment  │
│ Category │  │ OrderItem│  │ Account  │
│ SKU      │  │ Address  │  │ Transaction│
└────┬─────┘  └────┬─────┘  └────┬─────┘
     │             │             │
     └─────── 上下文映射 ────────┘

同一术语在不同上下文中有不同含义:

  • "Product" 在商品上下文中包含详细描述、规格参数
  • "Product" 在订单上下文中只是一个ID + 名称 + 价格的快照
  • "Account" 在支付上下文中是资金账户
  • "Account" 在用户上下文中是登录账户

2.2 上下文映射(Context Mapping)

上下文之间的关系模式:

映射模式 说明 典型场景
合作关系(Partnership) 两个团队协同演进 核心业务的紧密子域
共享内核(Shared Kernel) 共享一小部分模型 共用的领域对象
客户-供应商(Customer-Supplier) 上游供应,下游消费 订单 → 支付
遵奉者(Conformist) 下游完全遵从上游模型 对接第三方API
防腐层(Anti-Corruption Layer) 下游翻译上游模型 对接遗留系统
开放主机服务(Open Host Service) 提供标准化协议 对外暴露REST API
发布语言(Published Language) 使用公认的交换模型 Protobuf/JSON Schema

2.3 子域划分

Text Only
┌─────────────────────────────────────────┐
│              电商业务域                    │
├────────────┬────────────┬───────────────┤
│  核心子域    │  支撑子域    │   通用子域      │
│            │            │              │
│ • 交易流程  │ • 库存管理   │ • 用户认证     │
│ • 营销策略  │ • 物流跟踪   │ • 消息通知     │
│ • 推荐算法  │ • 客服工单   │ • 文件存储     │
│            │            │              │
│ 战略价值最高 │ 必要但非核心  │ 可外采/通用方案  │
└────────────┴────────────┴───────────────┘

3. 战术设计

3.1 实体(Entity)

实体具有唯一标识,通过ID区分,而非属性值。

Java
public class Order {
    private OrderId id;           // 唯一标识
    private CustomerId customerId;
    private List<OrderItem> items;
    private OrderStatus status;
    private Money totalAmount;

    // 实体的業務行为
    public void addItem(Product product, int quantity) {
        validateCanModify();
        OrderItem item = new OrderItem(product, quantity);
        items.add(item);
        recalculateTotal();
    }

    public void confirm() {
        if (items.isEmpty()) {
            throw new DomainException("订单不能为空");
        }
        this.status = OrderStatus.CONFIRMED;
        registerEvent(new OrderConfirmedEvent(this.id));
    }

    private void validateCanModify() {
        if (status != OrderStatus.DRAFT) {
            throw new DomainException("只有草稿状态的订单可以修改");
        }
    }
}

3.2 值对象(Value Object)

值对象没有唯一标识,通过属性值相等来判断是否相同,且不可变

Java
// 值对象(Value Object):无唯一标识,通过属性值相等性判断,且不可变
// 使用Java record实现:自动生成equals/hashCode/toString
public record Money(BigDecimal amount, Currency currency) {
    // 紧凑构造器(Compact Constructor):在创建时校验业务规则
    public Money {
        if (amount.compareTo(BigDecimal.ZERO) < 0) {
            throw new IllegalArgumentException("金额不能为负");  // 保护不变量
        }
        Objects.requireNonNull(currency, "货币不能为空");
    }

    // 值对象的行为:返回新对象而非修改自身(不可变性)
    public Money add(Money other) {
        assertSameCurrency(other);  // 跨币种计算保护
        return new Money(amount.add(other.amount), currency);
    }

    public Money multiply(int quantity) {
        return new Money(amount.multiply(BigDecimal.valueOf(quantity)), currency);
    }

    // 私有校验:确保两个金额的币种一致
    private void assertSameCurrency(Money other) {
        if (!currency.equals(other.currency)) {
            throw new DomainException("货币类型不一致");
        }
    }
}

// 地址值对象:通过属性组合描述一个完整的概念
public record Address(String province, String city, String district, String detail) {
    // 构造时校验必填字段
    public Address {
        Objects.requireNonNull(province);
        Objects.requireNonNull(city);
    }

    // 值对象可以包含有意义的业务方法
    public String fullAddress() {
        return province + city + district + detail;
    }
}

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

聚合是一组相关对象的集合,聚合根是外部访问聚合的唯一入口

聚合设计原则:

  1. 聚合内保持一致性——事务边界在聚合内部
  2. 聚合间通过ID引用——不直接持有其他聚合的引用
  3. 聚合尽量小——只包含必须保持一致性的对象
  4. 通过聚合根修改内部状态——外部不能绕过聚合根
Java
// === 聚合根(Aggregate Root) ===
// Order是聚合根,外部只能通过Order操作内部的OrderItem
public class Order {
    private OrderId id;
    private List<OrderItem> items;  // OrderItem属于Order聚合内部,不能被外部直接访问

    // 聚合根提供操作内部对象的方法,保护业务不变量
    public void addItem(ProductId productId, String name, Money price, int qty) {
        OrderItem item = new OrderItem(productId, name, price, qty);
        this.items.add(item);
    }

    // 通过聚合根删除内部实体
    public void removeItem(ProductId productId) {
        items.removeIf(i -> i.getProductId().equals(productId));
    }
}

// OrderItem不是聚合根,生命周期依赖于Order
public class OrderItem {
    private ProductId productId;  // 通过ID引用其他聚合(而非直接持有Product对象)
    private String productName;   // 快照数据:订单创建时的商品名称
    private Money unitPrice;      // 快照数据:订单创建时的单价
    private int quantity;

    // 计算小计金额(利用值对象Money的行为)
    Money subtotal() {
        return unitPrice.multiply(quantity);
    }
}

3.4 领域事件(Domain Event)

领域事件表示领域中已经发生的有意义的事情,用于解耦聚合间的交互。

Java
// === 领域事件定义 ===
// 用过去式命名(OrderConfirmed),表示“已经发生的事实”
public record OrderConfirmedEvent(
    OrderId orderId,
    CustomerId customerId,
    Money totalAmount,
    LocalDateTime occurredAt       // 事件发生时间
) implements DomainEvent {
    // 便捷构造器:自动填充事件发生时间
    public OrderConfirmedEvent(OrderId orderId, CustomerId customerId, Money totalAmount) {
        this(orderId, customerId, totalAmount, LocalDateTime.now());
    }
}

// === 在聚合根中发布事件 ===
// 继承AbstractAggregateRoot获得事件发布能力(Spring Data提供)
public class Order extends AbstractAggregateRoot<Order> {
    public void confirm() {
        // 业务校验...
        this.status = OrderStatus.CONFIRMED;
        // 注册领域事件,会在仓储save()时自动发布
        registerEvent(new OrderConfirmedEvent(id, customerId, totalAmount));
    }
}

// === 事件处理器 ===
// 在另一个限界上下文中监听并处理事件,实现聚合间解耦
@Component
public class OrderEventHandler {

    // @EventListener:同步事件监听(与发布者在同一事务内)
    @EventListener
    public void onOrderConfirmed(OrderConfirmedEvent event) {
        // 触发库存扣减(跨聚合操作)
        inventoryService.deductStock(event.orderId());
    }

    // @TransactionalEventListener:在事务提交后才执行,避免事务回滚导致重复通知
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void sendNotification(OrderConfirmedEvent event) {
        // 发送订单确认通知(异步操作,不影响主流程)
        notificationService.notify(event.customerId(), "订单已确认");
    }
}

3.5 领域服务(Domain Service)

当业务逻辑不自然属于任何一个实体或值对象时,使用领域服务。

Java
// 领域服务:处理跨实体/跨聚合的业务逻辑,不自然属于任何单个实体
@DomainService
public class PricingService {

    // 订单价格计算:涉及订单和客户两个聚合,故放在领域服务中
    public Money calculateOrderPrice(Order order, Customer customer) {
        Money basePrice = order.calculateSubtotal();    // 订单基础价

        // 第一步:根据会员等级应用折扣
        Money discounted = applyMemberDiscount(basePrice, customer.getLevel());

        // 第二步:应用满减优惠活动
        Money afterPromo = applyPromotions(discounted, order.getItems());

        return afterPromo;
    }

    // 会员折扣策略:使用模式匹配根据等级确定折扣率
    private Money applyMemberDiscount(Money price, MemberLevel level) {
        BigDecimal rate = switch (level) {
            case GOLD -> new BigDecimal("0.9");      // 金牌会员九折
            case SILVER -> new BigDecimal("0.95");    // 银牌会员九五折
            default -> BigDecimal.ONE;                // 普通用户不打折
        };
        return price.multiply(rate);
    }
}

3.6 仓储(Repository)

仓储为聚合提供持久化抽象,屏蔽底层存储细节。

Java
// === 仓储接口(定义在领域层) ===
// 领域层只定义接口,不依赖任何持久化技术
public interface OrderRepository {
    Optional<Order> findById(OrderId id);          // 按ID查找聚合
    void save(Order order);                         // 保存聚合(新增或更新)
    void delete(Order order);                       // 删除聚合
    List<Order> findByCustomerId(CustomerId customerId);  // 按客户ID查询
}

// === 仓储实现(基础设施层) ===
// 使用JPA实现领域层定义的接口,体现“依赖倒置原则”
@Repository
public class JpaOrderRepository implements OrderRepository {

    @PersistenceContext
    private EntityManager em;       // JPA实体管理器

    @Override
    public Optional<Order> findById(OrderId id) {
        // 查询持久化实体,然后通过Mapper转换为领域对象
        return Optional.ofNullable(em.find(OrderEntity.class, id.value()))
                .map(OrderMapper::toDomain);  // 数据库实体 → 领域对象的转换
    }

    @Override
    public void save(Order order) {
        OrderEntity entity = OrderMapper.toEntity(order);  // 领域对象 → 数据库实体
        em.merge(entity);           // merge支持新增和更新
    }
}

4. 事件驱动架构与CQRS

4.1 事件驱动架构(EDA)

Text Only
┌──────────┐   Event    ┌──────────────┐   Event    ┌──────────┐
│ 订单服务  │ ─────────→ │  消息中间件    │ ─────────→ │ 库存服务  │
│          │            │ (Kafka/RMQ)  │            │          │
└──────────┘            │              │            └──────────┘
                        │              │   Event    ┌──────────┐
                        │              │ ─────────→ │ 通知服务  │
                        └──────────────┘            └──────────┘

EDA的优势:

  • 松耦合:生产者不需要知道消费者的存在
  • 可扩展:新增消费者不影响现有系统
  • 弹性:消费者可以独立伸缩
  • 最终一致性:适合分布式系统

4.2 CQRS模式(命令查询职责分离)

Text Only
              ┌──────────────────────────────────────┐
              │            CQRS 架构                   │
              │                                      │
   Command    │  ┌──────────┐    ┌───────────────┐   │
  ──────────→ │  │ 命令处理器 │───→│  写模型(领域)   │   │
              │  └──────────┘    └───────┬───────┘   │
              │                         │ 事件        │
              │                         ▼            │
              │                  ┌──────────────┐    │
              │                  │   事件存储     │    │
              │                  └──────┬───────┘    │
              │                         │ 投影        │
              │                         ▼            │
    Query     │  ┌──────────┐    ┌───────────────┐   │
  ──────────→ │  │ 查询处理器 │───→│  读模型(视图)   │   │
              │  └──────────┘    └───────────────┘   │
              └──────────────────────────────────────┘

CQRS实现:

Java
// === CQRS命令端:负责写操作 ===
// 命令对象:封装写操作的意图和数据
public record CreateOrderCommand(
    CustomerId customerId,
    List<OrderItemRequest> items,
    Address shippingAddress
) {}

// 命令处理器:编排用例,调用领域对象执行业务逻辑
@Service
public class OrderCommandHandler {

    private final OrderRepository orderRepository;
    private final PricingService pricingService;

    @Transactional  // 命令处理在事务内执行,保证原子性
    public OrderId handle(CreateOrderCommand cmd) {
        // 1. 创建聚合根
        Order order = Order.create(cmd.customerId(), cmd.shippingAddress());
        // 2. 添加订单项(通过聚合根方法保护业务规则)
        cmd.items().forEach(item ->
            order.addItem(item.productId(), item.name(), item.price(), item.qty())
        );
        // 3. 持久化聚合
        orderRepository.save(order);
        return order.getId();
    }
}

// === CQRS查询端:负责读操作,可以使用专门优化的读模型 ===
// 查询对象:封装查询条件和分页参数
public record OrderSummaryQuery(CustomerId customerId, int page, int size) {}

// 查询处理器:直接从读优化的视图中查询,不经过领域模型
@Service
public class OrderQueryHandler {

    private final OrderReadRepository readRepository;  // 读专用仓储,可能是ElasticSearch/Redis等

    public Page<OrderSummaryDTO> handle(OrderSummaryQuery query) {
        return readRepository.findSummariesByCustomer(
            query.customerId(), query.page(), query.size()
        );
    }
}

4.3 事件溯源(Event Sourcing)

Java
// === 事件溯源(Event Sourcing)的聚合根 ===
// 不直接存储当前状态,而是存储所有事件,通过回放事件重建状态
public class Order {
    private OrderId id;
    private OrderStatus status;
    private List<DomainEvent> uncommittedEvents = new ArrayList<>();  // 未提交的新事件

    // 从历史事件序列重建聚合状态(每次加载时调用)
    public static Order reconstitute(List<DomainEvent> events) {
        Order order = new Order();
        events.forEach(order::apply);  // 依次回放每个事件
        return order;
    }

    // 业务操作:只产生事件,不直接修改状态
    public void confirm() {
        raise(new OrderConfirmedEvent(id, LocalDateTime.now()));
    }

    // 产生事件:先apply更新状态,再加入未提交列表
    private void raise(DomainEvent event) {
        apply(event);                  // 立即更新内存状态
        uncommittedEvents.add(event);  // 记录待持久化的事件
    }

    // 事件处理:根据事件类型更新聚合状态(模式匹配)
    private void apply(DomainEvent event) {
        switch (event) {
            case OrderCreatedEvent e -> {
                this.id = e.orderId();
                this.status = OrderStatus.DRAFT;       // 创建时设为草稿状态
            }
            case OrderConfirmedEvent e -> {
                this.status = OrderStatus.CONFIRMED;    // 确认时转为已确认
            }
            default -> throw new IllegalArgumentException("Unknown event");
        }
    }
}

4.4 事件溯源的幂等性(Idempotency)

事件溯源系统中,幂等性是保证数据一致性的关键,需要在三个层面解决:

1. 命令端幂等:防止重复命令产生重复事件

Java
@Service
public class IdempotentCommandHandler {

    private final EventStore eventStore;
    private final IdempotencyStore idempotencyStore;  // Redis/DB

    @Transactional
    public OrderId handle(CreateOrderCommand cmd, String idempotencyKey) {
        // 1. 检查命令是否已处理过
        if (idempotencyStore.exists(idempotencyKey)) {
            return idempotencyStore.getResult(idempotencyKey, OrderId.class);
        }

        // 2. 执行命令
        Order order = Order.create(cmd.customerId(), cmd.shippingAddress());
        eventStore.append(order.getId(), order.getUncommittedEvents());

        // 3. 记录幂等键(与事件写入在同一事务中)
        idempotencyStore.save(idempotencyKey, order.getId(), Duration.ofHours(24));

        return order.getId();
    }
}

2. 事件存储幂等:防止重复事件写入

Java
public interface EventStore {
    /**
     * 追加事件(乐观并发控制)
     * @param expectedVersion 期望的当前版本号,防止并发写入
     * @throws OptimisticConcurrencyException 版本冲突时抛出
     */
    void append(AggregateId id, List<DomainEvent> events, long expectedVersion);
}

// 实现:每个事件携带递增版本号
@Entity
@Table(uniqueConstraints = @UniqueConstraint(
    columnNames = {"aggregate_id", "version"}  // 联合唯一约束保证幂等
))
public class EventRecord {
    @Id @GeneratedValue
    private Long id;
    private String aggregateId;
    private long version;       // 聚合内递增版本号
    private String eventType;
    private String payload;
    private Instant occurredAt;
}

3. 事件消费端幂等:投影(Projection)的幂等处理

Java
@Component
public class OrderProjection {

    private final OrderReadRepository readRepo;
    private final ProjectionOffsetStore offsetStore;

    /**
     * 处理事件时记录offset,确保重放安全
     * 即使消息被重复投递(如Kafka rebalance),也不会产生副作用
     */
    @Transactional
    public void handle(DomainEvent event, long offset) {
        // 1. 检查是否已处理过此偏移量
        if (offsetStore.isProcessed("order-projection", offset)) {
            return;  // 幂等跳过
        }

        // 2. 更新读模型
        switch (event) {
            case OrderCreatedEvent e -> readRepo.insert(toSummary(e));
            case OrderConfirmedEvent e -> readRepo.updateStatus(
                e.orderId(), OrderStatus.CONFIRMED
            );
        }

        // 3. 记录已处理偏移量
        offsetStore.markProcessed("order-projection", offset);
    }
}

要点总结:Event Sourcing 系统中实现幂等的核心手段: - 命令端:幂等键(Idempotency Key)去重 - 存储端:乐观锁(版本号)+ 唯一约束 - 消费端:偏移量追踪(Offset Tracking)或事件ID去重


5. DDD分层架构

5.1 四层架构详解

Text Only
┌───────────────────────────────────────────────────┐
│                 用户接口层 (Interfaces)              │
│  Controller / DTO / Assembler / 前端适配            │
├───────────────────────────────────────────────────┤
│                 应用层 (Application)                │
│  ApplicationService / CommandHandler / DTO         │
│  编排用例、事务管理、权限检查                          │
├───────────────────────────────────────────────────┤
│                 领域层 (Domain)                     │
│  Entity / ValueObject / Aggregate / DomainService  │
│  Repository接口 / DomainEvent / Factory            │
│  ★ 核心业务逻辑,不依赖任何外部技术 ★                 │
├───────────────────────────────────────────────────┤
│                基础设施层 (Infrastructure)           │
│  Repository实现 / ORM映射 / 消息中间件适配            │
│  外部API网关 / 缓存实现                              │
└───────────────────────────────────────────────────┘

5.2 依赖方向

Text Only
接口层 → 应用层 → 领域层 ← 基础设施层
              依赖倒置原则
         (领域层定义接口,基础设施层实现)

5.3 各层职责对比

层次 职责 依赖 典型类
用户接口层 HTTP适配、参数校验、DTO转换 应用层 Controller, DTO
应用层 用例编排、事务、权限 领域层 ApplicationService
领域层 核心业务逻辑 无外部依赖 Entity, VO, DomainService
基础设施层 技术实现细节 领域层接口 JpaRepository, KafkaPublisher

6. Spring Boot DDD实战

6.1 项目结构

Text Only
order-service/
├── src/main/java/com/example/order/
│   ├── interfaces/              # 用户接口层
│   │   ├── rest/
│   │   │   ├── OrderController.java
│   │   │   └── dto/
│   │   │       ├── CreateOrderRequest.java
│   │   │       └── OrderResponse.java
│   │   └── assembler/
│   │       └── OrderAssembler.java
│   │
│   ├── application/             # 应用层
│   │   ├── OrderApplicationService.java
│   │   ├── command/
│   │   │   └── CreateOrderCommand.java
│   │   └── query/
│   │       └── OrderQueryService.java
│   │
│   ├── domain/                  # 领域层
│   │   ├── model/
│   │   │   ├── Order.java           # 聚合根
│   │   │   ├── OrderItem.java       # 实体
│   │   │   ├── OrderId.java         # 值对象
│   │   │   ├── Money.java           # 值对象
│   │   │   └── OrderStatus.java     # 枚举
│   │   ├── event/
│   │   │   └── OrderConfirmedEvent.java
│   │   ├── service/
│   │   │   └── PricingService.java
│   │   └── repository/
│   │       └── OrderRepository.java  # 接口
│   │
│   └── infrastructure/          # 基础设施层
│       ├── persistence/
│       │   ├── JpaOrderRepository.java
│       │   ├── entity/
│       │   │   └── OrderEntity.java
│       │   └── mapper/
│       │       └── OrderMapper.java
│       ├── messaging/
│       │   └── KafkaEventPublisher.java
│       └── config/
│           └── DomainEventConfig.java

6.2 完整代码示例

Java
// === Spring Boot DDD完整代码示例 ===

// === 用户接口层:处理HTTP请求,参数校验,DTO转换 ===
@RestController
@RequestMapping("/api/orders")
public class OrderController {

    private final OrderApplicationService orderAppService;
    private final OrderAssembler assembler;  // DTO与命令对象的转换器

    @PostMapping
    public ResponseEntity<OrderResponse> createOrder(@RequestBody @Valid CreateOrderRequest req) {
        CreateOrderCommand cmd = assembler.toCommand(req);  // 请求DTO → 命令对象
        OrderId orderId = orderAppService.createOrder(cmd);
        return ResponseEntity.status(HttpStatus.CREATED)
                .body(new OrderResponse(orderId.value()));
    }

    @PostMapping("/{orderId}/confirm")
    public ResponseEntity<Void> confirmOrder(@PathVariable String orderId) {
        orderAppService.confirmOrder(new OrderId(orderId));
        return ResponseEntity.ok().build();
    }
}

// === 应用层:编排用例流程,不包含业务规则 ===
@Service
@Transactional  // 应用层管理事务边界
public class OrderApplicationService {

    private final OrderRepository orderRepository;
    private final PricingService pricingService;     // 领域服务
    private final CustomerRepository customerRepository;

    // 创建订单用例:应用层负责编排,领域层负责业务规则
    public OrderId createOrder(CreateOrderCommand cmd) {
        // 1. 获取客户聚合
        Customer customer = customerRepository.findById(cmd.customerId())
                .orElseThrow(() -> new EntityNotFoundException("客户不存在"));

        // 2. 创建订单聚合根(工厂方法)
        Order order = Order.create(cmd.customerId(), cmd.shippingAddress());

        // 3. 通过聚合根方法添加订单项
        cmd.items().forEach(item ->
            order.addItem(item.productId(), item.name(), item.price(), item.quantity())
        );

        // 4. 调用领域服务计算最终价格
        Money finalPrice = pricingService.calculateOrderPrice(order, customer);
        order.applyFinalPrice(finalPrice);

        // 5. 持久化聚合(仓储会自动发布领域事件)
        orderRepository.save(order);
        return order.getId();
    }

    // 确认订单用例
    public void confirmOrder(OrderId orderId) {
        Order order = orderRepository.findById(orderId)
                .orElseThrow(() -> new EntityNotFoundException("订单不存在"));
        order.confirm();           // 调用聚合根的业务方法
        orderRepository.save(order);
    }
}

// === 领域层 - 聚合根:包含核心业务逻辑,不依赖任何外部框架 ===
public class Order extends AbstractAggregateRoot<Order> {

    private OrderId id;
    private CustomerId customerId;
    private Address shippingAddress;
    private List<OrderItem> items = new ArrayList<>();
    private OrderStatus status;
    private Money totalAmount;
    private LocalDateTime createdAt;

    // 静态工厂方法:封装聚合创建逻辑,确保初始状态合法
    public static Order create(CustomerId customerId, Address address) {
        Order order = new Order();
        order.id = OrderId.generate();    // 生成唯一标识
        order.customerId = customerId;
        order.shippingAddress = address;
        order.status = OrderStatus.DRAFT; // 初始状态为草稿
        order.createdAt = LocalDateTime.now();
        return order;
    }

    // 添加商品:聚合根保护业务不变量 + 自动重算总额
    public void addItem(ProductId productId, String name, Money price, int qty) {
        if (status != OrderStatus.DRAFT) {
            throw new DomainException("只有草稿订单可以添加商品");
        }
        items.add(new OrderItem(productId, name, price, qty));
        recalculateTotal();               // 保持总额与订单项一致
    }

    // 确认订单:状态转换 + 发布领域事件
    public void confirm() {
        if (items.isEmpty()) {
            throw new DomainException("空订单不能确认");
        }
        if (status != OrderStatus.DRAFT) {
            throw new DomainException("当前状态不允许确认");
        }
        this.status = OrderStatus.CONFIRMED;
        registerEvent(new OrderConfirmedEvent(id, customerId, totalAmount));  // 发布事件
    }

    // 私有方法:汇总所有订单项小计金额
    private void recalculateTotal() {
        this.totalAmount = items.stream()
                .map(OrderItem::subtotal)     // 获取每项小计
                .reduce(Money.ZERO, Money::add);  // 累加求和
    }
}

// === 基础设施层 - 仓储实现:将领域对象与数据库实体解耦 ===
@Repository
public class JpaOrderRepository implements OrderRepository {

    private final SpringDataOrderRepository springRepo;  // Spring Data JPA自动实现
    private final OrderMapper mapper;                     // 领域对象↔数据库实体双向转换

    @Override
    public Optional<Order> findById(OrderId id) {
        return springRepo.findById(id.value()).map(mapper::toDomain);
    }

    @Override
    public void save(Order order) {
        OrderEntity entity = mapper.toEntity(order);
        springRepo.save(entity);
    }
}

6.3 防腐层示例

Java
// === 防腐层(Anti-Corruption Layer) ===
// 用于对接第三方系统,将外部模型翻译为领域模型,防止外部概念污染领域层

// 领域层定义的支付网关接口
public interface PaymentGateway {  // interface定义类型契约
    PaymentResult pay(OrderId orderId, Money amount);
}

// 基础设施层的适配器实现:将支付宝 SDK 转换为领域接口
@Component
public class AlipayGatewayAdapter implements PaymentGateway {  // extends继承;implements实现接口

    private final AlipayClient alipayClient;  // 第三方支付宝SDK

    @Override  // @Override重写父类方法
    public PaymentResult pay(OrderId orderId, Money amount) {
        // 1. 领域模型 → 第三方格式(出站转换)
        AlipayTradePayRequest request = new AlipayTradePayRequest();
        request.setOutTradeNo(orderId.value());
        request.setTotalAmount(amount.amount().toString());

        // 2. 调用第三方API
        AlipayTradePayResponse response = alipayClient.execute(request);

        // 3. 第三方结果 → 领域模型(入站转换)
        return response.isSuccess()
                ? PaymentResult.success(response.getTradeNo())
                : PaymentResult.failure(response.getSubMsg());
    }
}

7. 面试题精选

Q1: 实体和值对象的区别?

实体(Entity): - 有唯一标识(ID) - 通过ID判断是否相同 - 可变,有生命周期 - 示例:用户、订单

值对象(Value Object): - 没有唯一标识 - 通过属性值判断是否相等 - 不可变 - 示例:金额、地址、日期范围

Q2: 聚合的设计原则有哪些?

  1. 保护业务不变量:聚合边界内保持强一致性
  2. 尽量小:只包含必须同时变化的对象
  3. 通过ID引用其他聚合:不持有其他聚合的直接引用
  4. 一个事务只修改一个聚合:聚合间通过事件通信
  5. 通过聚合根访问:外部不能绕过聚合根修改内部状态

Q3: CQRS适用什么场景?不适用什么场景?

适用: - 读写负载差异大(读多写少) - 读模型和写模型差异大 - 需要独立扩展读写 - 事件溯源系统

不适用: - 简单CRUD应用 - 读写模型几乎一致 - 实时性要求极高(CQRS引入延迟) - 团队对DDD理解不足

Q4: 领域事件和集成事件的区别?

维度 领域事件 集成事件
范围 限界上下文内部 跨限界上下文
传输 进程内(Spring Event) 消息中间件(Kafka)
一致性 强一致 最终一致
格式 领域对象 DTO/序列化对象

Q5: 如何让贫血模型演进到充血模型?

  1. 识别业务规则:找出Service中的if-else逻辑
  2. 归还行为:将属于实体的行为移到实体内部
  3. 引入值对象:把基本类型包装为有行为的值对象
  4. 保护不变量:在构造/修改时校验业务规则
  5. 逐步重构:不要一次性改造,按聚合逐步演进

Q6: DDD中如何处理跨聚合的事务?

  • 方案一:Saga模式 —— 通过一系列本地事务+补偿机制实现
  • 方案二:领域事件 —— 聚合A提交后发布事件,聚合B监听并处理
  • 方案三:Process Manager —— 通过流程管理器协调多个聚合
  • 反模式:在一个事务中修改多个聚合(破坏聚合边界)

8. 检查清单

战略设计检查

  • 是否识别并划分了核心域、支撑域、通用域?
  • 限界上下文边界是否清晰?
  • 上下文映射关系是否明确?
  • 统一语言词汇表是否建立?
  • 每个上下文是否有独立的团队负责?

战术设计检查

  • 实体是否有唯一标识?
  • 值对象是否不可变?
  • 聚合边界是否足够小?
  • 聚合根是否保护了业务不变量?
  • 是否通过ID引用其他聚合(而非直接引用)?
  • 领域事件是否用过去式命名(OrderCreated, PaymentCompleted)?
  • Repository接口是否定义在领域层?

架构检查

  • 领域层是否零外部依赖?
  • 应用层是否只编排用例而不含业务逻辑?
  • 基础设施层是否实现了领域层定义的接口?
  • 是否使用防腐层对接外部系统?
  • DTO和领域对象是否严格分离?

9. 推荐资源

书籍

  • 《领域驱动设计》—— Eric Evans(蓝皮书)
  • 《实现领域驱动设计》—— Vaughn Vernon(红皮书)
  • 《领域驱动设计精粹》—— Vaughn Vernon(入门推荐)
  • 《解构领域驱动设计》—— 张逸

在线资源

开源项目


下一步学习:结合06-微服务架构07-服务治理,将DDD落地到微服务系统中。