领域建模战术:解密 CQRS(读写责任分离)的物理架构与设计
在开展大型企业复杂业务系统开发时,我们的数据库往往面临着截然不同的双重访问压力:一是**高吞吐、强一致的写事务**(如提交订单、扣减库存);二是**多维度、复杂的读查询**(如多表联查、模糊检索、数据分析)。
如果强行将写事务的“充血领域模型”与读查询的“多表联查 DTO”绑定在同一个物理模型和同一个数据库上,会导致领域模型充斥大量的查询噪音(Getter 暴露、贫血字段),且数据库由于索引过多导致写性能崩溃。
为了将**“写状态改变”**与**“读状态展示”**彻底剥离,领域驱动设计(DDD)推荐使用**CQRS(命令查询职责分离)**架构模式。
本文将系统拆解 CQRS 的物理隔离拓扑、双向数据模型、以及读写最终一致性流转机制。
一、 核心范式对比:单体共享模型 vs. CQRS 读写分离
CQRS 模式将系统彻底切割为“命令侧”(写模型)与“查询侧”(读模型):
| 特征维度 | 传统共享模型 (Shared Model) | CQRS 架构模式 (Command Query Segregation) |
|---|---|---|
| 模型表达 | 同一个 JPA Entity 实体既充当写操作校验,又暴露字段给读视图。 | **写模型 (Command Model)**:强内聚充血领域模型。**读模型 (Query Model)**:极其扁平的视图或 DTO。 |
| 数据库设计 | 写与读共享同一个 MySQL 数据库物理表。 | **写数据库 (Write DB)**:规范化设计(3NF),消灭冗余。**读数据库 (Read DB)**:去规范化设计(NoSQL / ES),专为极速检索优化。 |
| 缓存一致性 | 需要复杂的缓存淘汰与回填代码。 | 通过**领域事件(Domain Event)**驱动异步刷新,达成最终一致。 |
| 高并发表现 | 读与写事务互相争抢表锁与行锁,吞吐率受限。 | **读写彻底解耦**。写节点与读节点可独立水平扩展。 |
二、 CQRS 读写物理分库与事件同步流转拓扑
在高级 CQRS 架构中,读写通常不仅是代码类分离,更会进一步演进为**物理数据库的分离**:
[ 客户端业务写命令 (Command) ] [ 客户端数据读请求 (Query) ]
│ │
▼ 1. 发起写操作 ▼ 1. 发起复杂多表联查
[ 写模型服务 (Command Context) ] [ 读模型服务 (Query Context) ]
│ │
├─ 2. 写入物理库: Write DB (MySQL) ├─ 2. 查询物理库: Read DB (Elasticsearch / Redis)
├─ 3. 本地事务内保存并抛出领域事件 │
│ ▼ 3. 返回极扁平数据 DTO
▼ 4. 异步发送事件广播
[ 消息队列 (Kafka / RocketMQ) ]
│
▼ 5. 监听事件并消费更新
[ 读库同步消费者 (ReadSyncConsumer) ] ── 6. 强回填 ──> [ Read DB (同步完成) ]
---三、 代码实战:在应用服务层编排 CQRS 的写与读逻辑
以下展示了如何在应用层,干净地将写命令(Command)与读查询(Query)分流处理,防止读查询逻辑侵入领域层:
1. 写模型侧:应用服务处理命令(Command Side)
package com.company.sales.command.application;
import com.company.sales.command.domain.SalesOrder;
import com.company.sales.command.domain.repository.OrderRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class OrderCommandApplicationService {
private final OrderRepository orderRepository; // 仅面向写模型的仓储
public OrderCommandApplicationService(OrderRepository repository) {
this.orderRepository = repository;
}
/**
* 写用例:付款(严格走充血领域模型不变性校验)
*/
@Transactional
public void payOrder(String orderId) {
SalesOrder order = orderRepository.findById(orderId);
order.pay(); // 执行状态变更并隐式积累领域事件
orderRepository.save(order); // 保存并自动广播事件以刷新读库
}
}
2. 读模型侧:直接调用极简 DAO 拉取查询视图(Query Side)
package com.company.sales.query.application;
import com.company.sales.query.dao.OrderQueryDAO;
import com.company.sales.query.dto.OrderSearchViewDTO;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class OrderQueryApplicationService {
private final OrderQueryDAO orderQueryDao; // 直连读库的只读 DAO
public OrderQueryApplicationService(OrderQueryDAO queryDao) {
this.orderQueryDao = queryDao;
}
/**
* 读用例:多维复合检索(不经过任何领域模型,直接返回扁平 DTO)
*/
public List<OrderSearchViewDTO> searchOrders(String customerId, String status, int page, int size) {
// 直接从 Elasticsearch 或 MySQL 只读从库中拉取完全拼装好的扁平视图数据
return orderQueryDao.search(customerId, status, (page - 1) * size, size);
}
}
---四、 总结
CQRS 模式是微服务架构应对超大流量、复杂数据查询场景的“破局战术方案”。
它通过**在架构层将修改(Command)与查询(Query)物理截断,消除了领域模型充当“万能查询对象”的技术死角,释放了领域层的高表达力;并结合异步事件刷新读库,达成了读写资源的按需水平扩展**。掌握这套 CQRS 物理隔离拓扑与读写代码分流规范,是治理复杂遗留系统查询淤积、主导高并发电商及金融核心重构设计的核心看家本领!
本站所有文章、数据、图片均来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。



暂无评论
还没有人评论过本文,快来发表你的高见吧!