16-现代架构扩展-DDD领域驱动设计


title: "16 现代架构扩展(选读:CQRS/ES/Saga)"


学习目标

  • 理解 CQRS(Command Query Responsibility Segregation)的核心思想:读写分离
  • 理解 Event Sourcing(事件溯源)的核心思想:用事件流表示状态
  • 理解 Saga/Process Manager 的核心思想:跨聚合/跨 BC 的流程编排
  • 重点:掌握"何时用/何时不用",避免过度设计

核心概念

CQRS(命令查询职责分离)

什么是 CQRS

CQRS 把"写模型(Command Model)"和"读模型(Query Model)"分离:

  • 写模型:领域模型(聚合、实体、值对象),用于处理命令
  • 读模型:优化的查询视图(DTO、投影),用于处理查询
何时用 CQRS

适合场景

  • 读写比例差异大(例如读多写少)
  • 读写性能需求不同(例如写需要强一致性,读可以最终一致性)
  • 查询复杂度高(例如需要跨多个聚合的复杂报表)

不适合场景

  • 简单的 CRUD 应用
  • 读写比例接近
  • 团队规模小,维护成本敏感
伪代码示例
text 复制代码
// 写模型(Command Side)
class OrderApplicationService {
  createOrder(cmd):
    order = new Order(...)
    repository.save(order)
    publish(OrderCreated)
}

// 读模型(Query Side)
class OrderQueryService {
  getOrderList(filters):
    return readModelRepository.findByFilters(filters)  // 优化的查询视图
}
Mermaid 图:CQRS 读写分离(概念)

project
User/UI
Commands
Queries
Command Side

Domain Model + Repository
Events
Query Side

Read Model

Event Sourcing(事件溯源)

什么是 Event Sourcing

Event Sourcing 用事件流来表示聚合的状态:

  • 不直接保存聚合的当前状态
  • 保存所有导致状态变化的事件
  • 通过重放事件来重建聚合状态
何时用 Event Sourcing

适合场景

  • 需要完整的审计日志(所有状态变化都有记录)
  • 需要时间旅行(可以查询任意时间点的状态)
  • 需要事件重放(例如调试、数据分析)

不适合场景

  • 简单的 CRUD 应用
  • 事件流很长,重建状态成本高
  • 团队对 Event Sourcing 不熟悉
伪代码示例

说明:以下为 概念性伪代码 。本书不提供 Event Sourcing 的参考实现(例如 EventStore、以及 Order.apply(...) 的重放逻辑)。

text 复制代码
// 保存事件(而不是保存状态)
class EventSourcedOrderRepository {
  save(order):
    events = order.getDomainEvents()
    for event in events:
      eventStore.append(order.orderNumber, event)
}

// 重建聚合(通过重放事件)
class EventSourcedOrderRepository {
  findById(orderNumber):
    events = eventStore.getEvents(orderNumber)
    order = new Order()
    for event in events:
      order.apply(event)  // 重放事件
    return order
}
Mermaid 图:Event Sourcing(概念)

replay
project
Command
Aggregate
Domain Events
Event Store
Aggregate Rehydration
Read Model/Projection

Saga / Process Manager(流程编排)

什么是 Saga

Saga 用于管理跨聚合/跨 BC 的长时间运行流程

  • 一个 Saga 协调多个聚合/BC 完成一个业务目标
  • 每个步骤都是独立的命令,可以独立回滚
  • 通过补偿事务(Compensating Transaction)处理失败
何时用 Saga

适合场景

  • 跨多个聚合/BC 的复杂流程(例如"下单 → 支付 → 发货 → 通知")
  • 需要最终一致性(不能在一个事务里完成)
  • 需要处理部分失败(例如支付成功但发货失败,需要退款)

不适合场景

  • 可以在一个事务里完成的简单流程
  • 不需要跨聚合/BC 的流程
  • 团队对分布式事务不熟悉
伪代码示例
text 复制代码
// Saga 编排器
class OrderFulfillmentSaga {
  start(orderId):
    // 步骤1:创建订单
    orderCreated = orderService.createOrder(orderId)
    if orderCreated.failed:
      return saga.fail()
    
    // 步骤2:支付订单
    paymentResult = paymentService.pay(orderId)
    if paymentResult.failed:
      orderService.cancel(orderId)  // 补偿
      return saga.fail()
    
    // 步骤3:发货
    shipmentResult = logisticsService.ship(orderId)
    if shipmentResult.failed:
      paymentService.refund(orderId)  // 补偿
      orderService.cancel(orderId)     // 补偿
      return saga.fail()
    
    return saga.complete()
}
Mermaid 图:Saga/Process Manager(概念)

Logistics Context Payment Context Order Context Saga / Process Manager Logistics Context Payment Context Order Context Saga / Process Manager alt [ship failed] alt [payment failed] [payment succeeded] CreateOrder 1 OrderCreated 2 Pay(orderId) 3 PaymentSucceeded/Failed 4 CancelOrder (compensate) 5 Ship(orderId) 6 Shipped/Failed 7 Refund(orderId) (compensate) 8 CancelOrder (compensate) 9

课堂讲授:何时不用这些模式

过度设计的信号

  1. 为了用而用:不是因为业务需求,而是因为"看起来高级"
  2. 复杂度上升但收益不明显:引入模式后,代码复杂度上升,但业务价值没有提升
  3. 团队不理解:团队对模式不熟悉,导致维护困难

渐进式采用

课堂建议:

  • 先做简单方案:能用简单方案解决,就不要用复杂模式
  • 遇到痛点再引入:例如读写性能差异大时再考虑 CQRS
  • 小范围试点:先在一个 BC 或一个用例中试点,验证效果后再推广

常见误区

  • 误区 1:所有项目都用 CQRS/ES:这些模式有成本,应该根据需求选择
  • 误区 2:CQRS = 两个数据库:CQRS 是逻辑分离,不一定要物理分离
  • 误区 3:Event Sourcing = 领域事件:Event Sourcing 是持久化策略,领域事件是通信机制
  • 误区 4:Saga = 分布式事务:Saga 是最终一致性,不是强一致性

课堂练习

  1. 给出一个你认为"适合用 CQRS"的场景,并说明理由。
  2. 给出一个你认为"不适合用 Event Sourcing"的场景,并说明理由。
  3. 设计一个 Saga 流程(文字描述即可):假设"订单 → 支付 → 发货 → 通知"流程,请写出每个步骤的补偿动作。

自测题

  1. CQRS 的核心思想是什么?何时用/何时不用?
  2. Event Sourcing 的核心思想是什么?何时用/何时不用?
  3. Saga 的核心思想是什么?何时用/何时不用?

延伸阅读


选读说明:本章内容属于高级主题,适合有一定 DDD 基础的同学深入学习。初学者可以先跳过,掌握基础模式后再回来学习。

相关推荐
Rabbit_QL1 天前
【水印添加工具】从零设计一个工程级 Python 图片水印工具:WaterMask 架构与实现
开发语言·python
张柏慈1 天前
Java性能优化:实战技巧与案例解析
java
天“码”行空1 天前
简化Lambda——方法引用
java·开发语言
z20348315201 天前
C++对象布局
开发语言·c++
Beginner x_u1 天前
如何解释JavaScript 中 this 的值?
开发语言·前端·javascript·this 指针
数据与后端架构提升之路1 天前
Seata 全景拆解:AT、TCC、Saga 该怎么选?告别“一把梭”的架构误区
分布式·架构
带刺的坐椅1 天前
MCP 进化:让静态 Tool 进化为具备“上下文感知”的远程 Skills
java·ai·llm·agent·solon·mcp·tool-call·skills
java1234_小锋1 天前
Java线程之间是如何通信的?
java·开发语言
檐下翻书1731 天前
在线绘制水流量示意图
论文阅读·架构·毕业设计·流程图·论文笔记
张张努力变强1 天前
C++ Date日期类的设计与实现全解析
java·开发语言·c++·算法