用于协议缓冲区的新Go API

“ Golang开发人员”课程开始的前夕,我们为您准备了官方Golang博客的文章翻译。








介绍



我们很高兴地宣布,发布了用于协议缓冲区的Go API的主要修订版,该协议缓冲区是Google与语言无关的数据交换格式。



更新API的前提条件



Rob Pike于2010年3月引入了 Go的第一个协议缓冲区绑定。 Go 1不会再发布两年。



自从首次发布以来的十年中,该软件包与Go一起发展壮大。其用户的要求也在增长。



许多人希望使用反射来编写程序以与协议缓冲区消息一起使用。该软件包reflect允许您查看Go类型和值,但是省略了协议缓冲区类型的系统信息。例如,我们可能需要编写一个函数来扫描整个日志并清除所有注释为包含敏感数据的字段。注释不是Go的类型系统的一部分。



另一个普遍的需求是使用协议缓冲编译器生成的数据结构以外的其他数据结构,例如,动态消息类型,能够表示编译时未知类型的消息。



我们还注意到,问题的共同根源是接口proto.Message标识生成的消息类型的值,并在描述这些类型的行为时略过。当用户创建实现该接口的类型(通常无意中通过将消息嵌入另一个结构)并将这些类型的值传递给期望生成消息值的函数时,程序崩溃或行为异常。



所有这三个问题具有相同的根源和一种解决方案:接口Message必须完全定义消息的行为,并且对值进行操作的函数Message必须自由接受可以正确实现接口的任何类型。



由于在保持包的API兼容性的同时无法更改Message类型的现有定义,因此我们认为是时候开始研究protobuf模块的新的不兼容的主要版本了。



今天,我们很高兴发布此新模块。我们希望你喜欢它。



反射



反射是新实现的旗舰功能。就像该包reflect提供了Go的类型和值的视图一样,google.golang.org/protobuf/reflect/protoreflect包也提供了根据协议缓冲区类型系统的值的视图。



对于本文而言,完整的软件包描述protoreflect将花费很长时间,但是,让我们看看如何编写我们前面提到的日志清除功能。



首先,我们必须编写一个.proto定义扩展名的文件,例如google.protobuf.FieldOptions,以便我们可以将字段注释为机密。



syntax = "proto3";
import "google/protobuf/descriptor.proto";
package golang.example.policy;
extend google.protobuf.FieldOptions {
    bool non_sensitive = 50000;
}


我们可以使用此选项将某些字段标记为不敏感。



message MyMessage {
    string public_name = 1 [(golang.example.policy.non_sensitive) = true];
}


接下来,我们需要编写一个Go函数,该函数接受一个任意消息值并删除所有敏感字段。



// Redact      pb.
func Redact(pb proto.Message) {
   // ...
}


此函数接受proto.Message -由所有生成的消息类型实现的接口。此类型是包中定义的类型的别名protoreflect



type ProtoMessage interface{
    ProtoReflect() Message
}


为了避免填充生成的消息名称空间,该接口仅包含一个protoreflect.Message提供对消息内容的访问的返回方法



(为什么要使用别名?因为它protoreflect.Message具有返回原始方法的相应方法,所以proto.Message我们需要避免两个包之间的导入周期。)



该方法protoreflect.Message.Range为消息中的每个填充字段调用一个函数。



m := pb.ProtoReflect()
m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
    // ...
    return true
})


通过protoreflect.FieldDescriptor描述字段的协议缓冲区和包含字段值的protoreflect.Value的类型来调用range函数



该方法protoreflect.FieldDescriptor.Options将字段选项作为消息返回google.protobuf.FieldOptions



opts := fd.Options().(*descriptorpb.FieldOptions)


(为什么要使用类型断言?由于生成的包descriptorpb取决于protoreflect,因此protoreflect包在不调用导入周期的情况下无法返回特定类型的选项。)



然后,我们可以检查选项以查看扩展的布尔值的值:



if proto.GetExtension(opts, policypb.E_NonSensitive).(bool) {
    return true //   non-sensitive 
}


请注意,这里我们查看的是字段描述符,而不是字段我们感兴趣的信息是关于协议缓冲区类型系统,而不是Go。



这也是我们简化了protoAPI的一个例子原始proto.GetExtension返回值和错误。新的proto.GetExtension仅返回该值,如果缺少该字段,则返回该字段的默认值。扩展解码错误报告给Unmarshal



一旦我们确定了需要编辑的字段,就很容易清除它:



m.Clear(fd)


综合以上所有内容,我们的编辑功能如下所示:



// Redact      pb.
func Redact(pb proto.Message) {
    m := pb.ProtoReflect()
    m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
        opts := fd.Options().(*descriptorpb.FieldOptions)
        if proto.GetExtension(opts, policypb.E_NonSensitive).(bool) {
            return true
        }
        m.Clear(fd)
        return true
    })
}


更好的版本可以递归地放入消息值字段。我们希望这个简单的示例为协议缓冲区中的反射及其用法提供一个介绍。



版本号



我们将原始版本的协议缓冲区称为APIv1,将较新的版本称为APIv2。由于APIv2与APIv1向后不兼容,因此我们需要为每个APIv使用不同的模块路径。



(此API的版本是不一样的作为协议缓冲语言的版本:proto1proto2,和proto3。APIv1和APIv2 -在转到,其既支持语言版本的该特定实现proto2proto3



在模块github.com/golang/protobuf - APIv1。



google.golang.org/protobuf模块-APIv2中。我们利用了更改导入路径的需要,以切换到与特定托管服务提供商无关的路径。 (我们认为google.golang.org/protobuf/v2为了更清楚地说明这是API的第二个主要版本,但从长远来看,这是一条最佳选择



有些会快速切换;其他可能会无限期保留在旧版本上。即使在同一程序中,某些部分可能使用一个API,而其他部分可能使用另一个。因此,重要的是我们继续支持使用API​​v1的程序。



  • github.com/golang/protobuf@v1.3.4 是APIv2之前的APIv1的最新版本。
  • github.com/golang/protobuf@v1.4.0是基于APIv2实现的APIv1版本。该API相同,但是新API支持基本实现。此版本包含用于在proto.MessageAPIv1和APIv2之间进行转换的功能,以促进它们之间的转换。
  • google.golang.org/protobuf@v1.20.0 — APIv2. github.com/golang/protobuf@v1.4.0, , APIv2, APIv1, .


(为什么v1.20.0要从版本开始?为清楚起见。我们不希望APIv1到来v1.20.0,因此一个版本号就足以唯一区分APIv1和APIv2。)



我们打算继续支持APIv1,而不设置任何截止日期。



这种安排可确保任何程序,无论使用哪个版本的API,都只会使用一个协议缓冲区实现。这使程序可以逐步实现或根本不实现新的API,同时保持新实现的好处。选择最低版本的原则意味着程序可以保留在旧的实现中,直到维护者决定将其更新为新的版本为止(直接或通过更新依赖项)。



需要注意的其他功能



该软件包google.golang.org/protobuf/encoding/protojson使用规范的JSON映射将协议缓冲区消息与JSON来回转换,还修复了旧软件包jsonpb的许多问题,这些问题很难更改而不会给现有用户造成新问题。



该包为消息 google.golang.org/protobuf/types/dynamicpb的实现提供了实现proto.Message,消息的协议缓冲区类型在运行时确定。



该包google.golang.org/protobuf/testing/protocmp提供用于将消息的协议缓冲区与数据包进行比较的功能github.com/google/cmp



该软件包 google.golang.org/protobuf/compiler/protogen为编写协议缓冲区编译器插件提供支持。



结论



该模块google.golang.org/protobuf是对协议缓冲区的Go支持的主要改进,为反射,自定义消息传递和清理的API提供了一流的支持。我们打算继续支持以前的API作为新API的包装,从而使用户可以按照自己的进度逐步实施新API。



我们此次更新的目标是增强旧API的优势并解决其弱点。完成新实现的每个组件后,我们开始在Google代码库中使用它。逐步推出使我们对新API的可用性以及新实现的更好性能和正确性充满信心。我们相信它已准备好投入生产。



我们对该版本感到非常兴奋,并希望它将在接下来的十年或更长时间里很好地服务于Go生态系统!






了解有关该课程的更多信息。







All Articles