While web and mobile are proven channels for selling products and services to our customers, APIs have opened up new avenues of eco-system development around channel partners and resellers. Even the enterprise customers irrespective of their size & scale have expressed interest in integrating to datacenter infrastructure, colocation and Utilization APIs for seamless day-to-day operations to meet the modern day application demands.
Based on the experience of building an API Program and Governance, one of the key challenges is API Versioning. While it looks simple, it has its own overhead in doing the same. We follow Industry standards of at least 2 versions of backward compatibility, so our customers are given sufficient time to move to the latest and the greatest.
Our approach, solution, Journey of API versioning is detailed below,
- Why separate out Release versions from API version naming?
- Why increasing the API Version Nos too soon is a mistake?
- How to support SDKs for multiple versions of APIs?
- When is the time to increase the version?
- Deprecated/unused fields for backward compatibility?
- How to support for multiple API versions at any given point?
- Where to specify version of an API?
- How it affects your Sandbox environment?
Why separate out Release versions from API version naming ?
APIs are for developers. So, it becomes hard to convince the non-developer community (Product management, release management, Delivery Excellence) as which version of API is being released. So our APIs have unique names (E.g. Names of fishes / counties etc. in alphabetical order) for each release. So, our road map for APIs, being shared with non-developer community has these release names and the development team works towards these release names.
The bug fixes / patches have minor revision numbers associated with release names.
The following table shows the different API release nomenclature, significant to relevant audiences.
Note: The above table depicts the association of release nomenclature with most significant audiences. But it does not mean, it is irrelevant to other audiences. E.g, when a patch / bug fix is released, the end customer will be notified, but that does not impact the API Version being used by the customer.
Why increasing the API Version Nos too soon is a mistake?
Unlike portal applications where releases can be too often, the API world does not have the luxury to release new version of APIs frequently as there are very important parameters to consider.
- Backward compatibility of the API
- New version adoption by the customers.
- SDK re-generations for the changed APIs / newly introduced APIs.
We treat the complete API Set (Group of APIs serving an application) under one version. If the version increases, the version of the entire API Set increases.
How to support SDKs for multiple versions of APIs?
One important point to consider is to leverage the code-gen to generate SDKs otherwise; writing SDKs for every release is a parallel project on its own. We use the swagger code-gen
(With little tweaks) for generating the APIs, which has saved us a lot of development time.
When is the time to increase the version?
We increase the version Number (E.g. v1 to v2), only when there are huge amount of new functionalities / APIs being added or if the structure of request/response of APIs changes drastically from the previous version. In gist, when API reaches a stage, where backward compatibility is impossible.
We have at least 4 to 6 releases in a year, but not necessarily every release will have an increment in the version number. Every release will of course have a release name associated with it.
How do we deal with Deprecated/unused fields for backward compatibility?
For every release, which does not need a version number increment; we either add new APIs or add new fields / attributes to the existing APIs.
Some of the fields which are not needed are termed as deprecated but not removed. This is to ensure, the code being used by the customer application does not break. The API catalog for every release documents these fields is deprecated. These fields would be removed in the next version release.
We leverage the swagger specification for defining the API contract, which helps in documenting and testing the APIs. Every change in the API is readily available on the spec, which can be viewed and tested on the Swagger UI.
How to support multiple API versions at any given point?
We have seen applications supporting up-to 3 versions at any given point in time in the industry. But, is it a standard? Not sure.
Supporting multiple versions of APIs at the same time is a huge challenge. It is not just about supporting different versions of APIs but also about maintaining the entire workflow / components supporting the APIs. Not to forget the Database. The entire application, right from the front facing APIs to the Backend database should be considered for
versioning. So, either we have to maintain 3 separate set of codebases or manage the backward compatibility within the same codebase and database, which will not scale after a certain point. Imagine, we have 3 versions (v1, v2 & v3) of APIs and each version is released every year. Then v1 and v3 are released 2 years apart. So, maintaining and supporting backward compatibility for a 2 year old codebase and database along with other 2 latest versions will be a nightmare.
So the strategy adopted in Cloud exchange APIs is that, we will support only 2 versions at any point in time, assume x & (x -1) and x being the current version and (x – 1) will be considered as a deprecated version. When we have a new version coming in, the current version (x) will be demoted as deprecated version (x -1). E.g., we have 2 versions v1 and v2. When v3 comes in as a current version, v2 will be considered as deprecated and v1 will be discarded. There will be only one database serving both the versions. Any database changes for current version release will be always designed keeping backward compatibility in mind.
No new features or changes will go into the deprecated APIs (i.e., x -1). They will be going only into the current version (x). The deprecated API version will be touched only for Bug fixes.
Where to specify version of an API?
Generally, there are 3 ways, version numbers are specified while making API calls.
a) HTTP Header.
b) Query Param (E.g.www.abc.com/xyz/resource?version=v1)
c) Part of URI (E.g. www.abc.com/xyz/v1/resource)
We considered 2 options “HTTP Header” and “Part of URI”. “HTTP Header” and “Query Param” approach is almost similar, unlike “Part of URI”. We finally zeroed it down to “Part of URI”. All the above mentioned approaches are very interesting and would like to articulate it below. But before that, we would like mention that, we use APIGEE Gateway to bring in one level of indirection before the actual call hits the API. This helps in adding additional features on top of APIs like OAuth, CORS support, Logging, statistics etc. (Decorator Pattern).
Approach 1, HTTP Header:
In this approach, the URL of the API never changes. From the API Consumer standpoint, there is no change in URL configuration and he / she has to just remember one URL. Version number is an optional field in the HTTP Header.
Every API call is intercepted by the Gateway; the gateway policy checks for the HTTP Header. If there is no version mentioned, then the API Gateway, by default delegates the call to the cluster where the current version of APIs is hosted. If the version is mentioned in the HTTP Header, then the Gateway delegates the call to the cluster where that specific version of APIs is hosted.
The API Gateway policy, intercepting and inspecting the version number of each API call and then delegating it to the right API cluster brings in an additional overhead when the overall API performance is considered.
Approach 2, version as part of Query Param:
The “Query Param” approach is almost similar to “HTTP Header” approach, where the version number is fetched from the HTTP Header instead of Query Param. But, using version number as query param will be confusing when it is used with other business filter parameters in the GET calls. Also passing the query parameter in POST / PUT/ PATCH / DELETE violates some of the best practices of REST principles.
Approach 3, version part of URI:
In this approach, the version is part of the URI. It is a very straight forward mechanism, where we have separate paths configured in the API Gateway pertaining to specific versions and each version path would point to its respective cluster of APIs. This approach works fine even without an additional Gateway layer where each path can be configured at load balancer level, unlike in approach 1, where there will be only one URL configured in Load Balancer and the decision making of hitting the right API version is delegated to the Gateway layer.
The downside of this approach is that, the API Consumer will have to configure different URLs for different versions of APIs. This is still acceptable because, anyway the API Consumer will have to maintain different API client code / SDKs and configurations for different API Versions.
We finalized Approach 2 (version part of URI) as it was simple and straight forward.
How it affects your Sandbox environment?
It is hard to maintain a separate environment for sandbox, which will be an additional overhead along with Dev, QA & UAT. But, it is worth investing in the infrastructure & effort as it would reduce a lot of last minute surprises for both Application developers as well as API Providers.
Whenever, there is a new API release, we invite the customers play around on sandbox environment and then seamlessly promote them to the production environment when they are ready.
Disclaimer: The versioning strategy being implemented in Equinix Cloud Exchange APIs is based on industry best practices and moreover, what really worked for us to maintain a set of highly scalable APIs, which can be easily manageable with growing business changes.