事件驱动架构和事件结构

Event-driven architecture and structure of events

我是 EDA 的新手,我已经阅读了很多关于好处的文章,并且可能有兴趣在我的下一个项目中应用它,但仍然不明白一些东西。

引发事件时,哪种模式最适合:

  1. 为事件命名 "CustomerUpdate" 并包括有关客户的所有信息(更新与否)
  2. 将事件命名为 "CustomerUpdate" 并仅包含真正更新的信息
  3. 为事件命名 "CustomerUpdate" 并包含最少的信息(标识符)and/or 一个 URI,让消费者检索有关此客户的信息。

我问这个问题是因为我们的一些活动可能很繁重而且很频繁。

感谢您的回答和时间。

Name the event "CustomerUpdate"

首先让我们从您的活动名称开始。事件的目的是描述已经发生的事情。这与命令不同,命令是针对尚未发生的事情

发出指令

您的活动名称“CustomerUpdate”在这方面听起来含糊不清,因为它可能描述过去或未来的事情。

CustomerUpdated 会更好,但即便如此,Updated 是另一个含糊不清的术语,并且在业务上下文中是非特定的。为什么在这种情况下更新了客户?是因为他们更改了付款明细吗?搬家了吗?他们是否从白银升级为黄金?事件可以根据需要进行具体化。

乍一看这似乎想得太多了,但是当您从事件有效负载中删除数据和上下文时,事件命名变得尤为重要,更倾向于 skinny 事件(“选项3" 来自你的问题,我将在下面讨论)。

这并不是说在这种粒度级别上定义事件总是合适的,只是它是一种在项目早期向您开放的途径,可能会在以后支付红利(或者可能会淹没你有成千上万的事件类型)。

回到你的实际问题,让我们依次考虑你的每个选项:

Name the event "CustomerUpdate" and include all information (updated or not) about the customer

让我们称这种“模式”为 Fat 消息。

胖消息(也称为快照)表示所描述实体在给定时间点的状态以及有效负载中存在的所有事件上下文。它们很有趣,因为消息本身代表服务和消费者之间的契约。它们可用于在业务域之间传达状态更改,其中可能首选在消费者处理消息期间出现所有事件上下文。

优点:

  • 自洽 - 可以在不了解其他系统的情况下完全使用。
  • 易于使用(更新插入)。

缺点:

  • 脆弱 - 服务和消费者之间的契约与消息本身耦合。
  • 如果消息以错误的顺序到达,很容易用旧数据覆盖当前数据(提示:您可以通过使用 event sourcing 模式来缓解这种情况)
  • 大。

Name the event "CustomerUpdate" and include only information that have really been updated

我们称此模式为 Delta 消息。

Delta 在许多方面类似于胖消息,但它们的生成和使用通常更复杂。这里的一个很好的例子是 JSONPatch 标准。

因为它们只是事件实体的部分描述,增量还带有一个 built-in 假设,即消费者对所描述的事件有所了解。出于这个原因,它们可能不太适合在业务领域之外发送,事件实体可能不太为人所知。

Deltas 在共享相同实体模型的系统之间同步数据时真正发挥作用,理想情况下持久保存在 non-relational 存储中(例如,no-sql)。在这种情况下,可以检索一个实体,应用增量,然后以最小的努力再次持久化。

优点:

  • 小于 Fat 消息
  • 在涉及共享实体模型的用例中表现出色
  • 可移植(如果基于 jsonpatch 等标准,或在较小程度上基于 diffgram)

缺点:

  • 与 Fat 消息类似,假设完全了解数据实体。
  • 很容易用旧数据覆盖当前数据。
  • 生成和使用复杂(特定用例除外)

Name the event "CustomerUpdate" and include minimum information (Identifier) and/or a URI to let the consumer retrieves information about this Customer.

我们称其为 Skinny 消息。

瘦消息与您定义的其他消息模式不同,因为 service/consumer 合同在消息中不再显式,而是隐含在稍后的某个时间,消费者将检索事件上下文.这解耦了契约和消息交换,这是一件好事。

这可能适合也可能不适合 cross-business 事件的域通信,具体取决于您的企业的设置方式。因为事件有效载荷非常小(通常是一个带有一些 headers 的 ID),所以除了事件的名称之外,没有上下文可以作为消费者处理决策的基础;因此,确保正确命名事件变得更加重要,尤其是当消费者可以通过多种方式处理 CustomerUpdated 消息时。

此外,在事件数据中包含实际资源地址可能不是好的做法 - 因为事件是已经发生的事情,事件消息通常是不可变的,因此事件中的任何信息都应该永远为真,以防万一事件需要重播。在这种情况下,资源地址可以很容易地变得过时,事件不会 re-playable.

优点:

  • 将服务合同与消息分离。
  • 事件名称中包含的事件信息。
  • 自然幂等(time-stamp)。
  • 通常很小。
  • 易于生成和使用。

缺点:

  • 消费者必须进行额外调用以检索事件上下文 - 需要其他系统的明确知识。
  • 事件上下文在消费者检索它时可能已经过时,这使得这种方法通常不适合某些 real-time 应用程序。

When raising an event, which pattern is the most suited?

我认为这个问题的答案是:这取决于很多事情,而且可能没有一个正确答案。

评论更新:也值得一读,非常古老的经典博客 post 关于消息传递:https://docs.microsoft.com/en-gb/archive/blogs/nickmalik/killing-the-command-message-should-we-use-events-or-documents (also here: http://vanguardea.com/killing-the-command-message-should-we-use-events-or-documents/)

Martin Fowler 发表了关于“The Many Meanings of Event-Driven Architecture" (the content is based on this paper) 的精彩演讲,其中他提到了事件承载状态传输模式。

它似乎接近您的第二个选项 "Delta message",不同之处在于它不尝试描述实体,而是描述发生的命名业务事实并将所有必要的数据转移到了解这个事实。

我认为在设计域事件时如何为持久层建模并不重要。同样,我认为您的消费者在设计域事件时如何为自己的持久层建模并不重要。

因此,我认为将事件作为补丁直接应用到数据上这一事实并不明智(从消费者的角度来看),因为它会促使生产者进行设计给定消费者持久性模型的事件。

在那种情况下,我倾向于认为您是在设计持久性补丁,而不是域事件。

你怎么看?