Contracts and versioning
Every API and every event is a contract: a promise about shape and meaning that someone else's code depends on. Independent deployment, the main promise of distributed architectures, only holds if contracts can change without forcing every consumer to change at the same moment. This module covers strict and loose contracts, which changes break consumers, how to version without drowning in versions, and how to find out before release that a change will break someone.
1. The idea
| Strict contract | Loose contract | |
|---|---|---|
| Form | A schema both sides validate against (JSON Schema, Protocol Buffers, OpenAPI with strict validation) | Name-value pairs; consumers read only what they need and ignore the rest |
| Good | Errors caught early; precise; generated code; clear documentation | Producers can add fields freely; consumers rarely break |
| Bad | Any schema change needs coordination or a new version | Mistakes surface later, at runtime; less self-documenting |
Two habits make contracts last. Tolerant reader: consumers ignore fields they don't use and don't fail on new ones. Additive change: producers add, never rename or remove, and when they must, use expand and contract: add the new form, run both, move consumers, then remove the old.
Versioning, honestly
"Just version the API" is a fallacy of distributed architecture: every live version is code to maintain, test and secure. Common practice:
- Version only for breaking changes; add fields without a new version.
- Put the version where clients can see it (
/v2/orders, or a header or media type). - Support a small number at once, typically the current and the previous one.
- Publish a deprecation date, and measure who still calls the old version before turning it off.
2. How it works
Renaming a field without breaking anyone: expand and contract
The Orders API wants to rename total to amount (it now includes tax). Three teams consume it.
Consumer-driven contract tests
Each consumer publishes the parts of the contract it relies on, as tests. The provider runs all of them before releasing. Watch one catch a breaking change.
3. Try it yourself
Will this change break consumers?
Choose a change to the Orders API's response or request. See what happens to a strict consumer (validates the full schema) and a tolerant reader (reads only the fields it uses), and what to do about it.
4. Trade-offs
Strict vs loose
Strict contracts catch mistakes early and suit public or partner APIs where precision matters. Loose contracts and tolerant readers suit fast-changing internal services. Many teams use strict schemas with tolerant validation.
New version vs expand-contract
A new version is clean but doubles what you run until the old one is gone. Expand-contract avoids a version, at the cost of a transition period where both forms exist.
Coupling to the contract vs stamp coupling
Consumers coupled to only the fields they use survive change; consumers handed (and validating) a huge object are coupled to all of it (Module 07).
Consumer tests vs provider freedom
Consumer-driven contract tests protect consumers and tell providers exactly what's used. They also mean consumers' tests can block a provider's release, so keep them precise.
5. In real projects
Know your consumers. You can't safely change a contract if you don't know who uses it. Track callers (API keys, service identities, tracing) and which fields they read.
Events need versioning too. Event schemas live longer than API calls, because logs are replayed. Use a schema registry, allow only compatible changes, and include a version in each event.
Contract tests are cheap insurance. Tools like Pact let each consumer record its expectations and the provider verify them in its build, catching breaking changes before release instead of in production.
Say it in the interview
- "How do you change an API without breaking clients?" Make additive changes, have clients be tolerant readers, and use expand and contract for renames: add the new field, support both, migrate clients, then remove the old one. A new version only for changes that can't be made additively.
- "How many versions do you support?" Usually the current and the previous, with a published deprecation date, and usage metrics to know when the old one can go.
- "How do you know a change breaks someone?" Consumer-driven contract tests: each consumer's expectations run in the provider's build.
- Renaming a field in place "because it's a small change".
- Supporting every version forever.
- Not knowing who consumes an API.
6. Remember it
How this hook works: what "add, don't change" means
What the slogan means, phrase by phrase
- "Add, don't change"
- New optional fields are safe; renames, removals and type changes break someone.
- "read only what you need"
- Tolerant readers ignore unknown fields and depend on as little as possible.
Why this picture fits: it splits the job between both sides of the contract, which is the only way independent deployment works.
The story → the concept
| Plugs and sockets | Contracts |
|---|---|
| The socket's shape | The contract |
| A USB port added beside it | An additive change |
| Years of notice before a change | A deprecation date |
| An adapter in the drawer | Supporting the old version for a while |
The trap it saves you from
The coordinated release of five services because one field was renamed.
7. Common mistakes
8. Design exercise (30 minutes)
- List the consumers (10 min). For one API you own, list every consumer and the fields each reads.
- Plan a rename (10 min). Write the expand-and-contract steps for renaming one field, with a date for each phase.
- Write one contract test (10 min). As a consumer, write the expectations you'd send the provider: the request, and only the response fields you use.
9. Self-check quiz
Which change is safe for tolerant readers without a new version?
What is expand and contract?
A strict consumer receives a response with a new field it doesn't know. What happens?
How do consumer-driven contract tests work?
How many API versions should typically be live at once?
10. Go deeper
| Resource | Time | Why |
|---|---|---|
| Tolerant Reader (Martin Fowler) | 10 min | Why consumers should ignore what they don't need. |
| Consumer-Driven Contracts (Ian Robinson) | 30 min | The pattern behind contract testing. |
| Pact documentation | browse | A widely used contract-testing tool. |