Wednesday, January 28, 2009

Rest Exploration: Part 1. PUT versus POST

I have been working on a new set of Rest web services for and I keep coming across interesting challenges in defining a meaningful, REST-compliant API. I'll post more details as I work through the whole API.

Now, for the first challenge. I've discovered that PUT and POST are pretty complicated. Well, not complicated, but their proper use is not always obvious. I've read several articles that discuss PUT versus POST. Here's a brief list:
After all that reading, I think that I have a pretty decent understanding of the basics. Specifically, PUT is an idempotent method to create/set the full state of the resource specified at a particular URI. POST is not idempotent, nor is its operation stated so simply. Paul suggests that we think of POST as "create new URI as child of the current URI." In other words, POST is meant to add a new resource to a collection. In fact, the HTTP 1.1 spec nicely describes the distinction:
The fundamental difference between the POST and PUT requests is reflected in the different meaning of the Request-URI. The URI in a POST request identifies the resource that will handle the enclosed entity. That resource might be a data-accepting process, a gateway to some other protocol, or a separate entity that accepts annotations. In contrast, the URI in a PUT request identifies the entity enclosed with the request -- the user agent knows what URI is intended and the server MUST NOT attempt to apply the request to some other resource.

As you can see, the true meaning of POST is pretty unclear. It can do, more or less, anything that the service wants. I think that Paul suggests we use POST sparingly because it can be made to do pretty much anything we want. And, like Spiderman's learns,
"with great power comes great responsibility".

This part makes total sense to me. But, here's where I get confused. What if I want to modify only part of the state of a resource?

As I understand the "official" thinking, PUT can really only be used if I send the entire state of the resource. If I want to update just part of the state, then PUT doesn't seem like the right verb. Since POST is the more flexible verb, perhaps I could use POST to modify just the part of my state that I care about. But somehow, this seems exceedingly unsatisfying. First, it's definitely violoating Paul's suggestion of using POST for adding child resources. Second, it feels like I'm shoe-horning a portion of the PUT function into a POST.

So, let's make it a bit more concrete with the real example I'm working on. In AboutMyBaby, you create a scrapbook. A scrapbook has a bunch of state or metadata - e.g., title, owner, url, and "template" or "skin". The "template" or "skin" defines the look and feel of that particular scrapbook. There are hundreds of templates, each of which is its own resource in REST-speak. So, each scrapbook resource "has a" template resource. The API needs a way to change the template of a given scrapbook. It's really that simple.

So, what are my options?

Option 1: Use PUT in the traditional way.
Meaning: In my client, I must retrieve the entire current state of the scrapbook resource, modify the one item I care about (namely the template URI to use), and PUT it to the URI of the scrapbook.
PUT /scrapbook/1234/metadata HTTP/1.1

<amb_template>URI TO new template</amb_template>

Pros: Well, it's definitely REST and it definitely follows all the precepts people talk about.
Cons: It seems totally bizarre and overly complicated to force the client to retrieve the entire state of the scrapbook just to modify 1 part of it. This means that there must be additional work on both the client (to get the state prior to updating) and the server (to service the "get state" request).

Option 2: Use POST
Meaning: In my client, I just create a POST message that encapsulates the change I want to make.
POST /scrapbook/1234/metadata HTTP/1.1

<amb_template>URI to new template</amb_template>

Pros: The client need not create the entire state of the scrapbook metadata to alter just the template. The resulting XML message (or other format) is relatively compact - at least as compared to the message representing the full state of the resource.
Cons: It just doesn't seem RESTy. I know the precise thing I want to have happen. The operation should be idempotent. Moreover, this feels like the easy way out, and frequently, the easy way is not the architecturally correct way.

Option 3: Make a new URI and use PUT
Meaning: Instead of having the URI represent the "metadata" resource associated with a scrapbook, make a new URI that is just the template associated with the metadata associated with the resource.
PUT /scrapbook/1234/metadata/template HTTP/1.1

<amb_template>URI to new template</amb_template>

Pros: This feels more RESTy. The action is very clear-cut. The operation is definitely idempotent.
Cons: Although it is RESTy, it also seems like a slippery slope. That is, what if I want to change the title of the scrapbook? Should the title be it's own resource with its own URI? This seems like it would cause an explosion of URIs associated with each little characteristic of a scrapbook.

Option 4: Filter the URI and use PUT
Meaning: Use a URI that represents the metadata of the scrapbook, but "filter" it by using a query string. Then, the client can use PUT but only update one part of the metadata.
PUT /scrapbook/1234/metadata?template HTTP/1.1

<amb_template>URI to new template</amb_template>

Pros: This feels RESTy. The action is very clear-cut. The operation is definitely idempotent. This option prevents the runaway resource creation of option 3.
Cons: Although it seems RESTy, I'm not sure that query strings were meant for PUT operations. I think this may lead to strange results down the road because it almost feels like I'm tweaking the true meaning of PUT. Also, I defined PUT at the outset as a method which requires that you send the entire state of the resource. Clearly, in option 4, we are modifying that dictum.

As you can see, a seemingly simple operation ("update the template associated with a scrapbook") has turned into 4 potential service definitions. I am still thinking through this problem, but I would love to hear the advice, thoughts, and comments on the structure of this API.


Ben Margolin said...

Dave, I know you want to do things absolutely "correctly". I think that translates into what's easy for the consumers of this API. Like it or not, regardless of the REST ideals, that means POSTing just what you want to change. Some people's libraries may not support PUT well.

Oh and while you're at it, switching to a JSON payload instead of XML would probably make a lot of your API users happier, too, but that's a whole other can of worms... (unless of course you're respecting Content-type headers, for multiple representations, which is Bad Ass if so!)

Dave Viner said...

Hey Ben,

Definitely true. I'd like to understand the minimum "correct" way - then make whatever tradeoffs necessary to get it working well. But, without knowing the "right" way, then I'll never fully understand the tradeoffs.

That said, I definitely think that the PUT with ?subtype is the right approach. Amazon uses it for ACL editing within S3. So, that's a good indication that it's a reasonable approach.

For JSON, I actually have been using more and more JSON and I really like it.

But more on that in future posts.