Types of CQRS

By Vladimir Khorikov

CQRS is a pretty defined concept. Often, people say that you either follow CQRS or not, meaning that it is some kind of a binary choice. In this article, I’d like to show that there is some wriggle room in this notion and how different types of CQRS can look like.

Type 0: no CQRS

With this type, you don’t have any CQRS whatsoever. That means you have a domain model and you use your domain classes for both serving commands and executing queries.

Let’s say you have a Customer class:

public class Customer

{

public int Id { get; private set; }

public string Name { get; private set; }

public IReadOnlyList<Order> Orders { get; private set; }

public void AddOrder(Order order)

{

/* … */

}

/* Other methods */

}

With the type 0 of CQRS you end up with CustomerRepository class looking like this:

public class CustomerRepository

{

public void Save(Customer customer) { /* … */ }

public Customer GetById(int id) { /* … */ }

public IReadOnlyList<Customer> Search(string name) { /* … */ }

}

Search method here is a query. It is used for fetching customers’ data from database and returning it to a client (a UI layer or a separate application accessing your server through some API). Note that this method returns a list of domain objects.

The advantage of such approach is obvious: it has no code overhead. In other words, you have a single model that you use for both commands and queries and don’t have to duplicate the code at all.

The disadvantage here is that this single model is not optimized for read operations. If you need to show a list of customers in UI, you usually don’t want to display their orders. Instead, you most likely prefer to show only a brief information such as id, name and the number of orders.

The use of a domain class for transferring customers’ data from the database to UI leads to loading all their orders into memory and thus introduces a heavy overhead because UI needs the order count field only, not the orders themselves.

This type of CQRS is good for small applications with little or no performance requirements. For other types of applications, we need to move further.

Type 1: separated class structure

With this type of CQRS, you have your class structure separated for read and write operations. That means you create a set of DTOs to transfer the data you fetch from the database.

The DTO for Customer can look like this:

public class CustomerDto

{

public int Id { get; set; }

public string Name { get; set; }

public int OrderCount { get; set; }

}

The Search method now returns a list of DTOs instead of a list of domain objects:

public class CustomerRepository

{

public void Save(Customer customer) { /* … */ }

public Customer GetById(int id) { /* … */ }

public IReadOnlyList<CustomerDto> Search(string name) { /* … */ }

}

The Search method can use either an ORM or plain ADO.NET to get the data needed. This should be determined by performance requirements in each particular case. There’s no need to fall back to ADO.NET if a method’s performance is good enough.

DTOs introduce some duplication as we need to come up with the same concept twice: once for commands in a form of a domain class and once more for queries in a form of a DTO. But at the same time, they allow us to create clean and explicit data structures that perfectly align with our needs for read operations as they only contain data clients need to display. And the more explicit we are with our code, the better.

I would say that this type of CQRS is sufficient for most of enterprise applications as it gives a pretty good balance between code complexity and performance. Also, with this approach, we have some flexibility in terms of what tool to use for queries. If the performance of a method is not critical, we can use ORM and save developers’ time; otherwise, we may fall back to ADO.NET (or some lightweight ORM like Dapper) and write complex and optimized queries on our own.

If we want to continue separating our read and write models, we need to move further.

Type 2: separated model

This type of CQRS proposes using separated models and sets of API for serving read and write requests.

Type 2 of CQRS

That means that, in addition to DTOs, we extract all the read logic out of our model. Repository now contains only methods that regard to commands:

public class CustomerRepository

{

public void Save(Customer customer) { /* … */ }

public Customer GetById(int id) { /* … */ }

}

And the search logic resides in a separate class:

public class SearchCustomerQueryHandler

{

public IReadOnlyList<CustomerDto> Execute(SearchCustomerQuery query)

{

/* … */

}

}

This approach introduces more overhead comparing to the previous one in terms of code required to handle the complexity, but it is a good solution if you have a heavy read workload.

In addition to the ability to write optimized queries, type 2 of CQRS allows us to easily wrap read portion of API with some caching mechanism or even move read API to another server and setup a load-balancer/failover cluster. It works great if you have a massive disparity between the workload of writes and reads in your system as it allows you to scale the read part of it drastically.

If you need even more performance of read operations, you need to move to type 3 of CQRS.

Type 3: separated storage

That is the type that considered to be the true CQRS by many. To scale read operations even further, we can create a separate data storage optimized specifically for queries we have in our system. Often, such storage might be a NoSQL database like MongoDB or a replica set with several instances of it:

Type 3 of CQRS

The synchronization goes in background mode and can take some time. Such data storages are considered to be eventually consistent.

A good example here could be indexing of customers’ data with Elastic Search. Often we don’t want to use full-text search capabilities built into SQL Server as they don’t scale much. Instead, we could use non-relational data storage optimized specifically for searching customers.

Along with the best scalability for read operations, this type of CQRS brings the highest overhead. Not only should we segregate our read and write model logically, i.e. use different classes and even assemblies for it, but we also need to introduce database-level separation.

Summary

There are different types of CQRS you can leverage in your software; there’s nothing wrong with sticking to the type #1 and not moving further to the types 2 or 3 as long as the type #1 meets your application’s requirements.

I’d like to emphasize this once more: CQRS is not a binary choice. There are some different variations between not separating reads and writes at all (type 0) and separating them completely (type 3).

There should be a balance between the degree of segregation and complexity overhead it introduces. The balance itself should be found in each concrete software application apart, often after several iterations. I strongly believe that CQRS itself should not be implemented “just because we can”; it should only be brought to the table to meet concrete requirements, namely, to scale read operations of the application.

文档引用

http://enterprisecraftsmanship.com/2015/04/20/types-of-cqrs/

Types of CQRS的更多相关文章

  1. CQRS FAQ (翻译)

    我从接触ddd到学习cqrs有6年多了, 其中也遇到了不少疑问, 也向很多的前辈牛人请教得到了很多宝贵的意见和建议. 偶尔的机会看到国外有个站点专门罗列了ddd, cqrs和事件溯源的常见问题. 其中 ...

  2. 手撸一套纯粹的CQRS实现

    关于CQRS,在实现上有很多差异,这是因为CQRS本身很简单,但是它犹如潘多拉魔盒的钥匙,有了它,读写分离.事件溯源.消息传递.最终一致性等都被引入了框架,从而导致CQRS背负了太多的混淆.本文旨在提 ...

  3. OnionArch - 采用DDD+CQRS+.Net 7.0实现的洋葱架构

    博主最近失业在家,找工作之余,看了一些关于洋葱(整洁)架构的资料和项目,有感而发,自己动手写了个洋葱架构解决方案,起名叫OnionArch.基于最新的.Net 7.0 RC1, 数据库采用Postgr ...

  4. DDD CQRS架构和传统架构的优缺点比较

    明天就是大年三十了,今天在家有空,想集中整理一下CQRS架构的特点以及相比传统架构的优缺点分析.先提前祝大家猴年新春快乐.万事如意.身体健康! 最近几年,在DDD的领域,我们经常会看到CQRS架构的概 ...

  5. 谈一下关于CQRS架构如何实现高性能

    CQRS架构简介 前不久,看到博客园一位园友写了一篇文章,其中的观点是,要想高性能,需要尽量:避开网络开销(IO),避开海量数据,避开资源争夺.对于这3点,我觉得很有道理.所以也想谈一下,CQRS架构 ...

  6. AutoMapper:Unmapped members were found. Review the types and members below. Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type

    异常处理汇总-后端系列 http://www.cnblogs.com/dunitian/p/4523006.html 应用场景:ViewModel==>Mode映射的时候出错 AutoMappe ...

  7. 分享一个CQRS/ES架构中基于写文件的EventStore的设计思路

    最近打算用C#实现一个基于文件的EventStore. 什么是EventStore 关于什么是EventStore,如果还不清楚的朋友可以去了解下CQRS/Event Sourcing这种架构,我博客 ...

  8. 一种简单的CQRS架构设计及其实现

    一.为什么要实践领域驱动? 近一年时间我一直在思考一个问题:"如何设计一个松耦合.高伸缩性.易于维护的架构?".之所以有这样的想法是因为我接触的不少项目都是以数据库脚本来实现业务逻 ...

  9. 浅谈命令查询职责分离(CQRS)模式

    在常用的三层架构中,通常都是通过数据访问层来修改或者查询数据,一般修改和查询使用的是相同的实体.在一些业务逻辑简单的系统中可能没有什么问题,但是随着系统逻辑变得复杂,用户增多,这种设计就会出现一些性能 ...

随机推荐

  1. HTTPS 原理解析

    一 前言 在说HTTPS之前先说说什么是HTTP,HTTP就是我们平时浏览网页时候使用的一种协议.HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全.为了保证 ...

  2. Jsoup 使用教程:数据抽取

    1.使用DOM方法来遍历一个文档 问题 你有一个HTML文档要从中提取数据,并了解这个HTML文档的结构. 方法 将HTML解析成一个Document之后,就可以使用类似于DOM的方法进行操作.示例代 ...

  3. github-提交仓库

    git提交仓库主要分3快 1.用命令git add告诉Git,把文件添加到本地仓库(可以用.代替提交所有) $ git add readme.txt 2.用命令git commit告诉Git,把文件提 ...

  4. php集成动态口令认证

    这篇文章主要为大家详细介绍了php集成动态口令认证,动态口令采用一次一密.用过密码作废的方式来提高安全性能,感兴趣的小伙伴们可以参考一下 大多数系统目前均使用的静态密码进行身份认证登录,但由于静态密码 ...

  5. eclipse导入第三方jar包进入web项目的方法

    此方式是没有用maven进行构建的项目,纯动态项目. 具体方法: 1.通过Java Build Path导入. 比如我项目上要用servlet-api.jar这个包,我所用的web容器是tomcat, ...

  6. sokect编程进阶

    IO模型 什么是IO? IO:input和output的缩写,即输入/输出端口.每个设备都会有一个专用的I/O地址,用来处理自己的输入输出信息 同步.异步.阻塞.非阻塞 同步和异步的概念描述的是用户线 ...

  7. Web Service 中返回DataSet结果大小改进

    http://www.cnblogs.com/scottckt/archive/2012/11/10/2764496.html Web Service 中返回DataSet结果方法: 1)直接返回Da ...

  8. ubuntu14.04 安装pycharm

    参考链接: http://itsfoss.com/install-pycharm-ubuntu/ 怎样在ubuntu14.04上安装pycharm pycharm是一款为python开发而生的IDE. ...

  9. How to setup vsftpd FTP file Server on Redhat 7 Linux

    Forward from: https://linuxconfig.org/how-to-setup-vsftpd-ftp-file-server-on-redhat-7-linux How to s ...

  10. QQ等级表

    什么是QQ等级呢? 2003年,腾讯公司推出了QQ等级制度 . 最早是以小时,来计算的,那段时间,绝大部分QQ用户都在挂QQ,之后就有不少媒体指责其浪费能源,在有关部门的介入下,腾讯公司将QQ等级变为 ...