Skip to content

Comment on Semantic Versioning 2.0 (2013)parent

Comments

Nobody uses semantic versioning for online APIs, because it's expensive to maintain all of the existing versions of the API for all of time. That means that you make very few backwards-incompatible releases, which has a tendency to clump your backwards-incompatible changes into large, planned releases. Stripe's API is a great example of this.

Semver is meant for library versioning. When they say "API" they mean the API that the library exposes.

Many developers said this to me (about API versioning) when I was a PM and somehow I struggled to believe it was as intractable as they said.

I suspect the reality is the human cost of dealing with versioning in an API is the problem. Not that "it can't be done" but that nobody wants to incur the downstream costs in their lived work, either of running a v1 and a v2 binding, or of managing transitions, or of the whole "is a superset capable of managing with the reduced subset" thing.

If you want to be light hearted, IPv4 -> IPv6 is a versioning example where clean break is demanded, and we're at 35% worldwide. Clearly, sometimes, you have to wear a 20+ year versioning cost.

I used to thought experiment "what if we wrote a complete functional client in python, for every version change we release, and gave it to every client" type ideas. Incur the cost in them, to convert for our benefit.

Tech leads always went to no.

I suspect the reality is the human cost of dealing with versioning in an API is the problem.

Well, yes.

- You release a new API version. Now you find a bug. Now you need to fix the bug twice.

- You decide to change the way a feature works. You can't get rid of the old code. Now you have two copies of the same API that work differently and need to be documented and run tests. Perhaps they each need to maintain their own data models.

- Every API version means more code. That's more room for security vulnerabilities.

It’s certainly tractable, but the amount of effort can vary significantly on the amount of difference between the two versions.

In the simplest case, you can start by duplicating all of your existing API code, and make it addressable as a new endpoint (e.g. https://example.com/v2).

Now imagine you make a change like renaming all the API operations or all the parameters. You can make this directly in the v2 code without changing anything else; and you can maintain the two APIs in parallel like this indefinitely.

However, the maintenance costs of this approach can become significant over time, e.g.

1) Perhaps you develop a new feature, made available as a new API operation. Do you need to implement this for both API versions? Maybe some customers are still using V1 and they want the feature. Depending on how much V1 and V2 implementations have drifted, this effort might be significantly more than copying the code.

1b) And anytime you duplicate effort like this, you may also need to duplicate tests, canaries, monitors, client SDKs, and any other moving part.

2) If you radically change the data layer underneath the API, or some other critical aspect of its implementation, then you may need to make that change twice, in both V1 and V2 — unless it is encapsulated sufficiently such that the code for both can call it.

For example, if you have decided that you need to add caching to a common read API, then you may need to implement that in both of the V1 and V2 code path (unless the implementation of both versions calls some module common to both, and you can change this module to add caching - but this isn’t always possible).

3) There are some crosscutting concerns that inevitably affect all API versions. For example, if you are changing the way that authorization logic works, or changing the way that actions interact with resources, then for correctness/safety/security reasons, it may be necessary to ensure that all API versions operate identically. If you build a new access control feature for the V2 API, then that doesn’t do you any good if the V1 API ignores the same constraints. The same may also be true for observability changes like logging and monitoring — you may need to apply them to all APIs.

If you have sufficient reason to make a breaking change to the API, then it is likely that whatever motivated the new API will also cause their implementations to begin drifting apart. The more they drift, the greater the maintenance effort can be.

On the other hand, if the design of your system is such that you can largely leave the code for V1 alone, and you are sure you won’t need to change V1 even as you are significantly improving V2 (including all downstream dependencies, which the V1 code might also call into) then it might not be so problematic to operate multiple API versions this way. You would also duplicate all of your tests, etc. and leave the ones for V1 alone.

Indeed — if this is what your comment is getting at — in some systems you could conceivably implement the V1 API in terms of a call into the V2 code, as a sort of compatibility layer. (And that would also be a safer approach, if it is feasible, with respect to crosscutting concerns like authorization)

If you do end up actively maintaining and developing both API versions, then the total effort can in some cases become greater than 2x the cost of one API version, due to the additional effort of making sure that both code paths work correctly for all possible interactions.

If you end up having this conversation with someone in the future, then ask why the effort is greater than copying the V1 code to a new V2 endpoint and proceeding to change V2. This should focus the conversation on the specific implementation challenges unique to the situation.

It almost defeats the point of versioning on networked/web APIs really.

On the one hand you have library code that is semantically versioned, and for good reason those versions are immutable so if something changes you have to pull down a version. The old version is always going to work unless it's wrapping a poorly designed web API.

Web APIs are mutable, also for good reason, but it seriously hammers home the fact that whatever you build is forever and once it's out there, you can't walk it back.

The end result of that though isn't incredible API design, it usually forces your hand.

It's actually quite common to use the major version of semver for API versioning. Users of the API don't get to choose minor and patch versions though, they also don't need to because there's no breaking changes in them (ideally :) ).

Semver is hardly just major version numbers.

When you do want to maintain multiple simultaneous versions of an API that can be used simultaneously, then you might employ a SemVer style approach. Really, each version of the endpoint is addressed separately (as its own endpoint).

AWS has employed this strategy before when making significant changes to APIs. I believe DynamoDB is on version 2 for example.

I’d agree that versioning services this way is uncommon due to the expense of maintaining multiple simultaneous code stacks (which can be more than 2x the effort of one stack - see my other comment in this thread). But if client experience is paramount and/or migration is difficult then it may make sense.

AboutSource Built by g1lg1l

Hackerly is an independent reader for Hacker News, built on the public HN API. Not affiliated with Y Combinator.