Search code examples
restweb-serviceshttp-status-codes

Is Http response 207 MULTI-STATUS appropriate for multi task operations?


I've found that Http 207 can be used for bulk insert/delete/... etc. operations where we potentially have multiple items being inserted and partial success is ok from business perspective.

I wonder if the same thing applies if we have API Post calling some external services?

Example business scenario:

We have HttpPost endpoint that is creating an order.

HttpPost:
/api/order/

Now let's assume that this endpoint besides creation of an Order would be calling external service to also print a label for that order and another one to send some kind of confirmation via REST to external 3rd party system.

Now in this scenario we assume that creation of Order is a must for the success of this operation but both printing and external REST call can return error message. Potentially that is besides our control.

Now from what I've read the options on how to return this information would be:

  1. Http 200 with some kind of object containing necessary information. For example: { "isSucess": true, "isFailure": true, "erorMesage": "string" }
  2. Make the endpoint more RESTful and separate it into 3 actions which would be called, but in this case this is not applicable (at least from my perspective) because all of those things are tightly coupled from business perspective and they should be bound together
  3. Return 5xx error. 502 since not all of the actions succeeded and there were some internal problems, but what if one failed action is 502 and another 503
  4. Return 207 error with proper error response for each of the actions (Order creation: 200, Printing: 502, External 3rd party: 502)

Now I wonder which would be most suitable ? I'm kind of leaning towards 502 error or 207 since they in my opinion provide the most information about the actual state of the system.


Solution

  • This is a design question for how to use REST web service for a nested distributed transaction. There are many suitable answers. Here is my take on this, where I address each of your points, while referring to the REST spec.

    1. HTTP code 207 is not a good idea - This return code (207) is specified for systems that support WebDAV (https://en.wikipedia.org/wiki/WebDAV) extension on HTTP. WebDAV is distributed, however it is a specification that

    allows user agents to collaboratively author contents directly in an HTTP web server

    WebDAV is not exactly built for distributed transactions. Servers that can handle return code 207 need to have the WebDAV module installed in them. You may not even get support in several REST libraries for this return code. For example this API docs does not have code 207. https://docs.oracle.com/javaee/7/api/javax/ws/rs/core/Response.Status.html (this is an edge case, but it helps make the point).

    1. I prefer not use HTTP 50x if the request is partially fulfilled. Lets take the case when the server has created the order but could not post it to the third party REST server (either that server was down or network error). Here is the spec for 50x errors https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html:

    "Response status codes beginning with the digit "5" indicate cases in which the server is aware that it has erred or is incapable of performing the request. "

    The server is not at fault here. It has created the entity.

    1. My suggestion is to return HTTP code 202 on POST /api/order/, return a reference to the order and process the steps asynchronously. There are multiple transactions involved in this request - printing label, updating third party REST endpoint, etc. These can be updated in the order resource. For example, the POST /api/order/ can return in this format:

    HTTP STATUS 202 (Accepted) { "order": { "id": "1234567" } }

    The client should check for the complete status with GET /api/order/, which will return the individual status:

    { "id": 1234567, "label_printed": "failed", "third_party_updated": "success"}

    If any of the sub-transactions of an order has failed and should be reprocessed, it is an update on the existing order, and a call to PUT /api/order/, because the PUT method is to update the resource. The server will take the PUT request and retry the transactions as needed.

    Here are some additional answers that are suitable for this question:

    Transactions in REST?

    Transactions across REST microservices?