Years ago when I first learned about declarative programming, it seemed like magic. Separate your logic from your control flow, so that you can simply describe the behavior you want, and your software will magically know how to render it! But of course, there’s nothing magic about declarative languages like SQL or HTML, to name two of the most familiar. With those languages, your database engine or browser, respectively, takes your declarative code and turns it into instructions a computer can understand.
The power of declarative programming isn’t that we magically don’t have to code programmatically. Rather, the programmatic code we write is generalized. If you’re coding a SQL processor, the same tool can process any valid SQL. Similarly, a single browser can render any valid HTML. To change a Web page, change the HTML – but the code inside the browser remains the same.
Hypermedia applications – the type of applications the REST architectural style is intended for – are also supposed to be built declaratively. Take as an example the simplest of hypermedia applications: a static Web site. The application consists of a set of representations conforming to standard Internet Media Types (HTML files, JPEG images, and the like), interconnected via hyperlinks. The user navigates the application by following the hyperlinks, and the current location of the user in the application – in other words, the application state – is managed by considering the Web site to be a straightforward finite state machine, where the pages are the allowed states and the hyperlinks specify the allowed transitions. In other words, hypermedia are the engine of application state – the RESTful architectural constraint we call HATEOAS.
The programmatic code that makes this hypermedia magic work lies in the underlying HTTP infrastructure that knows how to resolve URIs, resources that know how to process requests via a uniform interface, and clients that know how to render representations that conform to standard Internet Media Types. These three capabilities (URI resolution, request processing, and Media Type rendering) are generalized – it doesn’t matter what URI, request, or representation you care to deal with as long as they conform to the constraints of the architecture.
So far, everything’s hunky dorey, as long as the HATEOAS constraint is handled manually – that is, a person changes the state of their application by clicking hyperlinks. Where so many developers run into trouble with HATEOAS, and by extension all of REST, is when they try to automate HATEOAs.
It’s no wonder so many coders want to automate HATEOAS. Automating HATEOAS, after all, is how to get the full power out of REST. Free yourself from a simple browser as the client, and instead code an arbitrary piece of software to serve as the RESTful client (which may not even have a user interface) – a piece of software that knows how to follow hyperlinks to gather all the metadata it needs to understand how to behave.
Easier said than done, because standard Internet Media Types are generally insufficient to instruct our arbitrary clients how to behave. We typically want to exchange arbitrary data, where the Media Type doesn’t tell the client enough about how to process it. REST’s answer? Custom Media Types, which take a standard Media Type like XML or JSON and adds a schema or other metadata specific to the situation. Now the developer must code the client to understand those Custom Media Types – as well as how to make the appropriate requests for representations that conform to them. Finally, developers find themselves coding the resources specifically to deliver those representations.
Let’s review. Automating HATEOAS means custom-coding resources that return custom-formatted representations to custom-coded clients. Hopefully you see what’s wrong with this picture! We’ve given up entirely on the benefits of declarative programming, instead falling into the trap of building tightly coupled, inflexible, spaghetti code. There’s got to be a better way.
The correct approach to automating HATEOAS is to maintain the generalization of the components of the architecture, just as we did with manual HATEOAS. We must generalize the code in our resources. We must generalize the Media Type processing code. And we must generalize the internal workings of our RESTful client. We don’t want our client to know ahead of time what behavior it is supposed to exhibit. Instead, all of its behavior should be learned by referencing hyperlinks. Furthermore, we shouldn’t have to teach our client ahead of time how to deal with the representations it receives. It must be smart enough to follow hyperlinks to gather whatever information it needs to process any representation.
I’m not saying it’s easy. But once you’ve written such a client, it will serve to implement any application you care to specify declaratively. Drop me a line if you’d like to see an example.