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 基础的同学深入学习。初学者可以先跳过,掌握基础模式后再回来学习。

相关推荐
俞凡25 分钟前
[大厂实践] 告别微服务
架构
JeffDingAI25 分钟前
【CANN训练营】在CANN8.5上体验Hello World开启Ascend C学习
c语言·开发语言·人工智能·学习
MyBFuture27 分钟前
C#表格与定时器实战技巧
开发语言·windows·c#·visual studio
数据与后端架构提升之路28 分钟前
系统架构设计师(软考高级)设计模式备考指南
设计模式·系统架构
YGGP34 分钟前
【Golang】LeetCode 2. 两数相加
开发语言·leetcode·golang
liuhaikang34 分钟前
鸿蒙高性能动画库——lottie-turbo
java·开发语言·nginx
Hcoco_me34 分钟前
大模型面试题41:RoPE改进的核心目标与常见方法
开发语言·人工智能·深度学习·自然语言处理·transformer·word2vec
Yy_Yyyyy_zz35 分钟前
2025 技术年终总结|近七年 Golang 工程实践、AI 应用落地与技术创作回顾
开发语言·golang·ai编程
国科安芯36 分钟前
核工业机器人电机驱动器CANFD隔离芯片国产替代方案
单片机·嵌入式硬件·性能优化·架构·机器人·安全性测试
面对疾风叭!哈撒给38 分钟前
Liunx之Docker 安装启动 influxdb2
java·spring cloud·docker