在复杂业务系统中,传统 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让系统永远干净、稳定、可维护。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐