CQRS + 命令模式 + 事件驱动 + 数据库持久化
在复杂业务系统中传统 CRUD 写法会导致读写逻辑混杂业务扩张困难无法做读写分离 / 缓存 / 从库优化后续流程库存、积分、通知强耦合缺少意图表达代码难以维护CQRSCommand Query Responsibility Segregation命令查询责任分离是解决上述问题的最佳方案。 配合命令模式让写操作标准化配合事件驱动解耦后续流程配合DDD 领域层保证业务纯净。很多人对 CQRS 的误解停留在不就是读写分开吗我 Controller 分两个接口也行啊。真正的 CQRS 价值在于命令模式→ 让写操作变成可追溯、可验证、可重试、可异步查询侧完全自由→ 不污染领域、不影响写模型、可随意做缓存 / 从库 / ES事件驱动→ 解耦业务、支持最终一致性、微服务无缝联动完美适配 DDD→ 写走领域、读走视图彻底分离CQRS 事件驱动写法命令 → 领域 → 发布事件 → N 个订阅者异步执行完全解耦可扩展可维护可测试。【命令端】 Controller → Command → CommandHandler → 领域层 → 发布领域事件 【查询端】 Controller → Query → QueryHandler → 直接读DB/缓存/ES一、真实业务流程接收创建订单命令命令校验领域规则校验保存订单到 MySQL真正落库发布订单创建事件异步扣减库存异步增加积分异步发送通知所有步骤完整闭环包含事务、包含持久化。二、完整可运行代码SpringBoot MyBatis MySQLcom.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 # DB1. 领域层纯净业务Order领域实体javaDatapublic 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领域服务javaServicepublic 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出站端口javapublic interface OrderRepositoryPort {Order save(Order order); // 保存订单到DBboolean exists(String orderId);// 检查订单是否已存在}OrderRepositoryAdapter适配器 → MySQL 实现javaComponentRequiredArgsConstructorpublic class OrderRepositoryAdapter implements OrderRepositoryPort {private final OrderMapper orderMapper;// 真正保存到数据库Overridepublic 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. 命令层命令模式 事务 落库CreateOrderCommandjavaDatapublic 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核心带事务、带落库javaServiceRequiredArgsConstructorpublic 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. 事件驱动完整OrderCreatedEventjavaDatapublic class OrderCreatedEvent {private String orderId;private String userId;private String skuCode;private Integer count;}DomainEventPublisherjavaComponentRequiredArgsConstructorpublic class DomainEventPublisher {private final ApplicationEventPublisher publisher;public void publish(Object event) {publisher.publishEvent(event);}}事件订阅者异步执行javaComponentpublic class OrderEventSubscriber {AsyncEventListenerpublic void handleStock(OrderCreatedEvent event) {System.out.println(【库存服务】扣减库存 event.getSkuCode() x event.getCount());}AsyncEventListenerpublic void handlePoint(OrderCreatedEvent event) {System.out.println(【积分服务】增加积分用户 event.getUserId() 100分);}AsyncEventListenerpublic void handleNotify(OrderCreatedEvent event) {System.out.println(【通知服务】发送订单通知 event.getOrderId());}}5. 查询层读模型自由优化javaServiceRequiredArgsConstructorpublic 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 MySQLOrderPOjavaDatapublic class OrderPO {private String orderId;private String userId;private String skuCode;private Integer count;private BigDecimal amount;private LocalDateTime createTime;}OrderMapperjavaMapperpublic 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让系统永远干净、稳定、可维护。