CQRS + 命令模式 + 事件驱动 + 数据库持久化

在复杂业务系统中,传统 CRUD 写法会导致:

  • 读写逻辑混杂
  • 业务扩张困难
  • 无法做读写分离 / 缓存 / 从库优化
  • 后续流程(库存、积分、通知)强耦合
  • 缺少意图表达,代码难以维护

CQRS(Command Query Responsibility Segregation)命令查询责任分离,是解决上述问题的最佳方案。 配合命令模式 让写操作标准化,配合事件驱动 解耦后续流程,配合DDD 领域层保证业务纯净。

很多人对 CQRS 的误解停留在: 不就是读写分开吗?我 Controller 分两个接口也行啊。

真正的 CQRS 价值在于:

  1. 命令模式 → 让写操作变成可追溯、可验证、可重试、可异步
  2. 查询侧完全自由 → 不污染领域、不影响写模型、可随意做缓存 / 从库 / ES
  3. 事件驱动 → 解耦业务、支持最终一致性、微服务无缝联动
  4. 完美适配 DDD → 写走领域、读走视图,彻底分离

CQRS + 事件驱动写法: 命令 → 领域 → 发布事件 → N 个订阅者异步执行 完全解耦,可扩展,可维护,可测试。

复制代码
【命令端】
Controller → Command → CommandHandler → 领域层 → 发布领域事件

【查询端】
Controller → Query → QueryHandler → 直接读DB/缓存/ES

一、真实业务流程

  1. 接收创建订单命令
  2. 命令校验
  3. 领域规则校验
  4. 保存订单到 MySQL(真正落库)
  5. 发布订单创建事件
  6. 异步扣减库存
  7. 异步增加积分
  8. 异步发送通知

所有步骤完整闭环,包含事务、包含持久化。

二、完整可运行代码(SpringBoot + MyBatis + MySQL)

复制代码
com.order
├── domain                  # 领域层(核心)
│   ├── entity              # 订单实体
│   ├── event               # 领域事件
│   └── service             # 领域服务
├── command                 # 命令(写)
│   ├── CreateOrderCommand.java
│   └── CreateOrderHandler.java
├── query                   # 查询(读)
│   ├── GetOrderQuery.java
│   └── GetOrderQueryHandler.java
├── event                   # 事件处理器
│   ├── StockSubscriber.java
│   ├── PointSubscriber.java
│   └── NotifySubscriber.java
├── application             # 应用层
├── adapter                 # 适配器(六边形)
└── infra                   # DB

1. 领域层(纯净业务)

Order(领域实体)

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java @Data public class Order { private String orderId; private String userId; private String skuCode; private Integer count; private BigDecimal amount; private LocalDateTime createTime; // 领域行为:核心业务规则 public void create() { if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) { throw new RuntimeException("订单金额必须大于0"); } if (count <= 0) { throw new RuntimeException("商品数量非法"); } this.orderId = "ORDER_" + System.currentTimeMillis(); this.createTime = LocalDateTime.now(); } } |

OrderDomainService(领域服务)

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java @Service public class OrderDomainService { public Order createOrder(String userId, String skuCode, Integer count, BigDecimal amount) { Order order = new Order(); order.setUserId(userId); order.setSkuCode(skuCode); order.setCount(count); order.setAmount(amount); order.create(); // 执行业务规则 return order; } } |

2. 仓储层(端口 + 适配器 → 真正落库)

OrderRepositoryPort(出站端口)

|-------------------------------------------------------------------------------------------------------------------------------|
| java public interface OrderRepositoryPort { Order save(Order order); // 保存订单到DB boolean exists(String orderId);// 检查订单是否已存在 } |

OrderRepositoryAdapter(适配器 → MySQL 实现)

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java @Component @RequiredArgsConstructor public class OrderRepositoryAdapter implements OrderRepositoryPort { private final OrderMapper orderMapper; // 真正保存到数据库! @Override public Order save(Order order) { OrderPO po = new OrderPO(); po.setOrderId(order.getOrderId()); po.setUserId(order.getUserId()); po.setSkuCode(order.getSkuCode()); po.setCount(order.getCount()); po.setAmount(order.getAmount()); po.setCreateTime(order.getCreateTime()); // MyBatis 插入数据库 orderMapper.insert(po); return order; } // 判断订单是否存在 @Override public boolean exists(String orderId) { return orderMapper.selectByOrderId(orderId) != null; } } |

3. 命令层(命令模式 + 事务 + 落库)

CreateOrderCommand

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java @Data public class CreateOrderCommand { private String userId; private String skuCode; private Integer count; private BigDecimal amount; public void validate() { if (userId == null || userId.isBlank()) throw new RuntimeException("用户ID不能为空"); if (count <= 0) throw new RuntimeException("数量必须大于0"); if (amount.compareTo(BigDecimal.ZERO) <= 0) throw new RuntimeException("金额非法"); } } |

CreateOrderCommandHandler(核心!带事务、带落库)

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java @Service @RequiredArgsConstructor public class CreateOrderCommandHandler { private final OrderDomainService orderDomainService; private final OrderRepositoryPort orderRepositoryPort; // 仓储端口 private final DomainEventPublisher eventPublisher; // 事务保证:订单创建 + 发布事件 原子性 @Transactional(rollbackFor = Exception.class) public String handle(CreateOrderCommand command) { // 1. 命令校验 command.validate(); //【关键】校验订单是否已存在(防重复提交) boolean exists = orderRepositoryPort.exists(command.getOrderId()); if (exists) { throw new RuntimeException("订单已存在,请勿重复提交"); } // 2. 领域创建 Order order = orderDomainService.createOrder( command.getUserId(), command.getSkuCode(), command.getCount(), command.getAmount() ); // 3. 【真正保存到数据库】 ✅ orderRepositoryPort.save(order); // 4. 发布事件 OrderCreatedEvent event = new OrderCreatedEvent( order.getOrderId(), order.getUserId(), order.getSkuCode(), order.getCount() ); eventPublisher.publish(event); // 5. 命令只返回ID,不返回查询数据 return order.getOrderId(); } } |

4. 事件驱动(完整)

OrderCreatedEvent

|---------------------------------------------------------------------------------------------------------------------------------------------|
| java @Data public class OrderCreatedEvent { private String orderId; private String userId; private String skuCode; private Integer count; } |

DomainEventPublisher

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java @Component @RequiredArgsConstructor public class DomainEventPublisher { private final ApplicationEventPublisher publisher; public void publish(Object event) { publisher.publishEvent(event); } } |

事件订阅者(异步执行)

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java @Component public class OrderEventSubscriber { @Async @EventListener public void handleStock(OrderCreatedEvent event) { System.out.println("【库存服务】扣减库存:" + event.getSkuCode() + " x" + event.getCount()); } @Async @EventListener public void handlePoint(OrderCreatedEvent event) { System.out.println("【积分服务】增加积分:用户" + event.getUserId() + " +100分"); } @Async @EventListener public void handleNotify(OrderCreatedEvent event) { System.out.println("【通知服务】发送订单通知:" + event.getOrderId()); } } |

5. 查询层(读模型,自由优化)

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java @Service @RequiredArgsConstructor public class GetOrderQueryHandler { private final OrderMapper orderMapper; // 查询:从库、缓存、自由组装 @DS("slave") @Cacheable(key = "#query.orderId") public OrderVO handle(GetOrderQuery query) { OrderPO po = orderMapper.selectByOrderId(query.getOrderId()); OrderVO vo = new OrderVO(); vo.setOrderId(po.getOrderId()); vo.setUserId(po.getUserId()); vo.setAmount(po.getAmount()); vo.setCreateTime(po.getCreateTime()); return vo; } } |

6. 基础设施(MyBatis + MySQL)

OrderPO

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java @Data public class OrderPO { private String orderId; private String userId; private String skuCode; private Integer count; private BigDecimal amount; private LocalDateTime createTime; } |

OrderMapper

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java @Mapper public interface OrderMapper { @Insert("INSERT INTO `order`(order_id, user_id, sku_code, count, amount, create_time) " + "VALUES(#{orderId}, #{userId}, #{skuCode}, #{count}, #{amount}, #{createTime})") int insert(OrderPO po); @Select("SELECT * FROM `order` WHERE order_id = #{orderId}") OrderPO selectByOrderId(String orderId); } |

命令时序

查询时序

三、真正具备以下能力 ✅

  1. 订单真正保存到 MySQL 数据库
  2. 命令模式完整:校验、意图、职责分离
  3. 事务保证:创建订单 + 发布事件 原子性
  4. 事件驱动:库存、积分、通知完全解耦
  5. CQRS 读写模型完全分离
  6. DDD 领域纯净,无技术污染
  7. 可扩展、可维护、可测试、可上线

四、CQRS 真正优势终于体现出来了

  1. 写模型只关注业务规则与事务
  2. 读模型怎么快怎么来(从库、缓存、ES)
  3. 业务扩展只需加事件,不用改旧代码
  4. 复杂系统不再混乱
  5. 微服务天然适配
  6. CQRS 不是读写接口分开。
  7. CQRS 是写模型(命令 + 领域 + 事务)与读模型(查询 + 视图)完全分离。

配合命令模式 让写操作标准化、可追踪。

配合事件驱动 让业务彻底解耦、无限扩展。

配合DDD让系统永远干净、稳定、可维护。

相关推荐
金融支付架构实战指南1 小时前
CQRS 命令 vs GOF 命令模式
ddd·命令模式·领域驱动模型
sevenll071 小时前
DocKit agentic MongoDB GUI 客户端 - 用自然语言和你的数据对话
数据库·mongodb·nosql·agent·桌面客户端
团象科技2 小时前
从一线实操案例拆解不同出海团队落地海外VPS运维独立站的路径细节
大数据·数据库·人工智能
小马爱打代码2 小时前
框架 - 组件 - 中间件:生产级参数配置指引
数据库·中间件
asdfg12589632 小时前
一文通俗理解JDBC中的核心概念+案例
java·数据库·oracle·jdbc
点灯小铭2 小时前
基于单片机与DAC0832的双路波形信号发生系统设计
数据库·单片机·mongodb·毕业设计·课程设计·期末大作业
小陈phd2 小时前
Text2SQL智能体学习笔记(二)——NL2SQL落地的隐形基石:元数据库
数据库·笔记·学习
霸道流氓气质2 小时前
阿里云 OSS 从零到实战:概念、配置与 Spring Boot 集成指南
数据库·spring boot·阿里云
茉莉玫瑰花茶2 小时前
综合案例 - AI 智能租房助手 [ 4 ]
数据库·python·ai·langgraph