Apache Kafka不适用于Event Sourcing!

Apache Kafka是一款很酷的产品,但如果你考虑将其用于事件溯源,建议你应该再考虑一下。

Kafka是一个在生产者和消费者之间传递消息的绝佳工具,可选的主题持久性能让你永久存储消息,是的,永远,如果你愿意。

因此,如果你的消息是事件,可以将Kafka用作事件存储或事件日志,但它实际上不适合用于事件溯源。

原因如下:

1. 加载当前状态
当服务收到请求对事件源实体进行状态更改的命令时,我们首先需要重新创建该对象的当前状态,我们通过从事件存储加载该特定实体ID的所有先前事件,然后将它们重新调用我们的领域对象中的相应方法,快速播放事件直到当前状态。

在Kafka中加载像这样的特定实体的事件并不容易。主题通常以 “ 订单 ”,“ 付款 ”或“ 通知 ” 等实体类型为中心进行分区,因此我们必须检查所有 “ 订单 ”的所有事件,并按其ID过滤它们以得到一个“ 订单”实体的状态,虽然这是可能的,但它并不实用。

一种替代方案是每个实体有一个主题,但我们最终可能会遇到数以千计的主题,而且最重要的是,订阅服务的下游需要一种方法来自动发现每个新实体的新创建的主题,这 不太实际。

2. 一致的写操作
当我们的实体状态被重新创建时,是时候执行传入命令所请求的业务逻辑了,如果业务逻辑失败,我们会向客户端返回错误,但如果成功则会发出新事件。在这种情况下,我们必须能够将新事件保存到我们的事件存储中,同时保证在此期间没有为此特定实体ID存储其他事件,否则我们可能会破坏领域对象的一致性。

OCC来救援

保证写入一致性的一种方法是利用事件存储的乐观并发控制,正确的事件存储为用户提供了一种方式,即“仅当实体的版本仍为x时才保存此事件”,Kafka不支持这一点,来自该领域专家的建议解决方法似乎是在其前面放置一个“数据库”以提供一致性控制点,虽然在某些情况下这个建议可能是一个可行的解决方案,但从长远来看,选择更适合特定问题的工具可能更明智。

3. 单写
获得一致性的另一种方法是确保序列化写入,即使用单写入器原则,这意味着我们确保关于特定实体ID的所有写入都发生在单个线程上。我们可以通过使我们的生产者堵塞等到其发布的事件被提交且可用,这种设计会对性能产生严重影响。

它适合吗?
那么,Kafka在事件源架构中是否有一席之地?也许,大概,它可能是你的事件存储的一个很好的补充,作为将事件传输到下游查询服务或读取模型的方式。

但是,在为我们的系统添加大量复杂的基础设施时,我们应该始终小心 - 所有这些都需要付出成本,因此请确保它足以满足你手头的问题!

Apache Kafka is not for Event Sourcing – SERIALIZE

在基辅召开的JEEConf会议上,Amitay Horwitz描述了他和他的团队如何实施事件溯源的发票系统,他们在生产2年半后遇到的挑战以及他们如何使用Kafka Streams实施新设计。

Wix的软件工程师Horwitz 于2015年开始与他的团队一起开展新的发票服务,目的是帮助他们的客户管理发票并在线接收付款。在设计新服务时,他们希望创建一个小而简单的库,该库非侵入式,维护数据完整性,并且能够轻松添加自定义视图。为了实现所有目标,他们决定使用事件源架构来实现服务。

他们也遇到了问题,客户有时无法看到新创建的发票,因为从写入到读取是最终一致性。

但他们最大的问题是重建视图。确保在没有事件进入的情况下触发重建,事实证明它比预期的更复杂,特别是在具有来自各种服务器的事件的分布式环境中。这些问题使Horwitz寻求替代架构,同时保持事件溯源的好处。

Horwitz将Kafka描述为一个复制的,容错的,分布式附加日志。它通常用于pub-sub或队列,但他指出它可以做更多。Kafka中的基本结构是一个分区的主题,一个逻辑队列。生产者将根据消息中的key将消息推送到每个分区,然后消费者可以使用这些消息。对于事件源系统至关重要的两个重要特性是在单个分区内维护消息之间的排序,并且消息即使在消耗之后也可以存储

Kafka Streams将流处理带给了Kafka。它有两个主要的抽象:

1. Horwitz认为流中的数据流是一种无限有序和可重放的不可变数据序列,这使得它对于事件源系统来说很有趣。保证了顺序。

2. 能在静态数据中看到Table,表Table存储聚合数据的某个时间点状态视图,该视图在接收到新消息时更新。

在使用Kafka的发票服务的新设计中,有一个快照状态存储,用于保存每个聚合的当前状态,从命令流接收命令后,命令处理程序Handler会根据聚合ID从快照状态存储中读取相应聚合的当前状态;然后,该处理程序Handler确定命令是成功被执行还是失败了,并通过结果流返回结果;如果命令执行成功,则会创建事件并将其发送到事件存储库;接下来,读取这边系统查询有新事件的流,并将状态存储中的聚合更新为其新状态(banq注:注意更新状态在读取这边,不是写入那边),他指出,命令处理程序Hanlder逻辑可以用非常简洁和声明的方式编写,在他的示例中只有60行Scala代码。

在新的架构中,Kafka位于中心,微服务与Kafka通信,彼此之间也通过Kafka进行通信。他们还可以在创建分析报告时将信息推送到Kafka或提取信息,总之,Horwitz指出,新设计给了他们几个优势:

1. 一个简单的声明式系统
2. 最终的一致性现在已被接受并优雅地处理
3. 添加或更改视图很容易
4. 使用Kafka提高了可扩展性和容错能力

新设计仍处于评估阶段,尽管他们在生产中大量使用卡夫卡。他指出,有人声称Kafka不适合CQRS或事件来源系统,但他认为,如果能进行权衡,Kafka可以被利用。如果使用不同的客户端属性保存页面视图事件,就可以根据该事件信息轻松创建聚合,这是一种事件溯源形式,他认为Kafka非常适合。

通过使用聚合的标识作为Kafka的分区键key,同一聚合的所有命令将最终位于命令主题的同一分区中,并将在单个线程中(单写)按顺序处理。这样,在前一个生成所有下游事件之前不会处理任何命令,并且Horwitz指出这将创建强大的一致性保证。

https://www.infoq.com/news/2018/07/event-sourcing-kafka-streams

https://www.youtube.com/watch?v=b17l7LvrTco