Stage 3 · Build
API Design & gRPC
Protobuf Contracts
Define proto3 messages, field numbers, packages, buf lint rules, and backward-compatible schemas.
Why Protocol Buffers
Protocol Buffers are a language-neutral, platform-neutral format for serializing structured data. They are smaller and faster than JSON. Protobuf definitions serve as the contract between services in gRPC architectures.
Proto3 Syntax
syntax = "proto3";
package user.v1;
message User {
string id = 1;
string email = 2;
string name = 3;
UserRole role = 4;
google.protobuf.Timestamp created_at = 5;
}
enum UserRole {
USER_ROLE_UNSPECIFIED = 0;
USER_ROLE_ADMIN = 1;
USER_ROLE_USER = 2;
USER_ROLE_VIEWER = 3;
}Proto3 uses field numbers instead of names for wire format. The first value of every enum must be 0 (UNSPECIFIED). This handles unknown enum values gracefully.
Field Numbers
Field numbers determine the wire format. They are permanent — you cannot change or reuse them. Low numbers (1-15) use one byte. Higher numbers use two bytes. Assign frequently used fields low numbers.
| Range | Wire Size | Use For |
|---|---|---|
| 1-15 | 1 byte | Frequently used fields |
| 16-2047 | 2 bytes | Less common fields |
| 2048+ | 3 bytes | Rarely used fields |
Once a field number is assigned, it is permanent. Deleting a field preserves its number as reserved. Reusing field numbers causes silent data corruption across versions.
Packages and Imports
syntax = "proto3";
package order.v1;
import "google/protobuf/timestamp.proto";
import "user/v1/user.proto";
message Order {
string id = 1;
string user_id = 2;
repeated OrderItem items = 3;
OrderStatus status = 4;
google.protobuf.Timestamp created_at = 5;
}
message OrderItem {
string product_id = 1;
int32 quantity = 2;
double price = 3;
}
enum OrderStatus {
ORDER_STATUS_UNSPECIFIED = 0;
ORDER_STATUS_PENDING = 1;
ORDER_STATUS_CONFIRMED = 2;
ORDER_STATUS_SHIPPED = 3;
ORDER_STATUS_DELIVERED = 4;
}Use package names to organize proto files. Import other proto files for shared types. The package name becomes the Go package path.
buf Lint Rules
version: v2
modules:
- path: proto
lint:
use:
- STANDARD
- COMMENTS
except:
- UNARY_RPC
rpc:
allow_google_protobuf_empty_requests: true
enum_zero_value_suffix: _UNSPECIFIED
service_suffix: Service
forbid_google_protobuf_empty_requests: true
breaking:
use:
- FILE
- PACKAGEbuf lint enforces consistent proto style. STANDARD includes rules for naming, comments, and structure. breaking detects backward-incompatible changes.
Backward Compatibility
- Never reuse field numbers — mark deleted fields as reserved.
- Never change field types — add new fields instead.
- Never change field numbers.
- New fields must have new numbers.
- Use optional for fields that may not be present.
- Default values must be backward-compatible.
message User {
reserved 2, 15, 9 to 11;
reserved "old_name", "deprecated_field";
string id = 1;
// field 2 was deleted — use reserved to prevent reuse
string email = 3;
string name = 4;
// fields 9-11 were deleted
string new_field = 12;
}Reserve deleted field numbers and names. This prevents accidental reuse. buf lint will flag any attempt to reuse reserved fields.
In gRPC architectures, proto files define the contract. Generate Go code from protos. Never hand-write protobuf serialization. The proto file is the single source of truth for message formats.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.