Real life Rest API Versioning for dummies
Once upon a time an API …
Once upon a time, the ACME Corporation was building a brand new IT product. It aimed at a new software to manage bookstores through a web interface and an API.
In the first steps, the developers drew up a first roadmap of their API based on the expectations of their first customers. They therefore built and shipped a microservices platform and released their first service contract for their early adopters.
Here is the design of this platform:
The High level design
More in depth
The platform and its roadmap
After shipping it into production, they drew up a roadmap for their existing customers to both improve the existing features and bring new ones.
As of now, we could think everything is hunky-dory isn’t it?
While engineers worked on improving the existing API, the sales representative have contracted with new customers. They enjoyed this product and its functionalities. However, they also ask for new requirements and concerns.
Some of them are easy to apply, some not.
For instance, a new customer asked the ACME engineers for getting a
summary for every book and additional REST operations.
However, last but not least, this customer would also get a list of authors for every book whereas the existing application only provides ONE author per book.
This is a breaking change!
A breaking change occurs when the backward compatibility is broken between two following versions.
For instance, when you completely change the service contract on your API, a client which uses the old API definition is unable to use your new one.
A common theoretical approach could be to apply versions on our APIs and adapt it according to the customer.
Unfortunately, the devil is in the details.
I will describe in this article attention points I struggled with in my last projects.
What do we versionize? How and where to apply it?
After answering to the first question: Do I really need API versioning? you then have to answer to this new one: what should we consider versioning?
You only have to version the service contract.
In the case of a simple web application based on a GUI and an API
Versioning is applied in the service contract of your API. If you change your database without impacting the APIs, why should you waste your time creating and managing a version of your API? It doesn’t make sense.
On the other way around, when you evolve your service contract, you usually impact your database (e.g., see the first example of breaking change above).
Moreover, the version is usually specified on the “middleware” side, where your expose your API.
How many versions must I handle?
Throughout my different experiences struggling with API versioning, the most acceptable trade-off for both the API provider and customer/client was to only handle two versions: the current and the deprecated one.
Now, you have to answer to this question: Where should I handle the version?
- On the Gateway?
- On Every Backend?
- On every service or on every set of services?
- Directly in the code managed by different packages.
Usually, I prefer manage it on the gateway side and don’t bother with URL management on every backend. It could avoid maintenance on both code and tests for every release. However, you can’t have this approach on monolithic applications (see below).
How to define it?
Here are three ways to define API versions:
- In the URL (e.g.,
- In a HTTP header (e.g.,
- In the content type (e.g.,
I strongly prefer the first one. It is the most straightforward.
For instance, if you provide your books API first version, you can declare this URL in your OpenAPI specification:
The version declared here is pretty clear and difficult to miss.
If you specify the version in a HTTP header, it’s less clear.
If you have this URL
/api/books and the version specified in this header:
X-API-VERSION: v1, what would be the version called (or not) if you didn’t specify the header? Is there any default version?
Yes, you can read the documentation, but who (really) does?
The first solution (i.e., version in the URL) mandatory conveys the associated version. It is so visible for all the stakeholders and could potentially avoir any mistakes or headaches while debugging.
What about the main software/cloud providers?
Before reinventing the wheel, let’s see how the main actors of our industry deal with this topic. I looked around and found three examples:
- The version is specified in the URL
- It only represents the major versions which handle breaking changes
- The version is specified in the URL
- The API version is still
- The version is specified in the URL
- The API version is still
Appropriate (or not) technologies
In my opinion, technologies based on the monolith pattern don’t fit handling properly API Versioning. If you are not eager to execute two versions of your monolith, you would have to provide both of the two versions within the same app and runtime.
You see the point?
You would therefore struggle with:
- testing both of two releases for every deployment even if a new feature doesn’t impact the deprecated version
- removing, add new releases in the same source code,… and loosing your mind.
In my opinion, best associated technologies are more modular whether during the development or deployment phases.
For instance, if you built your app with Container based (Docker, Podman, K8S,..) stack, you would easily switch from one version to another, and sometimes you would be able to ship new features without impacting the oldest version.
However, we need to set up our development and integration workflow to do that.
Configuration management & delivery automation
When I dug into API versioning, I realized it impacts projects organisation and, by this way, the following items:
- The source code management: one version per branch or not?
- The release process: How to create releases properly?
- Fixes, merges,…: How to apply fixes among branches and versions?
- The delivery process: How to ship you versions?
Yes it IS a big deal
Here is the least bad approach I think it works while addressing all of these concerns:
Source code configuration
When you want to have two different versions in production, you must decouple your work in several GIT (what else) branches.
For that, I usually put in place GitFlow.
Usually, using this workflow, we consider the
develop branch serves as an integration branch.
But, now we have two separate versions?
Yes, but don’t forget we have a current version and a deprecated one.
To handle API versions, we can use
You can easily declare versions regarding your API versions.
We can so have the following workflow:
- Develop features in feature branches and merge them into the
- Release and use major release numbers (or whatever) to identify breaking changes and your API version number
- Create binaries (see below) regarding the tags and release branches created
- Fix existing branches when you want to backport features brought by new features (e.g., when there is an impact on the database mapping), and release them using minor version numbers
- Apply fixes and create releases
As of now, we saw how to design, create and handle versions.
But, how to ship them?
I you based your source code management on top of GitFlow, you would be able now to deliver releases available from git tags and release branches. The good point is you can indeed build your binaries on top of these ones. The bad one, is you must design and automatise this whole process in a CI/CD pipeline.
Hold on, these programs must be executed against a configuration, aren’t they?
Nowadays, if we respect the 12 factors during our design and implementation, the configuration is provided through environment variables.
Yes, your API versioning will also impact your configuration. It’s so mandatory to externalise and versionize it.
You can do it in different ways.
You can, for example, deploy a configuration server. It will provide configuration key/values regarding the version.
If you want a live example, you can get an example in a workshop I held this year at SnowcampIO. The configuration is managed by Spring Cloud Config.
You can also handle your configuration in your Helm Charts if you deploy your app on top of Kubernetes. Your configuration values will be injected directly during the deployment.
Obviously if it’s a monolith, it will be strongly difficult. Why? Because you will loose flexibility on version management and the capacity on deploying several versions of your service.
Here is another point to potentially address when we implement API versioning. When you apply an authorisation mechanism on your APIs using OAuthv2 or OpenID Connect, you would potentially have strong differences in your authorisation policies between two major releases.
You would then restrict the usage of a version to specific clients or end users. One way to handle this is to use scopes stored in claims.
In the use case we have been digging into, we can declare scopes such as:
number:v2:read to specify both the authorised action and the corresponding version.
For example, here is a request to get an access_token:
And the response could be:
Next, you must validate every API call with the version exposed by your API gateway and the requested scope.
When a client tries to reach an API version with inappropriate scopes (e.g., using
book:v1:read scope for a client which only uses the v2).
You will throw this error:
And now something completely different: How to avoid versioning while evolving your API?
You probably understood it’s totally cumbersome.
Before putting in place all of these practices, there’s another way to add functionalities on a NON-versioned API without impacting your existing customers.
You can add new resources, operations and data without impacting your existing users. With the help of serialization rules, your users would only use the data and operations they know and are confident with. You will therefore bring backward compatibility of your API.
Just in case, you can anticipate API versioning by declaring a
V1 prefix on your API URL and stick to it while it’s not mandatory to upgrade it.
That’s how and why Spotify and Apple (see above) still stick to the
You probably understood when getting into this topic it’s a project management issue consequences that requires tackling difficult technical consequences To sum up, you need to ask to yourself these questions:
- Do I need it?
- Can I postpone API versioning by dealing with serialization rules and just adding new data or operations?
- Is my architecture design compatible?
- Are my source code management and delivery practices compatible?
After coping with all these points, if you must implement API versioning, you would need onboarding all the different stakeholders, not just developers, to be sure your whole development and delivery process is well aligned with practice.
And I forgot: Good luck!