Ideal API versioning
There is a significant conversation around how to version an HTTP or REST API. Versioning an API is good, because it helps you to sustain old API's while being able to change and adapt to future needs. But, what is the best way to handle this? A few options:
- Don't version at all — yikes! This makes dramatic changes to the interface or data schema challenging.
- Use the protocol version as a header attribute — This works, but you have to have greater intelligence within your interface to read the header before deciding how to behave.
- Put the protocol somewhere in the path of the URL — This is becoming more and more common.
My preference is the latter — putting the version in the path. But it is important to have a standard that works well and stick with it.
The challenge is where should we put the version? Like it or not, as much as you think your software is simple, it likely will be part of a larger system, and will grow in complexity as it matures. Planning for this up front helps. Most likely there are four elements to any given endpoint in an API:
- Service Address — this is the hostname part of your service. Sometimes this will never change. If you are a SaaS provider, it could change for each tenant. Keeping "tenant" information in the domain portion gives you a lot of flexibility, as it becomes easy to route differently without fundamentally changing your application.
- Product — the basic overarching product you are supporting. more than likely a given service address may have multiple products hosted under it. With modern traffic management and load balancers, you can support many from a single address, if you put some time into remembering to separate your product at the base level.
- Service — As microservices abound, you may have multiple sub-services underneath a single product. Think ahead and plan for this.
- Endpoint — You may only have a single endpoint. This is the actual action of your code. More than likely you will add endpoints to a given service over time.
Of these four elements, the version can be applied at any level. But I have found it most valuable to place it just before the endpoint. This gives you the granularity to support different versions of an endpoint within a service, rather than having to version an entire service on a minor change (making it less likely you will take advantage of the version change).
This comes together to look like:
{service or tenant}/{product}/{service}/{versionX}/{endpoint}
Or a few real-world mockup examples (using a car analogy for the win):
https://tesla-domain/car/engine/v1/telemetry
https://mycar.tesla-domain/car/engine/v1/telemetry
https://theircar.tesla-domain/car/engine/v1/telemetry
With this setup, doing routing becomes very easy. And if the telemetry endpoint has to change its data structure, one can easily increment the version number without having to effect the temperature endpoint, or the tires service. One could even put another product in there, such as the customer service endpoint. These all may look like:
https://mycar.tesla-domain/car/engine/v2/telemetry
https://mycar.tesla-domain/car/engine/v0/temperature
https://mycar.tesla-domain/car/tires/v1/pressure
https://mycar.tesla-domain/service/records/v1/history
Once you are doing versions this way, you can also enhance it for some negotiations with the client side by using an http response header to indicate if they are on the current API version. For instance, if one were to target /car/engine/v1/telemetry
, but /car/engine/v2/telemetry
is the preferred target, you could add:
API-Deprecated: preferred=/path/to/v2/telemetry
I hope this has caused some thought, when you next think about what to do with your api versions.