Here at Abiquo we just released our 2.0-GA version. And among many improvements and goodies we released our API. From the beginning, we have always wanted to stick to RESTFul principles of design. We all agreed that RESTFul APIs when done well are simple to use and easy to understand. We want to not only implement our own clients, but let other people implement their own abstractions of our application. There are many articles on how to version a RESTFul API. In this one I just want to present the thoughts and ideas that are behind the Abiquo API versioning.
Media Types
RESTFul APIs work with representations. A representation holds the state of a resource. These representations are composed of data, metadata that describes the data, and optionally, metadata that describes the metadata (normally used to verify message integrity). The metadata of a representation is represented as key-value pairs and sent in headers. This metadata includes the data type, or format. And the format of the representation is known as the media type. That is, when a GET request is performed for a resource, what it obtains is its current state in a specific media type. So we decided to handle versioning with media types. Different Content-type header defines the format of the representation. This is called content negotiation. Media types also accept variables, which are also represented as key-value pairs. For example, a valid mime type could be: application/vnd.company.mime+json;version=1.0. This can be very useful for versioning mime types and this is actually how we implemented the versioning. We all agreed to adopt the versioning of media types as the versioning technique for the Abiquo API. We decided to use custom vendor mime types. Custom mime types follow a convention. First the directory, we chose application because we were modelling our data model. Then the media type itself, which is usually in this form: vnd.company_name.type. In the case of an Abiquo rack for example: vnd.abiquo.rack. Followed by the format, for example the XML representation of a rack:
[code lang=”xml” light=”false”] application/vnd.abiquo.rack+xml<rack>
<name>example</name>
</rack>
[/code] By adding the version as a parameter: [code lang=”xml” light=”false”] application/vnd.abiquo.rack+xml;version=1.1
[/code]
We could have gone with the plain, common application/xml. But we thought that, although perfectly valid, it is not strict. It is not strict because clients are not asking for any arbitrary XML but for a specific representation in a specific format. In contrast an Accept header such as */* or application/xml is admitted. We encourage the use of the appropriate mime type though. But we do not admit those very same mime types for Content-type header. The Abiquo API does not deal with just any XML representation. In addition, as the headers arrive before the body, we can reduce network burden if the Content-type or Accept headers are unknown to the Abiquo API or invalid for the resource. One practical benefit is that an API client might be upgraded in parts. As the implementation of the representations proceeds, the media type can be modified.
Practical example
With versioning we wanted to handle changes, deletions and additions in our application. All of that without breaking the backward compatibility. Let’s say that we have this XML: application/vnd.abiquo.rack+xml
[code lang=”xml” light=”true”] <rack><name>example</name>
</rack>
[/code]
If we wanted to add a new property to the rack, for example description, we will end up with this representation:
[code lang=”xml” light=”true”] <rack><name>example</name>
<description>An example of an Abiquo rack</description>
</rack>
[/code]
This is the easiest scenario as old implementations of valid clients will just ignore the new property and keep working with the its old representation. Not even a version parameter is needed here. But what if the change is incompatible. For example, we want to further iterate our rack. We realize that a description is not enough and we want to add up to three descriptions. This could be a valid rack.
[code lang=”xml” light=”true”]<rack><name>example</name>
<descriptions>
<description lang="en_US">An example of an Abiquo rack</description>
<description lang="es_ES">Un ejemplo de rack Abiquo</description>
<description lang="es_CAT">Un example de rack d’Abiquo</description>
</descriptions>
</rack>
[/code] Well, this rack is incompatible with clients expecting the old rack version. This is where content negotiation comes in handy. By versioning the rack, a client might actually ask for any of those three representations. [code lang=”xml” light=”true”] GET /rack/1
Accept: application/vnd.abiquo.rack+xml;version=2.2
————–
<rack> /> <name>example</name> <descriptions>
<description lang="en_US">An example of an Abiquo rack</description>
<description lang="es_ES">Un ejemplo de rack Abiquo</description>
<description lang="es_CAT">Un example de rack d’Abiquo</description>
</descriptions>
</rack>
[/code] OTHER OPTIONS Tying the version to the actual media type instead of using parameters was an option too. The rack representation could then be: application/vnd.abiquo.rack+xml [code lang=”xml” light=”false”] <rack>
<name>example</name>
<rack>
[/code] And when modified it would become: application/vnd.abiquo.rack-v2+xml instead of application/vnd.abiquo.rack+xml;version=1.1 But we discarded this because even though it is not the same version of the representation, we felt like it was the very same mime type. Since parameters are something that adds data to the media type we think this is the right place. Another common version pattern for APIs is to add the version number as a path segment. [code lang=”bash” light=”false”] GET api/v1/rack/1
[/code] [code lang=”bash” light=”false”] GET api/v2/rack/1
[/code] But we did not like the idea of tying our API version to the URIs, as this is the resource identifier. In our opinion, the resource identifier should stay the same, even though the representations of the resources change.