转:命令和查询责任分离(CQRS)架构模式
读了“蓝皮书”距今差不多一年,它改变了我的软件开发和构建软件架构观。在我作为一名程序员期间,我尝试了许多不同的方式来构建软件。方法有很多,包括一个贫血的域模型(Anemic Domain Model)。构建贫血领域模型并无什么不妥,但对于较为复杂的业务逻辑应用,它可能不是最好的选择。最终结果只能是代码间高耦合的很多“意大利面条式的代码”。贫血领域模型使得其业务逻辑遍布整个代码,如果业务规则改变,需要经常更新多个地方的代码,想避免这种情况,编码时请牢记这点。
“编程的时候,总是想着那个维护你代码的人会是一个知道你住在哪儿的有暴力倾向的精神病患者。”—— Martin Golding。
典型的富领域模型将所有业务逻辑隐藏在模型内部,而大多数对象间相互关联。试图构建一个完美的模型来解决领域的业务逻辑,这往往是以此方式开发软件失败之所在,其结果是一个非常庞大的模型,而应该考虑有限的上下文,但这不是本文的主题。
拆分模型
不要试图在一个模型中的解决所有事情;将其分割成较小的部分,并自成体系。创建者将它们自然低聚合在一起。以汽车和客户为例,在这个例子中,两者都是聚合源,不应该将它们在(同一)模型中同时创建两个对象,而应该聚合为另一模型:将它们当作模型内分立的小模型。这将会使模型持久化或加载到内存中时更易于处理。
Greg Yung有一个很好的例子来阐述聚合源(aggregate roots)及其工作机制。如果校长问老师要他学生的概况,他不会把所有的学生带到校长办公室,但他会带一份校长要的学生信息清单。大多数情况下,将所有对象放在一个庞大的模型里无充足的理由。
关系数据库的问题
如今许多人使用ORM框架将域模型数据持久化到数据库中,他们使用关系数据库来作为数据库(有别人的吗?;-))。关系数据库的问题,是必须在读和写数据方面做出妥协。一般情况下,关系数据库在插入、更新、删除数据时工作更好,读取数据时则不然,取决于索引;这些缺陷在大多数情况下将减缓数据的选取(select)操作,却有助于插入(insert)、删除(delete)及更新(update)数据操作。
(关于更新:我已经收到此发言的一些评论。我所想说的是:如果你添加索引,以改善更新/删除操作,这可能会影响你对数据的选择操作。你必须做妥协,要么读数据快要么写数据快。)
如果决定数据库在读数据(选择)时更好,一个选择是非规范化数据库。一个去规范化的数据库意味着表中存在重复数据,以及表中可能包含骇人听闻的列数。
作为开发人员,往往没有太多地考虑这个问题,如何规划(scale)解决方案?我认为在确立一个可能要为大量用户服务的解决方案之前是首先应该考虑的问题,但并非说应该试图预测未来的事情,而是从一开始就应该采取一些简单的措施,不必想太多以后的事情。
命令和查询的责任分离
大多数应用程序读取数据比写入数据更频繁。基于此点认知,这,使你可以轻松地添加更多的数据库用于读取的解决方案将是一个好主意,是否如此呢?因此,可否仅为读取数据设立专用数据库呢?更妙的是,如果以某种方式设计数据库,以便它的读取速度更快呢?如果你基于CQRS架构描述的模式设计应用,将有一个可扩展性好和数据读取快速的解决方案。命令查询分离(CQS)是一种由Bertrand Meyer首先提出的模式。他基于对象级描述该模式。后来,这种模式摆脱了低水平的徘徊,被用于高级架构(模式)级别。我认为是Udi Dahan首先开始讨论这个架构原理(principle)。
在本文中我描述了一个非常接近Udi Dahan描述的架构。这种模式有几中实现(方式),其中之一正如由Greg Yung所述。Greg Yung的特色是用事件源(Event Sourcing)来描述CQRS,本文则不然。若是对该种描述有兴趣,Google一下即可。
CQRS架构简述
一个图形化的架构概述如下图所示,现对该图做一个简述。用户打开应用和第一个画面是加载,获取的数据来自查询侧。本例中它应用一WCF服务(诸如NHibernate)实现查询,从数据库读取数据到返回数据给图形用户界面的DTO中,该DTO被定制,以适应用户在屏幕上观看。查询数据库通常是非(或去)规范化的,以便提高数据读取速度。用户可以通过不同的画面浏览数据,查询过程是相同的。为用户画面量身定制的DTO从数据库中返回。最后,用户要改变某一画面上的数据。那么发生的情况是:基于改变数据的画面创建创建一个命令消息,并将该消息发送到下图的左侧---命令侧。命令消息被发送到领域模型以便校验是否与业务规则冲突,若某些业务规则失效(可有不同的实现方法),一个错误消息发送回客户端。如果该消息经领域模型无差错,它将被持久化到数据库,并与读取数据库(组)同步。该命令侧除错误信息外不应返回任何数据信息。如果遵循这个规则,命令侧只包含行为。这使得域模型(事件)日志很容易记录,也极易追踪用户想做的事情,而非仅是其动作结果的记录。本文描述的方法与Greg Yung所述略有不同。他的解决方案中提倡命令侧无数据库,数据库只用于报表,这是一个伟大的方法,但它使事情变得有点复杂,它需要新开发(green-field)的项目(?),然我所描述的方法可用于既有(brown-field)应用而无需改变现有架构。大多数情况下,只需为原有架构添加查询侧(query-side)。
为什么添加查询端的DTO?
在传统的领域模型,为解决领域的(业务)规则而创建对象,而非为浏览(或查阅)对象。他们具有行为,并非仅是(展现)形状。为使域对象更加可见(化),许多开发人员将域模型映射为定制的显示DTO,结果是开发者需要在很多对象和公开了getter和setter的领域模型之间进行映射(要了解为什么这样不好,可用Google搜索:getter和setter是邪恶的)。
如果您使用如同NHibernate的ORM,你必须为域模型添加getter(若要使用延迟装载(lazy loading)),这是可以确定的。该模型仍然受保护,以防止可以使之无效的不必要变化,每一变化须通过它的命令方法(实现)。
结论
从这个架构的所得就是:应用(程序)读取端(通常会获到大多数负载)的可扩展性,记录域模型所有事件的可行性(通过跟踪域模型中的命令和事件),以及只需要担心写入数据而非从数据库向GUI传送数据的域模型。我保证这能使很多事情更轻松。而最后一件事,是基于展现(view)创建对象(DTO),以帮助我们避免很多的映射和为画面(或展现)填入数据与数据库冲突的过多要求。我将在今后冠以体系结构部分的论述给出更多的细节。
注1)原架构图:

2)更新后架构图:

原文链接: http://blog.fossmo.net/post/Command-and-Query-Responsibility-Segregation-(CQRS).aspx
相关主题
WCF Data Services: A perfect fit for our CQRS implementation
转:命令和查询责任分离(CQRS)架构模式的更多相关文章
- 云计算设计模式(六)——命令和查询职责分离(CQRS)模式
云计算设计模式(六)——命令和查询职责分离(CQRS)模式 隔离,通过使用不同的接口,从操作读取数据更新数据的操作.这种模式可以最大限度地提高性能,可扩展性和安全性;支持系统在通过较高的灵活性,时间的 ...
- Command and Query Responsibility Segregation (CQRS) Pattern 命令和查询职责分离(CQRS)模式
Segregate operations that read data from operations that update data by using separate interfaces. T ...
- [转] (CQRS)命令和查询责任分离架构模式(一) 之 什么是CQRS
什么是CQRS? 这个问题网上可以找到很多资料,未接触过的童鞋请先查看Udi Dahan, Grey Young, Rinat Abdullin,园子里dax.net,以及Jdon社区上的相关文章. ...
- [转] (CQRS)命令和查询责任分离架构模式(二) 之 Command的实现
概述 继续引用上篇文章中的图片(来源于Udi Dahan博客),UI中的写入操作都将被封装为一个命令中,发送给Domain Model来处理. 我们遵循Domain Driven Design的设计思 ...
- asp.net core系列 62 CQRS架构下Equinox开源项目分析
一.DDD分层架构介绍 本篇分析CQRS架构下的Equinox开源项目.该项目在github上star占有2.4k.便决定分析Equinox项目来学习下CQRS架构.再讲CQRS架构时,先简述下DDD ...
- Equinox开源项目CQRS架构分析
CQRS架构下Equinox开源项目分析 一.DDD分层架构介绍 本篇分析CQRS架构下的Equinox开源项目.该项目在github上star占有2.4k.便决定分析Equinox项目来学习下CQR ...
- ASP.NET Core Web API下事件驱动型架构的实现(四):CQRS架构中聚合与聚合根的实现
在前面两篇文章中,我详细介绍了基本事件系统的实现,包括事件派发和订阅.通过事件处理器执行上下文来解决对象生命周期问题,以及一个基于RabbitMQ的事件总线的实现.接下来对于事件驱动型架构的讨论,就需 ...
- NET Core Web API下事件驱动型架构CQRS架构中聚合与聚合根的实现
NET Core Web API下事件驱动型架构在前面两篇文章中,我详细介绍了基本事件系统的实现,包括事件派发和订阅.通过事件处理器执行上下文来解决对象生命周期问题,以及一个基于RabbitMQ的事件 ...
- 危险!水很深,让叔来 —— 谈谈命令查询权责分离模式(CQRS)
多年以前,那时我正年轻,做技术如鱼得水,甚至一度希望自己能当一辈子的一线程序员. 但是我又有两个小愿望想要达成:一个是想多挣点钱:另一个就是对项目的技术栈和架构选型能多有点主动权. 多挣点钱是因为当时 ...
随机推荐
- Matlab中边缘提取方法简析
1.Matlab简述 Matlab是国际上最流行的科学与工程计算的软件工具,它起源于矩阵运算,已经发展成一种高度集成的计算机语言.有人称它为“第四代”计算机语言,它提供了强大的科学运算.灵活的程序设计 ...
- hive 学习笔记精简
创建表: drop table t create table if not exists t (t string) partitioned by (log_date string) row forma ...
- JS计算两个日期相差几天
function Computation(sDate1, sDate2){ var aDate, oDate1, oDate2, iDays aDate = sDate1.split("-& ...
- java多线程同步
一篇好文:java多线程机制同步原则 概括起来说,Java 多线程同步机制主要包含如下几点:1:如果一个类包含一个或几个同步方法,那么由此类生成的每一个对象都配备一个队列用来容纳那些等待执行同步的线程 ...
- Installing perl and writing your first perl program in Ubuntu
Installing perl and writing your first perl program in Ubuntu Installing perl and writing your f ...
- swift3.0基础语法(2)
变量/常量,元组声明 var aaa = 0;//声明变量aaa 首次赋值时自动解析为Int类型 var aaa:Int = 0;//声明Int类型变量aaa let aaa = 0;//声明常量aa ...
- BC第二场
GT and sequence Accepts: 385 Submissions: 1467 Time Limit: 2000/1000 MS (Java/Others) Memory Lim ...
- c++程序内存泄露检測工具
功能: 用于检測c++程序的内存泄露. 原理: 事实上非常easy,就是通过函数的重载机制,捕获应用程序的new, new[] , delete , delete[], malloc,calloc,f ...
- android开发_SimpleAdapter适配器
android开发_SimpleAdapter适配器 新建项目: 项目结构: drawable-hdpi文件夹中的图片是自己加入的.主要是在菜单选项中显示的图片: 运行效果: 代码部分: main ...
- Android适配器Adapter学习
在开发中我们需要绑定一些数据展现到桌面上,这是就需要AdapterView.AdapterView是ViewGroup的子类,它决定了怎么展现视图通过Adapter来绑定特殊的数据类型. Adapte ...