The Source for Java Technology Collaboration
User: Password:



Brian Repko

Brian Repko's Blog

Ain't gettin' no rest with REST

Posted by brianrepko on August 05, 2006 at 09:34 AM | Comments (6)

My reading about REST started with *THE* dissertation, looking at restlets, downloading (but not looking at) a restlets example, looking at RoR and how that fits in with REST and a bit on resource naming conventions. I really struggle with the "only 4 verbs" thing. I even have questions on standard CRUD operations.

Take a business object like "suppliers". I understand that I can get a list of suppliers with an HTTP GET /suppliers and can get a single supplier with an HTTP GET /suppliers/<id>. Updating the supplier is an HTTP POST /suppliers/<id> and deleting a supplier is an HTTP DELETE /suppliers/<id>. However, I'm not totally sure how to create a supplier. From a REST and UDDI paper as well as Rails, I get the notion that I POST somewhere to create a new ID and then I can do an HTTP PUT /suppliers/<id> to that ID. So what URI do I do the POST to? We'll come back to that by the end of this.

My first reaction to this was that I now have "Lazy-Loading DAOs via HTTP". That doesn't buy me much - I need services!

I should say that I love the notion of domain modeling through URI naming. Historical objects can have versions saved off with URIs like /supplier/<id>/archive/20050101000000 or /supplier/<id>/version/2 or /supplier/<id>/history/4. Child objects (both aggregation and construction) can have URIs based on the parent or on their own. A supplier contact could be /supplier/<id>/contacts/<id> or one of their purchase orders could be /supplier/<id>/orders/<id> and that order could also just be /order/<id> as well. Got it. Still feels like a web-based DAO for an anemic domain model.

I'm still not totally clear on how to request a type of representation - perhaps the simple answer is Accept headers or adding extensions to the URIs (.html or .xml).

I'm also not keen on the whole "completely lazy-loadedness" of the payloads. If the client needs a supplier AND its contacts, then that is 2 requests instead of potentially one where the response will return the contacts by value rather than by reference. Domain models where you need more complicated returns (JDO fetch plans) would be hard to add. Perhaps I'm trying to make a "Web Services Architecture" into a "Web Application Architecture".

So in researching the question - where do I put my domain model "methods", I started asking the question "are 4 verbs enough?". I found some interesting links that talk about URIs for both services AND domain and some cool stuff on business transactions and the notion of speech acts. This also got me into FDDs definition of a feature being "action result object" and really doubting that 4 verbs are enough.

I also re-acquainted myself with Coad's Domain Neutral Component and was getting the sense that sure REST works well for Party-Place-Thing or for Description but that the trouble would show itself with Role or for sure with Moment-Interval. The question there was how to RESTify state changes.

So, I picked a simple example to try, General Ledger Journal. Let's use this simple state model - Journals get created in "Pending" state. There is a background process that posts them and changes the state to "Posted". So Journals are created and never deleted. Journals can be updated when in "Pending" state and not after they are "Posted". Because they can never be deleted, we may need to "Void" them if they are Pending and "Reverse" them if they are Posted (which creates a reversal journal). So the question is how to "update the journal state".

One really bad way to do it is to leave it up to the client. For a Void, the client does a GET, changes state to void and then POSTs the updated version. For a Reverse, the client does a GET, changes state to REVERSE, creates the reversal, PUTs the reversal and POSTs the original with its state changed. Ok, that clearly will not work so we know it has to be done on the server, so what URI represents this method call. I got a couple of ways to do it and am looking for thoughts on these designs.

The first way to do this is to make URIs for the methods as well as the properties of your domain. So if you have a "void()" or "reverse()" method, then you could have the following URIs that you would POST to.
  • /journal/<id>/void
  • /journal/<id>/reverse
and the returning payload would include references to historical objects, the reversal transaction or other details.

Another way to do this is to model state as part of the domain so that "there is no void()" but rather there is a "setJournalState(JournalState.VOID)". This would become the following URI
  • /journal/<id>/state
and you would set it to VOID or REVERSE (both of which are their own URIs on the system). One big thing here is that not only the results of your action but also the parameters of those actions could make use of the URI naming scheme (or at least URNs defined as part of the API).

The other way that I thought about this was in viewing the void or reversal as a request on that journal. One can add a request for the journal as a part of the domain.
  • /journal/<id>/request/3 or /journal/<id>/operation/3

In the end, for journals, I like the first way - putting the method on the journal just as you would for a property. You only POST to those URLs. Either that or you mix in the third way (/journal/<id>/void to see all void requests and /journal/<id>/void/<id> to see an actual request - this doesn't work with void but could for other types of state changes).

So this brings me back to "how do I do a create" - I know there is a PUT but where is the "get me the next ID". Also, search/find capabilities with some control over getting the result by value or reference is a method on the collection of all such objects. I see these as the following URLs.
  • GET /supplier/search (for simple queries)
  • POST /supplier/search (for more complex queries?)
  • POST /supplier/create or /supplier/id (in order to add)

One post that I read asked about swapping two elements in an ordered list. Since the list is itself a URI, I guess that would be a swap method. I wasn't a big fan of the HTTP PATCH responses. That just feels like needing more verbs.

This is still an art - I started thinking about transactional services like an account funds transfer or objects with LOTS of state changes like a hotel reservation system. I think that those would be good examples to try to work out. In the end, I think that the design of services will find the same implementation behind lots of service URIs. The account fund transfer operation could be any one of the following
  • /user/brian/accounts/transfer (transfer between 2 of my accounts)
  • /accounts/<id>/transfer (transfer from this account to another)
  • /accounts/transfer (transfer from some account to another)
Each has their own meaning even though they are doing the same thing under the covers.

The last thing - and probably the topic of the next entry is the need for meta-data for the representation - and in particular how to do versioning for optimistic locking operations - the classic read-read-write-write problem.

Your thoughts on any of this?

Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • Brian,You've obviously researched this much more then I have...Could you share a bit about how REST contrasts with the document-centric approach to Web Services?I admit to not really grokking REST... It seems like a very RPC oriented mindset, and I'd appreciate any clarification that you could pass my way.Thanks,-JohnR

    Posted by: johnreynolds on August 05, 2006 at 10:38 AM


  • For a Reverse, the client does a GET, changes state to REVERSE, creates the reversal, PUTs the reversal and POSTs the original with its state changed. Ok, that clearly will not work

    Why does that "not work"?

    Posted by: grlea on August 06, 2006 at 10:45 PM


  • For changing the state, I'd highly recommend option 2 (PUT to "/journal/<id>/state"). Here's a write-up I did previously on this topic, and make sure to see the comment I added for a correction of an example in my main text.


    And my understanding also is to use POST for creation. I don't have an exact recommendation for a POST URI, but the response should include the URI for later PUTs. I think it's officially uncool, but I personally wouldn't be opposed to sending the resource body on the POST to avoid two requests.


    More miscellany: I haven't thought about the swap case. I'm also not sure about the best way to do a multi-step atomic transaction.See GData (at Google) for an example optimistic locking. For getting back multiple resources at once, I'd recommend a URI convention that allows for retrieving a resource plus children. For transfer, is it best to develop a general purpose atomic transaction system or have high level primitives like you show (high level seems more meaningful even if less REST-like). And finally, in many cases, I think anemic domain models rock, but then you have that transfer example, ...

    Posted by: tjpalmer on August 07, 2006 at 08:17 AM

  • it's lunacy. the 4 verb philosophy is clear example of "function following form". some guy got it in his head that this is "what it should look like" and he's going with it.

    Posted by: ilazarte on August 07, 2006 at 08:55 AM

  • Thanks for the comments folks.

    JohnR - My sense is that REST is definately more document oriented but unlike document-centric WebServices, you use the URI naming scheme to model the domain.

    grlea - The reverse doesn't work that way since you might fail in the middle. Its the "transactionality" of it.

    tjpalmer - yeah, I now get the POST to get a URL, PUT to the URL thing. I will look at GData - thanks!!

    Posted by: brianrepko on August 07, 2006 at 11:27 AM

  • RESTwiki contains a some useful information on how REST models things differently to Object-Orientation. See:

    http://rest.blueoxen.net/cgi-bin/wiki.pl?RestTriangle
    http://rest.blueoxen.net/cgi-bin/wiki.pl?RESTfulDesign
    http://rest.blueoxen.net/cgi-bin/wiki.pl?BenjaminsRESTTutorial
    http://rest.blueoxen.net/cgi-bin/wiki.pl?MinimumMethods

    and others. Also, see the rest wikipedia article which sums some aspects of REST up nicely:

    http://en.wikipedia.org/wiki/Representational_State_Transfer


    The core of prevailing REST philosophy is the rest triangle, where naming of resource is separated from the set of operations that can be performed on resources, and again from the kinds of information representations at those resources. Verbs and content types must be standard if messages are to be self-descripitve, and the requirements of the REST style met. Also, there should be no crossover between the corners of the REST triangle. names should not be found in verbs or content types, except as hyperlinks. Content should not be found in names or verbs. Verbs should not be found in names or content.


    REST can be seen a documented-oriented subset of Object-Orientation. It deliberately reduces the expressiveness of Objects down to the capabilities of resources to ensure compatability and interoperability between components of the architecture. Object-Orientation allows too great a scope of variation for internet-scale software systems such as the world-wide-web to develop, and doesn't evolve well as demands on the feature set change. REST is Object-Orientation that works between agencies, between opposing interests. For that you need to make compromises rather than doing things your own way.


    Now, to address your example:
    Verbs should not be part of the noun-space, so your urls


    /journal/{id}/void
    /journal/{id}/reverse

    should not be things you POST to. They should demarcate the "void" state and the "reverse" state of your journal entry. When you GET the void URL it should return the text/plain "true" if the transaction is void and "false" if the transaction is not void. A put of the text/plain "true" will void the transaction, possibling impacting the state demarcated by other resources. Reverse is similar. The URL should be "reversal" rather than "reverse". It should return the url of the reversing transaction, or indicate 404 Not Found to show no reversal. A PUT to the reverse would return 201 Created and further GETs would show the reversal transaction.


    Creation in REST is simple. Either the client knows the URL of the resource they want to create and PUT the resource's state to that URL, or the client requests a factory resource add the state it provides to itself. This is designed to either append the state provided or create a new resource to demarcate the new state. POST is more common. The PUT approach requires clients to know something about the namespace that they often shouldn't know outside of some kind of test environment.


    On swapping: This is something of an edge case, and this sort of thing comes up less often than you think when you are designing RESTfully from the start. The canonical approach would be to include the position of the resource as part of its content. PUTting over the top of that position would move it. This is messy because it crosses between noun and content spaces. Introducing a SWAP operation is also a problem. HTTP operates on a single resource, so there is no unmunged way to issue a SWAP request. Any such SWAP request would have to assume both of the resources of the unordered list are held by the same server, or that the server of one of these resources was able to operate on the ordered list.


    On transactions: The CRUD verb analogy is something of a bane for REST. I prefer cut-and-paste. Interestingly, cut-and-paste on the desktop is quite RESTful. A small number of verbs are able to transfer information in a small number of widely-understood formats from one application to another. The cursor identifies and demarcates the information that will be COPIED (GET) or CUT (GET + DELETE) and the position where the information or state will be PASTED to (PUT to paste over, POST to paste after). The CRUD analogy leaves us wondering how to do transactions, but with the cut-and-paste analogy the answer is obvious: Don't.


    In REST, updates are almost universally atomic. You do everything you need to do atomically in a single request, rather than trying to spread it out over several requests and having to add transaction semantics. If you can't see how to do without transactions you are probably applying REST at a lower-level than it is typically applied. In this example, whenever you post a new journal entry you do so as a single operation. POST to a complete representation of the journal entry to a factory resource.


    That is not to say that REST can't do transactions. Just POST to a transaction factory resource, perform several POSTS to the transaction that was created, then DELETE (roll-back) or POST a commit marker to the transaction.


    How REST maps to objects is up to the implementation. You can evolve your objects independently of the namespace, which is expected to remain stable forever once clients start to use it. The URI space is not a map of your objects, it is a virtual view of the state of your application. Resources are not required or even expected to map directly onto objects. One method of a resource may operate on one object but another may operate on a different object. This is especially the case when state is being created or destroyed.


    REST is about modelling the state of your application as resources, then operating on that virtualised state using state transfer methods rather than arbitrary methods with arbitrary parameter lists. REST advocates such as myself will claim this has significant benefits, but I'll refer you to the literature (especially the wikipedia page) rather than list them here.

    Benjamin
    http://soundadvice.id.au/blog/

    Posted by: fuzzybsc on September 29, 2006 at 07:11 PM



Only logged in users may post comments. Login Here.


Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds