在复杂业务系统中,传统 CRUD 写法会导致:
- 读写逻辑混杂
- 业务扩张困难
- 无法做读写分离 / 缓存 / 从库优化
- 后续流程(库存、积分、通知)强耦合
- 缺少意图表达,代码难以维护
CQRS(Command Query Responsibility Segregation)命令查询责任分离,是解决上述问题的最佳方案。 配合命令模式让写操作标准化,配合事件驱动解耦后续流程,配合DDD 领域层保证业务纯净。
很多人对 CQRS 的误解停留在: 不就是读写分开吗?我 Controller 分两个接口也行啊。
真正的 CQRS 价值在于:
- 命令模式 → 让写操作变成可追溯、可验证、可重试、可异步
- 查询侧完全自由 → 不污染领域、不影响写模型、可随意做缓存 / 从库 / ES
- 事件驱动 → 解耦业务、支持最终一致性、微服务无缝联动
- 完美适配 DDD → 写走领域、读走视图,彻底分离
CQRS + 事件驱动写法: 命令 → 领域 → 发布事件 → N 个订阅者异步执行 完全解耦,可扩展,可维护,可测试。
【命令端】
Controller → Command → CommandHandler → 领域层 → 发布领域事件
【查询端】
Controller → Query → QueryHandler → 直接读DB/缓存/ES
一、真实业务流程
- 接收创建订单命令
- 命令校验
- 领域规则校验
- 保存订单到 MySQL(真正落库)
- 发布订单创建事件
- 异步扣减库存
- 异步增加积分
- 异步发送通知
所有步骤完整闭环,包含事务、包含持久化。
二、完整可运行代码(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);
} |
命令时序

查询时序

三、真正具备以下能力 ✅
- 订单真正保存到 MySQL 数据库
- 命令模式完整:校验、意图、职责分离
- 事务保证:创建订单 + 发布事件 原子性
- 事件驱动:库存、积分、通知完全解耦
- CQRS 读写模型完全分离
- DDD 领域纯净,无技术污染
- 可扩展、可维护、可测试、可上线
四、CQRS 真正优势终于体现出来了
- 写模型只关注业务规则与事务
- 读模型怎么快怎么来(从库、缓存、ES)
- 业务扩展只需加事件,不用改旧代码
- 复杂系统不再混乱
- 微服务天然适配
- CQRS 不是读写接口分开。
- CQRS 是写模型(命令 + 领域 + 事务)与读模型(查询 + 视图)完全分离。
配合命令模式让写操作标准化、可追踪。
配合事件驱动让业务彻底解耦、无限扩展。
配合DDD让系统永远干净、稳定、可维护。
所有评论(0)