Because services deploy independently, you can never assume callers upgrade in lockstep. API versioning plus backward compatibility lets producers evolve without breaking existing consumers.
Because services deploy independently, you can never assume callers upgrade in lockstep. API versioning plus backward compatibility lets producers evolve without breaking existing consumers.
| Strategy | Example |
|---|
| URI path | GET /v2/orders/42 |
| Header | Accept: application/vnd.api.v2+json |
| Schema evolution | add fields, never remove/rename |
NON-BREAKING (safe):
✓ add a new optional field
✓ add a new endpoint
✓ add a new enum value (if clients tolerate unknowns)
BREAKING (needs a new version):
✗ remove or rename a field
✗ change a type or make a field required
✗ change semantics of an existing field
message Order {
string id = 1;
double total = 2;
string currency = 3; // NEW field 3 — old clients ignore it safely
}
Field numbers, not names, define the wire format, so adding fields is backward compatible.
Release v2 ─▶ run v1 + v2 together ─▶ migrate consumers ─▶ deprecate v1 ─▶ remove v1
Never remove a version while real traffic still uses it. Track usage before retiring.
Independent deployability only works if a producer can ship a change without coordinating every consumer's release.
Designing for additive change and supporting old versions during migration is what preserves that independence instead of forcing fragile big-bang upgrades.