【转】EntityFramework之领域驱动设计实践【扩展阅读】:CQRS体系结构模式

原文地址:http://www.cnblogs.com/daxnet/archive/2010/08/02/1790299.html

CQRS体系结构模式

本文将对CQRS(Command Query Responsibility Segregation,命令查询职责分离)模式做一个相对全面的介绍。可以这么说,CQRS打破了经典的领域驱动设计实践,在应用CQRS的整个过程 中,你将会以另一种不同的角度去考虑问题并寻求解决方案。比如,CQRS是事件驱动的体系结构,事件是如何产生如何分发又是如何处理的?事件驱动的体系结 构适用于哪些类型的应用系统?CQRS中的仓储,与经典DDD中的仓储又有何异同?等等这些问题,都给我们留下了无限的思考空间。

背景

在讲CQRS之前,我们先了解一下CQS(Command-Query Separation,命令查询)模式。名字上看,两者没什么差别,然而CQRS应该说是,在DDD的实践中引入CQS理论而出现的一种体系结构模式。 CQS模式最早由著名软件大师Bertrand Meyer(Eiffel语言之父,面向对象开-闭原则OCP提出者)提出,他认为,对象的行为仅有两种:命令和查询,不存在第三种情况。用他自己的话来 说,就是:“提问永远无法改变答案”。根据CQS,任何方法都可以拆分为命令和查询两个部分。比如,下面的代码:

隐藏行号 复制代码 代码
  1. private int i = 0;

  2. private int Add(int factor)
  3. {
  4.     i += factor;
  5.     return i;
  6. }

上面的代码中定义了两个Domain Event:CustomerCreatedEvent和ChangeNameEvent。在Customer聚合根的构造函数中,直接触发 CustomerCreatedEvent以便该事件的订阅者对Customer对象进行初始化;而在Customer聚合根的ChangeName方法 中,则直接触发ChangeNameEvent以便该事件的订阅者对Customer的first name和last name作修改。Customer的基类SourcedAggregationRoot则在领域事件被触发的时候通过Reflection机制获得内部的 事件处理函数,并调用该函数完成事件处理。在上面的例子中,也就是DoChangeName和DoCreateCustomer这两个方法。在这里需要注 意的是,类似DoChangeName和DoCreateCustomer这样的事件处理函数中,仅允许包含对对象状态的设置逻辑。因为如果引入其它操作的话,很难保证通过这些操作,对象的状态不会发生改变

深入思考上面的设计会发现一个问题,也就是当模型对象变得非常庞大,或者随着时间的推移,领域事件将变得越来越多,于是通过Event Sourcing来重建聚合根的过程也会变得越来越耗时,因为每一次从建都需要从最早发生的事件开始。为了解决这个问题,Event Sourcing引入了“快照(Snapshots)”。


快照(Snapshots)

Snapshot的设计其实很简单。标准的CQRS实现中,采用“每产生N个领域事件,则对对象做一次Snapshot”的简单规则。设计人员其实 可以根据自己的实际情况定义N的取值,甚至可以选用特定的Snapshot规则,以提高对象重建的效率。当需要通过仓储获得某一个聚合根实体时,仓储会首 先从Snapshot Store中获得最近一次的快照,然后再在由此快照还原的聚合根实体上逐个应用快照之后所产生的领域事件,由此大大加速了对象重建的过程。快照通常采用 GoF Memento模式实现。请注意:CQRS引入快照的概念仅仅是为了解决对象重建的效率问题,它并不能替代领域事件所能表述的含义。换句话说,即使引入快照,也不能表示我们能够将快照之前的所有事件从事件存储(Event Store)中删除。因为,我们记录领域事件的目的,是为了Event Sourcing,而不是Snapshots

image

事件存储(Event Store)

通常,事件存储是一个关系型数据库,用来保存引起领域对象状态更改的所有领域事件。如上所述,在CQRS结构的系统实现中,数据库已经不再直接保存 对象的当前状态了,保存的只是引起对象状态发生变化的领域事件。于是,数据库的数据结构非常单一,就是单纯的领域事件数据。事件数据的写入、读取都变得非 常简单高速,根本无需ORM的介入,直接使用SQL或者存储过程操作事件存储即可,既简单又高效。读到这里,你会发现,虽然系统是用的一个称之为 Event Store的机制保存了领域事件,但这个Event Store已经成为了整个系统数据存储的核心。更进一步考虑,Event Store中的事件数据是在仓储执行“保存”操作时,从领域模型中收集并写入的,也就意味着,最新的、最真实的数据仍然存在于领域模型中,正好符合DDD 面向领域的思想,同时也引出了另一深层次的考虑:In Memory Domain!

回到结构

在完成对“对象状态”、“事件溯源(Event Sourcing)”、“快照(Snapshots)”以及“事件存储(Event Store)”的讨论后,我们再来看整个CQRS的结构,这样就显得更加清楚。上文【CQRS体系结构模式】图中,用户操作被分为命令部分(图中上半部 分)和查询部分(图中下半部分)。

  1. 用户与领域层的交互,是以命令的方式进行的:用户通过Command Service向领域模型发送命令。Command Service通常被实现为.NET WCF Service。Command Bus在接收到命令后,将命令指派到命令执行器由其负责执行(可以参考GoF Command模式。TBD: 可以选择更符合CQRS实现的其它途径)。命令执行器在执行命令时,通过领域事件更改对象状态,并通过仓储保存领域对象。而仓储并非直接将对象状态保存到外部持久化机制,而仅仅是从领域对象中获得已产生的一系列领域事件,并将这些事件保存到Event Store,同时将事件发布到事件总线Event Bus
  2. Event Handler可以订阅Event Bus中的事件,并在事件发生时作相关处理。上文在讨论服务的时候,有个例子就是利用基础结构层服务发送SMS消息,在CQRS的体系结构中,我们完全可 以在此订阅Warehouse Transferred事件,并调用基础结构层服务发送SMS消息。Domain Model完全不知道自己的内部事件被触发后,会出现什么情况,而Event Handler则会处理这些情况(Domain Model与基础结构层完全解耦)
  3. 在Event Handler中,有一种特殊的Event Handler,称之为Synchronizer或者Denormalizer,其作用就是为了同步“Query Database”。Query Database是为查询提供数据源的存储机制,用户在UI上看到的查询数据均来源于此数据库。因此,CQRS不仅分离了用户操作,而且分离了数据源,这 样做的一个最大的优点就是,设计人员可以根据UI的需求来配置和优化Query Database,例如,可以将Query Database设计为一张数据表对应一个UI界面,于是,用户查询变得非常灵活高效。这里也可以使用DDD中的Repository结合ORM实现数据 读取,与处于Domain Layer中的Repository不同,这个Repository就是DDD中所提到的经典型仓储了,你可以灵活地使用规约模式。 当然,你也可以不使用ORM而直接SQL甚至No SQL,一切取决于用户需求与技术选型。我们还可以根据需要,对Synchronizer和Denormalizer的实现采用缓存,比如,对于无需实时 更新的内容,可以每捕获N个事件同步一次Query Database,或者当有客户端query请求时,再做一次同步,这也是提高效率的一种有效方法
  4. 用户UI通过Data Proxy获得查询结果数据,WCF将数据以DTO的形式发送给客户端

总结

本文介绍了CQRS模式的基本结构,并对其中一些重要概念作了注释,也是我在实践和思考当中总结出来的内容(PS:转载请注明出处)。学习过DDD 而刚刚开始CQRS的朋友,在阅读一些资料的时候势必会感到疑惑,希望本文能够帮助到这些朋友。比如最开始阅读的时候,我也不知道为什么一定要通过领域事 件去更改对象状态,而不是在对象状态变更的时候,去触发领域事件,因为当时我仍然希望能够在Domain Model中方便地使用getter/setter,我当时也希望能够让Domain Model同时适应于经典DDD和CQRS架构。在经过多次尝试后发现,这种做法是不合理、不可取的,也正如Udi Dahan所说:CQRS是一种模式,既然是模式,就是用来解决特定问题的。

还是一句老话:视需求而定。不要因为CQRS所以CQRS。虽然可以很大程度地提升系统性能,虽然可以使系统具有auditing的能力,虽然可以 实现Domain-Centralized,虽然可以让数据存储变得更加简单,虽然给我们提供了很多技术选型的机会,但是,CQRS也有很多不足点,比如 结构实现较繁杂,数据同步稳定性难以得到保证,事件溯源(Event Sourcing)出错时,模型对象状态的恢复等等。还是引用Udi Dahan的一句话:简单,但不容易!

 

原文地址:https://www.cnblogs.com/fcsh820/p/1866393.html