Dealing with Unsafe DELETEs in RESTful Services
Your DELETE request to the service just timed out. Surely, it's safe to send it again. Actually, it may not be.
In a previous column I discussed a very common scenario: The client sends a request to a service and the request times out. Because the client didn't receive a response, it's impossible for the client to tell if the request succeeded or failed. As I pointed out, for GET requests, this isn't a problem: Just send the request again. For updates and adds (PUT/PATCH/POST requests) the problem is more dire but the solution is relatively easy to implement. I even threw in a discussion of how to handle concurrency issues.
What I didn't discuss was DELETE requests. On the surface DELETEs might seem to be the same category as GETs: If a DELETE request times out, just send it again. In fact, the W3C specifies that DELETE should be idempotent (meaning that you can send a DELETE multiple times and nothing bad will happen). After all, if you want to delete Customer A123, then that Customer will be deleted no matter how many times you send the message. If the first request failed, then, on the second request, the client will get back a 200 code (200 OK, 202 Accepted or 204 No Content); if the first request did succeed, then the client will get back a 404 Not Found code. Either way, Customer A123 is gone.
But I'm here to make your life complicated. Just because the W3C specification says DELETEs are idempotent doesn't mean DELETEs are idempotent in your application. In fact, you may not want them to be.
Consider the case of SalesOrderLines associated with a SalesOrder. SalesOrder B456 might well have multiple SalesOrderLines associated with it -- one SalesOrderLine for each item purchased on the SalesOrder. It's not hard to imagine that those are identified with a compound key consisting of the SalesOrderId and the SalesOrderDetail's LineNumber. With this design, to delete the second item on SalesOrder B456, the client might use a URL like this:
But what happens after that second item is deleted? Does your application shuffle the third item up to become item number two? If so, repeatedly sending this message might result in every item on the SalesOrder being deleted. Or, when your application adds a new item to the order, could that new item be given the now empty number two slot? If so, then someone who's trying to add an item to the order while someone else is trying to remove one may find their items magically disappearing as they add them.
Obviously, that last scenario requires a confluence of events that are unlikely to occur. Unlikely enough that, when those events do conflow, it will be impossible to track the problem down.
A more obvious example might be a request that deletes the first item in an order:
Repeatedly sending this request will result in every SalesOrderLine in the SalesOrder being deleted until the request returns a 404 Not Found when no more items exist. That might, in fact, be what you want to support (though I can't imagine why). But it's not idempotent and your documentation should point that out. Of course, if your DELETE is idempotent, then that's following the specification and no documentation is required. Just saying.
Fortunately, if you want your DELETEs to be idempotent, the solution is easy to implement: Have your DELETEs reference a unique identifier for the item to be deleted and never re-use those identifiers. For my SalesOrderLine example, it would mean each item must be assigned a unique Id to be used when deleting the SalesOrderLine. The real request to delete a SalesOrderLine wouldn't reference the SalesOrder at all:
There's some potential inefficiency here because clients must now retrieve a SalesOrderLine before deleting it because that's the only way to get the SalesOrderLineId. However, this may just be a theoretical inefficiency: It's probably an unusual scenario where a client would delete a SalesOrderLine without having retrieved it at some earlier time.
But this makes an important point: A method is only going to be safe or unsafe if you make it so. Idempotency is not going to happen by itself (or even be desired). Addressing idempotency must be part of the initial design because trying to fix idempotency problems afterwards (or even just diagnosing them) is going to be both expensive and annoying.
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.