首页 > 解决方案 > 事件采购 - 为什么是专门的事件存储?

问题描述

我第一次尝试实现事件溯源/CQRS/DDD,主要用于学习目的,其中有事件存储和消息队列(如 Apache Kafka)的想法,并且您有来自事件存储 => Kafka 的事件连接 JDBC/Debezium CDC => Kafka。

我想知道为什么需要一个单独的事件存储,因为它的目的听起来可以由 Kafka 本身通过其主要功能和日志压缩或配置日志保留以实现永久存储来实现。我应该将我的事件存储在像 RDBMS 这样的专用存储中以馈入 Kafka,还是应该将它们直接馈入 Kafka?

在此处输入图像描述

标签: apache-kafkadomain-driven-designcqrsevent-sourcing

解决方案


Kafka 可以用作 DDD 事件存储,但如果这样做会因为缺少一些功能而变得复杂。

人们在聚合事件溯源中使用的两个关键特性是:

  1. 通过仅读取该聚合的事件来加载聚合
  2. 当同时为聚合写入新事件时,请确保只有一个写入器成功,以避免破坏聚合并破坏其不变量。

卡夫卡目前不能做这任何一个,因为 1 失败,因为你通常需要每个聚合类型有一个流(它不会扩展到每个聚合一个流,无论如何这不一定是可取的),所以没有仅加载一个聚合的事件的方法,由于https://issues.apache.org/jira/browse/KAFKA-2260尚未实现,因此 2 失败。

因此,您必须以不需要功能 1 和 2 的方式编写系统。这可以按如下方式完成:

  1. 与其直接调用命令处理程序,不如将它们写入流。每个聚合类型都有一个命令流,按聚合 id 分片(这些不需要永久保留)。这可确保您一次只为特定聚合处理一个命令。
  2. 为所有聚合类型编写快照代码
  3. 处理命令消息时,请执行以下操作:
    1. 加载聚合快照
    2. 针对它验证命令
    3. 写入新事件(或返回失败)
    4. 将事件应用于聚合
    5. 保存新的聚合快照,包括事件流的当前流偏移
    6. 向客户端返回成功(可能通过回复消息)

唯一的其他问题是处理失败(例如快照失败)。这可以在特定命令处理分区的启动期间处理 - 它只需要重播自上次快照成功以来的任何事件,并在恢复命令处理之前更新相应的快照。

Kafka Streams 似乎具有使这变得非常简单的功能 - 您有一个 KStream 命令,您将其转换为一个 KTable(包含快照,由聚合 id 键入)和一个 KStream 事件(可能还有另一个包含响应的流)。Kafka 允许所有这些以事务方式工作,因此不存在更新快照失败的风险。它还将处理将分区迁移到新服务器等(发生这种情况时会自动将快照 KTable 加载到本地 RocksDB 中)。


推荐阅读