Related
I need to delete multiple items by id in the batch however HTTP DELETE does not support a body payload.
Work around options:
1. #DELETE /path/abc?itemId=1&itemId=2&itemId=3 on the server side it will be parsed as List of ids and DELETE operation will be performed on each item.
2. #POST /path/abc including JSON payload containing all ids. { ids: [1, 2, 3] }
How bad this is and which option is preferable? Any alternatives?
Update: Please note that performance is a key here, it is not an option execute delete operation for each individual id.
Along the years, many people fell in doubt about it, as we can see in the related questions here aside. It seems that the accepted answers ranges from "for sure do it" to "its clearly mistreating the protocol". Since many questions was sent years ago, let's dig into the HTTP 1.1 specification from June 2014 (RFC 7231), for better understanding of what's clearly discouraged or not.
The first proposed workaround:
First, about resources and the URI itself on Section 2:
The target of an HTTP request is called a "resource". HTTP does not limit the nature of a resource; it merely defines an interface that might be used to interact with resources. Each resource is identified by a Uniform Resource Identifier (URI).
Based on it, some may argue that since HTTP does not limite the nature of a resource, a URI containing more than one id would be possible. I personally believe it's a matter of interpretation here.
About your first proposed workaround (DELETE '/path/abc?itemId=1&itemId=2&itemId=3') we can conclude that it's something discouraged if you think about a resource as a single document in your entity collection while being good to go if you think about a resource as the entity collection itself.
The second proposed workaround:
About your second proposed workaround (POST '/path/abc' with body: { ids: [1, 2, 3] }), using POST method for deletion could be misleading. The section Section 4.3.3 says about POST:
The POST method requests that the target resource process the representation enclosed in the request according to the resource's own specific semantics. For example, POST is used for the following functions (among others): Providing a block of data, such as the fields entered into an HTML form, to a data-handling process; Posting a message to a bulletin board, newsgroup, mailing list, blog, or similar group of articles; Creating a new resource that has yet to be identified by the origin server; and Appending data to a resource's existing representation(s).
While there's some space for interpretation about "among others" functions for POST, it clearly conflicts with the fact that we have the method DELETE for resources removal, as we can see in Section 4.1:
The DELETE method removes all current representations of the target resource.
So I personally strongly discourage the use of POST to delete resources.
An alternative workaround:
Inspired on your second workaround, we'd suggest one more:
DELETE '/path/abc' with body: { ids: [1, 2, 3] }
It's almost the same as proposed in the workaround two but instead using the correct HTTP method for deletion. Here, we arrive to the confusion about using an entity body in a DELETE request. There are many people out there stating that it isn't valid, but let's stick with the Section 4.3.5 of the specification:
A payload within a DELETE request message has no defined semantics; sending a payload body on a DELETE request might cause some existing implementations to reject the request.
So, we can conclude that the specification doesn't prevent DELETE from having a body payload. Unfortunately some existing implementations could reject the request... But how is this affecting us today?
It's hard to be 100% sure, but a modern request made with fetch just doesn't allow body for GET and HEAD. It's what the Fetch Standard states at Section 5.3 on Item 34:
If either body exists and is non-null or inputBody is non-null, and request’s method is GET or HEAD, then throw a TypeError.
And we can confirm it's implemented in the same way for the fetch pollyfill at line 342.
Final thoughts:
Since the alternative workaround with DELETE and a body payload is let viable by the HTTP specification and is supported by all modern browsers with fetch and since IE10 with the polyfill, I recommend this way to do batch deletes in a valid and full working way.
It's important to understand that the HTTP methods operate in the domain of "transferring documents across a network", and not in your own custom domain.
Your resource model is not your domain model is not your data model.
Alternative spelling: the REST API is a facade to make your domain look like a web site.
Behind the facade, the implementation can do what it likes, subject to the consideration that if the implementation does not comply with the semantics described by the messages, then it (and not the client) are responsible for any damages caused by the discrepancy.
DELETE /path/abc?itemId=1&itemId=2&itemId=3
So that HTTP request says specifically "Apply the delete semantics to the document described by /path/abc?itemId=1&itemId=2&itemId=3". The fact that this document is a composite of three different items in your durable store, that each need to be removed independently, is an implementation details. Part of the point of REST is that clients are insulated from precisely this sort of knowledge.
However, and I feel like this is where many people get lost, the metadata returned by the response to that delete request tells the client nothing about resources with different identifiers.
As far as the client is concerned, /path/abc is a distinct identifier from /path/abc?itemId=1&itemId=2&itemId=3. So if the client did a GET of /path/abc, and received a representation that includes itemIds 1, 2, 3; and then submits the delete you describe, it will still have within its own cache the representation that includes /path/abc after the delete succeeds.
This may, or may not, be what you want. If you are doing REST (via HTTP), it's the sort of thing you ought to be thinking about in your design.
POST /path/abc
some-useful-payload
This method tells the client that we are making some (possibly unsafe) change to /path/abc, and if it succeeds then the previous representation needs to be invalidated. The client should repeat its earlier GET /path/abc request to refresh its prior representation rather than using any earlier invalidated copy.
But as before, it doesn't affect the cached copies of other resources
/path/abc/1
/path/abc/2
/path/abc/3
All of these are still going to be sitting there in the cache, even though they have been "deleted".
To be completely fair, a lot of people don't care, because they aren't thinking about clients caching the data they get from the web server. And you can add metadata to the responses sent by the web server to communicate to the client (and intermediate components) that the representations don't support caching, or that the results can be cached but they must be revalidated with each use.
Again: Your resource model is not your domain model is not your data model. A REST API is a different way of thinking about what's going on, and the REST architectural style is tuned to solve a particular problem, and therefore may not be a good fit for the simpler problem you are trying to solve.
That doesn’t mean that I think everyone should design their own systems according to the REST architectural style. REST is intended for long-lived network-based applications that span multiple organizations. If you don’t see a need for the constraints, then don’t use them. That’s fine with me as long as you don’t call the result a REST API. I have no problem with systems that are true to their own architectural style. -- Fielding, 2008
We are currently in the process of wrangling smaller services from our monoliths. Our domain is very similar to a ticketing system. We have decided to start with the cancellation process of the domain.
Our cancel service has as simple endpoint "Cancel" which takes in the id of the ticket. Internally, we retrieve the id, perform some operations related to cancel on it and update the state of the entity in the store. From the store's perspective the only difference between a cancelled ticket and a live ticket are a few properties.
From what I have read, PATCH seems to be the correct verb to be used in this case, as am updating only a simple property in the resource.
PATCH /api/tickets/{id}
Payload {isCancelled: true}
But isCancelled is not an actual property in the entity. Is it fair to send properties in the payload that are not part of the entity or should I think of some other form of modeling this request? I would not want to send the entire entity as part of the payload, since it is large.
I have considered creating a new resource CancelledTickets, but in our domain we would never have the need to a GET on cancelled tickets. Hence stayed away from having to create a new resource.
Exposing the GET interface of a resource is not compulsory.
For example, use
PUT /api/tickets/{id}/actions/cancel
to submit the cancellation request. I choose PUT since there would be no more than one cancellation request in effect.
Hope it be helpful.
Take a look what exactly is RESTful way. No matter if you send PATCH request with isCancelled as payload or even DELETE if you want tickets to disappear. It's still RESTful.
Your move depends on your needs. As you said
I have considered creating a new resource CancelledTickets, but in our
domain we would never have the need to a GET on cancelled tickets.
I would just send DELETE. You don't have to remove it physically. If it's possible to un-cancel, then implement isCancelled mechanism. It's just question of taste.
REST is basically a generalization of the browser based Web. Any concepts you apply for the Web can also be applied to REST.
So, how would you design a cancel activity in a Web page? You'd probably have a table row with certain activities like edit and delete outlined with icons and mouse-over text that on clicking invoke a URI on the server and lead to a followup state. You are not that much interested how the URI of that button might look like or if a PATCH or DELETE command is invoked in the back. You are just interested that the request is processed.
The same holds true if you want to perform the same via REST. Instead of images that hint the user that an edit or cancel activity is performed on an entry, a meaningful link-relation name should be used to hint the client about the possiblities. In your case this might be something like reserve new tickets, edit reservation or cancel reservation. Each link relation name is accompanied by a URL the client can invoke if he wants to perform one of the activities. The exact characters of the URI is not of importance here to the client also. Which method to invoke might either be provided already in the response (as further accompanying field) or via the media type the response was processed for. If neither the media type nor an accompanying field gives a hint on which HTTP operation to use an OPTIONS request may be issued on the URI beforehand. The rule of thumb here is, the server should teach a client on how to achieve something in the end.
By following such a concept you decouple a client from the API and make it robust to changes. Instead of a client generating a URI to invoke the client is fed by the server with possible URIs to invoke. If the server ever changes its iternal URI structure a client using one of the provided URIs will still be able to invoke the service as it simply used one of the URIs provided by the server. Which one to use is determined by analyzing the link relation name that hints the client when to invoke such URI. As mentioned above, such link relation names need to be defined somewhere. But this is exactly what Fielding claimed back in 2008 by:
A REST API should spend almost all of its descriptive effort in defining the media type(s) used for representing resources and driving application state, or in defining extended relation names and/or hypertext-enabled mark-up for existing standard media types. (Source)
Which HTTP operation to choose for canceling a ticket/reservation may depend on your desing. While some of the answers recommended DELETE RFC 7231 states that only the association between the URI and the resource is removed and no gurantee is given that the actual resource is also being removed here as well. If you design a system where a cancelation might be undone, then DELETE is not the right choice for you as the mapping of the URI to the resource should not exist further after handling a DELETE request. If you, however, consider a cancelation to also lead to a removal of the reservation then you MAY use DELETE.
If you model your resource in a way that maintains the state as property within the resource, PATCHing the resource might be a valid option. However, simply sending something like state=canceled is probably not enough here as PATCH is a calculation of steps done by the client in order to transform a certain resource (or multipe resources) into a desired target state. JSON Patch might give a clue on how this might be done. A further note needs to be done on the atomicy requirement PATCH has. Either all of the instructions succeed or none at all.
As also PUT was mentioned in one of the other answers. PUT has the semantics of replacing the current representation available at the given URI with the one given in the request' payload. The server is further allowed to either reject the content or transform it to a more suitable representation and also affect other resources as well, i.e. if they mimic a version history of the resource.
If neither of the above mentioned operations really satisfies your needs you should use POST as this is the all-purpose, swiss-army-knife toolkit of HTTP. While this operation is usually used to create new resources, it isn't limited to it. It should be used in any situation where the semantics of the other operations aren't applicable. According to the HTTP specification
The POST method requests that the target resource process the representation enclosed in the request according to the resource's own specific semantics.
This is basically the get-free-out-of-jail card. Here you can literally process anything at the server according to your own rules. If you want to cancel or pause something, just do it.
I highly discourage to use GET for creating, altering or canceling/removing something. GET is a safe operation and gurantees any invoking client that it wont alter any state for the invoked resource on the server. Note that it might have minor side-effects, i.e. logging, though the actual state should be unaffected by an invocation. This is a property Web crawler rely on. They will simply invoke any URI via GET and learn the content of the received response. And I assume you don't want Google (or any other crawler) to cancel all of your reservations, or do you?
As mentioned above, which HTTP operation you should use depends on your design. DELETE should only be used if you are also going to remove the representation, eventhough the spec does not necessarily require this, but once the URI mapping to the resource is gone, you basically have no way to invoke this resource further (unless you have created a further URI mapping first, of course). If you designed your resource to keep the state within a property I'd probably go for PATCH but in general I'd basically opt for POST here as here you have all the choices at your hands.
I would suggest having a state resource.
This keeps things RESTful. You have your HTTP method acting as the verb. The state part of the URI is a noun. Then your request body is simple and consistent with the URI.
The only thing I don't like about this is the value of state requires documentation, meaning what states are there? This could be solved via the API by providing possible states on a meta resource or part of the ticket body in meta object.
PUT /api/tickets/:id/state
{state: "canceled"}
GET /api/meta/tickets/state
// returns
[
"canceled",
...
]
GET /api/tickets/:id
{
id: ...
meta: {
states: [
"canceled",
...
]
}
}
For modelling CANCEL Action in Restful way :
Suppose we have to delete a note in DB by providing noteId(Note's ID) and Note is a pojo
1] At controller :
#DeleteMapping(value="/delete/{noteId}")
public ResponseEntity<Note> deleteNote( #PathVariable Long noteId)
{
noteServiceImpl.deleteNote(noteId);
return ResponseEntity.ok().build();
}
2]Service Layer :
#Service
public class NoteServiceImpl {
#Autowired
private NotesRepository notesDao;
public void deleteNote(Long id) {
notesDao.delete(id);
}
}
3] Repository layer :
#Repository
public interface NotesRepository extends CrudRepository<Note, Long> {
}
and in 4] postman : http://localhost:8080/delete/1
So we have deleted note Id 1 from DB by CANCEL Action
I have a timespan I want to delete in a REST API.
It doesn't have an id so calling HTTP DELETE on "/timespan/" is not really possible. The implementation would be possible, but I would rather not put in the extra effort (requires some database modifications) unless there is a good reason to add it.
I considered calling DELETE on "/timespan/" with "start" and "end" inside the request but to my understanding this clashes with the way REST works.
Is it legit to call DELETE on "/timespan//" or maybe a concatenation such as "/timespan/+" or should I implement IDs after all?
You are correct. DELETE doesn't take a body.
RFC 7231:
A payload within a DELETE request message has no defined semantics;
sending a payload body on a DELETE request might cause some existing
implementations to reject the request.
I've seen what you want done as
DELETE /sites/{siteId}/maintenance
but that's really not optimal. If maintenance is a resource, it needs some way of being uniquely identified. If it's a property of a resource, then you delete it via PUT or PATCH on that resource.
Assuming your resource is a maintenance performed on a site. The API could have:
DELETE /sites/{site-id}/maintenances/{maintenance-id} to delete one maintenance. The typical delete.
DELETE /sites/{site-id}/maintenances to delete all maintenances of a given site. Not usual, and dangerous.
DELETE /site/{site-id}/maintenances?start={start-date}&end={end-date} to delete all maintenances in a timespan. The answer to your question.
Rationale: the timespan is not part of the resource, it's a filtering attribute on the collection of resources. Therefore, it should not be part of the URI (Uniform Resource Identifier). We should use query string parameters for filtering.
Another option:
POST /site/{site-id}/clean-up to delete all maintenances of a given site that are within the timespan specified in the request body. Also an answer to your question.
Rationale: some advocate that a REST API can offer more "coarse-grained" operations that closely resemble the business capability in complement to (or in the place of) more CRUD-like APIs. Here's a reference. In the particular case, the POST operation executes a "clean-up business process". The start and end dates go in the request body. The operation should return 200 (not 201) for success.
Instead of designing a low-level CRUD-like REST API and let the callers know details about your domain, you could let the client POST their intent and let the server decide what to delete. Seems like the right approach if you ant to prevent users from accidentally (maliciously?) deleting resources. More in Fine grained CRUD resources versus Coarse Grained resources at http://www.thoughtworks.com/insights/blog/rest-api-design-resource-modeling
If you are following the RESTful principle closely, you would need to retrieve a list of resources (partial as proposed by #moonwave99) and invoke DELETE on every resource whose timestamp is older than a certain threshold. This conveis the semantics of delete while being completely idempotent. You, however, need a resource identifier therefore (which you should have obtained via the previous retrieve).
The next choice would be to set off a PUT request where you send each and every entry that should be available after the request within that request. In practice this is however a no-go as to much data would need to be transfered.
Last but not least, you would have the possibility to delete resources via PATCH where you pass the necessary instructions to the server it needs to transform the resource(s) from state_before to state_after. In case of json-patch you have the remove operation at hand. However, the spec does not provide any possibility to remove a state if a certain condition was met - a combination of test+delete would be handy in that case. The quintesence is though, that not the server is responsible for filtering out certain data but the client which has to send each necessary step to the server. Therefore, a client has to retrieve the current state of a resource collection like /api/sampleResources which could be an array of JSON objects and do the decission locally:
HTTP/1.1 200 OK
...
ETag: "7776cdb01f44354af8bfa4db0c56eebcb1378975"
...
[
{
...
"timestamp": "2015-07-17T19:40:00",
...
},
{
...
"timestamp": "2014-10-05T10:00:00",
...
},
{
...
"timestamp": "2015-07-16T15:00:00",
...
},
{
...
"timestamp": "2014-12-31T00:00:00",
...
}
]
If all entries from the last year should be deleted, a JSON-PATCH request would need to look like this:
PATCH /api/sampleResources
...
ETag: "7776cdb01f44354af8bfa4db0c56eebcb1378975"
...
[
{ "op": "remove", "path": "/1" },
{ "op": "remove", "path": "/3" }
]
The path is specified in JSON Pointer notation and is 0 based, therefore the sample above removes the second and fourth entry of the list, which are the entries of 2014.
As the resource can be modified between the lookup and the patch generation, it is highly recommended to use ETag features to gurantee that the patch is executed on the right state. If the ETags do not match, the request fails with a precondition failure.
Note however, that PATCH is not idempotent! So sending the same request twice may delete more entries as intended. This is not the case with PUT, as the state after the request is exactly the state you told the resource to have. Therefore, sending the same request twice does not change anything state-wise. In comparison to a bunch of DELETE requests, PATCH is atomic - either all or none of the operations are executed, while the DELETE requests are all independent.
I agree strongly, that there is a need for conditional and partial PUT and DELETE operations to adhere to the business needs. For simplicity reasons I would really recommend to use partial GET and DELETE operations instead of PUT or PATCH.
I am creating a web client which has the purpose of modifying a set of database tables by adding records to them and removing records from them. It must do so atomically, so both deletion and insertion must be done with a single HTTP request. Clearly, this is a write operation of some sort, but I struggle to identify which method is appropriate.
POST seemed right at first, except that RFC 2616 specifies that a POST request must describe "a new subordinate" of the named resource. That isn't quite what I'm doing here.
PUT can be used to make changes to existing things, so that seemed about right, except that RFC 2616 also specifies that "the URI in a PUT request identifies the entity enclosed with the request [...] and the server MUST NOT attempt to apply the request to some other resource," which rules that method out because my URI does not directly specify the database tables.
PATCH seemed closer - now I am not cheating by only partly overwriting a resource - but RFC 5789 makes it clear that this method, like PUT, must actually modify the resource specified by the URI, not some subordinate resource.
So what method should I be using?
Or, more broadly for the benefit of other users:
For a request to X, you use
POST to create a new subordinate of X,
PUT to create a new X,
PATCH to modify X.
But what method should you use if you want to modify a subordinate of X?
To start.. not everything has to be REST. If REST is your hammer, everything may look like a nail.
If you really want to conform to REST ideals, PATCH is kind of out of the question. You're only really supposed to transfer state.
So the common 'solution' to this problem is to work outside the resources that you already have, but invent a new resource that represents the 'transaction' you wish to perform. This transaction can contain information about the operations you're doing in sequence, potentially atomically.
This allows you to PUT (or maybe POST) the transaction, and if needed, also GET the current state of the transaction to find out if it was successful.
In most designs this is not really appropriate though, and you should just fall back on POST and define a simple rpc-style action you perform on the parent.
First, allow me to correct your understanding of these methods.
POST is all about creating a brand new resource. You send some data to the server, and expect a response back saying where this new resource is created. The expectation would be that if you POST to /things/ the new resource will be stored at /things/theNewThing/. With POST you leave it to the server to decide the name of the resource that was created. Sending multiple identical POST requests results in multiple resources, each their own 'thing' with their own URI (unless the server has some additional logic to detect the duplicates).
PUT is mostly about creating a resource. The first major difference between PUT and POST is that PUT leaves the client in control of the URI. Generally, you don't really want this, but that's getting of the point. The other thing that PUT does, is not modify, if you read the specification carefully, it states that you replace what ever resource is at a URI with a brand new version. This has the appearance of making a modification, but is actually just a brand new resource at the same URI.
PATCH is for, as the name suggest, PATCHing a resource. You send a data to the server describing how to modify a particular resource. Consider a huge resource, PATCH allows you to send just the tiny bit of data that you wish to change, whilst PUT would require you send the entire new version.
Next, consider the resources. You have a set of tables each with many rows, that equates to a set of collections with many resources. Now, your problem is that you want to be able to atomically add resources and remove them at the same time. So you can't just POST then DELETE, as that's clearly not atomic. PATCHing the table how ever can be...
{ "add": [
{ /* a resource */ },
{ /* a resource */ } ],
"remove" : [ "id one", "id two" ] }
In that one body, we have sent the data to the server to both create two resources and delete two resources in the server. Now, there is a draw back to this, and that is that it's hard to let clients know what is going on. There's no 'proper' way of the client of the two new resources, 204 created is sort of there, but is meant have a header for the URI of the one new resource... but we added two. Sadly, this a problem you are going to face no matter what, HTTP simple isn't designed to handle multiple resources at once.
Transaction Resources
So this is a common solution people propose, and I think it stinks. The basic idea is that you first POST/PUT a blob of data on the server the encodes the transaction you wish to make. You then use another method to 'activate' this transaction.
Well hang on... that's two requests... it sends the same data that you would via PATCH and then you have fudge HTTP even more in order to somehow 'activate' this transaction. And what's more, we have this 'transaction' resource now floating around! What do we even do with that?
I know this question has been asked already some time ago, but I thought I should provide some commentary to this myself. This is actually not a real "answer" but a response to thecoshman's answer. Unfortunately, I am unable to comment on his answer which would be the right thing to do, but I don't have enough "reputation" which is a strange (and unnecessary) concept, IMHO.
So, now on to my comment for #thecoshman:
You seem to question the concept of "transactional resources" but in your answer it looks to me that you might have misunderstood the concept of them. In your answer, you describe that you first do a POST with the resource and the associated transaction and then POST another resource to "activate" this transaction. But I believe the concept of transactional resources are somehow different.
Let me give you a simple example:
In a system you have a "customer" resource and his address with customer as the primary (or named) resource and the address being the subordinate address. For this example, let us assume we have a customer with a customerId of 1234. The URI to reach this customer would be /api/customer/1234. So, how would you now just update the customer's address without having to update the entire customer resource? You could define a "transaction resource" called "updateCustomerAddress". With that you would then POST the updated customer address data (JSON or even XML) to the following URI: POST /api/customer/1234/updateCustomerAddress. The service would then create this new transactional resource to be applied to the customer with customerId=1234. Once the transaction resource has been created, the call would return with 201, although the actual change may not have been applied to the customer resource. So a subsequent GET /api/customer/1234 may return the old address, or already the new and updated address. This supports well an asynchronous model for updating subordinate resources, or even named resources.
And what would we do with the created transactional resource? It would be completely opaque to the client and discarded as soon as the transaction has been completed. So the call may actually not return a URI of the transactional resource since it may have disappeared already by the time a client would try to access it.
As you can see, transactional resources should not require two HTTP calls to a service and can be done in just one.
RFC 2616 is obsolete. Please read RFC 723* instead, in particular https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.3.
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.