Why HATEOAS is not the witch to burn
Join the DZone community and get the full member experience.
Join For FreeA bit late to the party, but some arguments require thinking and finding yourself in the problem a pattern was extracted from, to be able to judge it correctly.
HATEOAS for dummies
Hypertext As The Engine Of Application State is the model upon which the web works: the state of user agent's interaction with the server over HTTP is defined by the page it's visiting now, and not from some data structure kept inside the client's machine. In this model, a state transition as following a link (or submitting a form) means that usually the URL of the next transition is embedded in the representation of the current resource.
For example, you're visiting this page at some URL on css.dzone.com after having clicked on a Twitter link, or another link on a DZone page; maybe even one on the entry point css.dzone.com.
At the API level, HATEOAS implies that the output of your API calls contains further links to other resources; depending on your accepted formats, these links can be placed inside headers, or in JSON or Atom bodies of the response.
If you build all your APIs following this rule of thumb, this would mean that the /posts resource would contain links to all the /post/1, /post/2. To the pitchforks, this is a useless abstraction!
More seriously: design decisions
I suspect of every design "rule", that is a decision taken in some context and blindly transported into other domains and projects. I firmly believe the form of your software is shaped by its context (talking in Alexandrian terms) and not by gurus building an application with a different (from yours) number of users in a different domain, serving different type of clients and with different maintenance requirements.
When I first heard about HATEOAS and read Mike Amundsen's book Building Hypermedia APIs, I thought: this movement is trying to rebuild web applications, which embed in responses the links to the next resources, but with machines on the client side instead of humans. I've never seen intelligent machines capable of navigating with a browser, so this is doomed to fail.
Then I started to work on applications with complex workflows, where your interaction doesn't stop with a POST and a GET to retrieve the result but goes on for 4, 5 or more different requests; having to support more than one kind of client, including deployed Android devices. And I found myself happy to know including URLs in responses was allowed.
Allocating responsibilities
Design is also about the assignment of responsibilities to the different components of your application; in the case of web applications, we are usually talking at least of two separate nodes, the client and the server. In the case of mobile native applications, a further specialization, the client side may have been deployed months or years ago without the capability to upgrade (users don't push the upgrade button, I'm sorry).
In the face of changing requirements and evolution of the workflow, what can you support with a fixed client? It depends on how your design responds to this.
For example, recently I was working on the entry point of an API, which we'll call /widgets. When executing a POST to /widgets, the user should go to the page of the newly created widget, and it is sent there with a Location header containing the full URL:
HTTP/1.1 201 Accepted Location: http://www.example.com/widgets/42
This is an HATEOAS design: you're not relying on knowing more URLs than the initial URL to navigate through. In this case, the advantage is clear as the client doesn't know the :id in the /widgets/:id that he has to load.
To solve this only problem, you could pass back the new :id in the body of the response, in some acceptable format:
{"id": 42, ...}
So why the client should follow Location instead of composing its own URL? First of all, the former choice is even less work for the client: it's the server that has to compose an URL to return instead of just dumping a resource. However, what is really happening is that you are separating responsibilities between client and server so that the client knows the basic step of a workflow (create a widget, load it, do some other action on it) and the server governs the actual locations of the response. So the HATEOAS design allows you, the server owner, to change some interesting things in the future:
- the server may decide to not send the user directly to /widgets/:id but execute an intermediate redirect. For example, some carriers requires this intermediate redirect to one of their pages to allow user identification. If the client just follows Location headers correctly, you can do any number of redirects between the POST on /widgets and the next step /widget/:id.
- the server may decide to send the user to another host or application altogether. During migrations from legacy system A to new and fresh system B, it may happen that all new widgets that have been created on A are redirected to B after that, in order to phase out part of A gradually and not in a big bang.
- You can change the internal URLs accordingly, for example putting widgets under /user/:name/widgets/:id where :name is the user that executed the POST. This supports the change of using ids that are unique for a user instead of globally.
I'm not saying you need to appreciate these changes to your system, or that they will ever happen to you; just that this design on a simple creation POST/retrieval GET query allows you to accomodate them without changing the clients (which may be deployed on thousands of Android and iOs devices).
In more complex interactions, where multiple POSTs modify the state of the resource on the server, there are many more new roads that it can take: making some selected clients skip some steps according to business decisions; change the order of operations according to their performance, to leave the quick ones at the start and that awful batch processing at the end, when they can even close the application and be notified later.
Conclusions
If you just need to perform CRUD operations, don't build an object-oriented application, choose a relational database and expose it through some views. In the same way, if you just need to perform CRUD operations with your API, don't expose links in resources: stick to POST and GET requests on fixed URLs. The same goes if you have total control over all clients of your API (in web applications as opposed to mobile applications or other systems that need to integrate you).
But HATEOAS is a model that allows you to allocate to the server some of the responsibilities of your API's use cases: which are the next steps, where they are executed, whether to skip any, or to perform further redirections. HATEOAS isn't a reminescence of WS-* as much as slapping the REST label over remote procedure calls is a reminescence of RMI and CORBA (too many acronyms today!)
Opinions expressed by DZone contributors are their own.
Comments