Say you've got a Person resource, and part of its representation includes a Location value which can have values like "at home", "at school" and "at work". How would you RESTfully expose activities like "go home", "go to work", "go to school", etc? For the sake of discussion, let's stipulate that these activities take time, so they are executed asynchronously, and there are various ways in which they could fail (no means of transportation available, transportation breakdown during travel, other act of God, etc.). In addition, the Person resource has other attributes and associated operations that affect those attributes (e.g. attribute=energy-level, operations=eat/sleep/excercise).
Option 1: Overload POST on the Person resource, providing an input parameter indicating what you want the person to do (e.g. action=go-to-school). Return a 202 from the POST and expose activity-in-progress status attributes within the Person's representation that the client can GET to observe progress and success/failure.
Benefits: keeps it simple.
Drawbacks: amounts to tunneling. The action taking place is buried in the payload instead of being visible in the URI, verb, headers, etc. The POST verb on this resource doesn't have a single semantic meaning.
Option 2: Use PUT to set the Person's location to the state you'd like them to have. Return a 202 from the PUT and expose activity-in-progress attributes for status polling via GET.
Benefits: Not sure I see any.
Drawbacks: really, this is just tunneling with another verb. Also, it doesn't work in some cases (both sleeping and eating increase energy-level, so PUTting the energy-level to a higher value is ambiguous in terms of what action you want the resource to perform).
Option 3: expose a generic controller resource that operates on Person objects. For example, create a PersonActivityManager resource that accepts POST requests with arguments that identify the target Person and requested action. The POST could return a PersonActivity resource to represent the activity in progress, which the client could GET to monitor progress and success/failure.
Benefits: Seems a bit cleaner by separating the activity and its status from the Person resource.
Drawbacks: Now we've moved the tunneling to the PersonActivityManager resource.
Option 4: Establish separate controller resources for each supported action, e.g. a ToWorkTransporter resource that accepts POST requests with an argument (or URI element) that identifies the Person, plus a ToHomeTransporter, a ToSchoolTransporter, a MealServer, a Sleeper, and an Exerciser. Each of these returns an appropriate task-monitoring resource (Commute, Meal, Slumber, Workout) from their POST method, which the client can monitor via GET.
Benefits: OK, we've finally eliminated tunneling. Each POST means only one thing.
Drawbacks: Now were talking about a lot of resources (maybe we could combine the transporters into one Transporter that accepts a destination argument). And some of them are pretty semantically contrived (a Sleeper?). It may be more RESTful, but is it practical?
OK, I've been researching and pondering this for about a week now. Since nobody else has answered, I'll post the results of what I've learned.
Tim Bray, in RESTful Casuistry, talks about PUT-ing a state field vs POST-ing to a controller which will perform an operation affecting that state. He uses the example of a VM and how to RESTfully expose the function of a "reboot button". He says
"If I want to update some fields in an existing resource, I’m inclined to think about PUT. But that doesn’t work because it’s supposed to be idempotent, and rebooting a server sure isn’t. Well, OK, do it with POST I guess; no biggie.
But you’re not really changing a state, you’re requesting a specific set of actions to happen, as a result of which the state may or may not attain the desired value. In fact, when you hit the deploy switch, the state changes to deploying and then after some unpredictable amount of time to deployed. And the reboot operation is the classic case of a box with a big red switch on the side; the problem is how to push the switch.
So, the more I think of it, the more I think that these resources are like buttons, with only one defined operation: push. People have been whining about “write-only resources” but I don’t have a problem with that because it seems accurate. The reboot and halt buttons don’t really have any state, so you shouldn’t expect anything useful from a GET."
Tim seems to settle somewhere between my #3 and #4 option, exposing multiple controller resources, but pulling back from "going overboard" and having separate controller resources for everything.
Tim's post led to another by Roy Fielding (It is OK to use POST) in which he says that for situations where there is a monitorable entity state, and an action to potentially change that state, he's inclined to use POST rather than PUT. In response to a commenter's suggestion to expose the monitored state as a separate PUT-able resource, he says
"we only use PUT when the update action is idempotent and the representation is complete. I think we should define an additional resource whenever we think that resource might be useful to others in isolation, and make use of the GET/PUT methods for that resource, but I don’t think we should define new resources just for the sake of avoiding POST."
Finally, Bill de hOra, in Just use POST discusses the specific case of using PUT vs. POST to update the state of a collection resource, and the tradeoffs therein.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With