如何在不发送模式或使用注册表的情况下使用 Akka Persistence 发展 Avro 模式?

How to evolve Avro schema with Akka Persistence without sending schema or using a registry?

我们正在考虑为基于 scala 的 Akka Persistence 应用程序采用序列化方法。我们认为我们的持久化事件很可能会 "evolve" 随着时间的推移,因此我们希望支持模式演进,并首先考虑 Avro。

我们希望避免在每条消息中包含完整架构。但是,在可预见的未来,这个 Akka Persistence 应用程序是唯一将序列化和反序列化这些消息的应用程序,因此我们认为不需要单独的模式注册表。

查看 avro 和各种 scala 库的文档,我看到了将模式包含在消息中的方法,以及如何使用它 "schema-less" 通过使用模式注册表,但是中间的方法呢案件?什么是无模式的正确方法,但以某种方式包含一个标识符,以便能够为反序列化的对象查找正确的模式(在本地部署的代码库中可用)?我真的会创建一个代表我的案例 class 的模式,但有一个额外的 "identifier" 字段用于模式版本,然后在运行时有某种标识符->模式的内存映射吗?

此外,为架构的每个版本设置一个 serializer/deserialize class 是否是正确的方法,这样它就知道如何将每个版本 to/from 翻译为最新版本?

最后,是否有关于如何对模式演化进行单元测试的建议?例如,将消息存储在 akka-persistence 中,然后实际更改 case class 的定义,然后杀死 actor 并确保其正确演化。 (我不知道如何在运行时更改案例 class 的定义。)

在这上面花了更多时间后,这是我想出的答案。

使用 avro4s,您可以使用默认的 data 输出流将架构包含在每条序列化消息中。或者,您可以使用 binary 输出流,它在序列化每条消息时简单地省略模式。 ('binary' 在这里有点用词不当,因为它所做的只是省略了模式。无论哪种情况,它仍然是 Array[Byte]。)

Akka 本身提供了一个 Serializer 特性或一个 SerializerWithStringManifest 特性,它会自动在你序列化的对象中包含一个 "schema identifier" 字段。 因此,当您创建自定义序列化程序时,您可以扩展适当的特征、定义您的架构标识符并使​​用 binary 输出流。当这些技术结合使用时,您将成功地使用 schema-less 序列化,同时包含架构标识符。

一种常用技术是 "fingerprint" 您的架构 - 将其视为字符串,然后计算其摘要(MD5、SHA-256 等)。如果您构造一个 in-memory 指纹到模式的映射,它可以用作您的应用程序的 in-memory 模式注册表。

因此,在反序列化时,您的传入对象将具有用于序列化它的模式的模式标识符 ("writer")。反序列化时,您应该知道用于反序列化的模式的标识符 ("reader")。 Avro4s 支持您同时指定 using a builder pattern 的方式,因此 avro 可以将对象从旧格式转换为新格式。这就是你支持 "schema evolution" 的方式。由于它的工作原理,您不需要为每个模式 版本 单独的序列化程序。您的自定义序列化程序将知道如何改进您的对象,因为这是 Avro 免费提供给您的部分。

至于单元测试,最好的选择是探索性测试。实际上在你的测试中定义一个案例 class 的多个版本,以及它的模式的多个伴随版本,然后通过编写将在该模式的不同版本之间演化对象的测试来探索 Avro 的工作原理。

不幸的是,这不会与您正在编写的代码直接相关,因为在测试时很难模拟 更改 您正在测试的代码。

我开发了一个原型来演示其中的几个答案,它是 available on github。它使用 avro、avro4s 和 akka 持久性。对于这一个,我通过在提交中实际更改它来演示了一个不断变化的代码库——你会检查提交 #1,运行 代码,然后移动到提交 #2,等等。它 运行 反对cassandra,因此它将演示需要使用新模式发展的重播事件,所有这些都无需使用外部模式注册表。