Why Versioning
For those who wish to scale their system from a legacy, coupled or even single-instance application to a more modern, and scalable system, there are always several general approaches to take. My suggestion is always to separate your front end and back end first and make them be able to run on their own instances without depending on other’s existence.
This separation of concern is the first step to scale and it makes sure the business logic and the front end can be run on two or more containers.
Next, you will need to create ‘trust’ between BE and FE by creating a contract that both parties agree on.
This step can be done by using an API contract manager. In my workplace(HBC), we have our in-house tool for this, APIBuilder, which is now open sourced. There are also several options on the market which do the job well (eg. Swagger).
With a properly documented API contract/definition, front end engineers can work on their own totally independently and vice-versa. And, the biggest benefit of doing this extra work is to make the future work of backward compatibility
Options You Have
URL segment
In this case, your URL
http://example.com/api/resource/detail
will become:http://example.com/api/v2.0/resource/detail
This is the easiest and most straightforward solution. But it has the version number as a part of the resource path, making it not fully comply with the concept of REST.
HTTP ‘Accept’ header
This is a better way to solve the versioning problem. Your HTTP header
accept: application/json
will become:accept: application/vnd.company.myapp-v3+json
Custom header
Very similar with the second method but instead using the well-known ‘accept’ header, we create another header just to indicate the version number, something like:
api-version: v2.0
What’s next
Now, let’s talk about implementation. With whichever versioning strategy is chosen, we will need some work to be done on both the client and the server.
client side
Basically, we will need a middleware handler or some equivalent for the client so that we don’t need to duplicate the same code all the time.
Depending on the HTTP client you have, the solution can be very different.
You can refer to this Github repo (superagent-use-middleware) where you can find the middleware library I wrote for ‘superagent’ HTTP client for browsers (so, in JavaScript). In this case, you can wrap the network utility and create an entry point method to specify the API version, something like:
1 | agent.get('example.com/some/resource/detail') |
Assume the API version is a custom header field. Then will get:
server side
Similar to the client side, we just need a way to tell what version the client is requested by query the URL or the header fields. Then use the different route logic for the requested resource.
To achieve this, we can add middleware on the App instance level, or add application load balancer/service mesh to redirect the traffic to the instance which has logic for the requested API version. The second solution is in general better because then we can easily obsolete old endpoints and keep the app clean. But it apparently creates more overhead for developing.
Conclusion
Versioning the API is something we always know we should do, but we rarely do. We only realise when we have to do it when it’s halfway into the project and it gets much more difficult than we doing it in the very beginning. Here I listed three common ways, hope it can be helpful for someone in the future.