Versioning REST Web Services (Tricks and Tips)

In my previous post on this subject I described an approach to versioning the API of a REST/HTTP web service. This approach has significant advantages over the approach that is currently most common (i.e. embedding a version token in the URL). However, it does have some downsides. This post is an attempt to outline those and to present some ways to mitigate the negative impacts.

Nonstandard MIME media types

Using content negotiation to manage versions requires, by definition, the introduction of nonstandard media types. There is really no way around this. I personally don’t feel this is a Bad Thing. The new, nonstandard, media types do a much better job describing the sort of media the client is requesting. It does, however, mean that browsers – and perhaps some HTTP tools – will work less well with the web service.

The browser not working is a pretty big issue. They are almost certainly not the target consumer of the services, but having the browser not work raises the level of effort for exploring the API. If you have created a cool new service you want as few barriers to entry as possible. Personally, I always use curl when I am exploring but I know several people who would prefer to use a browser.

Unfortunately, I don’t really have a great general solution for browsers. That being said, in many situations a much can be done to make life better. For example, if the resources in question do not have HTML representations you could serve the current preferred format with a generic content type that browsers can render – e.g. text/plain or application/xml – to browsers.

Curl

One advantage of having the version token directly in the URL is that it makes it really easy to use curl against the service. By default curl makes requests with the Accept header field set to */*. For a reasonably designed service this would result in a response in the current preferred format. If you want to change to Accept header you need to invoke curl like this

curl --header 'Accept: application/vnd.foo.myformat-v1+xml' http://api.example/hello-world

That is not too horrible, really. It is a bit much to type all the time, but I have curl rc files for all the formats I deal with on a daily basis. If your service is implemented in Rails there is an even easier way. With Rails you give each format you support a short name that may be used as an “extension” for URLs. For example, if we define the short name for application/vnd.foo.myformat-v1+xml to be mf1 we can say this

curl http://api.example/hello-world.mf1

That is equivalent, from the point of view of a Rails based service, to the previous example. I imagine similar functionality could be implemented in most web frameworks. This effectively puts you back to having the version embedded in the URL, which is convenient for debugging and exploration. (It is still unsuitable for production use, though, for all the same reasons as other approaches to embedding the version in the URL.)

Nonobviousness

Another potential downside of using content negotiated versioning is that the various versions my be less discoverable, compared to a version-in-the-URL approach. I am not entirely sure this is true – after all there is a version token in the media type – but if it is true it would be a Good Thing.

Do you really want people “discovering” a version of the API that was deprecated a year ago? I think it might be better, in either approach, to use version tokens that are not readily guessable. Obviously, previous versions of and API will be documented and remain accessible, but raising some barriers to entry on depreciated parts of a system seems appropriate to me.

Unfamiliarity

This may be the biggest issue of all. People are just not very familiar, and therefore comfortable, with content negotiation. This in spite of the fact that it has been a fundamental part of HTTP since forever. I think this features obscurity is waning now, though, because it is such a powerful feature.

Two years ago Rails got content negotiation support. (That link seems to be broken at the moment. You can see part of the post I am talking about by going here and searching for “The Accept header”.) As frameworks like Rails keep adding and improving their support for this powerful feature the community of developers will become more familiar and comfortable with it. What is needed now is more education in the community on how best to utilize this feature.


If you’re interested in REST/HTTP service versioning be sure not to miss the rest of the series.

Comments 9

  1. Subbu Allamaraju wrote:

    Very interesting idea.

    Posted 15 May 2008 at 12:52 pm
  2. Dan Kubb wrote:

    Peter, in RFC 2616 section 14.1 there’s a reference to a content-type versioning system built into Content Negotiation. Note how some of the examples show a “level” parameter added to the media range examples. Specifically it shows that you can specify different versions of HTML to be higher precedence than others.

    This same parameter can be seen in other RFCs and some Apache documentation too.

    Posted 17 May 2008 at 10:26 am
  3. Peter Williams wrote:

    Dan,

    I am not fond of the putting the version information in a parameter on the MIME type. (MIME type parameters are not specific to HTTP 1.1, but rather are a general feature of MIME media types.) The use of a parameter implies to me that the server can provide a reasonable default value for that parameter. I suppose you can do this for versions but it leads to some unfortunate outcomes.

    For example, if I support 3 different versions of some format and I
    get a request for ‘application/vnd.myformat’ what version should I
    return? From a safety stand point I would have to return the oldest
    (i.e. least preferred) version because there might be clients out
    there that only support that version of the API. Unfortunately, that
    is probably the MIME type that will be used when newcomers are
    exploring the interface. So you are forced to encourage the use of
    the least preferred API available.

    Posted 17 May 2008 at 12:19 pm
  4. Frank Alic wrote:

    Hey Peter! Your approach sounds interesting and makes sense to me. But how about version-controlled content negotiation when it comes to the point where you want to combine APP and REST? APP has its own content type. Wouldn’t the version-controlled content type break the clients? I’m aware that your proposal is focused on the ressource in the common sense asuming, one has defined his own exchange protocol. How about using a custom Version header to rely on a specific API version? In the context of the APP and REST scenario this makes more sense to me.

    Posted 19 May 2008 at 5:38 am
  5. Peter Williams wrote:

    Frank,

    I do have some thought on how this versioning approach relates to standard formats. However, I have not worked in an environment where I have needed to do that so my thoughts are not fully formed yet.

    I will try to write those up and publish them soon.

    Posted 19 May 2008 at 9:19 am
  6. Mike Amundsen wrote:

    About version-controlled MIME types and standard types such as Atom:

    It seems that standard MIME types can be used along with additional ‘q-type’ values (‘v’ values?). Since the idea here is to create new media types anyway (application/vnd….) it does not seem to be a stretch to suggest adding a new qualifier: “text/plain; v=1.0″, etc. The absence of a version parameter can be treated as equivalent to asking for the most recent version.

    Posted 20 May 2008 at 8:58 pm
  7. Peter Williams wrote:

    it does not seem to be a stretch to suggest adding a new qualifier: “text/plain; v=1.0″, etc. The absence of a version parameter can be treated as equivalent to asking for the most recent version.

    The idea of using a version media type parameter is probably workable. However, I disagree with your idea regarding handling the absence of such a parameter. The default value for such a parameter would have to be to be “most compatible” version available. In most cases that means the oldest version.

    Consider the following scenario. You deploy some service. A third party writes an application that acts a consumer of the service, but they do not include an explicit version parameter. A few months after they have finished work and moved on to something else you deploy a new version of the service. Now, suddenly, this external application stops working for no particularly obvious reason.

    One core principle of versioning must be that any request that works in one version of the API will, in all future versions of the API, either a) continue to work in a compatible way, or b) fail with an explicit version compatibility error.

    Posted 21 May 2008 at 9:10 am
  8. Jim Loverde wrote:

    Regarding the use of a version qualifier and default behavior, why not just require the version qualifier in the very first version of your service.

    That way it’s not possible for any client to exist that hasn’t explicitly stated the version that it works with.

    Clients can discover the available versions by querying without the Accepts header and a 406 with the accepted versions/formats can be returned.

    Posted 05 Sep 2009 at 6:06 pm
  9. Thomas wrote:

    You have done it again. Very nice article. So as far as exploring goes, I really prefer using a browser rather than curl.

    Posted 06 Jan 2011 at 6:54 am

Trackbacks & Pingbacks 2

  1. From Versioning RESTful Web Services « Ka anyi kwuo okwu on 18 May 2008 at 4:58 pm

    [...] RESTful Web Services 18 05 2008 Peter Lacey posts here and here on how to version RESTful Web Services using custom MIME media types and I find this very [...]

  2. From codeoncotton » Blog Archive » Versioning REST web services on 19 May 2008 at 12:25 pm

    [...] the way: He also outlines the downsides of his approach and provides some  ways to migrate the negative [...]