10

I have a question regarding REST API design. Here is a simple (maybe too simple) API:

GET /ecommerce/order/123

POST /ecommerce/order (create a new order)

PUT /ecommerce/order/123 (update an existing order)

DELETE /ecommerce/order/123 (cancel order)

But what if I wanted the customer to enter a reason for an order to be cancelled? I would need to send post data to the API, but that wont work with DELETE. To cater for this the I would have to change DELETE to PUT. I would then post two different resources for update and cancel.

Another solution would be to change the API:

GET /ecommerce/order/123

POST /ecommerce/order/create (create a new order)

PUT /ecommerce/order/update/123 (update an existing order)

DELETE /ecommerce/order/cancel/123 (cancel order)

I'm not sure which is the best option.

There is a more general question about how REST API's handle multiple commands for a single resource.

Any input would be appreciated! I'm going to be reading REST in Practice very soon but this question is niggling away at me.

Rob Hruska
  • 118,520
  • 32
  • 167
  • 192

3 Answers3

7

One option might be to create a new resource. CancelledOrder, perhaps.

You might then POST a new CancelledOrder:

POST /ecommerce/cancelledOrder
Entity:
    order: /ecommerce/order/123
    reason: "Problem with order"

You could also/instead PUT a CancelledOrder:

PUT /ecommerce/cancelledOrder/123
Entity:
    reason "Problem with order"

The application could then delete order 123 or update its status to "Cancelled", or do whatever it is your business rules require. To top it off, you could then not support the DELETE method directly for /ecommerce/order/N, returning a 405 Method Not Allowed.

The PUT solution can use idempotence to its advantage; PUTting the CancelledOrder multiple times would always still result in the order being cancelled.

It should be noted that your suggestions for changing the API (e.g. /ecommerce/order/create) are likely not to be RESTful, since you're defining methods in the resource identifiers.

Rob Hruska
  • 118,520
  • 32
  • 167
  • 192
  • I like this answer. Resources and domain entities are not one to one. – Junior Developer Jun 13 '11 at 21:09
  • 1
    I don't really like the CancelledOrder name, imho a better resource name would be OrderCancellation or in other words, exposing an action as a resource. This is also quite nice in case of reversible actions as you could use delete on the "action". – Maxem Jun 15 '11 at 16:11
  • Also when this doesn't quite fit, I like the what this comment mentions to the same issue: http://programmers.stackexchange.com/a/270421/211215 – Almund Jan 15 '16 at 09:07
4

I know this is a very late answer, but I suggest to use the first set of commands, but change the cancel order command to:

POST /ecommerce/order/123/cancel

Which is a generic way to handle various operations on an existing resource. I don't see why an order cancellation would lead to deletion of the order itself, at least not instantly. The users would probably still want to see the order in the system. The order is also where you would store the reason for the cancellation.

2

(The question is quite old, but I just thought I'd improve it, as I don't like any solutions out there.)

The solution is simple. Put a reason into the request's body.

DELETE /ecommerce/order/123
Content-Type: text/plain
Content-Length: 48

Order was cancelled due to a customer's request.

The semantics of the body is up to you to decide. If you only want a plaintext reason, I'd use text/plain as shown above. If more complicated metadata is required, I'd complicate things further.

In my opinion this is way better than Javaesque RPC-like OrderCancellator.execute(order) (because POST is HTTP's name for "execute").

Beware, that while spec doesn't say a thing about it, some severs may discard DELETE request's body. A draft on HTTP/1.1 message semantics clarifies:

Bodies on DELETE requests have no defined semantics. Note that sending a body on a DELETE request might cause some existing implementations to reject the request.

Another option is to issue a header:

DELETE /ecommerce/order/123
X-Reason: Cancelled due to an UFO invasion.

Whenever to choose headers or entity body depends on size and format of the data. Some data fits fine in HTTP headers, some does not. One certainly can pass numeric ticket ID, it's uncertain about strings (think Unicode) and it's certain nobody in their sane mind wants to pass a Base64 JPEG there.

Community
  • 1
  • 1
drdaeman
  • 11,159
  • 7
  • 59
  • 104
  • To me, the cancellation reason seems like information that belongs in a resource of some sort. Headers (from my understanding) ought to be meta information about the request itself, rather than having actual business data. The `DELETE` body/entity suggestion has potential, but the links you've provided do pose some concerns. – Rob Hruska May 02 '12 at 16:01
  • AFAIK there's nothing that says DELETE can't have side effects, so you don't have to explicitly create a cancellation resource using ambiguous POST semantics (that would delete the order along with the creation of cancellation record). In a same way, GET is considered idempotent, but writing a record into access log file or [increasing a hit counter](http://www.intertwingly.net/blog/784.html) does not violate it. PUTting (replacing) an order with an updated version marked as cancelled seems fine to me, though. – drdaeman May 02 '12 at 20:47
  • True points. I actually looked at my answer again today and found myself having reservations about it. It still works, but there are better, more hypertext-driven ways to accomplish this. I was working up another answer but got distracted. – Rob Hruska May 02 '12 at 20:51