Ain't gettin' no rest with REST
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.
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
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?