当前位置: 首页 > 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://blur.apjjykv.cn
http://aeroneurosis.apjjykv.cn
http://allecret.apjjykv.cn
http://agitative.apjjykv.cn
http://bucentaur.apjjykv.cn
http://actuality.apjjykv.cn
http://chapfallen.apjjykv.cn
http://beuthen.apjjykv.cn
http://charisma.apjjykv.cn
http://childing.apjjykv.cn
http://berliozian.apjjykv.cn
http://callable.apjjykv.cn
http://chillily.apjjykv.cn
http://cherubim.apjjykv.cn
http://actualize.apjjykv.cn
http://calipee.apjjykv.cn
http://anovular.apjjykv.cn
http://canonry.apjjykv.cn
http://briefly.apjjykv.cn
http://autoff.apjjykv.cn
http://barbarously.apjjykv.cn
http://auriculate.apjjykv.cn
http://ark.apjjykv.cn
http://archicerebrum.apjjykv.cn
http://abloom.apjjykv.cn
http://accommodation.apjjykv.cn
http://abaft.apjjykv.cn
http://aposelene.apjjykv.cn
http://cg.apjjykv.cn
http://attributively.apjjykv.cn
http://www.dtcms.com/a/262204.html

相关文章:

  • 大语言模型训练阶段
  • Tang Prime 20K板OV2640例程
  • 30套精品论文答辩开题报告PPT模版
  • (八)聚类
  • node js入门,包含express,npm管理
  • 【3.3】Pod详解——容器探针部署第一个pod
  • Python 可迭代的对象、迭代器 和生成器(另一个示例:等差数列生成器)
  • 设计模式 | 组合模式
  • Excel之证件照换底色3
  • Ubuntu无法显示IP地址
  • 【算法设计与分析】(二)什么是递归,以及分治法的基本思想
  • Mac homebrew 安装教程
  • 左神算法之Zigzag方式打印矩阵
  • Redis分布式锁核心原理源码
  • SpringCloud系列(40)--SpringCloud Gateway的Filter的简介及使用
  • 和ai对话:讨论一个简单的理财方案
  • Halcon 常用算子总结
  • 基于 SpringBoot 实现一个 JAVA 代理 HTTP / WS
  • MyBatis实战指南(八)MyBatis日志
  • 热传导方程能量分析与边界条件研究
  • HarmonyOS实战:自定义表情键盘
  • < OS 有关 4 台 Ubuntu VPSs 正在被攻击:nginx 之三> 记录、分析、防护的过程 配置 ufw Fail2Ban 保护网络上的主机
  • 个人计算机系统安全、网络安全、数字加密与认证
  • Github 2025-06-29php开源项目日报 Top10
  • RK3588集群服务器性能优化案例:电网巡检集群、云手机集群、工业质检集群
  • Mac电脑手动安装原版Stable Diffusion,开启本地API调用生成图片
  • 基于云的平板挠度模拟:动画与建模-AI云计算数值分析和代码验证
  • Linux中部署Nacos保姆级教程
  • Wpf布局之WrapPanel面板!
  • Java面试宝典:基础二