领域事件

捕获对领域有影响的有趣事件的记忆

2005年12月12日

这是我在2000年代中期进行的《进一步的企业应用程序架构开发》写作的一部分。不幸的是,此后太多其他事情吸引了我的注意力,所以我没有时间进一步研究它们,而且在可预见的未来我也没有看到太多时间。因此,这些材料非常草稿形式,我不会进行任何更正或更新,直到我有时间再次处理它。

我星期二去巴布尔的餐厅吃饭,用信用卡支付。这可以建模为一个事件,其事件类型为“购买”,其主体为我的信用卡,其发生日期为星期二。如果巴布尔使用旧的手动系统,并且直到星期五才传输交易,则通知日期将为星期五。

事情发生了。并非所有事情都很有趣,有些事情可能值得记录,但不会引起反应。最有趣的事情会引起反应。许多系统需要对有趣的事件做出反应。通常,您需要知道系统为何以这种方式做出反应。

通过将系统输入引导到领域事件流中,您可以保留系统所有输入的记录。这有助于您组织处理逻辑,并允许您保留系统输入的审计日志。

工作原理

一个领域事件的本质在于,您使用它来捕获可以触发您正在开发的应用程序状态更改的事物。然后处理这些事件对象以引起系统更改,并将其存储以提供审计日志

图 1:事件将输入引导到单个源。

图 1有助于说明这一点。这里我们有一个系统,它从用户界面、消息系统和对数据库表的某些直接操作接收输入。为了将这些解析为领域事件,我们拥有与这些输入流中的每一个交互并将其输入转换为领域事件流的组件,这些组件存储在持久日志中。然后,事件处理器从日志中读取事件并处理它们,触发我们的应用程序执行它应该执行的操作。

在这个流中,系统的第一个输入层除了创建和记录事件之外,对刺激没有任何作用。然后,第二层可以忽略实际的输入源,它只是对事件做出反应并处理它。

对于此示例,我只显示了一个事件日志。在实践中,如果事件具有不同的响应要求,将日志分开通常是有意义的。用户界面通常需要比许多远程消息系统快得多的响应时间,因此将用户界面流量放入不同的日志并使用单独的处理器处理它们是有意义的。

该图暗示了一种异步管道和过滤器式的交互方式,但这并不是该方法的必要部分。实际上,一种常见的方法,特别是对于用户界面刺激,是让 UI 处理程序在同步交互中直接调用事件处理器。

每个领域事件都捕获来自外部刺激的信息。由于这是记录的,并且我们希望将日志用作审计跟踪,因此此源数据必须是不可变的。也就是说,一旦创建了事件对象,就不能更改此源数据。但是,事件上还有另一种数据记录系统对其做了什么 - 我称之为处理数据。我将领域事件上的数据描述为不可变的源数据,它捕获事件的含义,以及可变的处理数据,它记录系统对事件的响应。信用卡收费的源数据将包括收费金额、供应商等。处理数据可能包括它出现在哪个报表上。如果您的平台对不可变对象有特殊支持,则可能值得将事件拆分为两个对象以利用此功能。

尽管源数据永远不会改变,但系统可能需要处理更改 - 通常是因为原始事件不正确。您可以通过将此更改作为单独的追溯事件来处理。然后,处理器处理追溯事件以纠正早期错误事件的后果。通常,这种处理可以在非常通用的级别上完成。

事件数据的第三类,但只偶尔出现,是从事件流中其他数据的推导中缓存的数据。在这些情况下,事件处理器在处理当前事件时汇总来自过去事件的信息,并将该汇总数据添加到当前事件中以加快未来的处理。与任何缓存一样,重要的是要表明此数据可以自由删除并重新计算,如果发生任何调整。

不同的事件发生在不同的原因,因此使用不同类型的事件很常见。然后,事件处理器将使用事件类型作为其调度机制的一部分。事件类型可以使用事件的子类型、单独的事件类型对象或两者的混合来表示。

不同类型的事件通常携带不同的数据,因此使用事件的子类型来处理事件类型非常适合这一点。子类型的问题是会导致类型激增,如果大部分数据相同,这尤其令人沮丧。混合方法使用子类型来处理不同的数据,并使用事件类型对象来发出调度信号。

事件是关于某个时间点发生的事件,因此事件自然包含时间信息。在执行此操作时,重要的是要考虑可以与事件一起存储的两个时间点:事件在世界中发生的时刻和事件被注意到的时刻。这些时间点对应于实际时间和记录时间的概念。

当然,您并不总是需要这两个时间点,但您应该始终考虑是否需要这两个时间点。危险在于选择一个时间点,并且在当时或之后不清楚您选择了哪个时间点。因此,我还建议您明确命名时间点以指示它是哪一个。

您可能需要多个记录时间点来记录不同系统何时注意到事件。

何时使用它

通过领域事件捕获系统刺激是一个重大决定。它对应用程序施加了一种独特的架构风格和编程模型,这种模型通常看起来很笨拙 - 至少最初是这样。目前尚不清楚这种方法在您习惯之后是否真的需要更多努力。

尽管它不寻常,但我认为使用这种方法有一些显着的好处。

事件的审计日志提供了一个完整的记录,这对审计和调试目的都很有价值。如果系统进入奇怪的状态,您将拥有导致该状态的所有输入的完整日志。通过存储实际处理的事件,您可以减少忽略将重要信息写入审计日志的可能性。

清晰的事件流使其他系统更容易在将来通过添加消息路由器来转移事件到新系统,从而替换应用程序的全部或部分。尽管以有利于其最终消亡的方式设计系统并不流行,但系统更换项目的频率应该意味着我们应该更加关注它。

领域事件对于事件溯源来说尤其重要,它是一种将系统组织起来的方式,以便所有更新都是通过领域事件进行的。