Links

pmuellr is Patrick Mueller

other pmuellr thangs: home page, twitter, flickr, github

Thursday, September 20, 2007

which flavor do you favor

A question: if you had to provide a client library to wrapper your RESTful web services, would you rather expose it as a set of resources (urls) with the valid methods (request verbs) associated with it, or provide a flat 'function library' that exposed the resources and methods in a more human-friendly fashion?

Example. Say you want to create a to-do application, which exposes two resources: a list of to-do items, and a to-do item itself. A spin on the "Gregorio table" might look like this:

resource URL template HTTP VERB description
to-do items /todo-items GET return a list of to-do items
to-do items /todo-items POST create a new to-do item
to-do item /todo-items/{id} GET return a single to-do item
to-do item /todo-items/{id} PUT update a to-do item
to-do item /todo-items/{id} DELETE delete a to-do item

(Please note: the examples below are painfully simple, exposing just the functional parameters (presumably uri template variables and HTTP request content) and return values (presumably HTTP response content), and not contextual information like HTTP headers, status codes, caches, socket pools, etc. A great simplification. Also note that while I'm describing this in Java code, the ideas are applicable to other languages.)

If you were going to convert this, mechanically, to a Java interface, it might look something like this:

For the first two rows of the table, you use the following interface:

1: public interface ToDoItems {
2:     public ToDo[] GET();
3:     public ToDo POST(ToDo item);
4: }

For the last three rows of the table, you use the following interface:

1: public interface ToDoItem {
2:     public ToDo GET(String id);
3:     public ToDo PUT(String id, ToDo item);
4:     public void DELETE(String id);
5: }

I'll call this the 'pure' flavor.

A different way of thinking about this is to think about the table as a flat list of functions. In that flavor, add another column to the table, named "function", where the value in the table will be unique across all rows. Presumably the function names are arbitrary, but sensible, like ToDoList() for the /todo-items - GET operation.

Here's our new table:

function resource URL template HTTP VERB description
ToDoList to-do items /todo-items GET return a list of to-do items
ToDoCreate to-do items /todo-items POST create a new to-do item
ToDoGet to-do item /todo-items/{id} GET return a single to-do item
ToDoUpdate to-do item /todo-items/{id} PUT update a to-do item
ToDoDelete to-do item /todo-items/{id} DELETE delete a to-do item

This flavor might yield the following sort of interface:

1: public interface ToDoService {
2:     public ToDo[] ToDoList();
3:     public ToDo   ToDoCreate(ToDo item);
4:     public ToDo   ToDoGet(String id);
5:     public ToDo   ToDoUpdate(String id, ToDo item);
6:     public void   ToDoDelete(String id);
7: }

I'll call this the 'applied' flavor.

Now, if you look at the combination of the two 'pure' interfaces, compared with the 'applied' interface, there's really no difference in function. In fact, the code to implement all these methods across both flavors would be exactly the same.

The only difference is how they're organized.

Now, the question is, which one is better?

Now, you might say I'm crazy, who would ever choose the 'pure' story over the 'applied' story? And my gut tells me you're right. The 'applied' story seems to be a better fit for humans, who are largely going to be the clients of these interfaces, writing programs to use them.

But this flies in the face of transparency, where we don't want to hide stuff so much from the user. HTTP is in your face, and all that. At what point do we hide HTTP-ness? If you don't want to hide stuff from your users, you might choose 'pure'.

And I wonder, are there other advantages of the 'pure' interface? You might imagine some higher-level programming capabilities (mashup builders, or even meta-programming facilities if your programming language can deal with functions/methods as first class objects) that would like to take advantage of the benefits of uniform interface (as in the 'pure' interface).

And of course, there's always the option of supporting both interfaces, as in something like this:

01: public interface ToDoService2 {
02:     public ToDo[] ToDoList();
03:     public ToDo   ToDoCreate(ToDo item);
04:     public ToDo   ToDoGet(String id);
05:     public ToDo   ToDoUpdate(String id, ToDo item);
06:     public void   ToDoDelete(String id);
07: 
08:     static public interface ToDoItems {
09:         public ToDo[] GET();
10:         public ToDo POST(ToDo item);
11:     }
12: 
13:     static public interface ToDoItem {
14:         public ToDo GET(String id);
15:         public ToDo PUT(String id, ToDo item);
16:         public void DELETE(String id);
17:     }
18: }

Even though you have 10 methods to implement, each real 'operation' is duplicated, in both the 'pure' and 'applied' flavors, so you really only have 5 methods to implement. No additional code for you to write (relatively), but double the number of ways people can interact with your service.

No comments: