Object Patterns
If there are many objects of the same type, the object URL should contains the id of the object.
http://www.xyz.com/library/books/668102
If this object is a singleton object of that type, the id is not needed.http://www.xyz.com/library
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://www.xyz.com/library/books/668102.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
Host: www.xyz.com
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
Host: www.xyz.com
Content-Type: application/xml
Content-Length: nnn
<book>
<title>Restful design</title>
<author>Ricky</author>
</book>
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
Host: www.xyz.com
Content-Type: application/xml
Content-Length: nnn
<book>
<title>Restful design</title>
<author>Ricky</author>
</book>
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
Host: www.xyz.com
POST /library/books/668102?action=buy HTTP/1.1
Host: www.xyz.com
Content-Type: application/xml; charset=utf-8
Content-Length: nnn
<user>
<id>ricky</id>
<addr>175, Westin St. CA 12345</addr>
</user>
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
Host: www.xyz.com
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.
http://www.xyz.com/library/books
http://www.xyz.com/library/dvds
http://www.xyz.com/library/books/668102/chapters
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 http://www.xyz.com/library/book?criteria=[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.
9 comments:
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.
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.
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).
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
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.
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.
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.
yawn... ive programming restful apps for 4 years
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?
Post a Comment