RESTfully atomically incrementing a counter using HTTP PATCH

So today I ran into the question of incrementing a counter in a RESTful manner, and wasn’t sure how to go about doing it. Googling around a bit didn’t find me a satisfactory answer, though I did find @idangazit asked the same question on Stack Overflow, but alas the question was answered by what I humbly felt was an inadequate answer.

Idan had “PUT vs. POST” in his question, but quoting the answer I just added to that question (#selfplagiarism!), I believe PATCH is the answer as RFC 2068 says very well:

The PATCH method is similar to PUT except that the entity contains a list of differences between the original version of the resource identified by the Request-URI and the desired content of the resource after the PATCH action has been applied. The list of differences is in a format defined by the media type of the entity (e.g., “application/diff”) and MUST include sufficient information to allow the server to recreate the changes necessary to convert the original version of the resource to the desired version.

So, for example, to update profile 123’s view count, I would do (using requests, what else?):
[sourcecode language=”python” light=”1″]
import requests

requests.patch(
‘http://localhost:8000/profiles/123’,
‘views + 1\n’,
headers={"Content-Type": "application/x-counters"}
)
[/sourcecode]

Which would emit something like:
[sourcecode light=”1″]
PATCH /profiles/123 HTTP/1.1
Host: localhost:8000
Content-Length: 10
Content-Type: application/x-counters
Accept-Encoding: identity, deflate, compress, gzip
Accept: */*
User-Agent: python-requests/0.10.0

views + 1
[/sourcecode]

Where the x-counters media type (which I just made up) is made of multiple lines of field operator scalar tuples. views + 1, views = 500, views - 1 or views + 3 are all valid syntactically (but some may be forbidden semantically). I can understand some frowning-upon making up yet another media type, but I think this approach matches the intention of the RFC quite well, it’s extremely simple and if the backend is implemented correctly, it’s atomically correct.

Suggestions for another approach?

EDIT: I’ve had a long discussion with a friend who disliked the use of a non-standard media type. Perhaps something like this is better, though I’m still not entirely convinced:
[sourcecode language=”python” light=”1″]
import requests

requests.patch(
‘http://localhost:8000/profiles/123’,
‘[{"field": "views", "operator": "+", "operand": 1}]’,
headers={"Content-Type": "application/json"}
)
[/sourcecode]
I’m not sure what’s the bigger crime – using a non-standard media type, which, in the words of the RFC, is “discouraged”, or using a standard generic serialization format as the media type, which doesn’t say much about the scheme you’d like to use within it. Both are better than anything else I can think of.

p.s.: escaping spaces in field names are left as an exercise to the reader, I suggest application/x-www-form-urlencoded or simply using sane field names, ffs.


Comments

One response to “RESTfully atomically incrementing a counter using HTTP PATCH”

  1. Why not just send the document using PATCH and assume all values are increments? I wrote about that here: http://tqdev.com/2017-restful-incrementing-using-patch