A Red Hat Colleague of mine, Bryan Kearney, recently solicited my advice about a RESTful interface his team was creating for an entitlement system being used for our products. Here’s what he wrote to me:
The project is called Candlepin, and it is an entitlement engine. You can see it at https://fedorahosted.org/candlepin/ and the code at http://git.fedorahosted.org/git/candlepin.git/. We are using RESTEasy as the engine, and I would say our API is at gen 2. We are no longer doing too much RPC style calls.. but are not yet doing a HATEOAS API.
Our current quandry is around how to model state trnasitions on resources. We have seen a couple of possible approaches, and none leaps out as a best practice yet.
Asssume I have a Consumer who has entitlements. The entitlement resource is really the relationship between the consumer and a product they have purchases. Today, we model this as:
GET /consumers (Gets all)
GET /consumers/{UUID} (Gets a Consumer)
GET /consumers/UID/entitlements (Gets the entitlements for a consumer)
GET /entitlements?consumer={UUID} (Gets the entitlements for a consumer)if we terminate a consumer, we want to terminate all his entitlements. We also think that we will not want to delete objects from the DB, and instead just filter out the terminated.
Approach 1
———-
First approach is to PUT /consumers/{UUID} the full consumer object with a new state. The method would then look for state changes and do the needful. This seems the most Hateoas-eyApproach 2
———-
Since we know we wont want to delete objects, we could hijackDEL /consumers/{uuid}
And not really delete, just terminate. This seems kinda hackey.
Approach 3
———–
Approach 3 would create “action” links. We would therefore supportPOST /consumers/{uuid}/terminate
and do the termination logic there. This would mean that the POST to /consumers/{uuid} would be pure object update only. This seems very RPCish
Approach 4
———–
The final one would be to support both/consumers/
and
/inactiveconsumers/So.. a
DEL /consumers/{uuid}
would allow me to now do a
GET /inactiveconsumers/{uuid}
We had a bit of an email exchange, but here’s the advice I gave him summarized.
Have your clients consume links, not a URI scheme
Modelling URI schemes is an implementation detail. You need to do it when designing your application and then finally implementing it because the URI scheme of your restful web services has a huge affect on how they work.
BUT, URIs are an implementation detail of your restful web services. URIs should be almost completely opaque to your clients. Instead only one top-level resource’s URI should be published to the outside world. Representations of this root URI should have links (or link headers) embedded within them to publish other entry points into your suite of web services. Why is this important?
- Location Transparency. Lets give an analogy. If you were writing a CORBA, Java RMI, or SOAP based application, would you publish each and every endpoint to the client? No, you would not. You would use a Naming service or UDDI to register a logical name for your endpoints. A client would go to this naming service to obtain the location of your object/service. Another analogy is, how do you interact with your browser? You read an HTML page and click on a word that is underlined as a link to go to a new document. You don’t cut and paste the actual URL in your browser window to go to another page. The same thing should happen with your web services.
- Clients become immune to URI scheme refactorings. Over time as your services evolve, you may want to change how the URI schemes are modeled and defined. If your clients are using a harded coded URI scheme and you change it, you’ve broken all clients that are using the older scheme. Links protect clients from refactorings and allow server developers the freedom to refactor to their hearts desire.
Avoid changing the meaning of an HTTP verb: specifically DELETE
I would like to preface this with I used to think the approach Bryan preferred was the best approach. His preferred approach was to have DELETE change the state of a consumer to make it inactive. IMO, this is changing the meaning of delete. Instead of removing a resource, which is what DELETE was designed to do, DELETE (in his example) is actually changing the state of a resource. This is bad, IMO. So what approach did I recommend from his list? Approach 3.
Use links to model operations (Approach 3)
I prefer Approach3. While it smelled RPCish to Bryan, IMO, it is not, if you model it correctly using link relationships. For example:
GET /consumers/{UUID}
would return.
<consumer id="333"> <name>Bill</name> ... <atom:link rel="terminate" href="/consumers/333/terminate"/> </consumer>
The href here is *IRRELEVANT*. URI’s should be opaque to the client (just as they are opaque when a human surfs the web through a browser).
Think of a link almost as a form. When you get the representation of the consumer, the atom:link elements act like forms. They tell client of possible state transitions, what representational information is expected, and what URL to post the representation too.
Having terminate as a link gives you a lot of flexibility. 1st iteration of the link might be an empty POST. 2nd iteration you might want simple form parameters to specify the parameters of the termination. 3rd iteration you might define a actual media type for termination. Because termination is now a resource you can store this type of information there and link it within your consumer representation.
Another interesting thing that can be done is to publish (or not publish) different links based on the state of the resource. For example, if a consumer in Bryan’s service has been terminated, a GET of that consumer resource might publish a “reinstate” link and remove the “terminate” link.
But isn’t this RPCish? No, it is not. Why? Well, think how you would implement something like this through an HTML/browser-based UI. How would you model terminate in this situation? From the consumer HTML page you would either have a link that brought you to a page with a HTML Form you had to fill out to terminate the consumer, or, the consumer HTML page would have a mini form that terminated the consumer. In each case, the form would point to a specific URI that handled the state transition.
There’s some other interesting aspects that came out of my dialogue with Bryan, but I’ll save those for another blog. Thoughts? Do you think it is ok to model operations as links?
Mar 25, 2010 @ 15:26:30
From the POV of the http uniform interface, the first solution is indeed the one that is respecting state change through representation transfers.
The option you provide, with a link, is good, but modeling the termination relationship as is is not enough. You just created a “terminate” resource, one on which doing a GET probably won’t give us anything.
I’m not sure creating a resource for this is useful. The followign would be acceptable.
POST /customers/333
Content-Type: application/vnd.entitlements+xml
And this would be built by specifying in your media type something along those lines:
When using the terminate relationship, the client is expected to submit an entity body that is compatible with a termination request. In the case of application/vnd.entitlements+xml, the document shoudl contain a TerminationRequest element.
That said, you can also simply remodel what resources are. Is your terminated customer still a customer? There’s nothing that prevents you from movig a terminated customer to a new resource.
DELETE /customer/333
200 OK
Content-Type: application/vnd.entitlements+xml
Subsequently,
GET /customer/333
301 Moved forever
Location: /customers/terminated/333
This respects the uniform interface, by issuing a delete on the original resource, creating a new one, and using response codes to let the client “follow its nose”. And that way, no need to create intermediate resources that wouldn’t make sense when modeling your domain (the terminate resource in the previous examples)
Mar 25, 2010 @ 18:50:30
Sebastien, having a “terminate” link gives your app more control over what the client is allowed to do. If the client is unable to terminate (because it is already terminated or the user is just not allowed) the server doesn’t have to publish it. Also, a GET would make sense on the terminate resource. A terminate resource could return the reason for terminate, who did the termination, and how it could be reinstated.
Mar 25, 2010 @ 17:39:05
POST just never seems to feel right when changing the state of a representation to me. What about Approach number 1 with PATCH rather than PUT and sending only the state change representation?
Mar 25, 2010 @ 21:06:02
IMHO, if the media type is well thought out the option #1 (PUT on the resources) would seem more appropriate. For example, if a customer has a ‘terminated’ property (e.g. customer.terminated = true|false) then modifying the *state* of the resource speaks for itself. In the end it is the representational *state transfer*. Media type may tell us as much as action links, may be even more…
I would go for option #3 (action links) only if termination has extra/special meaning. It is kinda the similar as option #1 but with extra emphasis/semantics on possible state transition (or extra importance) of the resource. If I had to go this way (although I would try option #1 to the best of my ability and focus efforts on proper media type design) I would probably use term ‘termination’ instead of ‘terminate’. Since we are working with resources nouns seem more natural or working with them (since HTTP methods *are* the verbs).
I totally agree on not overriding the DELETE method. Deletion of the resource is real deletion – just that. Overriding its behavior will confuse the hell out of clients.
Mar 26, 2010 @ 13:46:19
Siarhei, how is my example any different than the “edit” link relation? “edit” often points back to the same URI/resource as the GET request. Could not the “terminate” relationship say:
atom:link rel=”terminate” href=”/customers/333″ type=”application/customer+xml”
and require a represnetation of the customer sent back with the terminate property off/on?
BTW, the vast majority of the web doesn’t even use Approach #1.
Mar 28, 2010 @ 16:54:40
Bill, I am not saying that your example is much different than “edit” link relation. In fact, that is the exact analogy, as I understood, you’re trying to use.
What I am trying to say is that you don’t need a ‘edit’ link when PUT does just the same. Why would you want the “edit” link to point at the same resource, when PUT is much simpler, cleaner and (especially if the customer+xml is properly designed) even self-describing?
I can imagine that idea like rel=”edit” comes from Atom, but I am afraid is often taken out of context and “re-used” blindly. Where links like rel=”edit” come useful is if the content type is xhtml and you require a web form to be able to (eventually) perform a PUT. This is due to the fact that you can’t just let a user to submit a PUT out of thin air and need a client to do so anyway. That’s why “edit” links often point at a form that simply allow in a user-friendly way to fill a form and eventually perform the very same PUT.
Now, coming back to your example… The link rel=”terminate” would certainly work. But that’s not the point that I am trying to make.
The first point that I am trying to make out of it is that if terminate property is all (or almost all) there is to it then you don’t need a link with a special meaning. Just change the property(ies) value and do a PUT. The url and standard HTTP methods for that are known! The terminate property is already in the customer resource and is properly modeled in the meda type. Having a link for that is not necessarily wrong but can be superfluous.
Another point that I am trying to make, is that *if* termination is not just a single property and actually needs to be a *separate* resource then having a link pointing at the termination resource might make sense (although I still wouldn’t like it and use as a last resort when no better option/redesign is possible).
I guess there may be different reasons many sites don’t use Approach #1:
* They just use Atom and don’t care about being RESTful which may happen alot in publishing/blogs. This is not to say Atom is not RESTful! You can be as RESTful or unRESTful with Atom as you wish.
* Their RESTful interface is built mainly for humans and not robots (hence the usefulness of “edit” links that would point at the edit form which in turn will facilitate the PUT’ting).
* Overly complexity of resource model (see reasons I described above).
* Lack of understanding of REST principles. I also see many sites doing all kinds of unnecessary ‘creativity’ such as putting action in a URL (e.g. action=delete) or other RPC-ish stuff.
* Whatever other, not mentioned here, reason (which I might have easily missed).
Mar 26, 2010 @ 12:34:57
From a RESTfulness POV, the proposed use of POST violates the message self descriptiveness constraint. The meaning of POST /customers/22/terminate must not depend on server side state. In the example it does, because the meaning of the message depends on the current state of /customers/22. In order to make the message self descriptive (and the design RESTful) you need to include everything in the POST body. In the example case that means that you need to POST a representation of customer 22 to some termination handling resource.
Jan
Mar 26, 2010 @ 13:38:20
Jan, thats a crock. There are plenty of cases on the web where an empty form is POSTed to a resource. And no, you do not have to send a representation of the customer to a termination resource, just a representation of the termination. Also, on the web, you do not see clients sending entire representations to make one specific state transition. You see different URIs handling different transitions (multiple forms on the same web page). IMO, using PATCH is a huge hack and requires much deeper knowledge about the service than publishing a link for that specific state transition.
IMO, an atom:link is a mini form. It gives the target resource and the representation type you want to send as well as a URI to ping if you don’t know how to interact with the link.
BTW, machine-based clients could CARE-LESS about self description. Knowledge have interactions has to be pre-programmed well in advance.
Finally, I don’t disagree that Approach #1 is a fine approach, I just prefer links as they are more intuitive, are simple to use, and give you a lot more flexibility over time, and STILL RESTFUL.
Mar 26, 2010 @ 14:41:28
Bill,
you say: “There are plenty of cases on the web where an empty form is POSTed to a resource.”. Even if that is true it does not mean it is RESTful. Message self descriptiveness is a REST constraint by design. If it is violated, the design cannot be RESTful. No matter what you see on the Web.
Jan
Mar 26, 2010 @ 21:27:45
I refute that REST is ALL or NOTHING especially considering it is defined as a set of guidelines and principles and is NOT a protocol in and of itself. That being said, IMO, a null representation is a valid representation. No reason a resource can’t represent the empty set. If you’re into biblical interpretation games, even Roy says in his PhD “A resource can map to the empty set, which allows references to be made to a concept before any realization of that concept exists”
Finally, think about this exercise from an implementation standpoint. Things like termination might be something a bit more complex than a simple CRUD operation. You really think it is better from an implementation perspective to have to parse a POSTed representation in order to figure out which complex logic to execute? (Let alone all the other benefits of linking I mentioned in the blog).
Mar 27, 2010 @ 13:16:17
You got a bunch of things mixed up, Bill.
REST is defined as a set of constraints on architectural elements. This definition is ALL or NOTHING because if not all constraints of REST are adhered to, an architecture cannot be REST by definition. It is just silly to argue about that.
Of course you can have requests and responses without an entity. What I said is that message self descriptiveness is violated in your POST example because the meaning of the request depends on server side state.
The question of implementation aspects is completely out of scope when discussing the question how RESTful a design is. Besides that there is really no difference regarding implementation complexity. Coding up either of those resources in one of the common frameworks should be a matter of minutes.
Mar 29, 2010 @ 13:21:37
Comparing a posted representation to the current representation to determine which complex logic to execute is NOT how the web works in practice. In practice it is modeled more closely to the example I have given. For good reason.
You’ll have to explain your self-description argument here. The meaning of a POST in my terminate resource example is the same no matter when the request is executed. The meaning is to change the state to terminated. So, I don’t understand what you mean when you say that “the meaning of the request depends on server side state”. Also, considering Roy in his thesis says “REST constrains messages between components to be self-descriptive in order to support intermediate processing of interactions” I don’t see your point at all (he goes on to describe various *request* metadata). POST responses are not cached anyways.
Mar 31, 2010 @ 10:27:25
Bill,
ask yourself: when the server receives POST [empty] /customers/22/terminate, how does it verify that the client’s intention matches the current intended processing done by /customers/22/terminate?
See http://www.nordsc.com/blog/?p=414
Mar 31, 2010 @ 13:17:49
Jan, I want to understand your argument, sincerely.
(Is this your blog? I’ll reply there too.)
I would agree with the assessment that if the “terminate” link supported multiple actions then it would be an antipattern and not REST. But in my example, the “terminate” link does not represent multiple actions. It represents a state transition to terminated. There’s absolutely no rule against one state change of one resource affecting another. Also, in my example, there is no unterminate on the “terminate” link. The “terminate” resource only accepts empty representations which mean to terminate (a form param of doit=true is just pedantic). Instead there would be a “reinstate” link published by the consumer resource. What I like about this approach is that the consumer resource is guiding the client on what it can do next by publishing links within its representation. This is the whole point of HATEOAS and alot more self descriptive, especially if there are more complex rules for when and if a the consumer can be terminated or reinstated that can’t be described by the media type easily.
I also don’t get your timing point. Is your point that between T2 and T5 the state of the consumer may change and terminate becomes illegal? The server is always allowed to return a failure if the POST cannot be processed (i.e. precondition failed) if the timing is off and the terminate transition becomes illegal.
Finally The blog states that “There is nothing in the request that allows the server to understand the actual intention of the client at the time the server handles the POST request.” The server is receiving an empty representation. The server published (through the link) that it expects “empty” representations to do the terminate transition. Of course the server is going to understand the intent of the request, because that’s all it accepts. Again, if you’re into pedantic exercises, define a null media type or doing a form param doit=true. I’m not.
Mar 31, 2010 @ 13:45:13
You seem to think that the semantics of POST [empty] /customers/22/terminate include the notion that /customers/22/terminate is the ‘terminate’ resource of /customers/22 because the server told the client via hypertext before, yes?
This is not so because the message can only be considered in isolation – it does not depend on any previous interaction – or if it does it is not self descriptive.
Besides all this – why not use a RESTful solution in the first place? Or just use the straight-forward DELETE /customers/22 ?
Mar 31, 2010 @ 14:06:21
Where is the dependency on any previous interaction other than finding out where the termination resource is located?
» links for 2010-03-26 (Dhananjay Nene)
Mar 26, 2010 @ 20:03:13
Mar 27, 2010 @ 00:28:14
Guys, how many more years will you need to understand that REST is not a programming model… and you have not even started to talk about versioning, whatever API scheme you doom RESTful.
Mar 29, 2010 @ 12:43:30
There are multiple ways of handling versioning in REST+HTTP applications. Conneg, well-designed media types, or just simply new URL schemes. Much better and simpler options than anything else in the industry (if they even exist). In SOAP land you have a hard enough time getting the stacks themselves to operate between versions let alone your applications. But…I’m sure you have some snotty, condescending response to whatever I write…Cheers.
» links for 2010-03-27 (Dhananjay Nene)
Mar 27, 2010 @ 20:02:42
Daily Digest for March 29th
Mar 29, 2010 @ 06:09:05
Apr 01, 2010 @ 09:22:57
I would go with approach 3 as IMO it is the cleanest way of changing this specific state as it explicitly describes the operation that the server is going to perform.
PUTting the entire entity representation as in approach 1 just to change a single attribute seems overkill to me for several reasons.
1. Why should I send an entire object if I want to change only a single attribute?
2. Why should the server derive the callers intention by comparing two representations (one from the database and one that was sent)?
3. Why should the server read the object that is to be changed from the database before changing it?
What if the object was changed in the meantime in a way that makes it ambiguous for the server to determine the intent of the caller?
This all seems like a lot of code for a simple operation.
Bill, I like the atom links with rel=’terminate’ and an opaque URI. How does the caller know which HTTP verb to use? Is it always POST for atom links?
Frank
Apr 01, 2010 @ 11:00:27
Well, you know that PUT or POST will work at least from the atom link. If you’re “hacking” the service, then do a post. Server should respond with what methods it supports if the POST fails. You could always look up the documentation of the service, or the link. 🙂 Usually the “rel” attribute of links are expressed as a URL that you can follow to find out more information about that link.
Also, there’s a new method called PATCH for HTTP. I still prefer links though.