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-ey

Approach 2
———-
Since we know we wont want to delete objects, we could hijack

DEL /consumers/{uuid}

And not really delete, just terminate. This seems kinda hackey.

Approach 3
———–
Approach 3 would create “action” links. We would therefore support

POST /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?

  1. 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.
  2. 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?