当心!事件驱动架构中的反模式

Avatar
不若风吹尘
2024-06-18T16:35:57
261
0

事件驱动架构中有一些模式和常见实践,它们是解决各种问题的方案。问题在于,当你在没有这些问题的情况下应用这些模式,或者在一开始就避免了这些问题时,它们可能会变成反模式。以下是一些模式及其在特定上下文中的可能成为反模式的情况。

泄露内部细节

如果每个服务都有自己的存储/数据库,那么我们需要谨慎处理那些我们不希望泄露给服务边界的表结构和数据。业界已经达成共识,数据库层面的耦合是个坏主意,因为它限制了我们的进化能力。

image-1024x381.png

我们不希望一个服务直接从另一个服务的数据库通信查询。相反,我们应该提供一些 API,以便于版本控制、速率限制、监控等。

但在事件驱动架构中,如果你通过将内部数据结构泄露到事件中而暴露它们,你可能不会那么明显地意识到自己也在数据库层面进行耦合。

内部事件外部事件 之间存在区别。

内部事件是在服务边界内私有的。我们不想公开它们。在这种情况下,内部事件只限于服务内部。服务将是这些事件的生产者和唯一消费者。因此,你有更大的灵活性来演化和改变。

外部事件是公开的,旨在供其他服务边界使用。它们是合同。如果你不想打破现有的客户端,对于这些事件,你需要采用非常不同的版本策略。事件成为了你的 API,需要像对待 API 一样对待它们。

image-1-1024x648.png

通常,内部事件会被发布到其他服务边界,从而意外地泄露了你的数据的内部表结构细节。这通常是由于我们倾向于以数据为中心思考事件。

如果你的 UI 高度依赖于 CRUD(创建、读取、更新、删除)操作,你可能会发布基于 CRUD 的事件。我称之为“实体 CRUD”,这是一种更粗粒度的事件,包含与实体相关的所有属性的数据,如CustomerChanged

或者它可以更细致,即“属性 CRUD”。这样,你仍然以数据为中心,但事件只包含与实体属性变化相关的数据,例如CustomerNameChanged

随着变化数据捕获(CDC)工具的兴起,这种情况变得更加突出。任何对数据库的更改都会被 CDC 工具监控,然后根据这些更改生成事件并发布到代理(或日志)上。

image-2-1024x343.png

然后,其他服务边界会消费这些事件。通常,它们用于保持对另一个服务数据的本地缓存。

这导致使用事件作为数据分发的形式。

那么,何时会成为反模式?当你泄露内部实现细节(表结构)且没有意识到正在创建的耦合时。

基于 CRUD 的操作事件并不明确。CustomerChangedCustomerNameChanged 并没有向其他消费者指示为什么发生了变化,它只是表示数据发生了变化。如果你试图从更改的数据中推断原因,你可能会错误地解释事件的原因。

如果你想用事件表示工作流程,请事件明确。

命令

当你的工具仅支持发布订阅模式时,很容易将一切都变成事件。但并非如此。命令与事件是不同的。正如俗话所说,“手里拿着锤子,不是所有问题都是钉子”。

image-3-1024x280.png

命令触发行为,而事件通知我们发生了什么。可以将命令视为请求,而事件视为通知。命令的结果常常是事件。

命令只有一个消费者。必须有一个消费者。仅此而已。它们不使用发布订阅模式。强迫所有东西都遵循非所需模式会导致错误地应用更多模式。

顺序

通常希望按顺序处理消息,特别是在我之前提到的使用事件作为数据分发的情况下。这是因为你想确保不会因为处理新事件后才处理旧事件而覆盖旧数据。

不同的代理有不同的处理方式,但它们都有一个需要了解的限制。如果你想按顺序处理消息,那就会牺牲并发性。

代理可能会使用分区来管理这一点(例如 Kafka)。这意味着一个分区只有一个消费者。这个消费者可能负责多个分区,但每个分区只有一个消费者。这使得单个消费者能够有序地处理/消费消息。

697dbd30-d510-41d4-9966-7b83cb5c3039.png

这可能会限制扩展性和吞吐量,因为你无法同时处理消息。通常,这是通过竞争消费者的模式来实现的。更多内容请参考我的文章《竞争消费者模式:扩展性指南》。

有很多情况,你可能会认为自己需要顺序事件处理,但实际上并不需要。还有其他方法来处理这个问题,比如使用 Saga,它可以处理乱序的消息。在现实世界中,这其实很常见,关键是要拥抱异步。例如,可以参考我的文章《Pub/Sub 或队列中的消息排序》。

查询

当你开始进入事件驱动架构和异步消息时,你可能会想把一切都变成事件,这时就可能出现反模式。这类似于我在命令模式下的讨论。但并非所有东西都是异步的,尤其是查询。

对于命令/队列,有一种模式叫做请求响应,但我也注意到它适用于主题和事件。想法是有一个单独的队列(对于事件来说,是主题),用作响应通道。事件的初始发布者会监听这个其他频道以获取响应。

image-4-1024x304.png

问题是,这就像把方形钉子塞进圆形孔里。不要强求。

查询本质上是同步的请求响应。你想要立即知道并得到响应。使用事件和发布订阅完全不适合这种情况。

事件驱动架构中的反模式

并非所有东西都是事件。在事件驱动架构中,很容易落入这些反模式的陷阱。在使用事件作为数据分发以及由此产生的耦合时要谨慎。将内部数据模式泄露出来,与让另一个服务与你的私人数据库交互并理解其模式并无不同。你只是在数据库级别进行了耦合,只是通过事件的方式。

并非所有东西都需要使用发布订阅或异步。命令可能是同步的请求响应,也可能是异步的。但命令和事件具有不同的用途和语义。

查询本质上是同步的请求响应。

Last Modification : 9/20/2024 4:42:07 AM


In This Document