事件驱动架构和事件结构

24

我是新手EDA,已经阅读了许多关于其优点的内容,可能会在我的下一个项目中应用它,但仍然有些不理解。

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

  1. 将事件命名为"CustomerUpdate",并包括客户的所有信息(已更新或未更新)
  2. 将事件命名为"CustomerUpdate",并只包括真正被更新的信息
  3. 将事件命名为"CustomerUpdate",并包括最少的信息(标识符)和/或URI,以便使用者检索有关此客户的信息。

我提出这个问题是因为我们的一些事件可能很重,而且频繁。

感谢您的回答和时间。

2个回答

50
请将以下内容翻译成中文:

Name the event "CustomerUpdate"

首先,让我们从事件名称开始。事件的目的是描述已经发生的事情。这与命令不同,命令是为尚未发生的事情下达指令。

您的事件名称“CustomerUpdate”在这方面听起来有些模糊,因为它可能描述过去或未来的事情。

“CustomerUpdated”会更好一些,但即使如此,“Updated”也是另一个模糊的术语,在业务环境中没有具体含义。在这种情况下,客户是否更新了他们的付款信息?搬家了吗?升级到白金会员?事件可以根据需要变得具体。

这一点乍一看可能会被认为是过于深思熟虑,但随着您从事件负载中删除数据和上下文,并向着“skinny”事件(您问题中的“选项3”,我将在下面讨论)转移,事件命名变得尤为重要。

这并不意味着总是适合以这种细粒度定义事件,只是在项目早期就打开了这条路,后期可能会带来回报(或者可能会给您带来数千个事件类型)。

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

将事件命名为“CustomerUpdate”,并包括有关客户的所有信息(已更新或未更新)

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

Fat消息(也称为快照)表示在给定时间点上所描述实体的状态,并且负载中包含所有事件上下文。它们很有趣,因为消息本身代表服务和消费者之间的契约。它们可用于在业务领域之间通信状态更改,在此期间可能希望在消费者的消息处理期间存在所有事件上下文。

优点:

  • 自洽-可以完全在不了解其他系统的情况下使用。
  • 易于消费(upsert)。

缺点:

  • 脆弱-服务和消费者之间的契约与消息本身耦合。
  • 如果消息以错误的顺序到达,则很容易用旧数据覆盖当前数据(提示:您可以通过使用event sourcing模式来减轻此问题)
  • 大。
请将以下内容翻译成中文:

将事件命名为“CustomerUpdate”,并仅包括确实已更新的信息

让我们称这种模式为Delta消息。

在许多方面,Delta与Fat消息类似,但它们通常更复杂,难以生成和消费。这里有一个很好的例子,即JSONPatch标准。

由于它们只是事件实体的部分描述,因此Delta还带有一种内置假设,即使用者了解所描述的事件。因此,它们可能不太适合发送到业务领域之外,在那里事件实体可能不为人所知。

当在共享相同实体模型的系统之间同步数据时,Delta真正发挥作用,理想情况下,该模型存储在非关系型存储中(例如,无SQL)。在这种情况下,可以检索实体,应用Delta,然后再次进行持久化,而不需要太多的努力。

优点:

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

缺点:

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

将事件命名为“CustomerUpdate”,并包括最少信息(标识符)和/或URI,以便让使用者检索有关此客户的信息。

我们称之为消息。

与您定义的其他消息模式不同,瘦消息的服务/使用者合同不再显式存在于消息中,而是暗示消费者将在稍后的某个时间检索事件上下文。这样可以将合同和消息交换分离开来,这是一件好事。

这可能适用于跨业务域事件通信,也可能不适用,具体取决于企业的设置方式。由于事件负载非常小(通常是带有一些标头的ID),因此除了事件名称外,消费者没有其他上下文可基于其进行处理决策; 因此,如果有多种方法可以处理CustomerUpdated消息,则更重要的是确保事件的名称适当命名。

此外,在事件数据中包含实际的资源地址可能不是一个好的做法,因为事件已经发生,事件消息通常是不可变的,因此事件中的任何信息应该永远是真实的,以防需要重播事件。在这种情况下,资源地址很容易变得过时,事件将无法被重播。
优点:
- 将服务契约与消息分离。 - 事件名称中包含有关事件的信息。 - 自然幂等(带有时间戳)。 - 通常很小。 - 生成和使用简单。
缺点:
- 消费者必须进行额外的调用以检索事件上下文 - 需要明确其他系统的知识。 - 在消费者检索它的时候,事件上下文可能已经过时,使得这种方法通常不适用于一些实时应用程序。
引用:
当触发事件时,哪种模式最适合?
我认为答案是:这取决于很多事情,可能没有一个正确答案。

评论更新:还值得一读的是一篇非常古老、经典的关于消息传递的博客文章:https://learn.microsoft.com/en-gb/archive/blogs/nickmalik/killing-the-command-message-should-we-use-events-or-documents(也可以在这里找到:http://vanguardea.com/killing-the-command-message-should-we-use-events-or-documents/


1
感谢您提供这么详细的答案。当然,“这取决于情况”通常是一个明智的回答,但是上下文和优缺点列表将明确帮助我做出正确的选择。此外,现在我确认这些模式并不完全愚蠢。 - Cédric L. Charlier
1
@CédricL.Charlier - 没问题。如果你想阅读更多,请查看这个链接。它介绍了一种名为文档的消息类型,作为事件的替代品:http://blogs.msdn.com/b/nickmalik/archive/2007/08/07/getting-away-from-request-and-response-soa.aspx - tom redfern
1
“_Skinny_”事件的一个主要缺点不是在接收方决定检索时事件上下文可能已经改变了吗?或者您是建议必须维护每个事件上下文? - Stephen Drew
1
@StephenDrew - 是的,那是个好观点,谢谢。我已经更新了答案以反映这一点。 - tom redfern
1
我认为这是对不同风格的很好比较,尽管我不同意消息脆弱性的缺点:“脆弱 - 服务和消费者之间的合同与消息本身耦合。”只有在依赖无模式序列化格式(即纯JSON)时才是真的。像AvroProtobufThrift这样的格式即使在“fat”场景中也将合同与实际消息解耦。因此,我建议大家不要在这些情况下使用JSON,而是提出一种支持向后兼容的模式格式。 - Thomas Ploch
显示剩余2条评论

3
马丁·福勒(Martin Fowler)在关于 "事件驱动架构的许多含义" 的演讲中提到了事件携带状态转移模式,该内容基于这篇论文
它似乎与您的第二个选项“增量消息”相似,但不同之处在于它不试图描述实体,而是描述一个命名的业务事实,并携带所有必要的数据以理解此事实。
我认为,在设计领域事件时,您建模持久性层的方式并不重要,同样,在设计领域事件时,消费者建模其自己的持久性层也并不重要。
因此,我认为将事件视为消费者可以直接应用于其数据的补丁的优势并不明智,因为这会迫使生产者根据消费者的持久性模型来设计他们的事件。
在那种情况下,我倾向于认为您正在设计持久性补丁,而不是领域事件。
你怎么看?

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接