架构设计之解析CQRS架构模式!

CQS思想

  • CQS:命令和查询分离:Command and Query Segregation
  • 其核心思想是在任何一个对象的方法可以划分为两类:
  • 查询:获取数据,返回查询数据,但不改变数据状态。
  • 命令:改变数据状态,不返回任何数据。
  • CQRS模式的核心设计理念来自于一条设计原则,即单一职责原则
  • 所谓单一职责原则,指的是一个技术组件只应该负责具体一项职责。
  • 而不应该有多个导致该组件发生状态变化的操作。
  • 基于CQS的思想,任何一个方法都可以拆分为命令和查询两部分,如下:
private int data = 0;
private int update(int value) {
    data += value;
    return data;
}
  • 上述方法既改变了数据,又返回了数据状态。如果按照CQS的思想,则该方法可以拆成CommandQuery两部分,如下:
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只是一种非常简单的模式而已,并没有和事件、消息队列这些有强关联。
  • 读写分离部署+消息通信:
  • 会带来额外的系统复杂性和更高的运维成本。