Building RESTful services – part 2
This is part 2 of “Building RESTful services”. In Part 1 we talked about Level 1 and Level 2 of the Richardson Maturity Model.
Level 3 – Hypermedia
Hypermedia is really the most interesting aspect of REST and the aspect that is key to scalability and loose coupling. Sadly this is also the aspect that most services that aim to be RESTful have trouble with.
As humans using a web browser, we use this concept every single day. If you wanted to look at the sports section of msn, what do you do ? You type in http://www.msn.com in the browser, look for the hyperlink with the title “sports” and click on it. This concept of hyperlink is so simple, but is one of the most important reasons for the popularity and scalability of the web. Instead of using a hyperlink, imagine if you had to download a “document or a manual” for msn.com that explained the URI to type in to the browser for each section and every time you needed to access something you had to look at the document, find the URI corresponding to your section and type it into the browser. Now imagine if you had to do this for every single website, each having a different kind of document in a different custom format. The internet would certainly not be what it is today. Yet a majority of the APIs built today make us do this. This is where the hypermedia concept comes in.
Let’s say there is a bookstore which has an api for clients to access various lists (e.g. top sellers, new arrivals etc). To get a list of top sellers in the java category, a client may make the following call
and this returns the following
<books count="5"> <book purchased="true"> <id> 12345 </id> <title> Effective Java </title> <author> Joshua Bloch </author> </book> <book> <id> 23456 </id> <title> Java Concurrency </title> <author> Brian Goetz </author> </book> ... ... ... <book> <id> 56789 </id> <title> Clean Code</title> <author> Bob Martin </author> </book> </books>
This would typically be accompanied by documentation such as
Method – Get More Details about a book
Call the following URL
Method – Get Reviews for the book
Call the following URL
Method – Provide a “recommended” rating for the book
Call the following URL. Note that only customers who have purchased the book can provide the recommended rating (purchased flag should be true)
Method – Get Next set of books in the list
Call the following URL
It is the client’s responsibility to make sure that startIndex and count have the right values.
We’ve all seen API’s built and documented this way. This approach puts the burden of generating the URIs for subsequent actions completely on the client. These are the potential issues with the above approach -
- Errors in constructing the URIs
The primary responsibility for constructing the URIs rests on the clients, for any action that the client wants to perform based on the list. This is a simple use case, but as things get more complicated, the likelihood of a client constructing the URI incorrectly increases a lot.
- Tight Coupling
Since the URI formats need to be published and specified ahead of time for the clients to construct them, the service has very little flexibility in terms of changing the API. For example if the service wants to point to a completely different URL for getting reviews, it would be tough to do so without breaking existing clients.
- State transitions and business rules on the client side
Let’s say the service changes the business rule and any customer can now provide a rating for the book. This means that all clients that are using the current API now need to change their code to make this happen. This tightly couples business logic to client side code and severely restricts the service’s ability to iterate over business rule changes without massive coordination efforts with all clients.
This is where hypermedia shines. Take a look at the response below with additional hypermedia links (Relative links are used for brevity, but assume all the links are full links including host name)
<books> <book purchased="true"> <id> 12345 </id> <title> Effective Java </title> <author> Joshua Bloch </author> <link rel="moreInfo" href="/estore/catalog/books/id/12345"/> <link rel="reviews" href="http:/reviewStore/estore/reviews/id/9912345&customer=888"/> <link rel="rating" href="/estore/books/id/12345/rating?customer=888"/> </book> <book> <id> 23456 </id> <title> Java Concurrency </title> <author> Brian Goetz </author> <link rel="moreInfo" href="/estore/catalog/books/id/23456" /> <link rel="reviews" href="http:/reviewStore/estore/reviews/id/9923456&customer=888" /> </book> ... ... ... <link rel="next" href="/estore/reports/topsellers/software/java?startIndex=6&count=5"/> </books>
So what’s different here ? We have three new elements which contain a hyperlink with some metadata. Let’s look at each of them
<link rel="moreInfo"> ... </link>
<link rel="reviews"> ... </link>
<link rel="rating"> ... </link>
If you recall, the initial business rule was that only if the purchased=”true” flag is available, the client should display the rating link to the customer, and a change in this business rule affected the client. Instead, here notice that based on the business logic, the Service can either send back, or not send back a link called “rateBook”. The client simply looks for this link, and if it is not available, doesn’t display the rating link.
Similar benefits are realized by the presence of a “next” link. These examples highlight the point that by adding hyperlinks to control next steps and NOT making the clients construct URIs, the system as a whole has gained a lot of flexibility.
Hypermedia As The Engine Of Application State or HATEOAS refers to allowing clients to navigate through appropriate application states using hyperlinks. The above examples touch on this a little, but let’s look at this from a slightly different point of view. Going back to our eStore example from PART 1, let’s start with an empty order
GET /estore/order/178567 <order> <cart> <items> </items> <link rel="update" href="/estore/order/178567/cart"/> </cart> </order>
POST /estore/order/178567/cart <item code="abc" qty="1"/> //another item POST /estore/order/178567/cart <item code="def" qty="1"/>
GET /estore/order/178567 <order> <cart> <items> <item code="abc" qty="1"> <item code="def" qty="1"> </items> <link rel="update" href="/estore/order/178567/cart"/> </cart> <link rel="saveForLater" href="/estore/customer/1356/archive/order/178567"/> <link rel="cancel" href="/estore/order/178567"/> <link rel="address" href="/estore/order/178567/address" type="application/vnd.estore_address+xml"/> </order>
Notice that there are several new links as part of the order itself which indicate one of several next steps that can be done as part of the shopping process.
- The presence of the link of the type “saveForLater” indicates that one valid option for this order is for it to be saved for later, so that the customer can come back to it at some point. Note that if there were no items in the cart, this link would not be available .
- Indicates that this order is in a state where it can be cancelled if the customer wants to, (by making a delete request – this does need to be documented as part of a simple resource documentation). Again, only available for a non empty cart
- This serves as the link to enter the address for the order so that the checkout process can start.
The important point here is that the client doesn’t have to figure out from an external document as to what are the possible next states for the order. The possible next states are all captured with hyperlinks. Again, this makes for really loose coupling and provides the service a lot of flexibility in evolving business rules around Order processing without breaking existing clients. Also note that each of the link types either returns or accepts a certain content-type. While it is tempting to use application/xml, a specific format such as application/vnd.estore_order+xml, or application/vnd.estore_address+xml is preferred. Another important thing to keep in mind is that while the client is expected to use the hyperlinks from the response, the service should still guard against invalid requests for a given state.
Let’s go one more step, and assume that the customer decided to enter the address for the order.
POST /estore/order/178567/address <address> <street> ... </street> <zip> ... </zip> etc etc </address>
GET /estore/order/178567 <order> <link rel="cart" href="/estore/order/178567/cart"/> <link rel="saveForLater" href="estore/customer/1356/archive/order/178567"/> <link rel="cancel" href="/estore/order/178567"/> <link rel="address" href="/estore/order/178567/address"/> <link rel="payment" href="/estore/order/178567/payment"/> </order>
- The presence of the link of the type “payment” indicates that payment is a valid next step for the order in its current state. If the cart was empty with no items, or if there was no address associated with an order, our made up business rules wouldn’t have allowed us to enter payment information. But now, based on the link, the customer can proceed to payment
GET /estore/order/178567 <order> <link rel="cart" href="/estore/order/178567/cart"/> <link rel="cancel" href="/estore/order/178567"/> </order>
Allowing clients to dynamically discover next logical steps using hyperlinks, instead of upfront specification is a key part of RESTful design. Thinking in this way helps us realize a lot of benefits of RESTful services. Finally, taking this point further, it makes a lot of sense to expose only the stable starting point for a service, and allow clients to discover capabilities on the fly. For example, our eStore could simply advertise the well-known URI /estore/ and everything else is simply discovered by the client by following relevant links provided by the service at the right time.
GET /estore/ <estore> // Get current specials <link rel="specials" href="/estore/specials"/> // Starting point for all catalog related info <link rel="catalog" href="/estore/catalog"/> // POST to start a new order <link rel="order" href="/estore/order"/> // Starting point for all customer specific interaction including login <link rel="profile" href="/estore/customer"/> </estore>
While the concept of hypermedia is simple, it is the one aspect that people building RESTful services ignore most often. Using hypermedia is really significant in making a web service loosely coupled and highly flexible, allowing it to evolve over time without breaking clients.