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

相关推荐
怒放吧德德4 小时前
Netty 4.2 入门指南:从概念到第一个程序
java·后端·netty
雨中飘荡的记忆6 小时前
大流量下库存扣减的数据库瓶颈:Redis分片缓存解决方案
java·redis·后端
心之语歌8 小时前
基于注解+拦截器的API动态路由实现方案
java·后端
华仔啊9 小时前
Stream 代码越写越难看?JDFrame 让 Java 逻辑回归优雅
java·后端
ray_liang10 小时前
用六边形架构与整洁架构对比是伪命题?
java·架构
Java编程爱好者10 小时前
字节二面:被问“大模型知识过时了怎么解?”,我答“微调”,面试官当场黑脸:“听说过 RAG 吗?”
架构
Ray Liang11 小时前
用六边形架构与整洁架构对比是伪命题?
java·python·c#·架构设计
Java水解11 小时前
Java 中间件:Dubbo 服务降级(Mock 机制)
java·后端
葫芦的运维日志14 小时前
从手动部署到GitOps只需四步
架构
七月丶14 小时前
别再手动凑 PR 了:这个 AI Skill 会按仓库习惯自动建分支、拆提交、提 PR
人工智能·设计模式·程序员