Friday, October 21, 2011

The Trouble with REST

Note: this post has been superseded.

REST is easy to describe.  It goes a little something like this: "You have some representation, and you send (or receive) the whole thing to read it or make changes."  People coming from Clojure would understand it as REST sends values.  I can GET an object, receive the value, manipulate it, and PUT the new value.  It's so easy because it just uses HTTP!

Right?  Maybe not.  If REST is so easy, why is there HATEOAS*?  Shouldn't that have been obvious?  Why do we have arguments about versioning and parameters and formats and headers on Reddit?


REST is not easy.  It's quick to explain in a limited manner, but the basic explanation leaves out important details, like how one actually goes about designing a proper REST API, and what makes an API RESTful to begin with.

Let's design our first API.  With the CRUD operations mapped to HTTP verbs (POST, GET, PUT, DELETE), we start with a URL space for users. /users/21 for a specific user, or I can POST to /users to create a new user.  All well and good, but now I want to search for a user, which doesn't have an obvious, direct mapping onto HTTP verbs.

I can define a special URL, /search/user?name=bob or /users/search/bob.  I can define a new verb to SEARCH /users?name=bob.  I can (though this heads straight out of REST territory) define a parameter acting as a sub-method: /users?search=bob.  But then what if I want to find a particular Bob who is younger than 26 and has a GMail address?

The "simple" explanation just blew up in my face because there's a gaping hole in the map.  Nobody even labels it "Here be Dragons" because we're all optimists.

The post on Reddit today was focused on versioning.  There are those who think a "REST API" should never contain a version embedded in the URL, and apparently I just have to hope that nobody anywhere is running an old, incompatible client when that shiny new version is rolled out on the same URL.  If I changed the URL, that's like putting a version in the URL, which is forbidden.

This discussion also went off into whether it was better to version by asking for "Accept: application/vnd.my-api.user+v3" or something like that, instead of text/xml.

Speaking of formats, when an API provides both XML and JSON (and probably does one badly), that invokes more argument around format specification.  Accept header?  format=json query parameter?  And of course there's the (old?) Rails way of /users/get/31.json.  And hey, that "get" looks like a verb in the URL, which isn't RESTful.

And then there's the "use http" ethos.  If GET /users/superman is invoked with authorization (HTTP auth or session cookie (speaking of which... that's state and that's bad, mkay?)) and the user is not allowed to see that particular user, what code do you return... and how is it differentiated from "your session has timed out"?  Do you start adding random custom headers like X-Session: timed-out?

Let me not forget conflicting edits.  Most systems solve this by returning some sort of version identifier for an item, then requesting that identifier to be returned on save.  If another client has sent in an edit, then the identifier doesn't match what's stored on the server, and the edit is refused.  Isn't RPC roundly criticized for relying on server state?  Isn't the version tag inherently shared state?  Sure, REST may call for sending the entire state around in every request/response, necessary or not, but I really don't see a difference between sending an EditSequence to QBWC over SOAP, which is as RPC as you can get (until the world decided "let's use all document-literals all the time"), and sending a version tag back to bugzilla so it can detect mid-air collisions.  Well, at least we have HTTP 409!

If this is all supposed to be so simple, why do we have all these arguments?  And if Roy Fielding knows everything about REST, maybe he should drop by reddit and enlighten us once and for all.

Actually, for five minutes.  This is the Internet.

My answer to the question I boldly asked is that this stuff isn't easy nor clear-cut.  Programming is about wrestling the dragon on the path of implementation, and all of the paths have a dragon of some sort.  Pretty much anything can be "made to work," which is why most seasoned programmers hate everyone else's code—working code is not necessarily easy to understand, and nothing interesting is universally easy (since easy is subjective).

I think I must be reading the wrong part of the Internet again, because I'm wishing for less talk of "what makes something RESTful", and more focus on real-world API design considerations.  Especially the disadvantages of each approach.  Nobody wants to talk weakness; it gets left to be rediscovered over and over by those that walk the same road.

I think, in essence, the trouble with X is that nobody talks about the disadvantages of X.  Today, REST just happens to be filling in for X.**


* BTW, when did Service Oriented Architecture start meaning WS-* and J2EE?  I guess I'd better start calling it API Oriented.

** There's no conclusion here; this is a rant.

No comments: