API Versioning: Options; Recommendations and Best Practices

At an event the other day I presented a brief synopsis on what are generally regarded as the main ways to version your API and label the versions clearly. This led to an interesting conversation afterwards so I thought it would be good to share what we discussed in case you find it interesting too !
I've broken this down into three sections. The problem statement; the four ways to label versions and some best practices.

The Versioning Problem


  • thou shalt not break your clients

Changing your API is a pretty fundamental thing to do. You are literally changing the interface that clients will have been communicating with prior to your change. The point being that you don't want to break the clients that connect to you today while still needing to alter (adding, removing or changing) your API. This doesn't affect just what the API looks like i.e. the request or response formats it could also include functionality e.g. defaults now work differently.

Unfortunately, no matter how brilliantly you architect your solution  you may well find that, over time, you have to change it. (As an aside; it could be argued that micro-services get around this problem by being small (micro indeed!). In being so small they are so simple you won't have the space to change them  - but I'm not going there today ;-)

So, assuming that you do have to change your API then what you really want to do is to ensure that your clients know your API has changed and give them some way to programmatically decide, or otherwise, what version they are going to connect to so that they stay working.

Watch out: Sweeping statement coming.... There are some standards developing around API's and REST - but I'm not convinced, at this point, that they are influencing.

The Four ways

Before I say any more I'd just like to state that each option has their particular pros and cons. Which one you end up choosing may well be related to what your api-manager functionally  allows you to do or what the other parts of your infrastructure allow you to do.

1) The URI

In this method the version is very explicitly put into the API URI. For example:
          
  • .../maps/version/2
  • .../maps/version/2/roads/version/3
  • .../maps/v2/roads

The options above show three different ways to expose versions through your URI; firstly, a coarse grained whole-of-hierarchy model and then a more finely grained sub-topic method giving us the ability to version separate elements of the API (roads in this case). The third option just shows the slightly less expressive (and clunkier for the client to code?) model of having a single version argument (e.g. 'v2') without the explicit 'version' node in the URL hierarchy.

I, personally, do not like this model. From a purest standpoint it's argued that the URI in REST should represent the resource structure only. A version is not a resource it is an attribute of the resource. However, on a plus side I can see how it is very clear what's going on ! I also see this being recommended by many API tooling vendors. Here are some more pros and cons for this particular method.

ProsCons
Ability to version specific resource branches.New versions change resource name and location
Semantically dev friendlyComplex proliferation of URI aliases
Bookmarking is tightly coupledBookmarking is tightly coupled (see pros!)
Enables version navigation/discoveryEnables version navigation/discovery (see pros!)
Can't use URI easily to compare identity
New versions break existing hyperlinks

Already we can see how advantages to one person could be seen as disadvantages to another.

2) The Accept Header

There is a well-known  HTTP header called Accept which is sent on a request from a client to a server. For instance

  • Accept: application/json

This notation is saying that I, the client, would like the response to be in json please.
The cool thing here is that you can make up your own resource types e.g.

  • Accept: application/vnd.myapi.v2 + json

Now; hang on in here because that's a slightly strange syntax we've found ourselves looking at so let's walk through it....

The internet specifications say that I, as a vendor, can define (and have to register) a media type . If I do this it's called (no surprise) a 'vendor' media type. I should prefix my type with 'vnd' to make it clear it's my own definition. I then need to say what the actual type name is that I am defining e.g. 'myapi' . However, the specification states nothing about a version number so people have taken to saying that their media type name includes a version number e.g.


  • myapi.<version>. 

Now, because I, as the client application, still need to define what content type I really want (other than the version)  this can be added as a suffix to the requested media type e.g. '+ json' in this case.

btw: there is an alternative method of doing this without any pre-registering of the media-type using x. as the prefix e.g.

  • Accept: x.myapi.v2+json

The use of the Accept header feels like a very slight hack of the specification to me - but it works and is well-known, if not actually fully specified as such.

The biggest issue with this method is that it's rather hidden - and hidden things are always a little harder to work with. It's nearly always better to be explicit in what you are doing. I also suspect that asking your firewall admin to open up their firewall to any old vnd media type could lead to fun on occasion ;-) Having said that - it's still going to be easier to pass around the Accept: vnd header than it is to pass around custom request headers...

3) Custom request header

In the original HTTP specs you could define any old HTTP header you liked in your client request as long as you pre-fixed with 'X-' e.g.

  • X-MyNiceRequest-header: 23

The specs changed and you can still pass any old request header through but it doesn't have to be pre-fixed with 'X-' anymore e.g.

  • MyNiceRequest-header: 23

this means you could ask the clients of your API to pass in something like

  • api-version: 2

Hmm, I don't like this option on a couple of accounts:

  • That Firewall administrator is going to be looking at you again ! (or one of many routers along the way that you may not have control of) except this time it's not even a well-known header they have to open up for.
  • It's hidden again - just like the Accept header.
  • The Accept header is already a way to be quite explicit about what the client accepts so if we're going to hide stuff then at least let's use a way that's kinda known.

btw: if you think I'm being overly cautious about firewalls and routers not allowing certain headers through it comes from my days at IBM when I couldn't figure out why an HTTP request was working when I used a particular machine and not another. It turns out that the machines were connecting through different routers and one particular route had been configured to remove non-standard header types. It allowed the message through - including all the other headers - but just silently removed the ones it didn't like !

4) The URI Parameter

Here's an obvious one that I don't see many people using -


  • .../maps/roads?version=2


This would get through most firewall and router problems and is quite explicit. You could extend it to also have the subtree versioning as well.

I can see how, server-side, this feels a bit harder - the servers have to parse the entire param string before knowing where to route the request - something they try to avoid. In addition, there is the same argument as not putting version numbers into the URI - the parameters are for specifying the services function not attributes of the implementation (which is why gateways don't want to parse it !). Based on those alone I can see why you don't see it much in the wild.

Having said that - let's see who does use this method - Amazon, Google, Netflix. Hmmmmmm - maybe there's something in it !

Unfortunately, I don't see many API product vendors supporting it so, if you're buying an off-the-shelf API management product you may be out of luck and will have to hack it yourself - not ideal.

Best Practice


Best-practices will always be a combination of your preferences - think firewalls & router admins; naming conventions etc. Alongside this let us not forget the tooling. Some API-Manager products help you down certain versioning routes. That could well dictate your company policy.

Start thinking versioning from Day 1


By this I mean - when you design your actual API believe that it will change, even if not today - some day. This will affect what you code - not just your decisions as to where to put the version number !

If you're an agile developer then you could well be shouting at the screen right now.  Agile is all about today. Indeed, it specifically says - don't code for things that aren't in the spec. However, let's be clear: Coding for extensibility is not the same as actually putting in the extensions themselves - which is what the agile methodology talks about. Leaving room for future versions fits into agile just fine. (just imagine it as putting in a requirement in the spec saying 'allow for extensibility' ).

Defaults

So far I've discussed how to represent the version but what if clients don't find out that you have a new version or choose not to care and keep plugging away without specifying a specific version - what do you do then?

This is where you have to ask yourself what would the user expect - with the golden rule being

  • thou shalt not break a client

You could default to an older version of the API or the newest version, or the version they originally signed up to. I think this actually ends up being a question of asking yourself exactly what has changed between versions - if the client and server would see no difference if there was a client-server mismatch then send it to the new service. 

However, again, the vendors tooling that you're working with comes into play here. You may find that the tooling defaults to either the latest or the original version without you having control.

Workarounds

What to do  if you've already chosen your API-Manager and it doesn't do what you want it to? (This goes for all sorts of API-Manager features, not just versioning). Proxying your service is nearly always a viable option. Place a small proxy- preferably in an ESB - in front of your service and let it do that extra logic that you found to be missing from your gateway. I've discussed the relationship between ESBs and API-Management here.

Conclusions

Versioning an API is hard ! However, it's really no harder then keeping client and server code in synch has ever been.

  • Try to allow for extensibility in your code from day one.
  • With regard to how to specify the version - If you do have a versioning convention in mind then make sure that your infrastructure can handle it.
  • If you can, think about your versioning convention when making your API-Management gateway choice or you may find you just get sucked into whatever they have decided for you.
  • If all else fails think about putting a proxy in-front of your service to enable the function you need.

Comments

Popular posts from this blog