Moving Beyond Methods in REST
Thursday, 20 March 2008
Having complained before about the sad state of HTTP APIs, I’m somewhat happy to say that people seem to be getting it, producing more capable server-side and client-side tools for exposing the full range of the protocol; some frameworks are even starting to align object models with resource models, where HTTP methods map to method calls on things with identity. Good stuff.
However, something’s been bugging me for a long time about this. While there’s a nice internal logic to mapping HTTP methods to object methods, it doesn’t realise the power of having generic semantics.
Consider a resource;
class Person (Resource):
def GET(self):
# do acls...
# get the representation out of some persistent store
# translate to the format asked for
return representation
def PUT(self, representation):
# do acls...
# translate the representation to the appropriate format
# put the representation into some persistent store
# cook up a status message
return status_representation
def DELETE(self):
# do acls...
# delete the resource from some persistent store
# cook up a status message
return status_representation
def POST(self, representation):
return representation
In this interface, GET, PUT and DELETE all have well-defined semantics. So well-defined that they really shouldn’t need application-specific code; after all, they’re just manipulating state in well-known ways.
In fact, I’d posit that you can specify the behaviour of any RESTful resource by describing a) the processing that POST does, and b) any side effects of PUT and DELETE.
There are a lots of caveats around that, of course. You need to define access control for the methods, and specify if authentication is required. You need to specify the formats that the application can work with (potentially with differing answers for input and output, and possibly with appropriate translations). You’ll need to specify the processing that happens around query parameters (e.g., in filtering output for GET).
The thing is, none of those have an implementation that’s specific to this particular resource; instead, they’re better abstracted out, so that the implementation looks something like this;
@store_type("mysql") # tell the Resource what implements GET, PUT and DELETE
@acl("choose your ACL poision") # tell who / when access is allowed, per-method and finer-grained
class Person (Resource):
store_format = PersonML
def POST(self, representation):
# operate on the store...
return representation
def PUT_effect(self, representation):
# called IFF the presented representation is storable,
# but before it is available; raising an exception will back it out
return status_representation
class PersonML(Format):
translations = {
'application/xml': (self.to_xml, self.from_xml),
'application/json': (self.to_json, self.from_json),
}
def to_xml(self, native_input):
# do whatever you've got to do
return xml_output
def from_xml(self, xml_input):
# do whatever you've got to do
return native_output
...
I haven’t incorporated a way to handle query parameters here, but you get the idea.
The tantalising part of this approach is that it can be implemented close to your persistence layer; all you need is hooks in the right places for side effects and POST processing. In fact, as long as you’re willing to be flexible on consistency, you can almost do it with mod_rewrite (calling a completely separate script as well as PUTting/DELETEing the state doesn’t yet seem to be possible; ping me if you can figure out how to), and stuff like Apple’s FSEvent looks very, very interesting in this light.
Is anybody aware of anything along these lines out there in an existing tool or framework? I’ve been meaning to write some code along these lines for some time; if you’d like to help out, please drop me a line.
The other place that this view has impact is in describing RESTful applications, if you believe in doing such things. WADL, for example, gives GET PUT and DELETE equal weight with POST, when I’ve always suspected it would be more elegant if you took the abstraction up a notch and talked about state, rather than methods.
11 Comments
Mike Amundsen said:
Thursday, March 20 2008 at 3:45 AM
Eric Allam said:
Thursday, March 20 2008 at 4:21 AM
Mark Nottingham said:
Thursday, March 20 2008 at 4:57 AM
Pete Svensson said:
Thursday, March 20 2008 at 8:18 AM
Jeffrey Winter said:
Thursday, March 20 2008 at 11:17 AM
Chris Dent said:
Thursday, March 20 2008 at 11:29 AM
Justin Sheehy said:
Friday, March 21 2008 at 6:58 AM
Erik Johnson said:
Friday, March 21 2008 at 11:29 AM
Robert Thau said:
Friday, March 21 2008 at 12:26 PM
Mark Baker said:
Saturday, April 5 2008 at 8:35 AM
Jerome Louvel said:
Sunday, October 19 2008 at 3:43 AM