7

I'm developing an API that performs bulk update of a large number of items in a single call. This code will consist of a REST endpoint and the internal library code that it calls.

There are a few reasons why the update of a specific item may fail (e.g., the item has been deleted). The question I'm debating is how to return the result to the caller.

I'm leaning towards returning a collection that contains only the items with failures, but I could see scenarios where it would be helpful to return the result for all of them.

In either case, the response would contain a collection of objects like this:

class UpdateResponse
{
    int ItemId;
    ResultType Result;
}

Where ResultType is an enumeration that describes the result of the update.

Is there any compelling reason to choose one strategy over the other (return all vs. return failures)?

  • 1
    What are you returning as HTTP status code when your request is an entire success (=all updates done) and when it's a "partial" success ? This might be helpful to give you some hints. – AilurusFulgens Feb 3 '16 at 14:51
  • I was planning to return 200 in both cases for the bulk update. The endpoint that modifies a single item returns 404 or 409 in the failure cases. – Jack A. Feb 4 '16 at 12:25
4

You have two options. Either to use the 200 OK or the 207 Multi-Status.

I would not recommend to use the latter, as it is a WebDav status code (WebDav is an extension of HTTP) status code that forces you to respond with an XML document. Although it allows you to use multiple status code in your response.

My preference would goes to the 200 OK because it basically means: "yes your request has been completed". That is your case.

The second question is a choice between: "your request has been completed: here is a summary" or "your request has been completed: here is what goes wrong". The real question is: does the client needs to know that an item has been successfully updated ?

  • Yes: provide all results.
  • No: provide only failures. It will avoid for a client to parse the entire response with unnecessarily information.
1

There are a few reasons why the update of a specific item may fail (e.g., the item has been deleted). The question I'm debating is how to return the result to the caller.

Touchstone: how would you do it as a web page?

Presumably, the user would start out looking at a web page, with a form on it that would allow her to describe the task -- the jobs in the batch. The submit button would POST the form to the resource identified by form.action. The response would be another html page.

Expressed in REST terms, the initial state of the application would be described by a hypermedia document (the html page) with hypermedia controls (links/forms). The application would represent the controls based on its interpretation of the media-type (form parameters). The user would configure and choose which control to activate, which would dispatch a message to a resource on the server, which would respond with another hypermedia document describing the next application state.

That's RMM level 3, anyway. Up to you how much you want to push toward that goal, but I find it's easier to work backwards from "right" to "practical".

I'm leaning towards returning a collection that contains only the items with failures, but I could see scenarios where it would be helpful to return the result for all of them.

Again, how would you handle this with web pages? You'd probably send back the html that is most useful for the common case, and include a link to other resources that describe the result with different granularity. That will get you by in version 1, but eventually you'll discover that some users always prefer the secondary representation. So you'll set up a new resource that accepts the job batch, and returns the secondary representation (probably with a link to the first). Your initial web page would have two links on it, each posting the same batch job to a different resource, so that the user can express their preference for a representation right away, doing away with the extra click.

You can use the same trick with other representations. At RMM-3, your documents would have hypermedia controls to connect the application to other representations of the results, but at RMM-2 you can get by with two different resources that accept the same job batch, and produce different reports.

The key point here is that it is an easy problem to address later, you just add another resource that does the same work.

Assuming that you are using HTTP to transfer your documents back and forth, the right status code is most likely to be 201 (Created). You aren't describing the result of the work in the back end, you are describing what is happening to the resources. Here, you are presumably taking the application that submitted the job to "some representation of the results." Unless you have got time travel going on, the report of the results probably didn't exist before the request arrived at your server, so it is a natural fit that you are creating a new resource (the document describing the results).

It's not, so far as I can see, particularly critical to get this right; returning a 200 (OK) instead is probably fine. I call your attention to it, because I find that thinking about the document helps get past the mental block of confusing the "resource" with the "thing that's actually doing the work".

See: REST in Practice by Jim Webber.

  • Thanks for the detailed response. There are a couple things that have made designing this endpoint challenging. One is that it is intended for use by automated processes, not end users. The other is that the requirements are somewhat nebulous. Its purpose is to attach "additional data" to items. The only concrete use case I have to work from right now is "automated tagging" of items, where a process will find items that meet a set of criteria and add a particular tag to all of them. However, the subsystem is supposed to be flexible enough to be used for other purposes as well. – Jack A. Feb 5 '16 at 14:47
  • Suggestion: create a resource for the specific use case you need to meet now. When you have your next use case, create a new resource for it. Both resources do the same work (defer to the same implementation), but return two different representations of the result, for example. – VoiceOfUnreason Feb 5 '16 at 17:37
0

You'll probably have a hard time fitting this kind of request in a REST approach, since it is fundamentally aimed at accessing a single resource.

That being said, for the response I would simply return 200 OK if some failures doesn't mean global failure, and 409 Conflict if it does. You can put whatever you want in the body of these responses, so better to be exhaustive and mention successes as well as failures.

  • I disagree with your first sentence, a resource doesn't means necessarily an item. It's a matter of granularity and there is nothing in REST saying that a resource must be fine-grained. – AilurusFulgens Feb 4 '16 at 9:22
  • Yes. The difficulty resides in coming up with a good resource name for [a large number of items that you update at once] – guillaume31 Feb 4 '16 at 12:26
  • 1
    I tend to agree with both of you to an extent. It does seem a bit non-RESTful in some ways, but I'm constructing it as a patch to the collection resource, which seems reasonable. – Jack A. Feb 4 '16 at 12:29
0

I would argue that you should be returning a batch identifier of some sort, and provide another endpoint that the user can call to get whatever information they want about the batch. This would be especially handy if the batch contains a large number of items that take some time to process, as it would enable you to return a 202 Accepted response with the batch ID right away (so the request doesn't time out), then return the data on a subsequent call. So you would wind up accepting a POST to /api/batch to submit the batch, return a 202 with a batch ID, then also provide the ability for the user to submit a GET to /api/batch/:id/failures to get the failed records, or /api/batch/:id/processed to get all the successful ones, etc.

0

I know how I would do it in a WCF service.

class UpdateResponse
{
    int ItemId;
    ResultType Result;
}

Class Result
{
   Status   //"success"     = ALL items were successfully updated
            //"fail"    = At least 1 item in list failed

           //If status = "sucess" then ItemResponse will be empty
           //If status = "fail", this will contain an entry for EVERY 
           //item that was in the original request 
   List<UpdateResponse> ItemUpdateResponse          
}

The reasoning for having every item in the returned list on any failure is so that if the user of your API wants to relate their original list with the returned list, they can do so using the ordinal position.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.