InterviewKata
Software architecture · Level 5: Data and transactions · Module 25 of 46

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

A plug and socket. As long as the socket's shape stays the same, you can change the power station, the wiring and the appliance. Change the socket and every plug in the country needs an adapter. Smart standards add new things (a USB port beside the socket) without removing the old ones, and announce years ahead before retiring anything.
Strict contractLoose contract
FormA 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
GoodErrors caught early; precise; generated code; clear documentationProducers can add fields freely; consumers rarely break
BadAny schema change needs coordination or a new versionMistakes 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:

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

Two-sentence answers
  • "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.
Red flags interviewers notice
  • Renaming a field in place "because it's a small change".
  • Supporting every version forever.
  • Not knowing who consumes an API.

6. Remember it

Memory hook: "Add, don't change; read only what you need"Producers add; consumers tolerate. Everything else needs expand and contract or a new version.
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 socketsContracts
The socket's shapeThe contract
A USB port added beside itAn additive change
Years of notice before a changeA deprecation date
An adapter in the drawerSupporting 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

In-place breaking changesRenaming or removing a field in the current version breaks every consumer that uses it, at once.
Strict consumers of growing contractsConsumers that reject unknown fields turn every harmless addition into an outage.
Version sprawlFive live versions mean five code paths to test and secure. Set a support policy and stick to it.
Unversioned eventsReplaying last year's events through today's code fails if the schema changed without a version.

8. Design exercise (30 minutes)

  1. List the consumers (10 min). For one API you own, list every consumer and the fields each reads.
  2. Plan a rename (10 min). Write the expand-and-contract steps for renaming one field, with a date for each phase.
  3. 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?

Tolerant readers ignore fields they don't use.

What is expand and contract?

A rename without a coordinated release.

A strict consumer receives a response with a new field it doesn't know. What happens?

Strict validation turns additions into breakages; that's why tolerance matters.

How do consumer-driven contract tests work?

A breaking change fails the provider's build, before release.

How many API versions should typically be live at once?

Enough to let consumers migrate, few enough to maintain.

10. Go deeper

ResourceTimeWhy
Tolerant Reader (Martin Fowler)10 minWhy consumers should ignore what they don't need.
Consumer-Driven Contracts (Ian Robinson)30 minThe pattern behind contract testing.
Pact documentationbrowseA widely used contract-testing tool.