A pragmatic approach to Semantic Versioning

If you've been developing software for a bit, you're familiar with the concept of versioning software releases. Particularly for APIs, versions are the cornerstone of the evolution and change management process. Semantic Versioning (Semver) has emerged as the universal standard for communicating and managing API changes.

So what's the problem with Semver?

  1. The rules of SEMVER can be tough to understand and apply to a specific API aesthetic.

  2. Semver is inconsistent. It can sometimes be overly lenient and sometimes too strict in what's defined as breaking change. I'll speak to what constitutes a breaking change in this post.

  3. The software developer community lacks a shared understanding of semver, undermining its design goal as a communication tool.

Here're some practical guidelines for what you should consider a breaking change.

  1. Backward incompatible changes to the API signature (parameters (or their types), return types, etc.) that would result in a developer having to refactor their integration to your product.

  2. Changes to how your SDK interacts with the ecosystem (build tools, package managers) that require developer intervention.

  3. Behavioral changes to reasonable assumptions. For example, APIs that encourage a "fire and forget" invocation pattern (logging, counters, etc.) may not see implementation changes as breaking. But anything that adds a noticeable latency to the invocation can lead to sizable behavioral changes and customer breakages. Another example would be a backward incompatible change to serialization formats for objects stored on disk or sent over the wire.

  4. Changes to data collected and used by your service.

  5. Changes to the ABI (for programming languages like Java) that may fly under the radar for both the compiler and developer but could lead to subtle runtime breakages if the app is not recompiled.

  6. Changes that are impossible to roll back. For example, a database SDK may need to migrate on-device data to (say) a new schema to support new features. The app developer will never be able to roll back their launch and use the old version of the SDK.

Here're some guidelines on what need not be considered a breaking change.

  1. Breakages introduced by dependencies of your SDK. Unless you "shade" your dependencies and bundle them into your distribution (not recommended!), the symbols of your SDK's dependencies are also part of the app's namespace. Since this can be particularly problematic if the app uses an incompatible version of the same library, it is essential to depend only on highly stable libraries where breaking changes are rare and thoughtfully made.

  2. Breakages to unintended contracts. While your APIs indicate a specific contract, customers interpret their contract. Breaking even such implicit contracts leads to an unpleasant experience. For example, while you might document your API as a long-running asynchronous operation, there will always be customers that notice that your API returns immediately and design their UI to (say) not include a spinner. As the SaaS provider, you're burdened with not introducing feature flags that may inadvertently increase request latency just so that it does not break such implicit contracts. I vote to be brave about introducing these types of breakages.

  3. Rollbacks / Changes to Beta APIs. APIs still under flux should be tagged as Beta. Depending on the programming language and the SDK distribution mechanism, you can indicate this at the API level or tag the entire SDK as containing Beta level functionality. 

I'll keep these lists updated when I remember other classes of breakage. Good luck!

Previous
Previous

Things to know before you build SDKs (Copy)

Next
Next

How to build a SaaS API