| # A new Go API for Protocol Buffers | |
| 2 Mar 2020 | |
| Tags: protobuf, technical | |
| Summary: Announcing a major revision of the Go API for protocol buffers. | |
| OldURL: /a-new-go-api-for-protocol-buffers | |
| Joe Tsai | |
| Damien Neil | |
| Herbie Ong | |
| ## Introduction | |
| We are pleased to announce the release of a major revision of the Go API for | |
| [protocol buffers](https://developers.google.com/protocol-buffers), | |
| Google's language-neutral data interchange format. | |
| ## Motivations for a new API | |
| The first protocol buffer bindings for Go were | |
| [announced by Rob Pike](https://blog.golang.org/third-party-libraries-goprotobuf-and) | |
| in March of 2010. Go 1 would not be released for another two years. | |
| In the decade since that first release, the package has grown and | |
| developed along with Go. Its users' requirements have grown too. | |
| Many people want to write programs that use reflection to examine protocol | |
| buffer messages. The | |
| [`reflect`](https://pkg.go.dev/reflect) | |
| package provides a view of Go types and | |
| values, but omits information from the protocol buffer type system. For | |
| example, we might want to write a function that traverses a log entry and | |
| clears any field annotated as containing sensitive data. The annotations | |
| are not part of the Go type system. | |
| Another common desire is to use data structures other than the ones | |
| generated by the protocol buffer compiler, such as a dynamic message type | |
| capable of representing messages whose type is not known at compile time. | |
| We also observed that a frequent source of problems was that the | |
| [`proto.Message`](https://pkg.go.dev/github.com/golang/protobuf/proto?tab=doc#Message) | |
| interface, which identifies values of generated message types, does very | |
| little to describe the behavior of those types. When users create types | |
| that implement that interface (often inadvertently by embedding a message | |
| in another struct) and pass values of those types to functions expecting | |
| a generated message value, programs crash or behave unpredictably. | |
| All three of these problems have a common cause, and a common solution: | |
| The `Message` interface should fully specify the behavior of a message, | |
| and functions operating on `Message` values should freely accept any | |
| type that correctly implements the interface. | |
| Since it is not possible to change the existing definition of the | |
| `Message` type while keeping the package API compatible, we decided that | |
| it was time to begin work on a new, incompatible major version of the | |
| protobuf module. | |
| Today, we're pleased to release that new module. We hope you like it. | |
| ## Reflection | |
| Reflection is the flagship feature of the new implementation. Similar | |
| to how the `reflect` package provides a view of Go types and values, the | |
| [`google.golang.org/protobuf/reflect/protoreflect`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect?tab=doc) | |
| package provides a view of values according to the protocol buffer | |
| type system. | |
| A complete description of the `protoreflect` package would run too long | |
| for this post, but let's look at how we might write the log-scrubbing | |
| function we mentioned previously. | |
| First, we'll write a `.proto` file defining an extension of the | |
| [`google.protobuf.FieldOptions`](https://github.com/protocolbuffers/protobuf/blob/b96241b1b716781f5bc4dc25e1ebb0003dfaba6a/src/google/protobuf/descriptor.proto#L509) | |
| type so we can annotate fields as containing | |
| sensitive information or not. | |
| syntax = "proto3"; | |
| import "google/protobuf/descriptor.proto"; | |
| package golang.example.policy; | |
| extend google.protobuf.FieldOptions { | |
| bool non_sensitive = 50000; | |
| } | |
| We can use this option to mark certain fields as non-sensitive. | |
| message MyMessage { | |
| string public_name = 1 [(golang.example.policy.non_sensitive) = true]; | |
| } | |
| Next, we will write a Go function which accepts an arbitrary message | |
| value and removes all the sensitive fields. | |
| // Redact clears every sensitive field in pb. | |
| func Redact(pb proto.Message) { | |
| // ... | |
| } | |
| This function accepts a | |
| [`proto.Message`](https://pkg.go.dev/google.golang.org/protobuf/proto?tab=doc#Message), | |
| an interface type implemented by all generated message types. This type | |
| is an alias for one defined in the `protoreflect` package: | |
| type ProtoMessage interface{ | |
| ProtoReflect() Message | |
| } | |
| To avoid filling up the namespace of generated | |
| messages, the interface contains only a single method returning a | |
| [`protoreflect.Message`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect?tab=doc#Message), | |
| which provides access to the message contents. | |
| (Why an alias? Because `protoreflect.Message` has a corresponding | |
| method returning the original `proto.Message`, and we need to avoid an | |
| import cycle between the two packages.) | |
| The | |
| [`protoreflect.Message.Range`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect?tab=doc#Message.Range) | |
| method calls a function for every populated field in a message. | |
| m := pb.ProtoReflect() | |
| m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { | |
| // ... | |
| return true | |
| }) | |
| The range function is called with a | |
| [`protoreflect.FieldDescriptor`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect?tab=doc#FieldDescriptor) | |
| describing the protocol buffer type of the field, and a | |
| [`protoreflect.Value`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect?tab=doc#Value) | |
| containing the field value. | |
| The | |
| [`protoreflect.FieldDescriptor.Options`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect?tab=doc#Descriptor.Options) | |
| method returns the field options as a `google.protobuf.FieldOptions` | |
| message. | |
| opts := fd.Options().(*descriptorpb.FieldOptions) | |
| (Why the type assertion? Since the generated `descriptorpb` package | |
| depends on `protoreflect`, the `protoreflect` package can't return the | |
| concrete options type without causing an import cycle.) | |
| We can then check the options to see the value of our extension boolean: | |
| if proto.GetExtension(opts, policypb.E_NonSensitive).(bool) { | |
| return true // don't redact non-sensitive fields | |
| } | |
| Note that we are looking at the field _descriptor_ here, not the field | |
| _value_. The information we're interested in lies in the protocol | |
| buffer type system, not the Go one. | |
| This is also an example of an area where we | |
| have simplified the `proto` package API. The original | |
| [`proto.GetExtension`](https://pkg.go.dev/github.com/golang/protobuf/proto?tab=doc#GetExtension) | |
| returned both a value and an error. The new | |
| [`proto.GetExtension`](https://pkg.go.dev/google.golang.org/protobuf/proto?tab=doc#GetExtension) | |
| returns just a value, returning the default value for the field if it is | |
| not present. Extension decoding errors are reported at `Unmarshal` time. | |
| Once we have identified a field that needs redaction, clearing it is simple: | |
| m.Clear(fd) | |
| Putting all the above together, our complete redaction function is: | |
| // Redact clears every sensitive field in 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 | |
| }) | |
| } | |
| A more complete implementation might recursively descend into | |
| message-valued fields. We hope that this simple example gives a | |
| taste of protocol buffer reflection and its uses. | |
| ## Versions | |
| We call the original version of Go protocol buffers APIv1, and the | |
| new one APIv2. Because APIv2 is not backwards compatible with APIv1, | |
| we need to use different module paths for each. | |
| (These API versions are not the same as the versions of the protocol | |
| buffer language: `proto1`, `proto2`, and `proto3`. APIv1 and APIv2 | |
| are concrete implementations in Go that both support the `proto2` and | |
| `proto3` language versions.) | |
| The | |
| [`github.com/golang/protobuf`](https://pkg.go.dev/github.com/golang/protobuf?tab=overview) | |
| module is APIv1. | |
| The | |
| [`google.golang.org/protobuf`](https://pkg.go.dev/google.golang.org/protobuf?tab=overview) | |
| module is APIv2. We have taken advantage of the need to change the | |
| import path to switch to one that is not tied to a specific hosting | |
| provider. (We considered `google.golang.org/protobuf/v2`, to make it | |
| clear that this is the second major version of the API, but settled on | |
| the shorter path as being the better choice in the long term.) | |
| We know that not all users will move to a new major version of a package | |
| at the same rate. Some will switch quickly; others may remain on the old | |
| version indefinitely. Even within a single program, some parts may use | |
| one API while others use another. It is essential, therefore, that we | |
| continue to support programs that use APIv1. | |
| - `github.com/golang/protobuf@v1.3.4` is the most recent pre-APIv2 version of APIv1. | |
| - `github.com/golang/protobuf@v1.4.0` is a version of APIv1 implemented in terms of APIv2. | |
| The API is the same, but the underlying implementation is backed by the new one. | |
| This version contains functions to convert between the APIv1 and APIv2 `proto.Message` | |
| interfaces to ease the transition between the two. | |
| - `google.golang.org/protobuf@v1.20.0` is APIv2. | |
| This module depends upon `github.com/golang/protobuf@v1.4.0`, | |
| so any program which uses APIv2 will automatically pick a version of APIv1 | |
| which integrates with it. | |
| (Why start at version `v1.20.0`? To provide clarity. | |
| We do not anticipate APIv1 to ever reach `v1.20.0`, | |
| so the version number alone should be enough to unambiguously differentiate | |
| between APIv1 and APIv2.) | |
| We intend to maintain support for APIv1 indefinitely. | |
| This organization ensures that any given program will use only a single | |
| protocol buffer implementation, regardless of which API version it uses. | |
| It permits programs to adopt the new API gradually, or not at all, while | |
| still gaining the advantages of the new implementation. The principle of | |
| minimum version selection means that programs may remain on the old | |
| implementation until the maintainers choose to update to the new one | |
| (either directly, or by updating a dependency). | |
| ## Additional features of note | |
| The | |
| [`google.golang.org/protobuf/encoding/protojson`](https://pkg.go.dev/google.golang.org/protobuf/encoding/protojson) | |
| package converts protocol buffer messages to and from JSON using the | |
| [canonical JSON mapping](https://developers.google.com/protocol-buffers/docs/proto3#json), | |
| and fixes a number of issues with the old `jsonpb` package | |
| that were difficult to change without causing problems for existing users. | |
| The | |
| [`google.golang.org/protobuf/types/dynamicpb`](https://pkg.go.dev/google.golang.org/protobuf/types/dynamicpb) | |
| package provides an implementation of `proto.Message` for messages whose | |
| protocol buffer type is derived at runtime. | |
| The | |
| [`google.golang.org/protobuf/testing/protocmp`](https://pkg.go.dev/google.golang.org/protobuf/testing/protocmp) | |
| package provides functions to compare protocol buffer messages with the | |
| [`github.com/google/cmp`](https://pkg.go.dev/github.com/google/go-cmp/cmp) | |
| package. | |
| The | |
| [`google.golang.org/protobuf/compiler/protogen`](https://pkg.go.dev/google.golang.org/protobuf/compiler/protogen?tab=doc) | |
| package provides support for writing protocol compiler plugins. | |
| ## Conclusion | |
| The `google.golang.org/protobuf` module is a major overhaul of | |
| Go's support for protocol buffers, providing first-class support | |
| for reflection, custom message implementations, and a cleaned up API | |
| surface. We intend to maintain the previous API indefinitely as a wrapper | |
| of the new one, allowing users to adopt the new API incrementally at | |
| their own pace. | |
| Our goal in this update is to improve upon the benefits of the old | |
| API while addressing its shortcomings. As we completed each component of | |
| the new implementation, we put it into use within Google's codebase. This | |
| incremental rollout has given us confidence in both the usability of the new | |
| API and the performance and correctness of the new implementation. We believe | |
| it is production ready. | |
| We are excited about this release and hope that it will serve the Go | |
| ecosystem for the next ten years and beyond! |