通知事件的设计

Design of notification events

我正在设计一些事件,这些事件将在执行操作或系统中的数据更改时引发。这些事件可能会被许多不同的服务使用,并将被序列化为 XML,尽管更广泛地说,我的问题也适用于 Webhooks 等更现代的时髦事物的设计。

我正在特别考虑如何描述事件的变化,并且在不同的实现之间做出选择时遇到困难。让我说明一下我的困惑。

假设创建了一个客户,并引发了一个简单的事件。

<CustomerCreated>
  <CustomerId>1234</CustomerId>
  <FullName>Bob</FullName>
  <AccountLevel>Silver</AccountLevel>
</CustomerCreated>

现在假设 Bob 花了很多钱并成为金牌客户,或者确实有任何其他 属性 变化(例如:他现在更喜欢被称为 Robert)。我可以发起这样的活动。

<CustomerModified>
  <CustomerId>1234</CustomerId>
  <FullName>Bob</FullName>
  <AccountLevel>Gold</AccountLevel>
</CustomerModified>

这很好,因为 Created 和 Modified 事件的架构相同,并且任何订阅者都会收到实体的完整当前状态。然而,如果不跟踪状态本身,任何接收器都很难确定哪些属性发生了变化。

然后我想到了这样一个事件。

<CustomerModified>
  <CustomerId>1234</CustomerId>
  <AccountLevel>Gold</AccountLevel>
</CustomerModified>

这更紧凑,只包含已更改的属性,但缺点是接收方必须应用更改并在需要时重新组合实体的当前状态。此外,Created 和 Modified 事件的模式现在必须不同; CustomerId 是必需的,但所有其他属性都是可选的。

然后我想到了这个。

<CustomerModified>
  <CustomerId>1234</CustomerId>
  <Before>
    <FullName>Bob</FullName>
    <AccountLevel>Silver</AccountLevel>
  </Before>
  <After>
    <FullName>Bob</FullName>
    <AccountLevel>Gold</AccountLevel>
  </After>
</CustomerModified>

这涵盖了所有基地,因为它包含完整的当前状态,而且接收者可以找出发生了什么变化。 BeforeAfter 元素与 Created 事件具有完全相同的架构类型。但是,它非常冗长。

我一直在努力寻找任何好的事件示例;还有其他我应该考虑的模式吗?

与其尝试通过有效载荷 xmls' 字段传达所有信息,您可以根据 - 区分不同的操作 1.根据操作不同的端点URL(这是首选) 2. 将操作码(操作代码)作为 xml 文件中的一个元素,它告诉使用哪个操作来处​​理传入的请求。(更接近您的示例)

有一些企业模式适用于您的业务案例——消息传递及其变体,如果您的系统是可扩展的,那么应该使用企业服务总线。 ESB 允许可靠地处理事件和处理。

事件应该用于描述意图和详细信息,例如,您可以有一个 CustomerRegistered 事件,其中包含已注册客户的所有详细信息。然后在流的后面是一个 CustomerMadeGoldAccount 事件,它只真正需要捕获帐户已更改为黄金的客户的客户 ID。

由事件的消费者来构建他们感兴趣的系统当前状态。

这只允许在每个事件中存储最相关的信息,想象一下,如果每个更改单个 属性 的命令都必须引发一个包含所有属性的事件,那么假设一个客户有数百个属性之后,这很快就会变得笨拙。如果您只是发布一个通用的 CustomerModified 事件,也很难确定为什么会发生更改,这通常是一个关于实体当前状态的问题。

只捕获与事件相关的数据意味着发出事件的命令只需要有足够的实体数据来验证命令是否可以执行,它甚至不需要读取整个客户实体。

事件的订阅者也只需要为他们感兴趣的事物建立一个状态,例如。也许 'account level' 小部件正在监听这些事件,它只需要保留客户 ID 和帐户级别,以便它可以显示客户所处的帐户级别。

您将问题标记为 "Event Sourcing",但您的问题似乎更多关于事件驱动的 SOA。

我同意@Matt 的回答--"CustomerModified" 如果有多个业务原因导致客户发生变化,则不够细化以捕捉意图。

但是,我会进一步支持并要求您考虑为什么要将客户信息存储在本地服务中,而您似乎(大概)已经拥有客户的真实来源。使用客户信息的起点应该是在需要时从源头获取它。存储可以从源可靠地查询的信息副本很可能是一种不必要的优化(和复杂化)。

即使您确实需要在本地存储客户数据(并且肯定有需要这样做的正当理由),请考虑仅传递构造真实来源查询所需的数据(发出事件的服务) ):

<SomeInterestingCustomerStateChange>
  <CustomerId>1234</CustomerId>
</SomeInterestingCustomerStateChange>

所以这些事件类型可以根据需要进行细化,例如"CustomerAddressChanged"或简单的"CustomerChanged",由消费者根据事件类型查询所需的信息。

没有 "one-size-fits-all" 解决方案——有时随事件传递相关数据确实更有意义。同样,如果这是您需要前进的方向,我同意@Matt 的回答。

根据评论编辑

我同意使用 ESB 进行查询通常不是一个好主意。有些人以这种方式使用 ESB,但恕我直言,这是一种不好的做法。

你原来的问题和你对这个答案的评论,以及你对马特关于仅包括已更改字段的谈话的评论。这在许多语言中肯定是有问题的,你必须以某种方式区分 属性 是 empty/null 和 属性 不包含在事件中。如果事件正在获得 serialized/de-serialized from/to 静态类型,那么了解 "First Name is being set to NULL" 和 "First Name is missing because it didn't change".

之间的区别将是痛苦的(如果不是不可能的话)

根据您关于系统同步的评论,我的建议是发送每次更改的完整数据集(假设信号+查询不是一个选项)。这将数据的解释留给了每个消费系统,并将发布者的责任限制为发出更通用的事件,即 "Customer 1234 has been modified to X state"。这个事件似乎比其他选项更广泛有用,如果其他系统收到这个事件,他们可以按照他们认为合适的方式解释它。他们可以 dump/rewrite 自己的客户 1234 数据,或者他们可以将其与现有数据进行比较,然后仅更新更改的内容。仅发送更改的内容似乎更适合单个消费者或特定类型的消费者。

综上所述,我认为您提出的任何解决方案都不是 "right" 或 "wrong"。您最清楚什么最适合您的独特情况。