31 January 2011

URI design in REST

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:
  1. Easily testable in a browser - you just type in the format you want.
  2. Simple for most to understand - because you explicitly ask for the format you need.
Cons:
  1. 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.
  2. 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.




  3. 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.




  4. 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.




  5. 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:

  1. Again, easily testable in a browser - you just type in the format you want.
  2. Again, simple for most to understand - because you explicitly ask for the format you need.
Cons:
  1. 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.
  2. 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
This method seems to fix many of the problems mentioned with method 1 and I feel is also preferrable. The cons are relatively minor and it seems workable from a design perspective.

Analysis of Method 3

Pros:
  1. Consistent URIs for all objects regardless of format.
  2. Adheres to the expectations of HTTP with regard to requests
Cons:
  1. 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.
  2. 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.

Summary

Adhering to the blog title - there is no "right" way to do REST API design. From the analysis, I feel that method 3 is the "best" choice for our architecture and our particular needs. But all 3 have their place and all are used successfully in a variety of architectures. What do you think, what are the pros and cons I didn't think of?

1 comment:

  1. You should take a look at the WCF Web API extension on CodePlex. They've done a great job on dealing with mime types.

    http://wcf.codeplex.com/wikipage?title=WCF%20HTTP

    ReplyDelete