何时使用领域驱动设计 (5)

在CQRS中,所有的操作都是基于事件的,当客户端发起一个请求需要修改领域对象中的某个属性时,客户端会将修改属性的命令消息发送到系统中,命令处理器接收到命令消息之后,会根据聚合根的标识符(ID),从仓储中读取该聚合的所有事件,并根据这些事件重建聚合。在修改了属性之后,领域模型会产生一个事件,然后将这个事件保存到仓储中,与此同时,该事件还会被派送到事件消息总线。这种事件在CQRS模式中称为领域事件(Domain Events),因为它发生在领域层。接下来,事件处理器在收到属性修改的领域事件后,会相应地更新查询数据库;抑或会触发内部的有限状态机,以便在某些情况下当相关联的领域事件全部被接收之后,能够重新产生一条命令,对领域模型进行进一步的修改(比如订单在收到用户的支付之后,状态由WaitForPayment改为Paid)。这种读写分离的架构隔离了领域模型的修改部分与查询部分,使得它们能够以异构的平台和技术被开发和部署,甚至可以以不同的设计策略和资源分配对这两部分进行独立设计。此外,CQRS模式存储了整个系统从运行之初到当前的所有领域事件,也就是说它记录了整个系统从运行之初到当前所发生过的一切事情,这就使系统具有回溯到任何一个状态点的能力,这种机制我们通常称之为事件溯源(Event Sourcing)。 从领域驱动设计的角度,CQRS模式中也包含领域模型、仓储等概念,然而,实现方式与分层架构大不相同:

领域模型中不包含规约(Specifications),因为“写”端不具备查询功能

领域模型中聚合本身的行为(也就是方法)仅包含一个职责,就是派发领域事件(Domain Events)。例如,下面就是修改User聚合的Email属性的样例代码,从代码上看,它仅仅是派发了一个事件:

而User聚合本身也是一个事件订阅者,因此,它在接收到了这个事件后,会更新自己的属性:

何时使用领域驱动设计


我相信你肯定会有疑问:这不是多此一举么?在ChangeEmail方法中直接设置属性不就行了?然而,答案就是不行,因为当调用方通过User ID来向仓储读取User聚合的时候,仓储会从数据库中读出与这个ID相关的所有事件,然后逐一应用在User对象上,此时,上面的由InlineEventHandler所标识的HandleChangeEmailEvent事件处理方法就会被调用,从而完成对Email属性的设置。我相信你还会有疑问:仓储会读出所有的事件,然后逐一应用在User对象上?那如果与User对象相关的事件特别多,逐一应用这些事件岂不是会影响性能?在CQRS中,这一问题是通过快照解决的,基本思路就是在保存领域事件的时候,每隔一定数量的领域事件对聚合做一次快照,比如每1000个领域事件做一次快照,那么当我们需要恢复第1001个领域事件时,只需要读出这个快照,然后应用剩下的那一个领域事件即可,并不存在性能问题

领域模型中实体对象的属性都是只读的,因为修改需要通过领域事件来完成

仓储中不包含查询方法,因此,它仅有两个职责:保存聚合、根据聚合根的ID来读取聚合:

何时使用领域驱动设计

仓储所依赖的事件存储数据库中仅有一张数据表(或者说一种文档):领域事件表,大致包含这些信息:序列号、领域事件所发生的对象类型、领域事件所发生的对象ID、领域事件类型、领域事件发生时间以及领域事件的具体内容

多年前我也基于CQRS体系结构模式做了一个相对完整的案例:WeText,代码完全公开在Github,虽说使用的技术可能有些过时,但整个架构是事件驱动型的,并在一定程度上实现了CQRS体系结构模式以及领域驱动设计中的基本要素。这个案例的架构图如下,供参考:

何时使用领域驱动设计

(图片来源:本人的开源项目WeText,点击查看大图)

 

CQRS模式的实现非常复杂,所以大多数情况下它只会被运用在某个界定上下文(Bounded Context)中,甚至大多数情况下都不会完整地实现上图所述的整个结构。或许你的业务并不需要保存历史事件,那么你就没必要设计事件存储;或许你的客户端不希望以异步的形式向系统发出命令,那么你有可能就不需要命令消息总线。目前在世界上的确是有完整实现CQRS模式的事件驱动型软件项目,但却是风毛菱角。 同理,在事件驱动型架构中,并不一定需要采用CQRS架构模式(应该说绝大多数情况下不需要),还是那句话,你应该根据项目本身的特点以及研发团队的情况来决定使用哪些架构模式来实现事件驱动型架构,有时候你可能只需要一个非常简单的设计就能满足要求。但是,如果你希望在事件驱动型架构中实践领域驱动设计,那么CQRS应该是你所需要了解并深入学习的一种架构模式,它能更好地帮助你理解领域驱动设计,并在事件驱动型架构中更好地运用它。 下面我们再看看目前最为流行的架构风格:微服务架构。

微服务架构(Microservices Architecture)

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zysxxd.html