Rockin' good code, rockin' good times

Text

Why build Hypermedia APIs?

This week we started to build out the Reverb API. Being a greenfield project, we decided to investigate the best practices for building truly RESTful (that is, Hypermedia-based) APIs.

The key takeaway for such APIs is that no out-of-band knowledge is passed between the server and the client (meaning basically that you can get away without documentation). Instead, the API provides links to the client to follow to change the state of the server. Quoting Roy Fielding,

A REST API should be entered with no prior knowledge beyond the initial URI (bookmark) and set of standardized media types that are appropriate for the intended audience (i.e., expected to be understood by any client that might use the API). From that point on, all application state transitions must be driven by client selection of server-provided choices.

While many people tout the flexibility of hypermedia apis for being able to change server-side routing and not break clients, this contrarian post by DHH (of course) makes the valid point that unless the client is navigating links from the top level every time, you’re likely not to get the benefit.

The true benefit, in my mind, comes from the self-documenting nature of Hypermedia APIs. Instead of having to read some document, I can see every action I can take on a resource embedded within the response directly. This is very friendly for humans exploring the API and learning how to use it.

Standards for representing Hypermedia links: HAL, Siren, Collection+JSON

So with the assumption that we want to represent actions in the API as links, we investigated emerging standards for representing resources and links. The three big ones at the moment appear to be HAL, Siren, and Collection+JSON. Both Collection+JSON and Siren provide some interesting syntax for embedding actions (basically the equivalent of HTML forms) within the JSON response. However, both seem to be a bit verbose with wrapper elements and seemed like more work both to for humans to read and for api clients to parse. You can see a side by side comparison of a HAL and Siren document to really get the feel for this. The one thing I did like about Siren was the idea of embedding form actions. While HAL doesn’t specify a standard for this, we may find ourselves in the future extending the HAL media type to provide something similar as we move toward a more complex API. So far, we have already added the idea of HTTP verbs into links, which HAL doesn’t explicitly provide for, but also doesn’t disallow:

_links: {
  foo: { href: "/foo", method: "POST" },
  bar: { href: "/bar", method: "DELETE" },
}

Implementing Hypermedia APIs in Ruby: Grape vs Rails::API

Then it came down to choosing the library on the Rails side. We wanted something light weight and tailored to API building. While Rails::API seems like a decent approach and is championed by well respected people like Steve Klabnik, I was turned off by its lack of examples in the README and that it seemed to be trying to fit a round peg in a square hole by taking the ‘fat’ out of Rails rather than building something clean from the ground up.

Grape is a very popular project explicitly tailored to building APIs from the ground up. Grape’s documentation in just the README file makes it clear that the project is very small and easy to grok. It doesn’t try to do too much, instead providing hooks for other libraries to tap into.

It’s also seeing a lot of nice community support and extensions such as Garner from Artsy, which I first found out about in this excellent 10 minute talk on building APIs that combine server side caching with 304 Not Modified client side caching.

We also added the Grape-Swagger library, which generates a JSON documentation for your API, parseable by humans and by Swagger-UI. This means that we document our API directly in the Grape code and get a free beautiful representation to show to clients.

Generating Hypermedia JSON responses, RABL vs. Roar

While Grape can produce very simple JSON responses, we quickly realized that we want a standard and a framework for generating representations of our models in a clean way.

The first thing we tried was RABL which was very popular on github. Frankly, I am surprised at its popularity. This was not a pleasant library to use. Its declarative template syntax really starts to break down as soon as you try to do anything beyond the basics.

At every step of the way, we felt that the usage syntax was counterintuitive. RABL focuses in too much on the details - which is evidenced by the language concepts it uses. When you write RABL templates, you are talking about nodes and children - concepts very specific to the representation of the output. You are constantly thinking about how to attach this node to that, or how to embed attributes where RABL doesn’t seem to want them. When we tried to implement HAL-style linking in RABL, it was a nightmare.

Then we discovered Roar. Not only does it provide HAL support out of the box, but the way it’s architected is beautiful. You create Representers which map your models to representations in plain old ruby classes, not some strange templating language. The ability to extend those Representers with features such as HAL links is done cleanly via a plugin architecture.

I feel confident that if we were to move away from HAL, it’d be a matter of writing a different extension for Roar that would change the way our links are rendered, for example. The only questionable approach is their use of DCI-style extension due to the cache busting properties of runtime extension. I doubt this is a very big concern right now, but it may not be negligible at scale. Still, this is just an implementation detail and can be rewritten if needed.

TLDR: the Reverb.com Ruby Hypermedia API Stack

  1. Grape as a lightweight routing layer. But just like Rails controllers, beware of adding logic. Keep all logic in domain models.
  2. Roar for JSON representation. Use Roar::Representer::JSON::HAL for HAL linking support.
  3. Grape-Swagger for documenting the API.
  4. Garner for caching (we’re just starting to implement this)
  5. Coming soon: authentication - currently HTTP Basic Auth
  1. reverbdev posted this

blog comments powered by Disqus
Crafted in Chicago