views:

140

answers:

3

There is another similar question to mine, but the discussion veered away from the problem I'm encounting.

Say I have a system that deals with expense reports (ER). You can create and edit them, add attachments, and approve/reject them.

An expense report might look like this:

GET /er/1
=>
{"title": "Trip to NY", "totalcost": "400 USD",
 "comments": [
   "john: Please add the total cost",
   "mike: done, can you approve it now?"
   ],
 "approvals": [
   {"john": "Pending"}, {"finance-group": "Pending"}]
}

That looks fine, right? Thats what an expense report document looks like.

If you want to update it, you can do this:

POST /er/1
{"title": "Trip to NY 2010"}

If you want to approve it, you can do this:

POST /er/1/approval
{"approved": true}

But, what if you want to update the report and approve it at the same time? How do we do that? If you only wanted to approve, then doing a POST to something like /er/1/approval makes sense.

We could put a flag in the URL, POST /er/1?approve=1, and send the data changes as the body, but that flag doesn't seem RESTful.

We could put special field to be submitted, too, but that seems a bit hacky, too. If we did that, then why not send up data with attributes like set_title or add_to_cost?

We could create a new resource for updating and approving, but (1) I can't think of how to name it without verbs, and (2) it doesn't seem right to name a resource based on what actions can be done to it (what happens if we add more actions?)

We could have an X-Approve: True|False header, but headers seem like the wrong tool for the job. It'd also be difficult to get set headers without using javascript in a browser.

We could use a custom media-type, application/approve+yes, but that seems no better than creating a new resource.

We could create a temporary "batch operations" url, /er/1/batch/A. The client then sends multiple requests, perhaps POST /er/1/batch/A to update, then POST /er/1/batch/A/approval to approve, then POST /er/1/batch/A/status to end the batch. On the backend, the server queues up all the batch requests somewhere, then processes them in the same backend-transaction when it receives the "end batch processing" request. The downside with this is, obviously, that it introduces a lot of complexity.

So, what is a good, general way to solve the problem of performing multiple actions in a single request? General because its easy to imagine additional actions that might be done in the same request:

  1. Suppress or send notifications (to email, chat, another system, whatever)
  2. Override some validation (maximum cost, names of dinner attendees)
  3. Trigger backend workflow that doesn't have a representation in the document.

Its also an issue of performance. HTTP calls hit the network (which could be a problem if you have high latency or a flaky connection), so the fewer of them you can make, the better.

+5  A: 

The REST architecture says that a resource is managed by the server and identified by a URL.

In that light /er/1/approval is not a reasonable URL or model to use, unless you have an approval object or entity that you manage and manipulate on the server side. Seems to me the entity is the expense report itself, which means, /er/1 is your URL path.

Now, as for verbs... you can send (POST) any message you like to that resource.

set data:

{ action: "modify", data: { purpose : "Club hopping" } }

approve:

{ action: "approve" }

add item:

{ action:"additem", data: { amount:72.13, category:113, note:"client dinner" }}

etc.


From Fielding's Ch5, which defined REST,

The in-parameters (of a request) consist of request control data, a resource identifier indicating the target of the request, and an optional representation.

...and...

Control data defines the purpose of a message between components, such as the action being requested or the meaning of a response. It is also used to parameterize requests and override the default behavior of some connecting elements. For example, cache behavior can be modified by control data included in the request or response message.


Therefore if you'd like to perform multiple actions on a resource, then you should embed in the "control data" multiple messages or action requests. In my example, the posted data would be something like:

{ action: "modify", data: { purpose : "Club hopping" } }
{ action: "approve" }

But you'd probably want to generalize that so that it is:

{ actions: [ {action:"modify", data: {...} }, { action:"approve"} ] } 

The messages or actions your server can handle on each particular type of entity are up to you to define.

ps: sometimes REST implementations use HTTP PUT to create a resource and POST to modify or act on an existing resource.

and: I liked the article, How to GET a cup of coffee.

Cheeso
yeah, what he said.
Sky Sanders
Like the command pattern
Romain Hippeau
Great answer, I like it. Lets say I wanted to support 3 different formats of control data which were very similar: 1) simple data update, like in my `POST /er/1` example, 2) advanced update, like your `{action:...}` idea, and 3) advanced multi-update, like you generalized `actions` idea. Would I use different media types for each? app/er-update, app/er-update-adv, app/err-update-adv-multi?
Richard Levasseur
Similar question for when a resource has multiple encodings and for collection/member types. Essentially, any resource has a main mime type, but can come in a certain encoding (json, html, etc), and either as a single item or a list of items. Which means there are num_mime_types * num_encodings * 2 representations, seems wrong to have a mime type for each one?
Richard Levasseur
You may want to use different media types for a request that uses json, versus one that does not. I don't see using different media types to differentiate different json message schema.
Cheeso
I don't believe that each resource needs its own mime-type, but of course that depends on how you'd lke to structure your application. In SOAP 1.2, all requests are a single mime type (application/soap+_xml), and there's an optional action attribute on that mime type to indicate some additional information. If you define a custom media type, you may want to consider a similar extension mechanism to specify different message types.
Cheeso
You do need to be very careful with this pattern. It can easily violate the uniform interface by creating verbs that overlap with the HTTP verbs. e.g. {action: "delete" } I think this approach should only be adopted when there is really no other way of modelling the behaviour in a more resource oriented way. The disadvantage of using the "action" parameters is that it is much more difficult to embed links than with just url based requests.
Darrel Miller
The problem with the Fielding quotes that you picked is that he does not actually say you can put control data in the representation. Arguably, HTTP already defines the allowed actions and includes them in the HTTP request as the method. Adding more actions in the representation opens the door to confusion if the action contradicts the HTTP method. E.g. What if the action was "GetSummary" ?
Darrel Miller
Not a fan of embedding actions into the messages of a POST. This feels like it violates the REST principles of an agreed on set of verbs with defined meanings. In this case you have a POST method that can do anything - this seems exactly like the examples you see (of things NOT to do) where they add a URI parameter like "action=delete" to cause something to happen.
Gandalf
@Gandalf, I think it's easy to rule out action="delete" (as well as action="get"). POST-with-action is just a way of defining an application protocol. Without that protocol, in other words without a definition for the kinds of messages a POST can transport in a given application, the REST approach is pretty limited, and arbitrarily so. What will POST do, then, if I as app designer am not at liberty to decide? Who decides the extent of the POST semantics? If you like, you can send a noun, instead of a verb. "modifyrequest" instead of "modify". But it is the same thing.
Cheeso
@Darrel Miller, I don't agree that it's difficult to embed URLs in requests or responses when using an action parameter. Also, re: your statement that *Fielding doesn't say you can put control data in the representation.* In fact he directly implies the opposite. He says a request includes control data and an optional representation, implying they are distinct. I think you are inferring that under REST, all requests contain only representations; I disagree and think Fielding does, too.
Cheeso
@Cheeso Yes a request includes control data. Http calls these things HTTP headers and the Http method. A request can also contain a representation. From what I understand of your post, you are putting control data in the representation. Where does Fielding say that is ok?
Darrel Miller
Fielding does not stipulate that control data is passed only in HTTP Headers, as far as I know. Certainly not in the REST chapter, which is defined independently of HTTP. You are defining the content of any HTTP transmission as "representation"; that's where we disagree. I don't think that is required by REST. In fact I don't know a document that specifically applies REST to HTTP (an HTTP "binding" for REST as it were) where this might be stipulated. And there's more....
Cheeso
Even if you insist on defining the HTTP payload as "a representation" in the HTTP binding for REST, REST doesn't require that the representation being transmitted be a representation of the resource that gets the message. In sec 5.2.1.2, the REST chapter says that a representation in a message can be of *anything* including "a representation of the input data within a client's query form". Based on this, it's clear the representation transmitted could also encode a request or command, as I've recommended above. Free your mind.
Cheeso
To consider: How is transmission of a partial representation any different than transmission of an explicit action or command? Transmission of a partial representation, like `{title:"NYC Trip 2010"}` includes an implicit command or action: "update the title". What I am suggesting is simply to make the action or command explicit. Denying that as a reasonable approach means applications must ONLY transmit full representations of named resources, an unworkable and non-scalable approach. No surprise that it is also decidedly NOT how the web works today.
Cheeso
@Chesso See my answer below. I don't believe any of this is needed to do the workflow he is asking. Even your "How to GET a cup of coffee" example doesn't try to imbed actions into the resource, it gives links to other resources that can be manipulated to control the workflow through standatd HTTP verbs.
Gandalf
I saw your answer and I think it's a reasonable approach. On the other hand if he doesn't want to transmit around the full representation of a resource, there are other options. (But I don't know what you mean by "embed actions into the resource".)
Cheeso
I mean your 'action : modify' .... 'action : approve' type stuff. If the resource has a field called <status>...</status> and you update that field in the client a PUT the resource back then you've done the same thing without having to embed action commands that the server now needs to process.
Gandalf
The other thing I dislike about this approach is the lack of HATEOAS involved. You are not including or following any links in this workflow to alter/monitor state.
Gandalf
@Cheeso I understand what you mean about partial representations, which is why PUT does not allow that type of update and with POST I would never POST the partial representation directly to the resource itself, but to a related processing resource whose specific job is to process those commands. I do agree there is a way to do it, you just have to tread carefully, or you can end using HTTP as a tunnel. The introduction of PATCH does open the door to applying a set of change commands to a resource.
Darrel Miller
A: 

For manipulating the status of resources I often like to use "status buckets". The idea is that when you "add" an object into that bucket, it gets that status. It is like having in and out boxes on your desk. The location of the document defines its status.

So, you could do something simple like:

POST /Expenses/Approved
{ .. Expense document ... }

or for the more complex case that you hinted at in your document where multiple people have to approve the document.

POST /ExpenseApprover/John/ApprovedExpenses
{ .. Expense document ... }

If you need to submit an expense report for approval you can do

POST /ExpenseApprover/John/Pending
{ .. Expense document ... }

And don't forget hypermedia can make this process workflow enabled. Imagine someone creates an initial expense report, the server could response with the following JSON.

{ "id" : "234",
  "title": "Trip to NY", "totalcost": "400 USD",
  "submit_url": "/ExpenseApprover/John/Pending"
}

The client can POST to the submit_url to move the expense onto it's next step. Then when John retrieves the expense, he gets

{ "id" : "234",
  "title": "Trip to NY", "totalcost": "400 USD",
  "approve_url": "/ExpenseApprover/Finance/Pending",
  "denied_url": "/ExpenseApprover/John/Denied",
}

When the finance department do a

GET /ExpenseApprover/Finance/Pending

they could get a list of Pending Expenses,

{ PendingExpense: [
    { "id" : "234",
      "title": "Trip to NY", "totalcost": "400 USD",
     "approve_url": "/Expense/Approved",
     "denied_url": "/ExpenseApprover/Finance/Denied",
    }
   ]
}

Forgive my horrible JSON, but I hope you get the idea that including the link in the response you can guide the flow of your application. You can also stop worrying so much about what the url looks like because the client doesn't really care. The client reads the url from the response based on the property name and dereferences it. You can change your mind a million times on what the best url structure is and your clients will not be affected. Just don't change the property name!

These "status bucket" urls are used to hold a set of resources that have a similar status. The idea is that you POST a document into the collection:

POST /ExpenseApprover/Finance/Denied

{"id" : "234", "title": "Trip to NY", "totalcost": "400 USD"}

It is not necessary to uniquely define the particular expense that you are adding in the URL because the body document should contain some kind of identifying key value.
This technique is just as valid for flagging expenses has having discrepancies. You simply create a new resource that holds expenses with discrepancies and post your expense report into to it.

POST /Discrepancies
{"id" : "234", "title": "Trip to NY", "totalcost": "400 USD"}
Darrel Miller
Darrel, in your urls you don't include an identifier (eg `/er/1`), is this because of the document contains the ID, or simply because of your brevity in the examples? (I'm assuming the latter). I do get (and like) the concept of returning opaque URLs the clients then issue requests on (thats actually used elsewhere in the app). To try and adapt/clean up what you're suggesting, I guess the end result would be like:`GET /er/1 =>``{"title": "trip to ny",`` "approvals": [{"who": "john", "approve_url": "/er/1/approved/john"}]}`
Richard Levasseur
There'd be an "reject_url", too (char limit ftl). The values of those url's isn't so important as that they're there. I get that, and like it. What you propose _would_ allow the updating and approving of an ER, but what about other actions? Perhaps there is a "Report Discrepancy" action. You fill out the ER as best you can, maybe approve/reject it, maybe report a discrepancy, and then click "Save". How would you do this?
Richard Levasseur
@Richard No I intentionally did not include an Id in the urls because.... I'll edit the post to explain why.
Darrel Miller
Ok, but this still doesn't solve the problem that multiple-requests (and in the common case, backend transactions) will be needed to update the resource as desired - thats the crux of the issue.
Richard Levasseur
@Richard If I get chance tonight I give you another answer which is probably closer to what you are looking for. I'm experimenting currently with a variation of what Cheeso suggested but more constrained. Actions are limited to "Commands" as per the "Command Query Separation" principle, the media type is something like application/CommandList+xml, the verb is PATCH and you only do this on resources that are "Aggregate Roots" as per DDD terminology.
Darrel Miller
One thought I had was to based it on email - use the multipart/whatever mimetypes to say "i have multiple messages that need to be processed".
Richard Levasseur
A: 

I think you are making it more complicated then it needs to be. Treat your expense report as a complete resource and any edits to it are simply a matter of PUTing a new representation to the URI where the resource lives. No need to have custom actions to change status, just GET the resource - make your edits - then PUT it back. Done.

Gandalf
This works in the simple case, but breaks down when things get more complicated. What about when you have large attachments? Its unreasonable for the client to have to download and upload the same date just to change a small piece [1]. Its also a lot of wasted bytes, and increases the chances of conflicting edits. [1]: this can be mitigated by making them their own resource, but that puts us back to the original question I'm posing because it creates a second action, "add attachment".
Richard Levasseur
I don't understand your concerns with breaking a large resource into smaller resources and changing them to change state. It seems like you answered your own question just fine. I don't see how breaking up a resource "puts us back to the original question" either.
Gandalf
Because, by breaking it up, it requires multiple HTTP requests to update them (and most likely multiple backend transactions), which is what we're trying to avoid.
Richard Levasseur