解析CQRS架构模式

在日常开发过程中,关于如何正确划分操作的边界和职责一直是我们需要考虑的一个核心问题。针对这个问题,业界也诞生了一些新的设计思想和开发模式,其中最具代表性的就是今天要介绍的CQRS。

CQRS的全称是Command Query Responsibility Segregation,也就是命令和查询职责分离。从CQRS的名称上,我们不难看出该模式用来划分不同类型的操作。基于CQRS,我们在设计和实现具体的数据处理时需要引入一套专门的开发流程和方法。让我们一起来按一下。

CQRS模式设计理念

CQRS模式的核心设计理念来自于一条设计原则,即单一职责原则。所谓单一职责原则,指的是一个技术组件只应该负责具体一项职责,而不应该有多个导致该组件发生状态变化的操作。作为面向对象五个基本原则之一,在日常开发过程中应用非常广泛。而CQRS模式则在单一职责的基础上,对数据操作的类型进行了更进一步的划分,从而确保了系统的性能和安全性。让我们一起来看一下。

数据操作的类型

在详细介绍CQRS模式之前,我们先来对数据操作过程做一个简单的总结。数据操作的基本表现就是增加(Create)、检索(Retrieve)、更新(Update)和删除(Delete),也就是通常所说的CRUD。如果我们仔细分析CRUD,会发现其实可以把它进一步拆分为读和写两个部分,其中CRUD中的R代表读,而剩余的CUD则代表写。


在我们执行一个具体的数据操作是,如果该操作的结果是返回一个值,它就具有查询(Query)的性质,也就是读的性质;而如果一个操作的目的是要改变数据的状态,那么它就具有命令(Command)的性质,也就是写的性质。


在上图中,我们把数据操作对应了一个个具体的方法上。通常,一个方法可能是纯的命令操作或者是纯的查询操作,也可能是两者之间的混合体。如果一个方法没有返回值,那么它肯定是命令操作。而如果一个方法有返回值,那么取决于该方法是否改版了数据的状态,可以是查询操作,也可以是查询+命令操作。


这种把命令操作和查询操作分离开来、各司其职的模式就是CQRS模式。基于CQRS模式,开发人员通过命令操作来改变数据的状态,而通过查询操作来获取最新的数据。

CQRS模式给我们的启示就在于:当在设计一个方法时,应该严格按照命令操作或查询操作来区分方法的行为,而不推荐设计如上图中所展示的查询+命令型的数据操作方式。这样改变对象状态的方法就需要被设计成没有返回值,而查询方法则不应该改变数据的状态。


CQRS的优势

CQRS模式的优势的非常明显的,查询操作和命令操作的分离,带来的好处就是有助于系统性能。从CQRS的基本概念,我们可以看出它的设计思想实际上和读写分离类似。读写分离的一般做法是把涉及到数据修改的操作放在主库,而把数据查询操作放到从库。

显然,通过读写分离机制,数据库的访问效率得到了提升,而这点对于方法调用和执行过程同样适用。


除了读写分离之外,CQRS的一大特色是可以根据需要提供多个查询操作。因为数据模型本身是稳定而干净的,所以我们可以选择性地明确想要展示的数据部分,并自由的对它们进行组合,从而实现不同的查询操作。我们可以分别对这些查询操作的性能进行专门的优化。


另一方面,CQRS模式也能为系统带来安全性。就数据操作而言,查询操作不会影响数据的状态,所以没有安全风险。而命令操作则相反。因此,如果我们事先能够对系统中所有的查询操作和命令操作进行区分,那么就可以把安全性控制的重点工作放在命令操作上,确保数据模型的安全性。


在现实应用开发过程中,查询操作的数量一般都是远远高于命令操作的。所以,通过CQRS模式,我们就能确保安全工作更加聚焦,降低安全控制的成本,提高效率。

CQRS的组成结构

讨论完CQRS的基本操作和优势之后,我们来分析该架构模式的具体组成结构。下图展示了CQRS模式的一种表现形式。

 

在上图中,我们把来自用户界面的操作分成两个入口,一个入口对接应用程序的查询模型,而另一个入口对接命令模型。而查询模型和命令模型的背后都是保存在数据库中的业务数据。

如果采用这种结构,好处是可以在业务代码层面实现读写分离,更容易维护。同时,因为读写同一个数据库,所以不存在命令模型和查询模型两者之间存在数据不一致性问题,实现上也比较简单。

另一种CQRS模式的实现策略就是把数据库也进行拆分。

可以看到,这里我们把原来单个的数据库拆分成查询操作数据库和命令操作数据库,然后通过实现这两个数据库之间的数据一致性。实现数据一致的方式有两种,如果需要查询和命令操作对应数据的强一致性,那么就需要实现基于同步机制的强一致性。反之,我们可以使用基于异步消息的最终一致性

整合事件溯源和CQRS

CQRS模式通常可以作为一种独立的数据管理的有效手段,但也可以和其他架构模式进行整合。尤其是在领域驱动设计中,CQRS模式和事件溯源模式天然可以集成。我们已经在《10分钟带你彻底搞懂事件溯源架构模式》中详细介绍了事件溯源模式。这里,我们可以简单回顾事件溯源机制的两个核心问题,即

  1. 如何生成领域事件?
  2. 领域事件生成之后,如何获取领域对象的状态信息?

针对这两个问题,在实现过程中,事件溯源机制通常会和CQRS结合起来一起使用,其中命令操作用于生成领域事件,而查询操作则用来查询实体状态。

在这里,我们还是需要强调一点。CQRS和事件溯源之间其实并没有直接的关系。但命令和事件往往是成对出现的。所以,CQRS模式与领域事件结合之后可以构建高度低耦合系统。下图展示了整合CQRS和事件溯源之后的结构图。


基于上图,我们可以对CQRS和事件溯源的整合过程总结成四点:

  1. 把领域事件作为最核心的技术组件看待,围绕领域事件设计整个系统架构
  2. 使用专门的事件存储库来存储事件,而不是把它们保存在普通的持久化媒介中
  3. 使用命令服务更新实体状态并生成事件
  4. 通过查询服务提供实体状态的读取功能

在应用程序开发过程中,针对数据的操作可以分成“读”和“写”两大类。针对如何执行这两大类操作,我们可以引入今天介绍的CQRS模式。在CQRS模式中,通过严格区分命令操作和查询操作,开发人员可以高效而安全的对数据进行各种操作。而CQRS模式本身具备一定的架构体系,也可以和事件溯源机制进行整合,从而更好的应用到领域设计驱动中。在今天的内容中,我们也对这一主题进行了详细的讨论。在日常开发过程中,你可以按照今天介绍的内容进行尝试。

相关推荐

  1. 解释MVC和MVVM架构模式

    2024-07-15 18:54:03       38 阅读
  2. 架构模式——分层架构

    2024-07-15 18:54:03       29 阅读
  3. 架构模式——事件驱动架构模式

    2024-07-15 18:54:03       23 阅读
  4. php使用CORS解决跨域

    2024-07-15 18:54:03       42 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-15 18:54:03       66 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-15 18:54:03       70 阅读
  3. 在Django里面运行非项目文件

    2024-07-15 18:54:03       57 阅读
  4. Python语言-面向对象

    2024-07-15 18:54:03       68 阅读

热门阅读

  1. Go协程与通道的综合应用问题

    2024-07-15 18:54:03       23 阅读
  2. 【ROS2】测试

    2024-07-15 18:54:03       21 阅读
  3. 「Conda」在Linux系统中安装Conda环境管理器

    2024-07-15 18:54:03       18 阅读
  4. Python学习的第一天7.15

    2024-07-15 18:54:03       24 阅读
  5. 动态内存管理(C)

    2024-07-15 18:54:03       23 阅读
  6. 算法的时间复杂度和空间复杂度-概念

    2024-07-15 18:54:03       19 阅读
  7. Matlab

    Matlab

    2024-07-15 18:54:03      20 阅读
  8. C/C++指针&智能指针二

    2024-07-15 18:54:03       15 阅读