经典的应用系统结构、CQRS与事件溯源

有不少网友仍然对CQRS与事件溯源(Event Sourcing)不是很了解,对经典的应用系统架构与CQRS架构之间的差别没有一个大概的认识。本文基本上摘自Greg Young的CQRS Documents一文(由daxnet本人翻译并做了简要的注释),希望能够对应用系统架构的爱好者有所帮助。

【注:本文主体内容节选并翻译自Greg Young的CQRS Documents。如需查看原文,请单击此处

 

一、经典的应用系统结构

在了解基于领域驱动设计(Domain Driven Design, DDD)的应用系统之前,首先让我们了解一下经典的应用系统结构。这种经典的应用系统结构往往被认定为设计与开发分布式应用系统的一种标准化方案,下图描述了这种经典的应用系统结构。

image

上面的架构依赖于一个后台的数据存储系统。虽然通常情况下是一个关系型数据库,但这种数据存储系统可以用多种方式实现,比如采用键值存储、对象数据库或者甚至是XML文件。不管怎么样,这个数据存储系统保存的是领域模型中对象的当前状态。在数据存储系统的上层,就是整个系统的核心部分:应用服务器(Application Server)。在应用服务器中,有一个称之为“Domain”的部分,它包含了整个系统的业务逻辑,比如对来自于外部请求的业务验证等逻辑。于此同时,“应用服务”也是应用服务器中的一部分,它向外界提供了访问领域模型的接口,同时在调用者与领域模型之间实现了解耦。再外层就是Remote Facade,它通常可以是SOAP、定制的TCP/IP通信、通过HTTP进行传输的XML文档、TomCat,或者是输入数据的工作人员。Remote Facade的主要职责是为外部系统与应用服务器之间的交互提供远程代理和桥梁的作用。整个应用服务器的作用是对后台数据存储系统进行抽象与解耦,同时对整个系统的业务逻辑进行处理。这样的经典架构曾经非常流行,而且即便是现在,绝大部分系统仍然将其作为默认的首选架构风格。

现在让我们分析一下这样的经典应用系统结构。这样的架构有很多特点,当然,其中一些特点在某些应用场合表现得非常不错,而也有一些特定却无法适应其它的一些应用场合。作为架构师,我们需要善于总结和发现,使得所选架构能够最大程度地满足系统需求。

  • 简单
    这种经典的应用系统结构给我们带来的一个最大的特点就是简单。初级程序员可以很快地掌握这种系统结构并快速地了解到整个系统中各个部分是如何衔接和通讯的。“简单”又带来了这种架构风格的另一个特性,就是“通用”。它能够被用到绝大部分项目上,于是,这种架构风格也就成为了大家讨论系统架构的“通用语言”:说起分层,就是经典的三层(或者四层,或者五层,但不外乎就是UI层、业务层和数据层);说起数据存储系统,就是关系型数据库,马上也就联想到表、字段、约束、存储过程等等。这种“通用”的特性带来一个好处:开发人员对系统上手很快,比如某个项目新来了一名开发人员,他可以在短时间里熟悉系统结构和开发过程,这在一定程度上节省了项目的开支
  • 工具化、框架化
    由于“简单”和“通用”,于是,开发一套“通用”的支持经典架构的应用系统开发的工具/框架,就变得相对容易。目前已经有很多现成的框架,通过使用这些框架能够方便、快捷地搭建这种经典架构的应用系统。比如ORM,它提供了领域对象与关系型数据库的映射处理,在系统中引入ORM,开发人员就不需要自己去维护这种映射关系,大大提高了开发效率,节省了开发成本
  • 领域驱动
    在这种经典的应用系统结构上实践领域驱动设计是不可能的事情,即使之前有不少人试图通过这种架构来实践领域驱动(daxnet:之前我也认为DDD可以在这种架构上实践,而且从实现角度来看,并没有什么是行不通的,然而Greg Young却并不认同,原因是由于通用语言,当然Greg Young也不一定是完全正确的,但他要比我们实践的更多,他的推断更具参考价值,我们还需要在实践中去体会这种差别)。其原因简单地说就是这种架构中的对象模型无法正确地表述成通用语言。我们不难发现,这种经典的架构中,只有四种谓词:新建(Create)、读取(Read)、更新(Update)和删除(Delete),也就是我们常说的CRUD。由于Remote Facade是面向数据的(也就是面向DTO的),于是,Application Service也就需要提供相同的面向数据的接口。这也就意味着,领域模型也只能支持这四种谓词;而当我们跟领域专家进行交流的时候,我们往往采用的是“通用语言”,而通用语言的内容就很丰富了,完全不仅仅是这四种谓词
  • 项目规模
    当我们深入地去了解这种架构的时候,我们会发现,当项目规模变得很大的时候,有一个非常大的瓶颈,就是数据存储系统。尤其是关系型数据库。当然,虽然现在90%以上的系统采用的是关系型数据库,但是项目规模的扩大毕竟也不是一种常态,因此,这种瓶颈的存在也不会是什么太大的问题

总之,经典的应用系统结构能够适用于绝大多数的系统开发,它有着最大的优点,就是简单。然而,它不适应于基于领域驱动设计的项目开发,如果你非要在这样的架构下实践领域驱动,那必定是失败的。

 

二、CQRS与事件溯源

CQRS与事件溯源有着相辅相成的关系。CQRS允许事件溯源作为领域的数据存储机制。然而,使用事件溯源的一个最大的缺点是,你无法向你的系统提出类似“请告诉我所有名字为Greg的用户”这样的问题,这是由于事件溯源无法提供对象的当前状态而引起的。CQRS唯一支持的查询就是:GetById - 通过ID来获得某个聚合。下图为基于CQRS的应用系统结构,可以与上图的经典结构作一个对比。

image

对比两种不同风格的系统架构,我们发现,两者在客户端(Client)所需要投入的工作量大体上是相同的,因为两种不同架构下的客户端所做的操作大体相同:从系统获得DTO,将DTO转换为View Model并显示在UI上,接收用户输入,产生命令,然后通知应用系统做相应的操作。在查询功能的实现上,这两种系统架构所花费的成本也大体相同:在经典的应用系统结构中,查询是建立在领域模型上的,而在CQRS结构的应用系统中,查询则是另外一件事情:它由一个简单的读取访问层(Thin Read Layer)提供,而这个读取访问层会直接将数据映射在DTO上。通过对“命令与查询职责分离”的讨论,实现一个简单的读取访问层不会花费更大的成本,相反,在很多情况下会节省成本开支。

仍然从成本的角度考虑,这两种系统架构的最大区别应该是在领域模型及其持久化的部分。在经典的架构风格中,为了将领域模型持久化到数据库,通常情况下ORM承担了大量的工作,但这同时也在领域模型与持久化机制之间产生了“阻抗失衡”,这种效应最终导致在生产环境中产生了高额成本支出,同时,开发人员也需要更丰富的知识与更多的经验来处理由“阻抗失衡”导致的问题。在CQRS的架构风格中,从C部分(Command部分)来看,就不会存在这种“阻抗失衡”效应。领域模型产生事件,而数据存储系统则保存这些事件,领域模型所关注的仅仅就是这些事件而已。然而在Q部分(Query部分),则会产生“阻抗失衡”:事件处理器(Event Handlers)需要根据事件的具体数据来更新Query Database,这里的阻抗失衡产生于事件与数据关系模型之间。然而,Query部分的阻抗失衡效应要远小于由领域模型与关系型数据库之间产生的阻抗失衡效应,而且Query部分的阻抗失衡效应要更容易处理和解决。这是因为,事件本身是没有结构的,它仅仅表述对关系模型应该采取哪些措施。

从以上几点考虑,这两种不同风格的系统架构所要完成的工作量几乎是等同的,并不存在额外的工作量,也不存在更少的工作量。这是两件完全不同的工作。在建模的时候,基于CQRS架构风格的应用系统似乎有一些额外的工作要做,因为你需要定义一系列的事件对象,同时还要编写事件处理器;但这种工作量相对而言还是比较小的,它有效地降低了甚至避免了“阻抗失衡”效应。总而言之,事实上在大多数情况下,基于CQRS与事件溯源的应用系统更节约成本,而且更加高效。

 

三、简单总结

仍然不要因为感觉CQRS看上去更“先进”,就去生搬硬套,这只能给你带来失败的教训。还是要根据项目的实际情况对整个系统的架构与技术选型作出准确的判断,这也是架构师的主要职责所在。虽然我的博客中大部分内容都在讨论领域驱动设计与CQRS,但这并不表示我会去抵制经典的架构风格,我也同样在经典的应用系统结构上不断地学习和思考,我只是希望能够通过博客这么一种东西,来整理一些新鲜的事物,同时也帮助软件设计与架构的爱好者们开拓视野,丰富知识。

posted @ 2011-01-06 19:50  dax.net  阅读(18500)  评论(3编辑  收藏  举报