暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

领域驱动设计:CQRS

星河之码 2022-03-12
1641

尺有所短,寸有所长;不忘初心,方得始终。

在数据库设计中经常会提到读写分离,为了提升系统的性能,我们将读操作与写操作分开作用在不同的库中。在DDD中引入了CQRS的概念,设计软件之初将软件模型分为读模型和写模型。

一、什么是CQRS

命令查询职责分离:简称CQRS(Command Query Resposibility Segregation)。它是将软件设计中的命令-查询分离并应用在架构模式中的设计原则

在《Object-Oriented Software Construction》书中有一个概念:

Every method should either be a command that performs an action, or a query that returns data to the caller, but never both.

译文:一个方法要么作为一个【命令】执行一个操作,要么作为一次【查询】向调用方返回数据,但两者不能共存。

CQRS从模型层面,将即【命令】和【查询】分别使用不同的对象模型来表示。

  • 命令】:Command ,修改了对象的状态

    不应该返回数据,在Java中,方法应该声明为void。

  • 查询】:Query ,不应该通过直接或者间接的方式修改对象状态

    返回数据,在Java中,方法应该声明返回的数据类型。

在DDD架构中,通常会将查询和命令操作分开。具体落地时,可以将Command和Query分成两个工程,但是大多数情况下放在一个项目可以提高业务内聚性。这个可以根据实际情况决定。以下是一张CQRS官网的模型图

图解:

  • 这张图读写体现的是逻辑分离,物理层面使用的是同一个数据库,而实际上可以将数据库改成读库和写库的物理分离,只需要同步两个库即可。常用的解决方案是当写库发生更改时,通过Event事件机制通知读库进行同步。
  • 在实际的CQRS落地时,我们即使物理层面使用的是同一个数据库,但是可能还是会用到其他的物理存储,比如Elasticsearch,Redis等。当数据库发生更改时:
    • 发送Event事件通知ES/Redis进行数据更新同步。
    • 通过监听Mysql的binlog更新ES/Redis。

二、CQRS实现方式

2.1 读模型的数据源

上面提到读写模型可以将Command和Query分成两个工程使用不同的物理库,也可以放在一起使用同一个物理

库。无论是那种方式数据的来源应该都来自于业务实体对象(比如聚合根)

读模型的数据源形式分为:

  • 单进程单实体:数据来源于同一个进程空间的单个实体
  • 单进程跨实体:数据来源于同一个进程空间中的多个实体
  • 跨进程跨实体:数据来源于不同进程空间中的多个实体

进程空间:指某个单体应用或者单个微服务。

2.2 读写分离的形式

读写分离的形式基于数据来源的不同,CQRS可以在代码中实现读写分离(共享模型/分离模型),也可以在物理存储中实现读写分离(共享存储/分离存储)。两种方式是结合起来使用的:

  • 共享存储/共享模型

    共享存储(读写同一个数据库),共享模型(读写同一个领域服务,领域对象),数查询据通过模型转换后返回给调用方。

  • 共享存储/分离模型

    共享数据存储,代码中分别建立写模型和读模型,读模型是基于查询方式进行的建模。

  • 分离存储/分离模型

    数据存储和代码模型都是分离的,通常用于需要聚合查询多个子系统(比如微服务系统)。

2.3 CQRS模式

在DDD实践中,CQRS模式是由【读模型的数据源】与【读写模型的分离形式】组合使用

主要分为:

  • 单进程单实体 + 共享存储/共享模型
  • 单进程单实体 + 共享存储/分离模型
  • 单进程跨实体 + 共享存储/分离模型
  • 单进程跨实体 + 分离存储/分离模型
  • 跨进程跨实体 + 分离存储/分离模型

通过以上5中方式基本上可以覆盖常用的一些业务场景。

2.3.1 单进程单实体 + 共享存储/共享模型

对于简单的单体或者微服务应用,这种方式是最直接的,在单个领域实体模型同时用于读写操作,在向调用方返回查询数据时,只需要转换相应的领域模型即可。

2.3.2 单进程单实体 + 共享存储/分离模型

当单个实体的查询很复杂时,可以对读模型和写模型分别建模,以此维护读写过程彼此的清晰性。

2.3.3 单进程跨实体 + 共享存储/分离模型

在同一个进程空间中的跨实体查询时时候可以使用分离模型的形式。做join联合查询即可。

2.3.4 单进程跨实体 + 分离存储/分离模型

当系统数据量比较大,并发比较大,对性能要求比较高得时候,我们可以采用分离存分离存储/分离模型的方式,

采用专门的数据库来简化查询提升效率。

2.3.5 跨进程跨实体 + 分离存储/分离模型

在微服务中,每个服务都内聚性地管理自身的聚合根对象,意味着分离模型。而微服务的数据存储如果是独占式的,则数据存储一定是分离的

在这种方式中,查询的数据通常通过事件机制从不同的其他业务服务中同步得到,通过API向外暴露。

三、DDD、CQRS架构落地

根据上面的分析,在DDD+CQRS的架构落地时,Web、RPC、DB、MQ等基础设施层会依赖内部的抽象,属于一个对称性架构

所有的抽象都定义在圆圈内部,实现都在基础设施。而针对读写的应该都在应用层划分:

  • 当一个命令Command请求过来时,会通过应用层的CommandService去协调领域层工作,

  • 当一个查询Query请求过来时,则直接通过基础实施的实现与数据库或者外部服务交互。

在实际开发中,我们会发现Query和Command的有一些数据和抽象服务是公用的,为了提高代码的复用性,可以单独抽离出来一个公用的数据对象和抽象模块Shared

- END -


文章转载自星河之码,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论