【问题标题】:Event-driven architecture and structure of events事件驱动架构和事件结构
【发布时间】:2021-03-05 18:05:04
【问题描述】:

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

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

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

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

感谢您的回答和时间。

【问题讨论】:

    标签: events architecture soa event-driven eda


    【解决方案1】:

    将事件命名为“CustomerUpdate”

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

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

    CustomerUpdated 会更好,但即便如此,Updated 也是另一个模棱两可的术语,在业务环境中是不具体的。为什么在这种情况下更新了客户?是因为他们更改了付款细节吗?搬家了?他们是从白银升级到黄金的吗?活动可以根据需要进行具体化。

    起初这似乎是多虑了,但是当您从事件有效负载中删除数据和上下文时,事件命名变得特别相关,更倾向于 skinny 事件(您问题中的“选项 3” ,我将在下面讨论)。

    这并不是说在这个粒度级别定义事件总是合适的,只是它是一个在项目早期向您开放的途径,它可能会在以后产生红利(或者可能会让您陷入困境)数以千计的事件类型)。

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

    将事件命名为“CustomerUpdate”并包含所有信息(更新 与否)关于客户

    我们把这个“模式”称为信息。

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

    优点:

    • 自我一致 - 无需了解其他系统即可完全使用。
    • 使用简单(更新插入)。

    缺点:

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

    将事件命名为“CustomerUpdate”并仅包含具有 真的更新了

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

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

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

    当在共享相同实体模型的系统之间同步数据时,Deltas 真的很出色,理想情况下是持久保存在非关系存储中(例如,no-sql)。在这种情况下,可以检索实体,应用增量,然后以最小的努力再次持久化。

    优点:

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

    缺点:

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

    将事件命名为“CustomerUpdate”并包含最少信息 (标识符)和/或让消费者检索信息的 URI 关于这个客户。

    我们称其为 Skinny 消息。

    Skinny 消息与您定义的其他消息模式不同,因为服务/消费者合同不再在消息中明确显示,而是暗示在稍后消费者将检索事件上下文。这将合约和消息交换解耦,这是一件好事。

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

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

    优点:

    • 将服务合同与消息分离。
    • 事件名称中包含有关事件的信息。
    • 自然幂等(带时间戳)。
    • 通常很小。
    • 生成和使用简单。

    缺点:

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

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

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

    来自 cmets 的更新:也值得一读,一篇关于消息传递的非常古老的经典博文:https://docs.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/

    【讨论】:

    • 感谢这个详细的答案。自然,it取决于通常是一个聪明的答案,但上下文和优势/劣势列表显然会帮助我做出一个好的选择。另外,现在我确认这些模式并非完全愚蠢。
    • @CédricL.Charlier - 没问题。如果您热衷于阅读更多内容,请查看此内容。它引入了一种称为 Document 的消息类型来替代事件:blogs.msdn.com/b/nickmalik/archive/2007/08/07/…
    • skinny 事件的主要缺点不是当接收方决定检索它时事件上下文可能已更改,或者您是否建议每个事件上下文必须维护?
    • @StephenDrew - 是的,这是一个很好的观点,谢谢。我已经更新了反映的答案。
    • 我认为这是对不同风格的一个很好的比较,虽然我不同意消息脆弱的缺点:Brittle - the contract between service and consumer is coupled to the message itself.这仅在依赖无模式序列化格式时才是正确的,即纯 JSON。 AvroProtobufThrift 等格式将合约与实际消息分离,即使在“胖”场景中也是如此。因此,我不鼓励所有人在这些场景中使用 JSON,而是提出一种支持向后兼容模式演变的模式格式。
    【解决方案2】:

    Martin Fowler 就“The Many Meanings of Event-Driven Architecture”(内容基于this paper)做了一场精彩的演讲,其中提到了 Event-Carried State Transfer 模式。

    它似乎接近您的第二个选项“Delta 消息”,不同之处在于它不尝试描述实体,而是描述发生的命名业务事实并保留所有必要的数据以理解该事实.

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

    因此,我认为将您可以将事件作为补丁直接应用于您的数据这一事实作为一个优势(从消费者的角度来看)是不明智的,因为它会促使生产者设计他们的事件给定消费者的持久性模型。

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

    你怎么看?

    【讨论】: