You are already here, then I reckon you realize the value of API-led integration right off the bat; those chatters around decentralizing and democratizing access to your enterprise data which eventually would lead to a long-awaited self-service model. API-led connectivity approach rhymes perfectly with your service-oriented heritage and could hugely increase your productivity as a “Producer / Provider”.
APIs are contracts established between you and your API consumers. Ideally, you will never have to break this contract. This includes URI patterns, payload structures, field / parameter names, expected behavior, and everything else in between.
When your API has reached the point of expanding beyond its original intent and capacity, it’s time to consider the next version. Whether that next iteration is a whole number version bump or just a feature expansion, it’s important to consider the pros and cons of how you let your consumers (ex. those who may very well be your customers or partner ecosystems) know about it.
Unlike traditional software versioning, API versioning can have complex implications for the products using it downstream. There are a fair number of decisions that must be made when providing a versioned API. The weightiest decisions are how to determine when the version should change, engineering a versioning scheme, and how to encode the version to be known to consumers.
Versioning is the process of assigning unique version numbers to unique states of “Something”. I bet, or rather hope for, you to be a different person today than you were many years back. Muhammad Ali once said “The man who views the world at 50 the same as he did at 20 has wasted 30 years of his life.”. When you are mature enough, you tend listen more and talk less, do not shy away from responsibilities, less argumentative and more accommodating…etc. and the world around you perceive you differently.
Like you, API evolves and matures too! A major component of a mature API lifecycle is the way an API provider communicates with its consumers. To establish trust, the provider that develops a cadence of expectation delivery aligning with the API contract, can make or break platform adoption and interrupt or facilitate the business ecosystem. API Versioning is one aspect of such communications between you and your consumers.
API Versioning Approaches
When it comes to engineering a Versioning scheme to your APIs, you may consider Semantic Versioning for the APIs ex. MAJOR.MINOR.PATCH
PATCH increments when you do bug fixing, yet you remain backward compatible
MINOR increments when you add new functionality while still maintaining backward compatibility. Once incremented, it resets PATCH
MAJOR increments when you make breaking changes to the API. Once incremented it resets both MINOR & PATCH. Breaking changes may include ex. changing payload structure, removing and endpoint altogether, different API key for GET / POST…etc.
Now, how to encode versions in your API to let every consumer aware
One take at it might be Route Versioning, where you include versions in the URL (/api/v1.4.3/data/123). This is proven to be noticeably clear & structured approach and used by most of the internet really. It allows different documentation for each version branch. Consumers are free to use whatever version, and never breaks if they do read its documentation especially if the MAJOR version is different.
Yes, make no mistake, a consumer subscribed to an API, should have access to all its versions during the validity of the contract / subscription with you. It is up the consumers to use the version that serves them best, regardless of its recent releases. It is your burden to document and announce versions properly.
And there is this more techie practice via HTTP Headers. Since consumers (commonly known to be developers) will need to examine HTTP headers anyway ex. response code, error messages, time-based data, limit bounds, they may very well keep a close eye to other custom headers related to versioning.
The common approach to this is to have a new (registered) MIME-type and then users to include Accept and Content-Type headers for all requests and responses. For Example: A consumer sends:
[Accept: application/your.domain.mime-v1-4-3+json], a server responds with:
[Content-Type: application/your.domain.mime-v1-4-3+json]. Notice how the version is embedded in those headers.
A commonly relevant and well-known technique called Content Negotiation. This is how it works:
As mentioned before, a first step is to register a custom MIME-type (referred to as VND types, which can actually be JSON or XML for example), but now you can use it as custom Content-Type header. You will need to fill this IANA form for that. If you are curious, you can find the existing VND media types registered already, here!
Content negotiation mechanics works when your consumers (ex. user-agent) include several VND types they are willing (or able) to process. This is via comma-separated lists of MIME types, each combined with a
(q, default value = 1.0, ranges from 0.0 to 1.0), which is a parameter indicating the relative degree of preference between the different MIME types. Example:
[Accept: application/your.domain.mime -v1-4-3+json; q=1.0, application/your.client.mime-v1-2-0+json ; q=0.8]. This means if the request arrives to a server node that can process both API versions (1.4.3 & 1.2.0), the client prefers the latest version.
This helps if during development, you have created different branches for each version which leads to different versions deployed across different nodes, for example: if one server is outdated and only serves 1.2.0, while the other is at the latest branch 1.4.3; when such request comes to the former, the server will respond based on the 1.2.0, even the client prefers the latest, BUT the point here he won’t get - God forbid - a 404 error.
If a consumer / agent / client is sloppy enough and they does not provide an appropriate version in the header, indeed they get 404 as a response, but the server now responds with the Content-Type all the time, which has the version the server supports, and hence the consumer will realize the issue immediately.
If the consumer does not provide preferences altogether; you can always use your proxy ex. NGINX / HAProxy or your API Gateway to route the API call to the appropriate server based on the target server’s compatibility against the version included in the request’s Accept header.
Content negotiation is powerful, and it does not stop at these two headers only; same concept can be used for other headers ex.
User-Agent, …etc. when your server has different representations of your resources.
With the above tactics - and others of course, if the consumer just disregards the versioning scheme, documentation, versioning encoding (URL, or HTTP Headers) …etc. and decides to leapfrog to a MAJOR version that – by definition – breaks their code without making changes at their end to accommodate that, then it is their fault, and you have done your due diligence. The key takeaways can be briefed in the following points:
MINOR and PATCH changes / versions must be backward compatible.
Once a consumer subscribed to an API, they are subscribed to all its versions; it is your consumers’ decision on which branch to process.
Use custom MIME type & encode versions into URL & HTTP headers
Mention version in your responses, especially in error cases.
Be able to handle different versions on parallel and make use of content negotiation mechanism. Not easy-peasy, but an elite approach.
Use different branches during development for each version.