In the course of performing my duties at my day job I recently came across the need for our data to be accessible via an API. After researching the various types available, I settled on developing a REST API. The selection process wasn’t the interesting part of this exercise though. Actually implementing a REST API is what was.
REST is a set of principles
If there is one point that you should take away from this post after reading it, it is this: REST is a set of principles and not a specification. Consider for a moment someone you believe to be moral. Traits that may make them moral, such as trustworthiness, decency or honor, are characteristics of their morality; guidelines they follow in their life – a moral code. There is not a law that specifies what morality is, but there is a general sense of what is expected and what is meant when discussing morality. The same holds true for REST. There is not a specific implementation that makes an API RESTful, but instead certain expectations that one should strive to achieve.
The first place you run into “principles versus specification” is with versioning your API. There are really only two ways to implement the concept of versioning in a REST API – either through the syntax of the URI [this also includes using different (sub)domains for different versions] or by the use of HTTP headers. Within the use of HTTP headers, there are two primary options – use of the X-API-Version header or the use of custom media types. But which of these methods are correct? This exact question is discussed several times on the following websites:
After reading these author’s positions, and the ensuing discussions, you are still left with our original question of which method is correct. And there is the “principles versus specification” problem. There isn’t a right or wrong answer about this. There is only opinion and the solution that best serves the needs at hand.
Versioning your REST API
Seeing as that opinion is part of what drives your REST API implementation, and you are reading this blog post, let me take a moment to share with you my opinion on this subject. For me, there are 3 tenets that I have followed in developing my REST API. The two that pertain to versioning are:
- Do not use custom HTTP headers
- Do not use custom media types
Why these you ask? For the first one, it is because any proxy that is encountered along the transit path may either modify the headers or strip them off and not deliver them to their intended target. If you are relying on the ‘X-API-Version’ header to specify which version should be used, and it is not present in the request, then what do you do? Revert to the oldest version? Serve the latest? Neither of these are good responses, as what the client gets back cannot be relied on to be consistent. How are they to know whether or not this non-standard header is going to make it to your service or not?
For the second one, I do not believe that versioning of your API is as simple as saying you have a different representation of your data. Sure, if you take a look at the following two examples it’s only about a different representation:
<customer> <name>Joe</name> <phone>555-555-1212</phone> </customer>
<customer> <name>Joe</name> <phone> <home>555-555-1212</home> <mobile>555-555-2121</mobile> </phone> </customer>
But what happens when the business logic behind the POSTing to a resource does something different than it did before? The structure of the request, and perhaps even the response, remain the same, but the results of your actions are completely different. This is a version change too. Yes, technically, it’s a change to your application and not the API, but to the consuming client your application is your API. If the behavior of your API changes, even if it is really your application that has, then there is need for a new version of your API.
Do not use custom media types
Using custom media types to represent these types of changes just does not cut it. Custom media types can only be effectively (and questionably at that) used to serve a different representation of the response, but not a different behavior of your application. It is for this reason that I do not use either custom HTTP headers or custom media types and instead specify the API version in the URI. Yes, this is controversial to some but, as we have already established, the other methodologies are likely equally controversial. Heck, this approach even has its own problems for me, as I believe that a URI expresses identity, and identity does not change when a new version is introduced. By using the URI to store the version information we give the visual impression that we are representing different, distinct resources when in fact they are the same identity. But in the battle of “principles versus specification”, wherein the specification (or lack of one) does not give direction on this topic, and my belief in the principle that representing a change to the business logic is more important that representing identity distinctness, this is the solution that I use. You are certainly free to disagree and implement your own solution. The following is an overview of how I manage versioning in my API:
- I version my API with the versioning scheme of [Major].[Minor].[Revision]
- Anything that encompasses a major change to the way the API works results is an increment of the [Major] value. Let’s say that we have version 1.4.7 of our API. We have become successful and just purchased another company. In the process of integrating our two companies together, we have changed some of our fundamental business processes, whereas now how we process payments has completely changed due to new accounting rules, etc. We modify our API and in order to indicate that there have been significant business rule changes, and not just presentational changes, we change the version of our API to 2.0.0.
- Whenever there is a change in the way we interact with a REST resource, we increment the [Minor] value. Again, we start with our API version of 1.4.7. Using our previous examples, Example 1 and Example 2, we change our API version to 1.5.0 after implementing the changes reflected in Example 2.
- The [Revision] value of our API version is incremented whenever we make minor additions to our resources. Using Example 2, let’s say we want to add an additional value in the
node to represent a Work number. As long as our change only adds additional data to a resource without making any structure changes, then an increment from version 1.4.7 to 1.4.8 is in need. IMPORTANT: This [Revision] increase is only for when there are no changes to the existing structure! If the consuming application is expecting to find phone numbers under the XML path of “customer > phone” adding an additional one should not cause their code to break (assuming the application is written correctly). However, changing the name of the “Phone” node to “Phones” ABSOLUTELY will cause the the consuming application to break. This is when a [Minor] value change is needed.
- The [Revision] value is not used for bug fixes. Internally on our development team we have our own [Major].[Minor].[Revision] for the API codebase. While it will likely mirror the public API’s version most of the time, the two are not in lock step with one another. Version 1.4.7 of the API, from a consuming application’s viewpoint, is the same when there was a missing bracket in the response and the same after the required bracket was included in the response. The API version represents the business rules of the application and presentational structure of the requests and responses.
This management style results in URIs that look like these, for example:
What is involved in maintaining this approach?
Arguably, maintaining the API version in this manner requires a little bit more work. Each version of our API is configured to be hosted as its own application on the server. Our web server of choice is Apache. We use the mod_rewrite engine to set the appropriate ‘DocumentRoot’ value associated with each API version specified in the URI, defaulting to the latest version if the version specified is not recognized. Some of the reasons we do things this way are:
- It allows us to maintain more than one version of our API simultaneously.
- Our code does not become convoluted having to keep track of which version of which resource is supposed to be represented at which time.
Does this mean that for each increment of the [Revision] value we have a new installation of our API application that we install on the server and maintain? Yes. But when you think about this for a moment it’s not as bad as it sounds. Let’s say that we again have version 1.4.7 of our API. When we make a change requiring us to increment to version 1.4.8 we will no longer be making any changes to version 1.4.7. It will sit on the server for whatever amount of time we desire to continue supporting its version. When another change occurs, and we’re at version 1.4.9 of our API, we now have two old versions of the API that we no longer have to make changes to.
How long do you need to keep these old versions of the API lying around? Only you can answer this question, but a decent rule of thumb would seem to be that whenever you increment the [Minor] value you would be safe to remove support for the previous versions. Why you ask? Because if you recall from earlier an increment to the [Minor] value is the result of a change in the way we interact with the REST resource. If we have changed the overall structure of our resource, this seems like a good time to quit supporting previous versions of the structure. Not only does this seem like a good idea, but it will also very likely be necessary.
Another reason this is not as bad as it seems is due to this question: How often are you making changes to your API? Once you have put everything through its paces in development and have had the initial launch of your API how often are you going back to your data representation and adding a mobile phone, for example (and from our previous example)? I ask this question in regards to continuously adding structure to your responses that arguably should have been present from the initial launch. Now if you are feverishly adding new features and capabilities to your API then yes, this can become involved. But how else are you going to maintain it? Whether all the code is maintained in one installation of the application or segregated as I have described it is still code that has to be written. I would rather be adding new API functionality to an isolated instance of the codebase than worry about having to maintain backwards compatibility.
The 3rd tenet
The usage of a REST APi is header-driven. How you intend to interact with the data and business objects is conveyed in the use of GET, POST, PUT and DELETE headers. If you need information about the API endpoints, you use OPTION, TRACE and/or ALLOW. You define the format of your request using Content-Type and specify your desired result format using Accept. But just like I don’t use custom HTTP headers because of concern that proxies may strip them off, how can I guarantee that headers in my response actually make it to the calling application, even if they are all standard and do not use custom headers? Call me paranoid, but you can’t. For that reason, I have a third tenet that I have followed in developing my REST API, which is:
- Represent the headers in the response payload
Before I show an example of what this looks like, let me provide another reason for taking this step. I am a programmer. As a programmer, I am sometimes developing something that will be consumed and other times I am developing something that is doing the consuming. As the consumptive programmer, I very often run across a situation where I mumble under my breath at the decisions made by the developing programmer in how they arrived at the solution I am attempting to use. Often it is not that more involved to take a few extra steps that will make the life of the consumptive programmer much easier. This third tenant is one such easy extra step. Even if you don’t completely agree with the need for this extra step, or have not yet ran across a situation where it is beneficial, what is really the cost of just doing it anyways? I would proffer that the cost is negligible and that the goodwill earned from your consumptive programmers is worth the effort.
So what does this look like? Consider the following request and response examples:
GET /v1.4.7/countries HTTP/1.1 Host: api.awesomesite.com Accept: text/xml
Status Code: 200 OK Date: Wed, 16 Mar 2011 17:31:39 GMT Vary: Accept Content-Length: 432 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: application/xml <?xml version="1.0" encoding="UTF-8"?> <response> <countries> <country> <id>1</id> <name>United States of America</name> <regionCode>US</regionCode> <callingPrefix>011</callingPrefix> <callingCode>1</callingCode> </country> <country> <id>2</id> <name>Canada</name> <regionCode>CA</regionCode> <callingPrefix>011</callingPrefix> <callingCode>1</callingCode> </country> </countries> </response>
This is a typical GET request for a resource and the response you would normally receive as a result. Following my third tenant of REST API implementation, you would want to modify your response so that instead it looks like this:
Status Code: 200 OK Date: Wed, 16 Mar 2011 17:31:39 GMT Vary: Accept Content-Length: 512 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: application/xml <?xml version="1.0" encoding="UTF-8"?> <response> <headers> <ResponseCode>200</ResponseCode> <Vary>Accept</Vary> </headers> <countries> <country> <id>1</id> <name>United States of America</name> <regionCode>US</regionCode> <callingPrefix>011</callingPrefix> <callingCode>1</callingCode> </country> <country> <id>2</id> <name>Canada</name> <regionCode>CA</regionCode> <callingPrefix>011</callingPrefix> <callingCode>1</callingCode> </country> </countries> </response>
Notice that in this example I have included an additional “headers” node in my response. This is not part of the “countries” node, therefore it’s inclusion or exclusion in the response body will not impact our data being properly represented. What it has done though is ensure that in any situation in which response headers have gotten stripped, or a consuming application is not able to access them, that the header information is still represented. Which headers you choose to include in this node is completely up to you. I have elected to only include those that I deem the most important in maintaining RESTful communication. Concepts and data such as Response Codes and Vary headers are integral parts of RESTful communication – the date and time of the response are not. The reason I do not include the ‘Content-Type’ header and value in the “headers” node is because this header is used to tell the consuming application what format the response body is in. The consuming application will already have to know how to interpret the response in order to be told what format it is in, therefore it is information that is not needed (at the “headers” node level).
I hope that this post has gotten you thinking about some of the decisions you will have to make when developing your REST API and even if you don’t agree with the decisions I have made, you are at least now better prepared to face them. The one thing to remember, above all else, is that REST is a set of principles and not a specification. As long as you document your REST API and applications are capable of consuming it then no matter how you chose to implement it, it was the right way.