Skip to content

CQRS(Command Query Responsibility Segregation,命令查询职责分离)是《DDD 领域驱动设计》的常见搭档,核心思想一句话:把"读"和"写"拆成两个模型。这篇讲清它解决什么问题、怎么拆、以及它为什么常和 DDD、事件溯源一起出现。

一、解决什么问题:读和写"同床异梦"

传统做法里,读和写用同一个模型:

java
public class OrderService {
    // 写:创建订单,带一堆业务规则(校验、算价、状态机)
    public void placeOrder(PlaceOrderRequest req) { ... }

    // 读:返回订单详情,要展示友好的字段(用户信息、商品名、优惠明细)
    public OrderDetail getOrder(Long id) { ... }

    // 读:列表查询,又只要几个字段
    public List<OrderSummary> listOrders(Query q) { ... }
}

问题在于,读和写的需求天生不同

维度写(命令)读(查询)
关注业务规则、一致性展示友好、响应快
字段需要完整、精确只要几个展示字段
频率多(读多写少)
优化事务、约束缓存、索引、联表

硬用一个模型同时满足读写,结果两边都别扭:写的时候要迁就读的字段,读的时候要迁就写的结构。CQRS 的思路:干脆拆成两个模型,各管各的

二、CQRS 怎么拆

CQRS 把操作分成两类,分别走不同的模型:

java
// ① 命令(Command):改变状态,走"写模型"
public class PlaceOrderCommand {
    private Long customerId;
    private List<OrderItemCommand> items;
    // 只包含"下单需要的信息",不含读的字段
}

// 写模型:聚合根,承载业务规则(DDD 的充血模型)
public class OrderAggregate {
    private OrderStatus status;
    public void place(PlaceOrderCommand cmd) {
        // 校验、算价、状态流转…… 业务规则全在这里
    }
}

// ② 查询(Query):读取状态,走"读模型"
public class OrderQueryService {
    public OrderDetailView getOrder(Long id) {
        // 读模型:返回展示友好的 DTO,可能联表、聚合、冗余字段
        return orderReadMapper.selectDetail(id);
    }
}

核心规则

  • 命令只走写模型:命令(下单、支付、取消)通过聚合根处理业务规则,改数据库
  • 查询只走读模型:查询(详情、列表)走专门的读接口,返回为展示优化的 DTO
  • 两个模型可以指向同一张表,也可以分库分表——初期同库同表,读模型只读不写

三、CQRS 的分层结构

命令(Command)→ 写模型(聚合根/领域逻辑)→ 写库
查询(Query)  → 读模型(DTO/视图)        → 读库(可为从库/缓存)

读写分离后:

  • :专注业务规则,用 DDD 的聚合根保证一致性
  • :专注展示,可以联表、加冗余字段、挂缓存,怎么快怎么来
  • 独立扩展:读的压力大就加读从库、加缓存,不影响写模型

CQRS:命令走写模型,查询走读模型,读写分离

四、和 DDD 的关系(为什么总一起出现)

CQRS 和 DDD 是"天然搭档":

  • DDD 的聚合适合"写":聚合根承载业务规则、保证一致性,但用它做复杂查询很别扭(聚合是"一个整体",不适合联表、投影)
  • CQRS 把"读"从聚合里解放出来:读不再走聚合,直接用 DTO + 读接口,怎么展示怎么来

一句话:DDD 管"写"(聚合承载规则),CQRS 把"读"拆出去(读模型优化展示)。两者结合,写用充血聚合、读用轻量 DTO,各得其所。

五、什么时候用(别过度设计)

CQRS 不是免费的,它增加了复杂度(两套模型、可能的数据同步)。适用场景:

  • 读写模型差异大:读要展示的字段和写要处理的字段差很多
  • 读多写少、读性能要求高:读要独立优化(缓存、从库、物化视图)
  • 复杂业务 + DDD:配合充血聚合

不适合:简单 CRUD——读写模型一样,硬拆 CQRS 纯属增加负担。

小结

  • CQRS 把"读"和"写"拆成两个模型,解决读写需求冲突
  • 命令走写模型(聚合/业务规则),查询走读模型(DTO/展示优化)
  • 和 DDD 是搭档:DDD 管写,CQRS 拆读
  • 读写模型差异大、读性能要求高时才用,简单 CRUD 别硬拆

CQRS 常和"事件溯源"一起出现——一个管"读写分离",一个管"怎么记录状态变化",接着看《事件溯源 Event Sourcing》。