In the previous chapter, we defined our Record type in Go as this struct:
| type Record struct { |
| Value []byte `json:"value"` |
| Offset uint64 `json:"offset"` |
| } |
To turn that into a protobuf message we need to convert the Go code into protobuf syntax.
The convention for Go projects is to put your protobuf in an api directory. So run mkdir -p api/v1 to create your directories, then create a file called log.proto in the v1 directory and put this code in it:
| syntax = "proto3"; |
| |
| package log.v1; |
| |
| option go_package = "github.com/travisjeffery/api/log_v1"; |
| message Record { |
| bytes value = 1; |
| uint64 offset = 2; |
| } |
In this protobuf code, we specify that we’re using proto3 syntax—the latest version of protobuf syntax. Then we specify a package name for two reasons: because this protobuf package name is used as the package name in the generated Go code and because it prevents name clashes between protocol message types that have the same name.
These protobuf messages are equivalent to the Go structs shown earlier. You’ll notice the two syntaxes are very similar: in Go you have struct, and with protobuf you have a message—both with a list of fields. In Go you put the name of the field on the left followed by its type, and with protobuf you put the name of the field on right followed by its name (with an additional field ID).
Following the package declarations in the protobuf code, we define our Record type. Protocol buffer programmers use the repeated keyword to define a slice of some type, so repeated Record records means the records field is a []Record in Go.
I mentioned earlier that one handy feature of protobuf is the ability to version fields. Each field has a type, name, and unique field number. These field numbers identify your fields in the marshaled binary format, and you shouldn’t change them once your messages are in use in your projects. Consider fields immutable: you can stop using old fields and add new fields, but you can’t modify existing fields. You want to change fields like this when you make small, iterative changes—like when you add or remove features or data from a message.
Besides field versions, you’ll also want to group your messages by a major version. The major version gives you control over your protobuf when you overhaul projects to rearchitect your infrastructure or run multiple message versions at the same time for a migration period. Bumping major versions should be a rare occurrence because for most changes, field versioning is sufficient. I’ve only had to bump the major version of my protobuf twice, and if you look at Google’s API definitions[5] protobuf, they’ve only bumped their major version a couple times. So changing major versions is uncommon, but it’s nice to have the ability when you need it.
At the beginning of this section, I had you put the log.proto file into an api/v1 directory. The v1 represents these protobufs’ major version. If you were to continue building this project and decided to break API compatibility, you would create a v2 directory to package the new messages together and communicate to your users you’ve made incompatible API changes.
Now that we’ve created the protocol buffer messages, let’s compile your protobuf into Go code.