blob: 952d73cdcd62111d13a87e7e5287b9649f86ef75 [file] [log] [blame]
Russ Coxaf5018f2020-03-09 23:54:35 -04001# A new Go API for Protocol Buffers
22 Mar 2020
Damien Neil5365b3b2020-02-20 15:38:55 -08003Tags: protobuf, technical
Russ Coxfaf1e2d2020-03-14 09:44:01 -04004Summary: Announcing a major revision of the Go API for protocol buffers.
Russ Cox972d42d2020-03-15 15:50:36 -04005OldURL: /a-new-go-api-for-protocol-buffers
Damien Neil5365b3b2020-02-20 15:38:55 -08006
Russ Cox972d42d2020-03-15 15:50:36 -04007Joe Tsai
8
9Damien Neil
10
11Herbie Ong
Damien Neil5365b3b2020-02-20 15:38:55 -080012
Russ Coxaf5018f2020-03-09 23:54:35 -040013## Introduction
Damien Neil5365b3b2020-02-20 15:38:55 -080014
15We are pleased to announce the release of a major revision of the Go API for
Russ Coxaf5018f2020-03-09 23:54:35 -040016[protocol buffers](https://developers.google.com/protocol-buffers),
Damien Neil5365b3b2020-02-20 15:38:55 -080017Google's language-neutral data interchange format.
18
Russ Coxaf5018f2020-03-09 23:54:35 -040019## Motivations for a new API
Damien Neil5365b3b2020-02-20 15:38:55 -080020
21The first protocol buffer bindings for Go were
Russ Coxaf5018f2020-03-09 23:54:35 -040022[announced by Rob Pike](https://blog.golang.org/third-party-libraries-goprotobuf-and)
Damien Neil5365b3b2020-02-20 15:38:55 -080023in March of 2010. Go 1 would not be released for another two years.
24
25In the decade since that first release, the package has grown and
26developed along with Go. Its users' requirements have grown too.
27
28Many people want to write programs that use reflection to examine protocol
29buffer messages. The
Russ Coxaf5018f2020-03-09 23:54:35 -040030[`reflect`](https://pkg.go.dev/reflect)
Damien Neil5365b3b2020-02-20 15:38:55 -080031package provides a view of Go types and
32values, but omits information from the protocol buffer type system. For
33example, we might want to write a function that traverses a log entry and
34clears any field annotated as containing sensitive data. The annotations
35are not part of the Go type system.
36
37Another common desire is to use data structures other than the ones
38generated by the protocol buffer compiler, such as a dynamic message type
39capable of representing messages whose type is not known at compile time.
40
41We also observed that a frequent source of problems was that the
Russ Coxaf5018f2020-03-09 23:54:35 -040042[`proto.Message`](https://pkg.go.dev/github.com/golang/protobuf/proto?tab=doc#Message)
Damien Neil5365b3b2020-02-20 15:38:55 -080043interface, which identifies values of generated message types, does very
44little to describe the behavior of those types. When users create types
45that implement that interface (often inadvertently by embedding a message
46in another struct) and pass values of those types to functions expecting
47a generated message value, programs crash or behave unpredictably.
48
49All three of these problems have a common cause, and a common solution:
50The `Message` interface should fully specify the behavior of a message,
51and functions operating on `Message` values should freely accept any
52type that correctly implements the interface.
53
54Since it is not possible to change the existing definition of the
55`Message` type while keeping the package API compatible, we decided that
56it was time to begin work on a new, incompatible major version of the
57protobuf module.
58
59Today, we're pleased to release that new module. We hope you like it.
60
Russ Coxaf5018f2020-03-09 23:54:35 -040061## Reflection
Damien Neil5365b3b2020-02-20 15:38:55 -080062
63Reflection is the flagship feature of the new implementation. Similar
64to how the `reflect` package provides a view of Go types and values, the
Russ Coxaf5018f2020-03-09 23:54:35 -040065[`google.golang.org/protobuf/reflect/protoreflect`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect?tab=doc)
Damien Neil5365b3b2020-02-20 15:38:55 -080066package provides a view of values according to the protocol buffer
67type system.
68
69A complete description of the `protoreflect` package would run too long
70for this post, but let's look at how we might write the log-scrubbing
71function we mentioned previously.
72
73First, we'll write a `.proto` file defining an extension of the
Russ Coxaf5018f2020-03-09 23:54:35 -040074[`google.protobuf.FieldOptions`](https://github.com/protocolbuffers/protobuf/blob/b96241b1b716781f5bc4dc25e1ebb0003dfaba6a/src/google/protobuf/descriptor.proto#L509)
Damien Neil5365b3b2020-02-20 15:38:55 -080075type so we can annotate fields as containing
76sensitive information or not.
77
Russ Cox9dd3d9b2020-03-09 23:19:59 -040078 syntax = "proto3";
79 import "google/protobuf/descriptor.proto";
80 package golang.example.policy;
81 extend google.protobuf.FieldOptions {
82 bool non_sensitive = 50000;
83 }
Damien Neil5365b3b2020-02-20 15:38:55 -080084
85We can use this option to mark certain fields as non-sensitive.
86
Russ Cox9dd3d9b2020-03-09 23:19:59 -040087 message MyMessage {
88 string public_name = 1 [(golang.example.policy.non_sensitive) = true];
89 }
Damien Neil5365b3b2020-02-20 15:38:55 -080090
91Next, we will write a Go function which accepts an arbitrary message
92value and removes all the sensitive fields.
93
Russ Cox9dd3d9b2020-03-09 23:19:59 -040094 // Redact clears every sensitive field in pb.
95 func Redact(pb proto.Message) {
96 // ...
97 }
Damien Neil5365b3b2020-02-20 15:38:55 -080098
99This function accepts a
Russ Coxaf5018f2020-03-09 23:54:35 -0400100[`proto.Message`](https://pkg.go.dev/google.golang.org/protobuf/proto?tab=doc#Message),
Damien Neil5365b3b2020-02-20 15:38:55 -0800101an interface type implemented by all generated message types. This type
102is an alias for one defined in the `protoreflect` package:
103
Russ Cox9dd3d9b2020-03-09 23:19:59 -0400104 type ProtoMessage interface{
105 ProtoReflect() Message
106 }
Damien Neil5365b3b2020-02-20 15:38:55 -0800107
108To avoid filling up the namespace of generated
109messages, the interface contains only a single method returning a
Russ Coxaf5018f2020-03-09 23:54:35 -0400110[`protoreflect.Message`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect?tab=doc#Message),
Damien Neil5365b3b2020-02-20 15:38:55 -0800111which provides access to the message contents.
112
113(Why an alias? Because `protoreflect.Message` has a corresponding
114method returning the original `proto.Message`, and we need to avoid an
115import cycle between the two packages.)
116
117The
Russ Coxaf5018f2020-03-09 23:54:35 -0400118[`protoreflect.Message.Range`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect?tab=doc#Message.Range)
Damien Neil5365b3b2020-02-20 15:38:55 -0800119method calls a function for every populated field in a message.
120
Russ Cox9dd3d9b2020-03-09 23:19:59 -0400121 m := pb.ProtoReflect()
122 m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
123 // ...
124 return true
125 })
Damien Neil5365b3b2020-02-20 15:38:55 -0800126
127The range function is called with a
Russ Coxaf5018f2020-03-09 23:54:35 -0400128[`protoreflect.FieldDescriptor`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect?tab=doc#FieldDescriptor)
Damien Neil5365b3b2020-02-20 15:38:55 -0800129describing the protocol buffer type of the field, and a
Russ Coxaf5018f2020-03-09 23:54:35 -0400130[`protoreflect.Value`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect?tab=doc#Value)
Damien Neil5365b3b2020-02-20 15:38:55 -0800131containing the field value.
132
133The
Russ Coxaf5018f2020-03-09 23:54:35 -0400134[`protoreflect.FieldDescriptor.Options`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect?tab=doc#Descriptor.Options)
Damien Neil5365b3b2020-02-20 15:38:55 -0800135method returns the field options as a `google.protobuf.FieldOptions`
136message.
137
Russ Cox9dd3d9b2020-03-09 23:19:59 -0400138 opts := fd.Options().(*descriptorpb.FieldOptions)
Damien Neil5365b3b2020-02-20 15:38:55 -0800139
140(Why the type assertion? Since the generated `descriptorpb` package
141depends on `protoreflect`, the `protoreflect` package can't return the
142concrete options type without causing an import cycle.)
143
144We can then check the options to see the value of our extension boolean:
145
Russ Cox9dd3d9b2020-03-09 23:19:59 -0400146 if proto.GetExtension(opts, policypb.E_NonSensitive).(bool) {
147 return true // don't redact non-sensitive fields
148 }
Damien Neil5365b3b2020-02-20 15:38:55 -0800149
150Note that we are looking at the field _descriptor_ here, not the field
151_value_. The information we're interested in lies in the protocol
152buffer type system, not the Go one.
153
154This is also an example of an area where we
155have simplified the `proto` package API. The original
Russ Coxaf5018f2020-03-09 23:54:35 -0400156[`proto.GetExtension`](https://pkg.go.dev/github.com/golang/protobuf/proto?tab=doc#GetExtension)
Damien Neil5365b3b2020-02-20 15:38:55 -0800157returned both a value and an error. The new
Russ Coxaf5018f2020-03-09 23:54:35 -0400158[`proto.GetExtension`](https://pkg.go.dev/google.golang.org/protobuf/proto?tab=doc#GetExtension)
Damien Neil5365b3b2020-02-20 15:38:55 -0800159returns just a value, returning the default value for the field if it is
160not present. Extension decoding errors are reported at `Unmarshal` time.
161
162Once we have identified a field that needs redaction, clearing it is simple:
163
Russ Cox9dd3d9b2020-03-09 23:19:59 -0400164 m.Clear(fd)
Damien Neil5365b3b2020-02-20 15:38:55 -0800165
166Putting all the above together, our complete redaction function is:
167
Russ Cox9dd3d9b2020-03-09 23:19:59 -0400168 // Redact clears every sensitive field in pb.
169 func Redact(pb proto.Message) {
170 m := pb.ProtoReflect()
171 m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
172 opts := fd.Options().(*descriptorpb.FieldOptions)
173 if proto.GetExtension(opts, policypb.E_NonSensitive).(bool) {
174 return true
175 }
176 m.Clear(fd)
177 return true
178 })
179 }
Damien Neil5365b3b2020-02-20 15:38:55 -0800180
181A more complete implementation might recursively descend into
182message-valued fields. We hope that this simple example gives a
183taste of protocol buffer reflection and its uses.
184
Russ Coxaf5018f2020-03-09 23:54:35 -0400185## Versions
Damien Neil5365b3b2020-02-20 15:38:55 -0800186
187We call the original version of Go protocol buffers APIv1, and the
188new one APIv2. Because APIv2 is not backwards compatible with APIv1,
189we need to use different module paths for each.
190
191(These API versions are not the same as the versions of the protocol
192buffer language: `proto1`, `proto2`, and `proto3`. APIv1 and APIv2
193are concrete implementations in Go that both support the `proto2` and
194`proto3` language versions.)
195
196The
Russ Coxaf5018f2020-03-09 23:54:35 -0400197[`github.com/golang/protobuf`](https://pkg.go.dev/github.com/golang/protobuf?tab=overview)
Damien Neil5365b3b2020-02-20 15:38:55 -0800198module is APIv1.
199
200The
Russ Coxaf5018f2020-03-09 23:54:35 -0400201[`google.golang.org/protobuf`](https://pkg.go.dev/google.golang.org/protobuf?tab=overview)
Damien Neil5365b3b2020-02-20 15:38:55 -0800202module is APIv2. We have taken advantage of the need to change the
203import path to switch to one that is not tied to a specific hosting
204provider. (We considered `google.golang.org/protobuf/v2`, to make it
205clear that this is the second major version of the API, but settled on
206the shorter path as being the better choice in the long term.)
207
208We know that not all users will move to a new major version of a package
209at the same rate. Some will switch quickly; others may remain on the old
210version indefinitely. Even within a single program, some parts may use
211one API while others use another. It is essential, therefore, that we
212continue to support programs that use APIv1.
213
Russ Coxaf5018f2020-03-09 23:54:35 -0400214 - `github.com/golang/protobuf@v1.3.4` is the most recent pre-APIv2 version of APIv1.
Damien Neil5365b3b2020-02-20 15:38:55 -0800215
Russ Coxaf5018f2020-03-09 23:54:35 -0400216 - `github.com/golang/protobuf@v1.4.0` is a version of APIv1 implemented in terms of APIv2.
217 The API is the same, but the underlying implementation is backed by the new one.
218 This version contains functions to convert between the APIv1 and APIv2 `proto.Message`
219 interfaces to ease the transition between the two.
Damien Neil5365b3b2020-02-20 15:38:55 -0800220
Russ Coxaf5018f2020-03-09 23:54:35 -0400221 - `google.golang.org/protobuf@v1.20.0` is APIv2.
222 This module depends upon `github.com/golang/protobuf@v1.4.0`,
223 so any program which uses APIv2 will automatically pick a version of APIv1
224 which integrates with it.
Damien Neil5365b3b2020-02-20 15:38:55 -0800225
Russ Cox482079d2020-03-09 22:11:04 -0400226(Why start at version `v1.20.0`? To provide clarity.
227We do not anticipate APIv1 to ever reach `v1.20.0`,
228so the version number alone should be enough to unambiguously differentiate
229between APIv1 and APIv2.)
Damien Neil5365b3b2020-02-20 15:38:55 -0800230
231We intend to maintain support for APIv1 indefinitely.
232
233This organization ensures that any given program will use only a single
234protocol buffer implementation, regardless of which API version it uses.
235It permits programs to adopt the new API gradually, or not at all, while
236still gaining the advantages of the new implementation. The principle of
237minimum version selection means that programs may remain on the old
238implementation until the maintainers choose to update to the new one
239(either directly, or by updating a dependency).
240
Russ Coxaf5018f2020-03-09 23:54:35 -0400241## Additional features of note
Damien Neil5365b3b2020-02-20 15:38:55 -0800242
243The
Russ Coxaf5018f2020-03-09 23:54:35 -0400244[`google.golang.org/protobuf/encoding/protojson`](https://pkg.go.dev/google.golang.org/protobuf/encoding/protojson)
Damien Neil5365b3b2020-02-20 15:38:55 -0800245package converts protocol buffer messages to and from JSON using the
Russ Coxaf5018f2020-03-09 23:54:35 -0400246[canonical JSON mapping](https://developers.google.com/protocol-buffers/docs/proto3#json),
Damien Neil5365b3b2020-02-20 15:38:55 -0800247and fixes a number of issues with the old `jsonpb` package
248that were difficult to change without causing problems for existing users.
249
250The
Russ Coxaf5018f2020-03-09 23:54:35 -0400251[`google.golang.org/protobuf/types/dynamicpb`](https://pkg.go.dev/google.golang.org/protobuf/types/dynamicpb)
Damien Neil5365b3b2020-02-20 15:38:55 -0800252package provides an implementation of `proto.Message` for messages whose
253protocol buffer type is derived at runtime.
254
255The
Russ Coxaf5018f2020-03-09 23:54:35 -0400256[`google.golang.org/protobuf/testing/protocmp`](https://pkg.go.dev/google.golang.org/protobuf/testing/protocmp)
Damien Neil5365b3b2020-02-20 15:38:55 -0800257package provides functions to compare protocol buffer messages with the
Russ Coxaf5018f2020-03-09 23:54:35 -0400258[`github.com/google/cmp`](https://pkg.go.dev/github.com/google/go-cmp/cmp)
Damien Neil5365b3b2020-02-20 15:38:55 -0800259package.
260
261The
Russ Coxaf5018f2020-03-09 23:54:35 -0400262[`google.golang.org/protobuf/compiler/protogen`](https://pkg.go.dev/google.golang.org/protobuf/compiler/protogen?tab=doc)
Damien Neil5365b3b2020-02-20 15:38:55 -0800263package provides support for writing protocol compiler plugins.
264
Russ Coxaf5018f2020-03-09 23:54:35 -0400265## Conclusion
Damien Neil5365b3b2020-02-20 15:38:55 -0800266
267The `google.golang.org/protobuf` module is a major overhaul of
268Go's support for protocol buffers, providing first-class support
269for reflection, custom message implementations, and a cleaned up API
270surface. We intend to maintain the previous API indefinitely as a wrapper
271of the new one, allowing users to adopt the new API incrementally at
272their own pace.
273
274Our goal in this update is to improve upon the benefits of the old
275API while addressing its shortcomings. As we completed each component of
276the new implementation, we put it into use within Google's codebase. This
277incremental rollout has given us confidence in both the usability of the new
278API and the performance and correctness of the new implementation. We believe
279it is production ready.
280
281We are excited about this release and hope that it will serve the Go
282ecosystem for the next ten years and beyond!