总算讲清DDD领域驱动设计中的领域事件(Domain Event)了

学习什么是领域事件.何时而且为何要使用领域事件。 •学习如何将领域事件建模成对象,什么时候应该为领域事件建立惟一的身份标识。 •学习一个轻量级的发布-订阅[Gamma et al]模式。 •学习哪些组件用于发布事件,哪些组件用于订阅事件。 •学习为何咱们须要一个事件存储.如何实现事件存储、如何使用事件存储。 •学习S aaSOvation团队是如何经过不一样的方式将领域事件发布给自治系统前端

1 什么时候、为何使用领域事件?

1.1 定义

使用领域事件来建模发生在领域中的一些事情。这是一个功能强大的建模工具,让人爱不释手。 使用领域事件时,首先就是要对不一样事件进行定义。数据库

《领域驱动设计》一书中并未给出领域事件的定义。由于该模型是在该书出版后才被提出。 当前对领域事件的定义:领域专家所关心的发生在领域中的一些事件。 将领域中所发生的活动建模成一系列的离散事件。每一个事件都用领域对象来表 示……领域事件是领域模型的组成部分,表示领域中所发生的事情。[Evans, Ref, P-20] 一markdown

个领域事件将致使进一步的业务操做,在实现业务解耦的同时,还有助于造成完整的业务闭环。网络

领域事件能够是业务流程的一个步骤,好比一个事件发生后触发的后续动做,好比密码连续输错三次,触发锁定帐户的动做。架构

1.2 识别领域事件

  • “若是发生……,则……”
  • “当作完……的时候,请通知……”(这里的通知自己并不能构成一个事件,而只是代表咱们须要向外界发出通知)

在这些场景中,若是发生某种事件后,会触发进一步的操做,那么这个事件极可能就是领域事件。因为领域事件须要发布到外部系统,好比发布到另外一个限界上下文。因为这样的事件由订阅方处理,它将对本地和远程上下文产生深远的影响。并发

那领域事件为何要用最终一致性,而不是传统SOA的直接调用?异步

聚合的一个原则:一个事务中最多只能更改一个聚合实例。因此分布式

  • 本地限界上下文中的其余聚合实例即可以经过领域事件的方式同步
  • 用于使远程依赖系统与本地系统保持一致。解耦本地系统和远程系统还有助于提升双方协做服务的可伸缩性。

聚合建立并发布事件。订阅方能够先存储事件,而后再将其转发到远程订阅方,或不经存 储,直接转发。除非MQ共享了模型的数据存储,否则即时转发须要XA(两阶段提交)。微服务

考虑在系统非高峰时期,批处理过程一般进行一些系统维护工做,好比删除过时对象、建立新对象以支持新业务需求或通知用户所发生的重要事件。这样的批处理过程一般需复杂 查询且需庞大事务支持。若这些批处理过程存在冗余会怎么样? 系统中发生的每一件事情,咱们都用事件形式捕获,而后将事件发布给订阅方处理,能简化系统吗?确定的!它可消除先前批处理过程当中的复杂查询,由于咱们可以准确知道在什么时候发生何事,限界上下文也由此知道接下来应该作啥。在接收到领域事件时,系统可当即处理。本来批量集中处理的过程能够分散成许多粒度较小的处理单元,业务需求也由此更快知足,用户也可及时进行下一步操做。工具

领域事件驱动设计可切断领域模型之间的强依赖。 事件发布完成后,发布方没必要关心后续订阅方事件处理是否成功,便可实现领域模型的解耦,维护领域模型的独立性和数据一致性。 在领域模型映射到微服务架构时,领域事件可解耦微服务,微服务间的数据没必要要求强一致性,而是基于事件的最终一致性。

触发领域事件

领域事件由外部命令触发。触发命令能够是领域服务,也能够是实体的某一个方法或者行为。

触发事件的用法

走canal增量同步数据库数据,经过监听特定表的数据变动来触发生成事件的调用。如此有利于主流业务的解耦,提升维护和可读性。(具体生成事件的操做固然仍是放在对应领域的微服务中,canal监听消费端能够理解为一个任务调度平台)。这样的实现逻辑相对简单。

那不一样领域事件,如何处理呢?

3 处理领域事件

3.1 微服务内

领域事件发生在微服务内的聚合间,领域事件发生后完成事件实体的构建和事件数据持久化,发布方聚合将事件发布到事件总线,订阅方接收事件数据完成后续业务操做。

微服务内大部分事件的集成,都发生在同一进程,进程自身便可控制事务。但一个事件若同时更新多个聚合,按一次事务只更新一个聚合原则,可考虑引入事件总线。

微服务内应用服务,可经过跨聚合的服务编排和组合,以服务调用方式完成跨聚合访问,这种方式一般应用于实时性和数据一致性要求高的场景。这个过程会用到分布式事务,以保证发布方和订阅方的数据同时更新成功。

在微服务内,不是说少用领域事件,而是推荐少用事件总线。DDD是以聚合为单位进行数据管理,若一次操做会修改同一微服务内的多个聚合的数据,就需保证多个聚合的数据一致性。 为了解耦不一样聚合,需采用分布式事务或事件总线,而事件总线不太方便管理服务和数据的关系,可用相似saga之类的分布式事务技术。总之需确保不一样聚合的业务规则和数据一致性。

3.2 微服务间

跨微服务的领域事件会在不一样限界上下文或领域模型间实现业务协做,主要为解耦,减轻微服务间实时服务访问压力。

领域事件发生在微服务间较多,事件处理机制也更复杂。跨微服务事件可推进业务流程或数据在不一样子域或微服务间直接流转。

跨微服务的事件机制要整体考虑事件构建、发布和订阅、事件数据持久化、MQ,甚至事件数据持久化时还可能需考虑引入分布式事务。

微服务间访问也可采用应用服务直接调用,实现数据和服务的实时访问,弊端就是跨微服务的数据同时变动须要引入分布式事务。分布式事务会影响系统性能,增长微服务间耦合,尽可能避免使用。

5 领域事件设计

5.1 构建和发布

基本属性

至少包括以下:

  • 事件惟一标识(全局惟一,事件可以无歧义在多个限界上下文中传递)
  • 发生时间
  • 事件类型
  • 事件源

即主要记录事件自己以及事件发生背景的数据。

业务属性

记录事件发生那刻的业务数据,这些数据会随事件传输到订阅方,以开展后续业务操做。

事件基本属性和业务属性一块儿构成事件实体,事件实体依赖聚合根。领域事件发生后,事件中的业务数据再也不修改,所以业务数据能够以序列化值对象的形式保存,这种存储格式在消息中间件中也比较容易解析和获取。

为保证事件结构的统一,一般建立事件的基类,子类可自行继承扩展。因为事件没有太多业务行为,实现通常比较简单。

事件发布前需先构建事件实体并持久化。 事件实体的业务数据推荐按需发布,避免泄露没必要要业务信息。

事件发布方式

  • 可经过应用服务或者领域服务发布到事件总线或MQ
  • 也可从事件表中利用定时程序或数据库日志捕获技术获取增量事件数据,发布到MQ

5.2 事件数据持久化

意义

  • 系统之间数据对帐
  • 实现发布方和订阅方事件数据的审计

当遇到MQ、订阅方系统宕机或网络中断,在问题解决后仍可继续后续业务流转,保证数据一致性。 毕竟虽然MQ都有持久化功能,但中间过程或在订阅到数据后,在处理以前出问题,须要进行数据对帐,这样就无法找到发布时和处理后的数据版本。关键的业务数据推荐仍是落库。

实现方案

  • 持久化到本地业务DB的事件表,利用本地事务保证业务和事件数据的一致性
  • 持久化到共享的事件DB。业务、事件DB不在同一DB,它们的数据持久化操做会跨DB,所以需分布式事务保证业务和事件数据强一致性,对系统性能有影响

5.3 事件总线(EventBus)

意义

实现同一微服务内的聚合之间的领域事件,提供事件分发和接收等服务。 是进程内模型,会在微服务内聚合之间遍历订阅者列表,采起同步或异步传递数据。

由于在微服务内部在同一个进程,事件总线相对好配置,它能够配置为异步的也能够配置为同步的。若是是同步就不须要落库。推荐少用微服务内聚合之间的领域事件,它会增长开发复杂度。 而微服务之间的事件,在事件数据落库后,经过应用服务直接发布到MQ。

事件分发流程

  • 如果微服务内的订阅者(其它聚合),则直接分发到指定订阅者
  • 微服务外的订阅者,将事件数据保存到事件库(表)并异步发送到MQ
  • 同时存在微服务内和外订阅者,则先分发到内部订阅者,将事件消息保存到事件库(表),再异步发送到MQ

5.4 MQ

跨微服务的领域事件大多会用到MQ,实现跨微服务的事件发布和订阅。 虽然MQ自身有持久化功能,但中间过程或在订阅到数据后,在处理以前出问题,须要进行数据对帐,这样就无法找到发布时和处理后的数据版本。关键的业务数据推荐仍是落库。

5.5 接收&&处理

微服务订阅方在应用层采用监听机制,接收MQ中的事件数据,完成事件数据的持久化后,就能够开始进一步的业务处理。领域事件处理可在领域服务中实现。

  • 事件是否被消费成功(消费端成功拿到消息或消费端业务处理成功),如何通知消息生产端?

由于事件发布方有事件实体的原始的持久化数据,事件订阅方也有本身接收的持久化数据。通常能够经过按期对帐的方式检查数据的一致性。

  • 在采起最终一致性的状况下,事件消费端若是出现错误,消费失败,但以前的业务都成功了,虽然记录了event dB,但后续如何处理,人工介入吗?若是人工介入再解决,前端用户会不会看到数据不一致,体验很差?

失败的状况应该比例是不多的。失败的信息可采用屡次重试,若是这个还解决不了,只能将有问题的数据放到一个问题数据区,人工解决。固然要确保一个前提,要保证数据的时序性,不能覆盖已产生的数据。

通常发布方不会等待订阅方反馈结果。发布方有发布的事件表,订阅方有消费事件表,可采用对帐方式发现问题数据。

管理

大型系统的领域事件有不少:

  • 作好源端和目的端数据的对帐处理,发现并识别处理过程当中的异常数据

异步的方式通常都有源端和目的端按期对帐的机制。好比采用相似财务冲正的方式。若是在发布和订阅之间事件表的数据发现异步数据有问题,须要回退,会有相应的代码进行数据处理,不过不一样的场景,业务逻辑会不同,处理的方式会不同。有的甚至还须要转人工处理。

  • 发现异常数据后,要有相应的处理机制
  • 选择适合本身场景的技术,保证数据正确传输

6 总结

领域事件在设计时咱们要重点关注领域事件,用领域事件来驱动业务的流转,尽可能采用基于事件的最终一致,下降微服务之间直接访问的压力,实现微服务之间的解耦,维护领域模型的独立性和数据一致性。

领域事件驱动机制可实现一个发布方N个订阅方的模式,这在传统的直接服务调用设计中基本是不可能作到的。

领域事件 V.S CQRS

CQRS主要是想读写分离,将没有领域模型的查询功能,从命令中分离出来。领域事件主要目的仍是为了微服务解耦,在连续的业务处理过程当中,以异步化的方式完成下一步的业务处理,下降微服务之间的直连。 它们的共同点就是经过消息中间件实现从源端数据到目的端数据的交互和分离。

若是你就是不想用领域事件,聚合之间还能够经过应用层来协调和交互。应用服务是全部聚合之上的服务,负责服务的组合和编排,以及聚合之间的协调。

参考

  • 《实现领域驱动设计》
  • 《领域驱动设计》
相关文章
相关标签/搜索