Content negotiation vs URL-based versioning

Hi all,

I was thinking about the versioning discussion we were having earlier, and I’d like to suggest a compromise that would allow us to avoid the complexity of content negotiation, but still keep our URLs fairly stable. The trick is to mint URLs only on breaking changes.

The strategy can be summed up in these rules:

  • Clients MUST ignore fields they do not recognise.
  • Content-Type identifiers MUST include a version number with major and minor versions (e.g. application/fred.facility.v1-5+json, application/fred.collection.v2-0+json) and MUST be backwards compatible with other versions with the same major and lower minor version numbers.
  • Servers MUST serve a Content-Type response header along with successful (2xx) responses.
  • Servers MAY increment the minor version number of content they serve at a particular URL.
  • Servers MUST use a new URL if they increment the major version number of content.

If these rules are obeyed, we can easily make expanding change to the API without content negotiation or breaking existing clients. Backwards-incompatible changes will require a new URL-space and will cause existing links to point to old API versions, but this will be comparatively infrequent (and perhaps will never happen). Clients who encounter older versions of the service than they expect will be able to use the Content-Type to work out which elements not to rely on.

A simple way for servers to implement a new URL-space per major version would be to include “v1”, “v2” etc in all URLs, but this should not be part of the published contract, otherwise clients would rely on details of URL structure.

As a side note, name-spacing identifiers of a facility under a single parent is safer than having them as top-level elements, because that will ensure they can never clash with other top-level elements we wish to introduce. Also, it would simplify processing for clients who would otherwise have to examine every element for the identifier=“true” flag.