On granularity of REST APIs

technology - 29 Dec 2016
Sripathi Krishnan

I believe that read APIs should return as much information as possible; while write APIs should modify as little as possible.

While building a HipChat addon, we wanted to update a user’s status message. So we look at the API docs, and there’s an API to update a user.

See https://www.hipchat.com/docs/apiv2/method/update_user

This is a PUT request. The semantics of PUT are simple – it replaces whatever is on the server with the data the client sends.

Look at the section at the bottom of that document, there’s a little recommendation from HipChat developers –

Any missing fields will be treated as deleted, so it is recommended to get the user, modify what you need, then call this resource to update

Other than the fact that you are now making two API calls JUST to update a user’s status, there is another major flaw with this. Race conditions. In between the GET and PUT requests, another client can update the user. And our code will very conveniently overwrite their changes.

This, of course, is bad design. So let’s look at what alternatives REST offers

Solution # 1 : Use version numbers / dates

Every entity now has a version number, or a last modified date, which is returned in GET requests. When you PUT, you send this version number or date back. If someone else updated in between, the version number would have changed, and the PUT request can fail the request.

Yes, it works. But this has to be introduced with every API for it to work. Unless you build common infrastructure for this, each API developer will get it wrong.

And another thing – it does nothing about the fact that we have to make TWO api calls to change the user’s status.

Solution # 2: Introduce PATCH request

Patch requests are a new means to update just a portion of the resource. We call PATCH /users/{id}, and send in just the status message, and the server will do the needful…

… except, patch isn’t really a standard. nginx doesn’t even allow PATCH requests, unless you patch it’s configuration (sorry, pun unintended). There’s no formal definition of a PATCH request.

Patch can work well for our status code, but it’s no panacea. For example, how do you add an element to a list in the JSON? Do you have to replace the entire list? Or can you append to the list?

So how would I solve it?

Over the years, I have learnt something The.Hard.Way.

Read requests should be coarse grained. Return all information as required to render the page; it won’t hurt as much as two separate API calls.

On the other hand, write requests must be fine grained. Find out common operations clients need, and provide a specific API for that use case.

Which means, I would introduce another API PUT /users/{id}/update_status. This will only update the user status, and nothing else. I can do this in a single call, it will be much faster on server side, and no need for a separate API call to fetch the user details.

Does this eliminate race conditions? No, it does not. But it makes them exceedingly rare. It’s like the advice for developers – avoid large commits. We all know that prevents merge conflicts; this is just the same principle.

Like what you read? Share this blog on:

Are You Geared For Tomorrow?

Look Ahead