Avro 联合:向前兼容?
Avro union: forward compatibility?
我们的系统由多个微服务组成,这些微服务发出和使用以 avro 格式编码的事件(请参阅底部的架构)。一个特定的用例如下:服务 A 发出关于主题 T1 的事件(InvoiceEvents 类型),服务 B 和 C(不同的开发团队)正在使用 T1。例如。服务 B 是税务团队的一部分,而服务 C 是产品履行团队的一部分。
我原以为以下内容是正确的(但似乎不是):
- 模式可以通过添加新的联合类型(即 InvoiceCreated 字段 "payload")从版本 1 (v1) 发展到版本 2 (v2) - 查看底部的示例模式。
- 要升级到 v2 的生产服务 A(即生产遵循 v2 的事件)
- 一些消费服务(例如服务 C)仍然可以使用 v1,因为它们对新的事件类型(即 InvoiceCreated)不感兴趣。在这种情况下,"payload" 字段将在反序列化时使用默认(空)值。
- 最终且仅当出于业务原因需要时,如果需要对新事件类型(即 InvoiceCreated)做出反应,服务 C 才能升级为使用 v2。
但是服务 C 无法反序列化 InvoiceCreated 类型的新事件。具体是抛出:
org.apache.avro.AvroTypeException: Found com.elsevier.q2c.schema.avro.invoice.InvoiceCreated, expecting unionorg.apache.avro.AvroTypeException: Found com.elsevier.q2c.schema.avro.invoice.InvoiceCreated, expecting union at org.apache.avro.io.ResolvingDecoder.doAction(ResolvingDecoder.java:292) at
avro 联合类型是否不向前兼容(如上所述)?它们是否仅如 Confluent Schema Registry tests 所暗示的那样向后兼容。避免微服务耦合的建议方法是什么?我猜不能使用avro union..
谢谢!!
相关 link 没有明确答案:Avro-union-compatibility-mode-enhancement-proposal
架构 v1:
[
...
{
"type":"record",
"name":"InvoiceEvents",
"namespace":"bla.bla.schema.avro.invoice",
"fields":[
{
"name":"payload",
"type":[
"null",
"bla.bla.schema.avro.invoice.InvoiceDrafted"
],
"default":null
}
]
}
]
schema v2(添加了新的联盟类型:InvoiceCreated):
[
...
{
"type":"record",
"name":"InvoiceEvents",
"namespace":"bla.bla.schema.avro.invoice",
"fields":[
{
"name":"payload",
"type":[
"null",
"bla.bla.schema.avro.invoice.InvoiceDrafted",
"bla.bla.schema.avro.invoice.InvoiceCreated",
],
"default":null
}
]
}
]
经过一番思考,我们可能会选择选项 3,因为不跳过/丢失事件对项目来说比解耦更重要:
- 处理自定义反序列化器中的异常并跳过事件(可能会丢失有趣的事件 - 为了不丢失事件,必须在所有生产服务之前升级所有使用服务)
- 将所有自定义记录联合转换为单独的可选字段(可能会丢失有趣的事件,因为更改是向前兼容的并且消费服务不会阻塞)
- 在新的自定义记录类型上使用模式的所有消费服务中接受反序列化错误/块消费和颠簸版本(这保证不会丢失任何有趣的事件)。
如果有更好的选择请评论,我错过了!
更新:
似乎选项 (2) 现在可以以更简洁的方式实现,因为现在您可以拥有多种类型的主题 (https://github.com/confluentinc/schema-registry/pull/680)。这意味着一个主题可以有不同的值类型(例如 InvoiceCreated、InvoiceEdited 等)而不使用 avro 联合,而每个不同的类型将有自己的进化线!
我们的系统由多个微服务组成,这些微服务发出和使用以 avro 格式编码的事件(请参阅底部的架构)。一个特定的用例如下:服务 A 发出关于主题 T1 的事件(InvoiceEvents 类型),服务 B 和 C(不同的开发团队)正在使用 T1。例如。服务 B 是税务团队的一部分,而服务 C 是产品履行团队的一部分。
我原以为以下内容是正确的(但似乎不是):
- 模式可以通过添加新的联合类型(即 InvoiceCreated 字段 "payload")从版本 1 (v1) 发展到版本 2 (v2) - 查看底部的示例模式。
- 要升级到 v2 的生产服务 A(即生产遵循 v2 的事件)
- 一些消费服务(例如服务 C)仍然可以使用 v1,因为它们对新的事件类型(即 InvoiceCreated)不感兴趣。在这种情况下,"payload" 字段将在反序列化时使用默认(空)值。
- 最终且仅当出于业务原因需要时,如果需要对新事件类型(即 InvoiceCreated)做出反应,服务 C 才能升级为使用 v2。
但是服务 C 无法反序列化 InvoiceCreated 类型的新事件。具体是抛出:
org.apache.avro.AvroTypeException: Found com.elsevier.q2c.schema.avro.invoice.InvoiceCreated, expecting unionorg.apache.avro.AvroTypeException: Found com.elsevier.q2c.schema.avro.invoice.InvoiceCreated, expecting union at org.apache.avro.io.ResolvingDecoder.doAction(ResolvingDecoder.java:292) at
avro 联合类型是否不向前兼容(如上所述)?它们是否仅如 Confluent Schema Registry tests 所暗示的那样向后兼容。避免微服务耦合的建议方法是什么?我猜不能使用avro union..
谢谢!!
相关 link 没有明确答案:Avro-union-compatibility-mode-enhancement-proposal
架构 v1:
[
...
{
"type":"record",
"name":"InvoiceEvents",
"namespace":"bla.bla.schema.avro.invoice",
"fields":[
{
"name":"payload",
"type":[
"null",
"bla.bla.schema.avro.invoice.InvoiceDrafted"
],
"default":null
}
]
}
]
schema v2(添加了新的联盟类型:InvoiceCreated):
[
...
{
"type":"record",
"name":"InvoiceEvents",
"namespace":"bla.bla.schema.avro.invoice",
"fields":[
{
"name":"payload",
"type":[
"null",
"bla.bla.schema.avro.invoice.InvoiceDrafted",
"bla.bla.schema.avro.invoice.InvoiceCreated",
],
"default":null
}
]
}
]
经过一番思考,我们可能会选择选项 3,因为不跳过/丢失事件对项目来说比解耦更重要:
- 处理自定义反序列化器中的异常并跳过事件(可能会丢失有趣的事件 - 为了不丢失事件,必须在所有生产服务之前升级所有使用服务)
- 将所有自定义记录联合转换为单独的可选字段(可能会丢失有趣的事件,因为更改是向前兼容的并且消费服务不会阻塞)
- 在新的自定义记录类型上使用模式的所有消费服务中接受反序列化错误/块消费和颠簸版本(这保证不会丢失任何有趣的事件)。
如果有更好的选择请评论,我错过了!
更新: 似乎选项 (2) 现在可以以更简洁的方式实现,因为现在您可以拥有多种类型的主题 (https://github.com/confluentinc/schema-registry/pull/680)。这意味着一个主题可以有不同的值类型(例如 InvoiceCreated、InvoiceEdited 等)而不使用 avro 联合,而每个不同的类型将有自己的进化线!