The first version of a service contract is easy. The hard part is version four, when three teams depend on the old shape, one consumer is still on a mobile release from last year, and the field you want to rename is load-bearing.
gRPC gives you strong tools for this. It does not give you discipline.
Design for evolution, not purity
Protobuf is built around additive change. Use that:
- Add fields; do not repurpose them.
- Reserve field numbers and names when you remove something.
- Keep enums open by handling unknown values, or put a new value behind a capability flag.
- Prefer
optionaland wrapper types when “unset” is meaningfully different from zero.
A schema that reads a little redundantly but survives three years of change is better than an elegant one that forces a synchronized release across every consumer.
Boundaries should follow ownership
A common failure is a “shared” proto package that every service imports and every team edits. Within a quarter it contains fields nobody owns and comments that contradict the code.
Put contracts next to the team that owns the domain, publish them as versioned artifacts, and treat a breaking change like a public API change — because it is one. If two services need to negotiate the same schema daily, they are probably one service with an expensive network hop.
Errors are part of the contract
HTTP status codes are not enough. Define your error model explicitly with google.rpc.Status details or a domain-specific error payload. Consumers need to distinguish:
- retryable transient failure,
- invalid input the client can fix,
- authorization failure,
- and “this will never work, stop retrying.”
If the client cannot tell those apart, it will retry all of them.
Test the edges
Contract tests catch the failures that unit tests miss: field renames, enum drift, and a default value that silently changes meaning. Generate clients in CI and compile against the published artifact. It is unglamorous, and it is the cheapest insurance you will buy this quarter.
Good boundaries are boring. Requests are fast, failures are legible, and nobody needs a meeting to add a field.