Background and the Issue
We just began switching our existing architecture over to a proper SOA design (yeah!) and decided on using REST (yeah!) for the "API". One of the first discussions we got into was what the REST URI's will look like in the new design. We all agree that proper URI design is a core factor in determining the success of REST so the debate is lively and its good to see everyone passionate about the technology.One of the sticking points is how to return different formats of the Restful "objects" - say XML and JSON. Our desires come down to 3 possible designs all of which are considered acceptable from what I have read. So let me present you the design possibilities before I go into the pros and cons of each:
Method 1 - Use the URI to configure the return type
So to get a customer with ID 123 you would call:http://server:port/api/customer/123.json
(for JSON)http://server:port/api/customer/123.xml
(for XML)Method 2 - Use a query parameter to set the return format
http://server:port/api/customer/123?format=json
http://server:port/api/customer/123?format=xml
Method 3 - Use the HTTP headers to get the objects
http://server:port/api/customer/123
(GET request using an XML or JSON mime-type in the request header)
One of our architects choose the first for the prototyping we were doing and when I saw it, something didn't sit right about it. So I decided to do a little research -- and it turns out that there are a lot of folks who do it this way, including the Twitter REST API, and the seeming "reference" text on the subject (RESTful Web Services by Leonard Richardson & Sam Ruby).
Even though I found a lot of great references praising this method, that nagging sense that I was doing something wrong kept creeping in, so I figured it was worth the time to analyze the situation a bit more. So I came up with some reasons for an against each design to help me decide on a proper way to go:
Analysis of Method 1
The first solution has several advantages and it is certainly a quick solution to multiple formats.Pros:
- Easily testable in a browser - you just type in the format you want.
- Simple for most to understand - because you explicitly ask for the format you need.
- Can lead to ambiguity in the API. For instance, lets say you needed to return data that required multiple input parameters, like some reporting data. You request could be something like this (I put the parameters in []):
http://server:port/api/reportdata/[Sales]/[January]
But if you need to specify the format, what does the final URL look like:
http://server:port/api/reportdata/Sales/January.xml
http://server:port/api/reportdata/Sales.xml/January
http://server:port/api/reportdata.xml/Sales/January
While this is a sort of a minor problem (you could just say "put it at the end"), it does represent something you will have to think about when writing the calls and something you will have to explain whenever a 3rd party uses your API.
- Requires a separate handler for each URI type that you add - meaning its more difficult to write a generic format parser and reuse it on all the methods. On the receiving side, most languages (.net and java) have annotation type tags that can be placed above code to handle a specific format. So you client has something like:
public method URLHandler()
{
[annotation to handle "/customer/[ID].xml"]
doReturnXML();
[annotation to handle "/customer/[ID].json"]
doReturnJSON();
[annotation to handle "/customer/[ID].newformat"]
doReturnNewFormat();
}
rather than a generic format identifier:
public method URLHandler()
{
[annotation to handle "/customer/[ID]"]
format = getFormat(request)
doReturnCustomer(format);
}
You can certainly do this generic handler, but you end up parsing the type out of the URL which can be tricky if your URI doesn't contain the format or contains the format keywords in the URI.
- Provides some confusion with the return type.
What are you really requesting, if we watch the wire while the browser makes this request, you see the browser pass an HTTP GET with an accept header of type "HTML" and the function returns you XML or JSON data. This seems wrong to me. From a browser you won't notice this as much, but if you are actually writing a client you will have to understand that you are not asking for the format in the request header - just the URL. - Can't rely on the REST hosting framework to enforce the accept formats and deny requests the server cannot fulfill without manually checking the formats. If you are going to be doing this, you probably aren't going to be writing the whole REST framework from the socket up - you are going to be depending on .NET or a Java library to "help" you out with the coding and you just want to slap some tags on a function to do handle it. If you stick the return type in the URI, you are not letting the framework handle the format negotiations for you. So you are essentially manually overriding what part of the framework was designed to handle.
- Put yourself in a position to really complicate your URI. Lets say you have developed your solution using method 1). Now the team comes along and says, we also want to be able to return the customer with spanish tags to expedite the rollout to Mexico (or whatever wacky format users now want). The logical place to put this with this design would be in the URI so that you aren't mixing methods:
http://server:port/api/customer/123.json.mx
or maybe
http://server:port/api/mx/customer/123.json
or worse (mix the two designs above)
http://server:port/api/customer/123.json?language=mx
This seems to be fighting against something that is already supported in the HTTP header into the URI instead.
Analysis of Method 2
The first method has some problems, so lets look at Method 2.Pros:
- Again, easily testable in a browser - you just type in the format you want.
- Again, simple for most to understand - because you explicitly ask for the format you need.
- Still have the issue of confusion with the return type.
The format query parameter serves as an override to whatever HTTP header request type you have passed. - Still have an unnecessarily complicated URI to specify the format you need without relying on the headers:
http://server:port/api/customer/123?format=xml&language=mx
Analysis of Method 3
Pros:- Consistent URIs for all objects regardless of format.
- Adheres to the expectations of HTTP with regard to requests
- Much more difficult to test than method 1) or 2), but still doable with a plugin in most browsers (Firefox). Since you overriding the browsers default return type (HTML), you must use a plugin to alter the request that gets sent.
- More training of the staff to understand how HTTP works and why you have to fill out the HTTP request information properly to get the data you want.