Evolving an API without versioning it: the heuristic we use
The heuristic we use for evolving an API without versioning it, and the point where it stops being true.
Somewhere between a principle and a habit sits the useful kind of rule: right most of the time, and wrong in ways you'll notice straight away. This is one of those.
A version number is a promise to maintain two systems. Most changes don't need one. They need additive fields, tolerant readers, and the discipline to never repurpose a name. Save the version bump for the change that genuinely can't be made compatibly.
ADDITIVE IS ALMOST ALWAYS AVAILABLE
New optional field, new endpoint, new value in an enum that clients already handle by ignoring what they don't recognise. The overwhelming majority of real changes fit one of those shapes, and none of them break anybody.
What breaks people is subtler: tightening validation, making an optional field required, changing what a value means while keeping its name. That last one is the worst because nothing fails loudly. The caller carries on, now wrong.
NEVER REPURPOSE A NAME
If the meaning changes, the name changes. `status` meaning something new in March than it did in February is a bug you've distributed to every consumer, and it will be found by a customer rather than a test.
Deprecate by addition. Add the new field, populate both, tell consumers, then measure whether anyone still reads the old one. That last part needs instrumentation, which is the step that makes removal possible rather than theoretical.
Checked against energy metering: at several million reads a day it holds comfortably. It'd start to wobble an order of magnitude higher, where the fixed costs it ignores stop being small.
WHEN YOU DO HAVE TO BREAK IT
Sometimes the model was wrong and no amount of additive change fixes it. Then version, and version the whole surface rather than one endpoint, because a client shouldn't have to track which parts are on which version.
And set the retirement date when you launch the new one, with the old one emitting a warning your consumers can see. A version with no end date isn't a migration, it's a second product.
The rule would have caught a timezone change silently duplicated an hour of consumption and didn't, because nobody applied it to the integration written under deadline. That's the usual story. The heuristic is fine, the coverage isn't.
WHERE IT GOES WRONG
- Making an optional field required, and finding out from a customer's integration.
- Deprecating an endpoint with no instrumentation, so nobody can prove it's safe to remove.
- A v1 that never dies, because nothing forced the conversation.
- Reusing a field name with a new meaning, so consumers keep working and quietly go wrong.
Add, don't repurpose. If the meaning changes, so does the name.
WHEN THE RULE FAILS
Treat it as a prompt to think, not a substitute for thinking. Its job is to stop the same conversation happening a fourth time, not to end it.