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 的聚合根保证一致性
- 读:专注展示,可以联表、加冗余字段、挂缓存,怎么快怎么来
- 独立扩展:读的压力大就加读从库、加缓存,不影响写模型
四、和 DDD 的关系(为什么总一起出现)
CQRS 和 DDD 是"天然搭档":
- DDD 的聚合适合"写":聚合根承载业务规则、保证一致性,但用它做复杂查询很别扭(聚合是"一个整体",不适合联表、投影)
- CQRS 把"读"从聚合里解放出来:读不再走聚合,直接用 DTO + 读接口,怎么展示怎么来
一句话:DDD 管"写"(聚合承载规则),CQRS 把"读"拆出去(读模型优化展示)。两者结合,写用充血聚合、读用轻量 DTO,各得其所。
五、什么时候用(别过度设计)
CQRS 不是免费的,它增加了复杂度(两套模型、可能的数据同步)。适用场景:
- 读写模型差异大:读要展示的字段和写要处理的字段差很多
- 读多写少、读性能要求高:读要独立优化(缓存、从库、物化视图)
- 复杂业务 + DDD:配合充血聚合
不适合:简单 CRUD——读写模型一样,硬拆 CQRS 纯属增加负担。
小结
- CQRS 把"读"和"写"拆成两个模型,解决读写需求冲突
- 命令走写模型(聚合/业务规则),查询走读模型(DTO/展示优化)
- 和 DDD 是搭档:DDD 管写,CQRS 拆读
- 读写模型差异大、读性能要求高时才用,简单 CRUD 别硬拆
CQRS 常和"事件溯源"一起出现——一个管"读写分离",一个管"怎么记录状态变化",接着看《事件溯源 Event Sourcing》。
