命令查询职责分离模式CQRS
《命令查询职责分离模式CQRS》共 0.4 万字,通篇围绕 数据库、安全、场景、日志 展开,归在「后端与系统架构」这一层。
适合给 AI 系统搭底座、要管得住的人
目录骨架(1 节)
- **背景和问题**
背景和问题
在传统的体系结构中,使用相同的数据模型来查询和更新数据库。这很简单,适用于基本的CRUD操作。然而,在更复杂的应用程序中,这种方法可能变得笨拙。例如,在读取端,应用程序可能执行许多不同的查询,返回具有不同形状的数据传输对象(dto)。对象映射可能变得复杂。在写端,模型可能实现复杂的验证和业务逻辑。结果,您可能会得到一个做得太多的过于复杂的模型。
读和写工作负载通常是不对称的,性能和规模需求非常不同。
最近不少学员想用Scala来重构Java项目。Scala的灵活高效这里就不用说了,Java MVC一套架构确实有它优点。但是开发调试效率确实慢很多。所以准备使用DDD中的命令查询职责分离模式(Command Query Responsibility Segregation,CQRS)重构项目。
首先我们先介绍下CQRS。
一:什么是CQRS
CQRS最早来自于Betrand Meyer(Eiffel语言之父,开-闭原则OCP提出者)在 Object-Oriented Software Construction 这本书中提到的一种 命令查询分离 (Command Query Separation,CQS) 的概念。其基本思想在于,任何一个对象的方法可以分为两大类:
- 命令(Command):不返回任何结果(void),但会改变对象的状态。
- 查询(Query):返回结果,但是不会改变对象的状态,对系统没有副作用。

- 数据的读和写表示形式之间常常存在不匹配,比如必须正确更新的附加列或属性,尽管它们不是操作的一部分。
- 当对同一组数据并行执行操作时,可能会发生数据争用。
- 由于数据存储和数据访问层的负载以及检索信息所需的查询的复杂性,传统方法可能会对性能产生负面影响。
- 管理安全性和权限可能变得复杂,因为每个实体都受读和写操作的约束,这可能会在错误的上下文中公开数据。
二:深入理解CQS
根据CQS的思想,任何一个方法都可以拆分为命令和查询两部分,比如:
private int i = 0;
private int Increase(int value)
{
i += value;
return i;
}
这个方法,我们执行了一个命令即对变量i进行相加,同时又执行了一个Query,即查询返回了i的值,如果按照CQS的思想,该方法可以拆成Command和Query两个方法,如下:
private void IncreaseCommand(int value)
{
i += value;
}
private int QueryValue()
{
return i;
}
操作和查询分离使得我们能够更好的把握对象的细节,能够更好的理解哪些操作会改变系统的状态。当然CQS也有一些缺点,比如代码需要处理多线程的情况。
CQRS是对CQS模式的进一步改进成的一种简单模式。 它由Greg Young在CQRS, Task Based UIs, Event Sourcing agh! 这篇文章中提出。“CQRS只是简单的将之前只需要创建一个对象拆分成了两个对象,这种分离是基于方法是执行命令还是执行查询这一原则来定的(这个和CQS的定义一致)”。
CQRS使用分离的接口将数据查询操作(Queries)和数据修改操作(Commands)分离开来,这也意味着在查询和更新过程中使用的数据模型也是不一样的。这样读和写逻辑就隔离开来了。

使用CQRS分离了读写职责之后,可以对数据进行读写分离操作来改进性能,可扩展性和安全。如下图:

主数据库处理CUD,从库处理R,从库的的结构可以和主库的结构完全一样,也可以不一样,从库主要用来进行只读的查询操作。在数量上从库的个数也可以根据查询的规模进行扩展,在业务逻辑上,也可以根据专题从主库中划分出不同的从库。从库也可以实现成ReportingDatabase,根据查询的业务需求,从主库中抽取一些必要的数据生成一系列查询报表来存储。
