CQRS
叫命令查询职责分离,事实上就是读写分离的意思。
- 不过这里的读写分离和我们通常所理解的数据库级别的读写分离是两个不同的概念。
CQRS指的读写分离是指在应用程序内部的代码级别的读写分离。
在本文中,我将对此做出详细解释。
CQS思想
CQS
:命令和查询分离:Command and Query Segregation
。其核心思想是在任何一个对象的方法可以划分为两类:
- 查询:获取数据,返回查询数据,但不改变数据状态。
- 命令:改变数据状态,不返回任何数据。
CQRS模式的核心设计理念来自于一条设计原则,即单一职责原则。
- 所谓单一职责原则,指的是一个技术组件只应该负责具体一项职责。
- 而不应该有多个导致该组件发生状态变化的操作。
基于CQS
的思想,任何一个方法都可以拆分为命令和查询两部分,如下:
private int data = 0;
private int update(int value) {
data += value;
return data;
}
上述方法既改变了数据,又返回了数据状态。
如果按照
CQS
的思想,则该方法可以拆成Command
和Query
两部分,如下:
private void update(int value) {
data += value;
}
private int query() {
return data;
}
对于命令侧是否返回数据实际业务诉求中并不一定能够完全统一。
比如:
- 某些业务场景下可能会有返回业务主键的诉求,比如下单操作返回订单号。
基本原则
CQS的主要原则是:
- 一个方法要么是命令,要么是查询,但不能两者兼有。
- 这种分离有助于提高代码的可读性和维护性,因为它明确了方法的用途。
CQRS架构
Command and Query Responsibility Segregation
- 即命令查询职责分离,是一种将命令和查询的责任明确分离的架构模式。
- 这种模式进一步扩展了CQS的思想,适用于更大规模的系统架构。
架构思想:
CQRS将系统的读操作和写操作分离到不同的模型中:
- 命令模型(
Command Model
):
- 处理数据的写操作(创建、更新、删除)。
- 查询模型(
Query Model
):
- 处理数据的读操作(查询)。
这种分离可以通过不同的数据模型、数据库甚至服务来实现,从而优化读写性能和可伸缩性。
CQRS
模式的应用非常简单,如下图所示:
假设服务为
UserService
,在非CQRS
模式下同时包含了查询和更新服务接口。
public class UserService {
// 根据id查询用户
UserId getUserId(int userId);
// 更新用户
void updateUser(User user);
}
应用
CQRS
模式之后的UserService
被拆分成了两个接口,分别承担查询和写职责。
/**
命令服务
*/
public class UserCommandService {
void updateUser(UserCommand command);
}
/**
查询服务
*/
public class UserQueryService{
User getUserById(int userId);
}
最终一致性
采用
CQRS
后,查询和命令两侧通常会采用独立的数据模型。
- 采用CQRS模式并没有强制要求必须要进行数据模型的分离。
在这种架构模式下,命令侧的数据变化后及时同步(事件、消息队列)到查询侧,两侧数据并非实时。
- 在一定的延时后两侧数据最终达成一致。
最后总结
CQRS
的使用者可以根据实际情况,将读写分离开单独部署,然后引入领域事件,使用消息队列做通信。
- 但是这些都是基于不同业务场景的架构选择,而非
CQRS
本身的要求。- 实际上
CQRS
只是一种非常简单的模式而已,并没有和事件、消息队列这些有强关联。读写分离部署+消息通信:
- 会带来额外的系统复杂性和更高的运维成本。
《重构:改善既有代码的设计》的作者也提醒要小心使用
CQRS
,不推荐将CQRS
复杂化处理。
参考资料: