当前位置: 首页 > news >正文

领域驱动设计(DDD)【26】之CQRS模式初探

文章目录

  • 一 CQRS初探:理解基本概念
    • 1.1 什么是CQRS?
    • 1.2 CQRS与CRUD的对比
    • 1.3 为什么需要CQRS?
  • 二 CQRS深入:架构细节
    • 2.1 基本架构组成
    • 2.2 数据流示意图
  • 三 CQRS实战:电商订单案例
    • 3.1 传统CRUD方式的订单处理
    • 3.2 CQRS方式的订单系统
      • 3.2.1 命令端实现
      • 3.2.2 查询端实现
      • 3.2.3 读模型DTO
    • 3.3 数据同步实现
  • 四 CQRS进阶:高级话题
    • 4.1 最终一致性处理
    • 4.2 CQRS的适用场景
  • 五 CQRS实践建议
    • 5.1 实施步骤
    • 5.2 常见陷阱与解决方案
    • 5.3 性能考量
  • 六 总结

一 CQRS初探:理解基本概念

1.1 什么是CQRS?

  • Greg Young把增、删、改功能称为 Command(命令),把查询称为 Query,这两种功能的职责不同,应该采用不同的方式来处理,因此叫做“命令查询职责分离”(Command Query Responsibility Segregation ),简称 CQRS。
  • CQRS(Command Query Responsibility Segregation,命令查询职责分离)是一种架构模式,它的核心思想是将系统的**写操作(命令)读操作(查询)**分离,使用不同的模型来处理。
  • 想象一下图书馆的管理方式:借书和还书(写操作)由前台工作人员处理,而查询书籍位置或可用性(读操作)则由咨询台负责。这种职责分离提高了效率,这正是CQRS的核心思想。

1.2 CQRS与CRUD的对比

  • 传统CRUD(Create, Read, Update, Delete)架构中,读写操作使用同一个数据模型:
客户端
服务层
同一数据模型
数据库

而CQRS将读写分离:

客户端
命令模型
查询模型
写数据库
读数据库

1.3 为什么需要CQRS?

  • 读写负载不均衡:大多数系统读操作远多于写操作
  • 性能优化:可以为读写分别优化
  • 简化复杂性:避免单一模型同时满足读写需求带来的妥协
  • 扩展性:读写可以独立扩展

二 CQRS深入:架构细节

2.1 基本架构组成

一个典型的CQRS系统包含以下组件:

  1. 命令端(Command Side)

    • 处理创建、更新、删除等操作
    • 通过命令(Command)触发
    • 产生领域事件(Domain Events)
  2. 查询端(Query Side)

    • 处理数据查询
    • 针对展示需求优化
    • 通常是非规范化的数据视图
  3. 同步机制

    • 保持命令端和查询端数据一致性
    • 可通过事件溯源(Event Sourcing)或定期同步实现

查询端
同步机制
命令端
客户端
定期同步
查询处理器
DTO投影
事件处理器
读数据库
命令处理器
聚合根
领域事件
写数据库
命令模型
发送命令
查询模型
发起查询

2.2 数据流示意图

客户端 命令模型 写模型存储 读模型存储 查询模型 发送命令(如"下订单") 更新写模型 确认更新 返回结果 异步更新读模型 查询订单状态 获取数据 返回数据 返回查询结果 客户端 命令模型 写模型存储 读模型存储 查询模型

三 CQRS实战:电商订单案例

通过一个电商订单系统来具体理解CQRS的实现。

3.1 传统CRUD方式的订单处理

  • 在传统方式中,我们可能会有一个Order类同时处理读写:
public class Order {private Long id;private String customerId;private List<OrderItem> items;private OrderStatus status;private Date createdDate;// 读方法public BigDecimal calculateTotal() {return items.stream().map(i -> i.getPrice().multiply(i.getQuantity())).reduce(BigDecimal.ZERO, BigDecimal::add);}// 写方法public void addItem(Product product, int quantity) {// 验证逻辑...items.add(new OrderItem(product, quantity));}
}

这种方式随着业务复杂化会变得难以维护。

3.2 CQRS方式的订单系统

3.2.1 命令端实现

  • OrderCommandService.java (处理写操作)
public class OrderCommandService {private final OrderRepository orderRepository;private final EventPublisher eventPublisher;@Transactionalpublic void createOrder(CreateOrderCommand command) {// 验证业务规则if (command.getItems().isEmpty()) {throw new IllegalArgumentException("订单不能为空");}// 创建聚合根Order order = new Order(command.getOrderId(),command.getCustomerId(),command.getItems());// 保存orderRepository.save(order);// 发布事件eventPublisher.publish(new OrderCreatedEvent(order.getId(),order.getCustomerId(),order.getItems(),order.getStatus()));}public void cancelOrder(CancelOrderCommand command) {// 类似实现...}
}

3.2.2 查询端实现

  • OrderQueryService.java (处理读操作)
public class OrderQueryService {private final OrderReadRepository readRepository;public OrderDTO getOrderById(String orderId) {return readRepository.findById(orderId).orElseThrow(() -> new OrderNotFoundException(orderId));}public List<OrderSummaryDTO> getOrdersByCustomer(String customerId) {return readRepository.findByCustomerId(customerId);}
}

3.2.3 读模型DTO

public class OrderDTO {private String orderId;private String customerId;private List<OrderItemDTO> items;private String status;private BigDecimal totalAmount;private Date createdDate;// 仅包含简单getter/setter
}public class OrderSummaryDTO {private String orderId;private String status;private BigDecimal totalAmount;private Date createdDate;private int itemCount;// 仅包含简单getter/setter
}

3.3 数据同步实现

  • 使用领域事件同步读写模型:
@Component
public class OrderEventListener {private final OrderReadRepository readRepository;@EventListenerpublic void handleOrderCreated(OrderCreatedEvent event) {OrderDTO orderDTO = new OrderDTO();orderDTO.setOrderId(event.getOrderId());// 其他字段映射...readRepository.save(orderDTO);}@EventListenerpublic void handleOrderCancelled(OrderCancelledEvent event) {OrderDTO order = readRepository.findById(event.getOrderId()).get();order.setStatus("CANCELLED");readRepository.save(order);}
}

四 CQRS进阶:高级话题

4.1 最终一致性处理

由于读写分离,CQRS系统通常是最终一致性的。处理方式包括:

  1. 事件驱动的更新:通过领域事件触发读模型更新
  2. 补偿事务:当更新失败时执行补偿
  3. 版本控制:检测和处理并发冲突

4.2 CQRS的适用场景

CQRS并非银弹,适合以下场景:

  • 读写负载差异大的系统
  • 复杂领域模型,读写需求差异大
  • 需要高性能查询的系统
  • 需要审计日志或历史追踪的系统

不适合的场景:

  • 简单CRUD应用
  • 对实时一致性要求极高的系统
  • 开发资源有限的小型项目

五 CQRS实践建议

5.1 实施步骤

  1. 从简单开始:可以先在单个有界上下文(Bounded Context)中尝试
  2. 明确边界:清晰划分命令和查询的边界
  3. 渐进式演进:从分离模型开始,逐步引入事件溯源等高级特性

5.2 常见陷阱与解决方案

陷阱解决方案
过度设计从实际需求出发,只在必要时引入CQRS
数据不一致实现健壮的事件处理机制,监控延迟
事件风暴使用事件溯源时合理设计事件粒度
开发复杂性提供充分的文档和示例代码

5.3 性能考量

  1. 读模型优化

    • 使用非规范化设计
    • 针对查询场景定制数据结构
    • 考虑使用专门的查询数据库(如Elasticsearch)
  2. 写模型优化

    • 使用聚合根保证一致性边界
    • 合理设计命令处理流程
    • 考虑批处理和异步处理

六 总结

CQRS是一种强大的架构模式,通过分离读写职责可以带来诸多好处:

  1. 领域模型更清晰:命令端专注于业务规则,查询端专注于展示需求
  2. 性能更优:可以针对读写分别优化和扩展
  3. 灵活性更高:可以轻松添加新的查询而不影响命令处理

然而CQRS也带来了额外的复杂性,应该根据项目实际需求谨慎采用。对于初学者,建议从一个小的、非核心的功能开始实践,逐步积累经验。

  • 记住,架构模式是工具而非目标,选择适合你项目的最简单有效的方案才是明智之举。
http://www.xdnf.cn/news/1074295.html

相关文章:

  • AlpineLinux安装部署elasticsearch
  • Kafka4.0初体验
  • Python爬虫:Requests与Beautiful Soup库详解
  • 重写(Override)与重载(Overload)深度解析
  • 【C++】C++中的友元函数和友元类
  • 71. 简化路径 —day94
  • Bugku——WEB篇(持续更新ing)
  • documents4j导出pdf
  • Ubuntu服务器(公网)- Ubuntu客户端(内网)的FRP内网穿透配置教程
  • 数据结构 哈希表、栈的应用与链式队列 6.29 (尾)
  • 现代 JavaScript (ES6+) 入门到实战(八):总结与展望 - 成为一名现代前端开发者
  • day46/60
  • H3C-路由器交换机-中继
  • 计算机组成原理与体系结构-实验一 进位加法器(Proteus 8.15)
  • 5 c++核心——文件操作
  • MySQL技巧
  • 如何优化RK3588集群的性能?支持12个RK3588云手机阵列
  • C++ 格式化输入输出
  • Java中对JSON的操作
  • 模拟多维物理过程与基于云的数值分析-AI云计算数值分析和代码验证
  • SpringCloud系列(41)--SpringCloud Config分布式配置中心简介
  • TCP/UDP协议深度解析(三):TCP流量控制的魔法—滑动窗口、拥塞控制与ACK的智慧
  • Java笔记
  • 野生动物检测数据集介绍-5,138张图片 野生动物保护监测 智能狩猎相机系统 生态研究与调查
  • 贝叶斯自学笔记——基础工具篇(一)
  • Python爬虫实战:研究Bleach库相关技术
  • 【linux】权限深入解析
  • [分布式并行] 流水线并行 PP(NaivePP/GPipe/F-then-B/PipeDream/1F1B)
  • #华为鲲鹏#华为计算#鲲鹏开发者计划2025#
  • 概率论符号和公式整理