何时/为什么使用领域事件
[Evans]书中并没有给出领域事件的正式定义。这种模式是在该书出版之后才提出来的。在讨论领域事件之前,让我们先来看看当前对领域事件的定义:
领域专家所关心的发生在领域中的一些事件。
将领域中所发生的活动建模成一系列的离散事件。每个事件都用领域对象来表示……领域事件是领域模型的组成部分,表示领域中所发生的事情。[Evans, Ref,P-2〇]
我们如何确定哪些事件对领域专家是重要的呢?在我们与领域专家讨论时,我们需要仔细地听,找到领域事件的线索。考虑以下领域专家所说的关键词汇:
- “当……”
- “如果发生……”
- “当……的时候,请通知我”
- “发生……时”
当然,对于“当...的时候,请通知我”,这里的通知本身并不能构成一个事件,而只是表明我们需要向外界发出通知。另外,领域专家可能还会说“如果发生这样的事情,它并不重要;如果发生那样的事情,它就很重要了(将“这样”和“那样”用你自己领域中的事件予以替换)。”根据你的组织文化,可能还有更多的事件用语。
有时,从领域专家的话中,我们看不出领域事件的迹象,但是业务需求依然有可能需要领域事件。领域专家有可能意识不到这些需求,只有在跨团队讨论之后他们才能意识到这些。发生这样的事情往往是由于领域事件需要发布到外部系统中,比如发布到另一个限界上下文中(2)。由于这样的事件由订阅方处理,它将对本地和远程上下文产生深远的影响。
领域专家和领域事件
虽然领域专家在起初可能意识不到所有类型的领域事件,但是通过讨论之后,他们是应该能够了解到其中的原因的。当团队成员对领域事件达成一致之后,领域事件便是通用语言的正式组成部分了。
当领域事件到达目的地之后——无论是本地系统还是外部系统——我们通常都将领域事件用于维护事件的一致性。这是有意而为之的,并且是根据设计而来的。这样可以消除两阶段提交(全局事务),还可以支持聚合(10)原则。聚合的其中一个原则是,在单个事务中,只允许对一个聚合实例进行修改,由此产生的其他改变必须在单独的事务中完成。因此,本地限界上下文中的其他聚合实例便可以通过领域事件的方式予以同步。另外,领域事件还可以用于使远程依赖系统与本地系统保持一致。本地系统和远程系统的解耦有助于提高双方协作服务的可伸缩性。
图8.1向我们展示了领域事件的产生、存储、分发和使用。领域事件既可以由本地限界上下文所消费,也可以由外部的限界上下文消费。
现在,让我们来考虑一下系统中的批处理过程。在系统的非高峰时期,批处理过程通常进行一些系统维护工作,比如删除过期的对象、创建新的对象以支持新的业务需求、或者通知用户所发生的重要事件等。这样的批处理过程通常需要复杂的查询,并且需要庞大的事务支持。如果这些批处理过程存在冗余又会怎么样呢?
那么,让我们重新思考一下。对于系统中发生的每一件事情,我们都用事件的形式予以捕获,然后将事件发布给订阅方处理,这能达到简化系统的目的吗?答案是肯定的。它可以消除先前批处理过程中的复杂查询,因为我们能够准确地知道在何时发生了什么事情,限界上下文也由此知道“接下来应该做什么”。在接收到领域事件时,系统可以予以立即处理。这样一来,原本批量集中处理的过程可以分散成许多粒度较小的处理单元,业务需求也由此得到更快地满足,用户也可以及时地进行下一步操作。
聚合上的每一个命令方法都会产生事件吗?与何时需要使用领域事件同等重要的是,我们需要知道何时不应该使用领域事件。出于技术实现和协作系统上的考虑,有时领域事件可以提供比领域专家所要求的更多的功能,比如可以用于事件源(4,附录A)。
我们将在集成限界上下文(13)中对此进行探讨,这里我们只讨论领域事件的核心方面。