Recipe

Protobuf Schema Design

Designing protobuf schemas that survive years of API evolution is a craft. This recipe distills the patterns Meridian uses internally to keep gateway payloads forward-compatible, backward-compatible, and resistant to the silent-drift bugs that plague long-lived RPC contracts.

1.Reserve field numbers aggressively

Field numbers are forever. Once a number ships to a single client, it can never be reused for a different semantic. Reserve removed fields explicitly so the compiler refuses to recycle them. Reserve numeric ranges for future extensions before you need them.

message Request {
  string id = 1;
  reserved 2, 3, 7 to 10;
  reserved "legacy_token", "old_region";
  string region = 4;
}

2.Prefer wrapper types for optional scalars

Proto3 scalars cannot distinguish "unset" from "zero". For any field where the absence of a value is semantically different from the default, use the well-known wrapper types fromgoogle.protobuf. This keeps your gateway response objects unambiguous when downstream services need to merge partial updates or compute deltas.

3.Version through package paths, not field flags

When a breaking change is unavoidable, do not mutate the existing message in place. Publish a sibling package (meridian.v2) and let the gateway translate between them. Field flags like use_new_format always leak across service boundaries and become impossible to retire.