Protocol Buffers Intro
Protobuf is the contract
Section titled “Protobuf is the contract”Protocol Buffers (protobuf) is the language you use to describe your data and services, and the format they travel in. It plays two roles at once:
- A schema language — a
.protofile that declares your messages and services in a way any language can compile. - A wire format — the compact binary encoding those messages use when they cross the network.
In gRPC, protobuf is the contract. Everything — the type safety, the codegen, the efficiency — flows from it.
A message is fields with numbers
Section titled “A message is fields with numbers”Here is the smallest interesting message:
syntax = "proto3";
message User { int64 id = 1; string name = 2; string email = 3;}The = 1, = 2, = 3 are field numbers, and they are the most important idea in protobuf. On the wire, protobuf does not send field names. It sends the field number and the value. So name = "Ada" becomes roughly “field 2, string, Ada” — a couple of bytes of tag plus the value.
flowchart LR
subgraph json["JSON on the wire"]
j["{"id":1,"name":"Ada","email":"[email protected]"}
— keys repeated as text"]
end
subgraph pb["Protobuf on the wire"]
p["[1→1][2→Ada][3→[email protected]]
— numbered tags + binary values"]
end Two consequences fall out of this one design choice:
- It’s compact. No repeated key names, no quotes, no whitespace — just tags and values.
- It’s evolvable. Because the number identifies the field, you can rename
nametofull_namein the.protowithout breaking anyone — the wire only ever saw field2. This is the foundation of safe schema evolution (a whole later lesson).
Why not just use JSON?
Section titled “Why not just use JSON?”JSON is wonderful for public APIs — readable, universal, zero setup. But for high-volume service-to-service calls it has real costs that protobuf avoids:
| JSON | Protobuf | |
|---|---|---|
| Size | Keys sent as text, every time | Numbered tags, binary values |
| Parse cost | Reflective text parsing | Schema-driven binary decode |
| Schema | Optional, drifts easily | Mandatory, compiler-enforced |
| Type safety | Runtime, hope-for-the-best | Generated types, compile-time |
| Evolution | Convention and courage | Field numbers make it structured |
The trade-off is honesty in the other direction: protobuf is not human-readable on the wire, and you need the .proto to interpret bytes. That’s a fine deal for internal traffic and a poor one for a curl-friendly public API.
The contract-first workflow
Section titled “The contract-first workflow”The discipline protobuf imposes is write the contract first. Before either team writes code, they agree on the .proto: these are the messages, these are the methods. Then codegen produces matching types on both sides. The schema can’t silently drift, because both the client and the server are generated from it.