https://github.com/gogo/protobuf不再维护https://github.com/gogo/protobuf/issues/691 (当前)
这是我们的依赖,它与新的golang/protobuf
版本不兼容,越来越多的包依赖它,因此我们需要替换golang/protobuf
版本,这取决于我们直接依赖的过时版本,甚至可能以这种方式破坏包裹
gogo/protobuf
依赖
把这个想出来(解决;计算出;弄明白
弄清楚是否会出现新的维护者或具有功能奇偶校验的不同插件?
只使用香草 protobuf?
测试
是的
我们使用的验证插件不再支持 GoGo
https://github.com/envoyproxy/protoc-gen-validate/pull/340
我认为最好的前进方式是跟随生态系统并从 gogo/protobuf 迁移。 随着越来越多的其他依赖项远离 gogo,我认为继续使用它会变得越来越困难。 当然,迁移需要大量的工作,所以如果我们这样做,我们需要想出一个好的计划。
@rvolosatovs可能更了解我们gogottn
生成器中设置的自定义选项,但这是我在 proto 文件中发现的显式选项的内容:
gogoproto.customname
、 gogoproto.stdtime
和gogoproto.stdduration
以及goproto_enum_prefix
选项开始。 这些相对容易删除,因为 Go 编译器会立即抱怨任何由此产生的问题。gogoproto.embed
选项意味着我们不能再访问嵌入的字段(Go 编译器会帮助我们找到那些),并且消息不再满足某些接口(这可能更困难)。gogoproto.nullable
选项会做更多的工作,因为我们将不得不开始使用 getter,并添加 nil-checks。 Go 编译器可能不会发现由此产生的问题。 可能的解决方法是暂时将这些字段设为私有,然后重写为 getter/setter,最后再次将这些字段设为公开。gogoproto.customtype
的字段和使用gogoproto.enum_stringer
选项的枚举。 对于那些我们经常将它们编组/解组为 JSON 的方式。 对于自定义的bytes
字段,例如 EUI、DevAddr 等,我们可以将类型(在 proto 消息中)更改string
(二进制兼容)。 对于枚举,我担心它会破坏 JSON API,因为它们现在被接受(由 UnmarshalJSON)作为字符串和整数。也许这也是开始考虑我们的 v4 API 的好时机,因为我可以想象我们可能会发现更多(API 突破)惊喜。
我认为最好的方法是首先尝试https://github.com/alta/protopatch。 根据结果:
api
目录中搜索和替换)protopatch
如果它是一个省力的特点。 不过,这实际上取决于选项 - 如果我们谈论的是customtype
- IMO 肯定有理由做出贡献,但也许像stdtime
- 不是那么多。展望未来,我认为我们不应该在运行时直接在内部组件中使用 vanilla protobuf protos(鉴于今天提供的 protobuf 功能集)。
仅将 protobuf 用于(反)序列化才有意义,因此用于存储和 API 层。 然而,在内部,使用普通的普通生成的 Go 原型对我来说没有意义。
因此,例如,NS:
*ttnpb.EndDevice
(vanilla 生成的 Go 类型),从存储的二进制数据中反序列化*ttnpb.EndDevice
转换为T_device
,(注意:也许最初或永远只是一个包装器)T_device
T_device
转换为*ttnpb.EndDevice
(注意:如果我们使用包装器,这可能是一项微不足道的、非常快速的任务,因为我们只需要修改更改的字段,甚至可以在二进制文件上执行直接数据)*ttnpb.EndDevice
,序列化成二进制数据参考也https://github.com/TheThingsNetwork/lorawan-stack/issues/342 (生成的populators)
我不赞成 gogo 的(较小的)替代方案。 感觉就像推罐头一样。 让我们尽可能保持原状,尤其是当我们需要再次决定前进的最佳方式时。
我同意我们可以考虑在某些地方使用中间类型,而不是到处依赖生成的 protos。 它基本上将数据传输对象(DTO:原型,也用于存储)与数据访问对象(DAO:我们如何使用它们)分开。 如果这主要是阅读,我们还可以声明接口,看看我们能做到多远。
也就是说,我不会将整个 NS 更改为使用T_device
,而是根据需要使用特定的结构和/或接口。
让我们将此讨论移至 11 月
@rvolosatovs您反对使用自定义 JSON 封送
如果我们最终直接使用 vanilla protos,那么巨大的迁移负担和大量的样板文件。
不过我并不真的反对,我只是认为我们应该首先尝试找到一个简单的非侵入性替代方案,如果这不可能,那么就重新处理整个事情。
恐怕我们开始依赖的任何插件都会在某个时候处于无人维护的状态。 一般来说,我赞成让事情尽可能接近香草。 如果这意味着nil
检查的频率比我们喜欢的要多,那么就这样吧。 它也可以对我们有利,因为我们知道事情没有设置,而不是初始化的结构。
我担心无论我们如何重构我们的整个代码库都会很痛苦。 我们的(gogo 生成的)proto 结构现在到处都在使用(gRPC API、HTTP API、事件、错误、内部、Redis DB 等),因此更改为其他内容(无论其他内容是什么)都会有所影响我们代码库中的几乎所有内容,以及它现在的样子,都同时进行。
硬性要求是我们不会破坏 v3 API 的兼容性。 即使我们决定在这种情况下开始使用 v4 API(至少在内部),我们仍然必须继续为现有用户支持该 v3 API。
从长远来看,我认为通过将我们的(版本化的、主要内部稳定的)外部 API 与我们的内部(非版本化、次要的稳定)API 和我们的(版本化的、稳定的)数据库文档分离,我们会对自己有很大的帮助. 然后我们可以编写或生成函数来在我们的内部 API 和其他 API 之间进行转换。
但我认为我们现在可以采取一些步骤:
为了保持我们的 v3 JSON API 兼容,我认为我们的第一个 TODO 是生成 JSON 编组器和解组器,它们了解如何编组/解组我们的自定义类型。 我认为这样做是明智的,因为Go 的协议缓冲区 JSON 格式的实现没有稳定性承诺,所以我们最好自己控制它。 这样做还可以让我们在编组为 JSON 时考虑字段掩码。 在grpc-gateway
运行时我们可以注册编解码器,所以我们可以编写一个编解码器来调用我们自己的(生成的)(un)编组器而不是 {gogo,golang}/protobuf 的 jsonpb。
我已经在这里尝试过: https :
这确实使 protobuf 抱怨类型注册表,因此我们可能需要从旧的 protos 中删除golang_proto.RegisterType
以使其工作。 删除它可能会破坏google.protobuf.Any
解析,但我们只在错误和事件中使用它们,因此我们可能会找到针对这些特定情况的解决方法。
这仅适用于过渡期,但对于长期解决方案,我们希望生成类似的转换器。
我已经在这里尝试了一个简单的服务: https :
请注意,这只会更改 grpc 服务本身。 grpc-gateway 在 JSON 端仍然使用旧的 gogo 东西,然后调用内部 gRPC 服务器,然后运行新的实现。
在此处推送了一些初始依赖项更新和向后兼容性解决方法: https :
我们越来越多的依赖项正在升级到 protobuf 1.4 和 V2 API,我们保持开放的时间越长,我们在尝试升级依赖项时遇到的问题就越多。
我们真的应该给予更多优先考虑,并决定我们将如何处理这一切。
请计划下周的电话会议,以便我们可以离线讨论。
我认为我们应该经历这个痛苦的过程,并在一两周内集中精力解决这个问题。 并且为了避免我们做其他事情,否则这会导致很多冲突。 拥有尽可能多的手需要确切地知道在哪些情况下我们要做什么,尽可能多地分配任务并密切关注奖品。
下一步:
unconvert
、 gofumpt
东西以及我们在 protoc 之上所做的任何其他事情protoc-gen-gogottn
切换到protoc-gen-gofast
(或最接近香草的任何东西)(gogoproto.*)
选项,以便它们呈现与现在相同gopls
和rf
可以帮助解决这个问题。(gogoproto.*)
选项并更新使用它的代码。 也许像gopls
和rf
可以帮助解决这个问题。gogoproto.populate
并更新测试(https://github.com/TheThingsNetwork/lorawan-stack/issues/342)gogoproto.customname
和更改EUI -> Eui
等。gogoproto.embed
。 我们确实需要确保消息仍然实现诸如ValidateContext(context.Context) error
和ExtractRequestFields(m map[string]interface{})
。gogoproto.nullable
并确保我们在可能的情况下使用 Getters,否则进行 nil 检查。@rvolosatovs让我们尝试为 v3.11.3 迈出第一步。 完成后,请重新添加其他受让人,让我们再次讨论。
最有用的评论
请计划下周的电话会议,以便我们可以离线讨论。
我认为我们应该经历这个痛苦的过程,并在一两周内集中精力解决这个问题。 并且为了避免我们做其他事情,否则这会导致很多冲突。 拥有尽可能多的手需要确切地知道在哪些情况下我们要做什么,尽可能多地分配任务并密切关注奖品。