views:

58

answers:

3

Hi, I'm embarking on a new web app we want do do it RESTfully. Now it is time to begin designing the interactions, and something sorta basic about REST has me stumped. I am trying to figure out the best way to mediate the impedance mismatch between REST and OO without falling down the slippery slope of RPC. Let me give a (contrived) example.

Widgets can be created, modified, and then submitted for review.

To modify a widget with the id of 123, the user does a PUT to /myapp/widget/123 and the new form data. The server repackages all the form data as a POJO and hands it off to the logic layer for validation and subsequent persistence, invoking WidgetManager.update(widgetPojo).

To submit a widget for review, the user clicks a button, which also does a PUT to /myapp/widget/123, but now the form data just has has one field, a status of "submitted" (I don't send all the form data again, just the field I want to change). However, now the server needs to invoke a different business object, WidgetStateManager.updateState(123, "submitted"), which is going to do some other specialized processing in addition to updating the state.

So, in an attempt to be RESTful, I've modeled both the widget updates and the submit for review action as PUTs to the same URL, /myapp/widget/123. So now, in my server side code, I need to figure out what a particular PUT request means in terms of the business functions, and therefore which business function(s) to invoke.

But how can I reliably determine which function to invoke merely by inspecting the values in the form data? It is SOOO tempting to pass an "action" field along with the form data, with a value like "update" or "submit for review" in the PUT! Then the server could do a switch based on that value. But that of course is not RESTful and is nothing more than dressed up RPC.

It just doesn't seem safe or scalable to infer what button was clicked just by examining the form data with a bunch of if-then-elses in the restlet. I can imagine dozens of different actions that could be taken on a widget, and therefore dozens of if-then-elses. What am I missing here? My gut tells me I haven't modeled my resources correctly, or I'm missing a particular resource abstraction that would help.

+1  A: 
Chris McCauley
Good point about the misuse of PUT. When the semantics don't really match one of DELETE, GET or PUT, it's a good idea to use POST as it does the least harm.
Stefan Tilkov
@Stefan, care to comment on the 'Review' resource v 'Needs review' attribute?
Chris McCauley
Aha. I just figured out that URI overloading = bad. It's hard to break old habits, from back when inventing a new URI was an expensive proposition. The fact that I am only needing to send a tiny subset of the data to indicate the intent is a clue that I am really talking about a new resource, with a new URI. A post to /myapp/review should do the trick. Thanks!
Kevin Pauli
+4  A: 

You aren't limited to mapping URLs to domain objects. A RESTful API has a small number of actions but a large number of resources to which the actions may be applied.

Create a widget:

POST to /rest/widget (returns "123")

("The POST method is used to request that the origin server accept the entity enclosed in the request as a new subordinate of the resource identified by the Request-URI in the Request-Line.")

Validate widget 123:

POST to /restapi/validator/123

(The resource is a notional "validator" for widget 123.)

Update widget 123:

PUT to /restapi/widget/123

Submit widget 123 for review:

POST to /restapi/reviewqueue

(There is only one review queue, so no need for /123.)

Delete a widget:

DELETE to /restapi/widget/123

Willis Blackburn
I like the idea of a ReviewQueue as a resource.
Darrel Miller
Good answer. I'd like to add that you should make sure to not hard-code those URI-dependencies, but rather discover them through links.
Stefan Tilkov
Thanks Willis. Similar answer to Chris's but he wrote it more at the level I needed to read to get my a-ha.
Kevin Pauli
A: 

It's hard to say without knowing more about the domain, but having a "to be reviewed" collection resource might be a good idea. As your resources flow through the process, you can move them from one list to the next. As a side benefit, you also get the option to do a GET on these lists to find out which resources are in that particular state.

Stefan Tilkov