Saturday, May 30, 2009

RESTFul Design Patterns

Summarize a set of RESTful design practices that I have used quite successfully.

Object Patterns

If there are many objects of the same type, the object URL should contains the id of the object.
If this object is a singleton object of that type, the id is not needed.

Get the object representation
HTTP GET is used to obtain a representation of the object. By default the URI refers to the object's metadata but not actual content. To get the actual content ...
HTTP header "Accept" is also used to indicate the expected format. Note also that the representation of the whole object is returned. There is no URL representation at the attribute level.
GET /library/books/668102 HTTP/1.1
Accept: application/json

Modify an existing Object

HTTP PUT is used to modify the object, the request body contains the representation of the Object after successful modification.

Create a new Object
HTTP PUT is also used to create the object if the caller has complete control of assigning the object id, the request body contains the representation of the Object after successful creation.
PUT /library/books/668102 HTTP/1.1
Content-Type: application/xml
Content-Length: nnn

<title>Restful design</title>
HTTP/1.1 201 Created

If the caller has no control in the object id, HTTP POST is made to the object's parent container with the request body contains the representation of the Object. The response body should contain a reference to the URL of the created object.
POST /library/books HTTP/1.1
Content-Type: application/xml
Content-Length: nnn

<title>Restful design</title>
HTTP/1.1 301 Moved Permanently
Location: /library/books/668102

Invoke synchronous operation of the Object
HTTP POST is used to invoke a operation of the object, which has the side effect. The operation is indicated in a mandated parameter "action". The arguments of the method can also be encoded in the URL (for primitive types) or in the request body (for complex types)
POST /library/books/668102?action=buy&user=ricky HTTP/1.1
POST /library/books/668102?action=buy HTTP/1.1
Content-Type: application/xml; charset=utf-8
Content-Length: nnn

<addr>175, Westin St. CA 12345</addr>

Invoke asynchronous operation of the Object

In case when the operation takes a long time to complete, an asynchronous mode should be used. In a polling approach, a transient transaction object is return immediately to the caller. The caller can then use GET request to poll for the result of the operation

We can also use a notification approach. In this case, the caller pass along a callback URI when making the request. The server will invoke the callback URI to POST the result when it is done.

Destroy an existing Object
HTTP DELETE is used to destroy the object. This release all the resources associated with this object.
DELETE /library/books/668102 HTTP/1.1

Container Patterns

The immediate parent of a container must be an object (can be a singleton object without an id or an object with an id). Container "contains" other objects or containers. If a container is destroyed, everything underneath will be destroyed automatically in a recursive manner.

In GET operation, by default the container only return the URL reference of its immediate children. An optional parameter "expand" can be used to request the actual representation of all children and descendants.

A more sophisticated GET operation can contain a "criteria" parameter to show only the children that fulfills certain criteria.
GET[author likes 'ricky']

Reference Patterns

In many case, objects are referring to each other. The reference is embedded inside the representation of the object that reference it. In case the object being referenced is deleted, all these references need to be fixed in an application specific way.


sethladd said...

If I may, I would not place an action query parameter into a URL to "invoke an operation on an object." That is RPC and not REST.

Instead, think about the Nouns in the system. What Noun (object) are you trying to create?

In your case, you are buying a book. That is the act of purchasing. Therefore, you should CREATE a Purchase. More specifically, you should POST to the Purchases resource.

The Purchase is a thing with an identity, metadata (by whom, when), and relationships (what book), and may have other actions (cancel?)

REST is about a lot of things, but it's definitely about Nouns.

pjz said...

Agree with sethladd wrt not putting actions into query params. Instead, as he said, think about what state you're changing and how that fits into your datamodel.

In your case, it might be, as sethladd suggests, POSTing a transaction that contains a record of the purchase. Or it might be as simple as POSTing 'state=purchased' to the book's URL. The details are of course dependent on your application.

John Levon said...

PUT is rarely suitable for modifying objects. As per the HTTP specification, the request body needs to contain the entire representation of the resource at the given URI.

This has a major downside: it's fundamentally racy. If we have two clients modifying object A, where one is changing attribute X and the other attribute Y, there is a classic "lost update" problem.

Much better is to use POST and allow partial updates in the request body (that is, a client can specify just the attribute they wish to change).

Randy Rizun said...

this is a great post with great comments

as for the "action" query parameter; perhaps the "purchase" action example is a bad example of an action

I do see what the author is getting at

how about this one: suppose you have a slot machine in vegas; you might have an action "pullArm", e.g., POST /slotMachines/10293?action=pullArm

it is more of an instantaneous action with indirect state change rather than a direct state change request such as a request to purchase

having said that, yes, pulling the arm can certainly be modelled in a two step fashion: POST /slotMachines/10293?arm_state=pulled followed by POST /slotMachines/10293?arm_state=not_pulled which would certainly model both the pulling and the letting go of the slot machine's arm

another example: maybe I want to reset a card in a telecom chassis: POST /rack/1/slot/2?action=reset

these are precisely the kinds of rest issues that I'm thinking about right now

again, great post

sethladd said...

With the slot machine arm pull example, simply model the Noun of the action. In this case, ArmPull.

POST /slot_machines/123/arm_pulls

You're Creating a new ArmPull. Now, you can talk about it, link to it, check it, etc.

People get confused when they try to use PUT to tweak individual properties of some object. You don't want to do that if that action is something that itself has state. If something has state, then it is a Noun, and it becomes its own restful resource.

Ricky Ho said...

Great feedback.

I agree that "purchase" is perhaps the wrong example, but I am trying to illustrate how to invoke a method to an object.

What I mean is how to do the REST equivalent of
object.method(params, ...)

And the convention I suggest is
POST http://objects/123?action=method&parm1=...

Sethladd model doesn't support methods, every "action" will be explicitly modeled as a "Transaction" resource. It is semantically same as
new Method(object, params, ...)

In the case of the book purchase example, I completely agree that a separate resource "Order" should be used because we want to keep track of user's purchases over time.

However, in the slot_machines example. There is no need to refer to the arm_pull in future. Then it makes no sense to create a arm_pull object and then immediately delete it.

So in summary, if there is a need to refer to a particular "action" in future, then this action should be modeled as a newly created transaction object. Otherwise, this action should be just a POST call with "action" parameter to an existing resource object.

Piers said...

I would say POSTing to arm_pulls is correct. The response would redirect the the client to the new arm_pull resource which would probably contain the results of the pull (e.g. 3 cherries). A version of the system may allow the client to look at the results of all arm_pulls etc. etc. Generally, if you find you are passing a method in the URI you just aren't thinking hard enough!

As regards the comment that "PUT is rarely suitable"... check out the benefits of PUT being idempotent.

Anonymous said...

yawn... ive programming restful apps for 4 years

desert_fox said...

sethladd brough up a good point of not including the action in the query parameter into the URL. This will minimize the exploitation of changing the method/+parameter while in transit. It's always a good practice to separate data and meta-data(methods, actions, etc.).

On a side note, is there a REST support for PHP?