Defining Protocol Buffer Messages
1. Creating Message Types
Example: Basic message
message Product {
string sku = 1;
string name = 2;
double price = 3;
bool in_stock = 4;
}
| Rule | Convention |
|---|---|
| Message name | PascalCase, singular noun |
| Field name | snake_case |
| Enum value | UPPER_SNAKE_CASE, prefixed with enum name |
2. Defining Scalar Field Types
| Proto Type | Go | Java | Python |
|---|---|---|---|
| double | float64 | double | float |
| float | float32 | float | float |
| int32 / int64 | int32 / int64 | int / long | int |
| uint32 / uint64 | uint32 / uint64 | int / long | int |
| bool | bool | boolean | bool |
| string | string | String | str |
| bytes | []byte | ByteString | bytes |
3. Assigning Field Numbers
| Guideline | Rationale |
|---|---|
| 1–15 for hot fields | Single-byte tag |
| Never reuse numbers | Breaks wire compatibility |
Use reserved 4, 7; | Prevents future reuse |
| Sequential, not sparse | Easier to manage |
4. Using Optional Fields
Example: Presence tracking
message UpdateUserRequest {
string id = 1;
optional string email = 2; // can distinguish "not set" from ""
optional int32 age = 3;
}
| Feature | Singular | optional |
|---|---|---|
| Presence on wire | Only if non-default | Always when set |
| HasField API | Not available | Available |
| Use case | Required-ish data | Patch/Update RPCs |
5. Using Repeated Fields
Example: Lists
message Cart {
repeated string product_ids = 1;
repeated LineItem items = 2; // [packed=true] is default for scalars
}
| Aspect | Note |
|---|---|
| Order | Preserved |
| Packed encoding | Default for scalar repeated in proto3 |
| Empty vs unset | Indistinguishable |
6. Defining Nested Messages
Example: Nested types
message Order {
message LineItem {
string sku = 1;
int32 qty = 2;
}
repeated LineItem items = 1;
}
// Referenced externally as Order.LineItem
| Use Case | Choice |
|---|---|
| Tightly coupled type | Nest inside parent |
| Reused across messages | Top-level message |
7. Creating Enumerations
Example: Enum with safe default
enum OrderStatus {
ORDER_STATUS_UNSPECIFIED = 0; // MUST be 0
ORDER_STATUS_PENDING = 1;
ORDER_STATUS_PAID = 2;
ORDER_STATUS_SHIPPED = 3;
ORDER_STATUS_CANCELED = 4;
}
| Rule | Detail |
|---|---|
| First value | Must be 0 and represent UNSPECIFIED |
| Naming | Prefix values with enum name to avoid collisions |
allow_alias = true | Permits duplicate numbers (aliases) |
8. Using Reserved Fields
Example: Reserve removed fields
message User {
reserved 2, 5, 9 to 11;
reserved "legacy_token", "old_email";
string id = 1;
string email = 3;
}
| Reserve | Prevents |
|---|---|
| Numbers | Accidental wire-format reuse |
| Names | Accidental JSON/text mapping collision |
9. Defining Map Fields
Example: Map field
message Request {
map<string, string> headers = 1;
map<int32, Product> products_by_id = 2;
}
| Constraint | Detail |
|---|---|
| Key types | Any integral or string (no float, bytes, message) |
| Value types | Any except another map |
| Order | Unordered |
repeated | Not allowed |
10. Using Oneof Fields
Example: Mutually exclusive payload
message Notification {
string user_id = 1;
oneof channel {
EmailPayload email = 2;
SmsPayload sms = 3;
PushPayload push = 4;
}
}
| Property | Detail |
|---|---|
| Only one field set | Setting another clears the previous |
| Cannot be repeated | Use repeated Wrapper instead |
| No maps inside | Wrap in a message |
11. Importing Proto Files
Example: Importing well-known + local files
syntax = "proto3";
package user.v1;
import "google/protobuf/timestamp.proto";
import "common/v1/address.proto";
message User {
string id = 1;
google.protobuf.Timestamp created_at = 2;
common.v1.Address address = 3;
}
| Tip | Detail |
|---|---|
| Use fully-qualified names | Disambiguates across packages |
| Avoid cycles | protoc rejects circular imports |
12. Setting Package Names
| Convention | Example |
|---|---|
| Versioned package | company.product.v1 |
| Directory layout | Mirrors package: proto/company/product/v1/*.proto |
| Bumping version | Copy to v2/ for breaking changes — never edit v1 |