Hypermedia ain't that fat

by Dan Cutting

Hypermedia APIs are designed around the concept that clients and services should not be tightly coupled, but discoverable. One prominent feature is that responses should contain explicit links for clients to use to take successive actions.

This post will explore one concern that often arises: that hypermedia responses are much larger than non-hypermedia responses since they need to include a lot of extra information in the form of links.

Welcome to Jazz Club

Let’s say we’re writing an API for a jazz club. One endpoint gives us a list of musicians present.

GET http://jazz.example.com/musicians

If our API does not use hypermedia, we can just return a JSON Array containing some names.

HTTP/1.1 200 OK
Last-Modified: Fri, 24 Apr 2015 21:58:36 GMT
Content-Type: application/json
Server: WEBrick/1.3.1 (Ruby/2.1.3/2014-09-19)
Date: Fri, 24 Apr 2015 22:13:35 GMT
Connection: Keep-Alive

["john","miles","louis"]

Now let’s add an endpoint that represents a profile for a user.

GET http://jazz.example.com/musicians/{name}

With a non-hypermedia API, this template would be hard-coded into our client, and the {name} variable replaced with a name from the previous response. Ignoring for the moment whether or not this is a good design, it does at least mean that the initial response can be terse (just 24 bytes).

If we instead decide to return a hypermedia response to our initial endpoint, we might end up with something like this.

HTTP/1.1 200 OK
Last-Modified: Fri, 24 Apr 2015 21:58:36 GMT
Content-Type: application/json
Server: WEBrick/1.3.1 (Ruby/2.1.3/2014-09-19)
Date: Fri, 24 Apr 2015 22:13:35 GMT
Connection: Keep-Alive

{
  "_links": {
    "self": {
      "href": "http://jazz.example.com/musicians"
    },
    "http://jazz.example.com/rels/musician": [
      {
        "href": "http://jazz.example.com/musicians/john"
      },
      {
        "href": "http://jazz.example.com/musicians/miles"
      },
      {
        "href": "http://jazz.example.com/musicians/louis"
      }
    ]
  }
}

The payload here is more complicated and about 10x the size of the simple Array (259 bytes excluding whitespace), but it has some advantages too:

  • Instead of hard coding a URI template into the client plus logic for generating a URI, our client can now simply visit the links in the response.
  • The service can change URIs at a later date without breaking the client, which is useful when we want to break up the service across different domains or move image resources to CDNs, etc.
  • The service can also use the presence or absence of links to indicate whether the client can perform certain actions.

This example uses the Hypertext Application Language (HAL) format to encode the links. There are alternatives, such as Collection+JSON and Siren, but they all seek to provide a standard way of encoding links in JSON (which lacks a native convention). Personally, I like HAL (so much so that I’ve written an Objective-C library called HyperBek for consuming it).

Big deal

So with three musicians, we’re an order of magnitude off if we consider only the payload. However if we throw in the HTTP headers, our response sizes are more similar (223 bytes to 456 bytes, or about double).

How does it compare for different numbers of musicians? Unfortunately, It just gets worse. At 100 musicians, HAL is more than 4x the size of a simple Array.

It seems there’s no way hypermedia responses could be a good idea. Fortunately, HTTP comes to our rescue.

HTTP provides mechanisms for compressing data in transit from the server to the client. Several different sorts of automatic compression can be applied and virtually all clients and servers are able to handle it. The most common type of compression is GZIP so what happens when we apply it?

[HAL / Array ratio]

It helps a lot!

This graph shows that when uncompressed, using HAL instead of a simple Array goes from just under 2x the size for one musician, to about 5x the size for 1000. By contrast, a GZIP representation starts at about 1.3x for a single musician and moves closer to the size of a simple Array as the number of musicians increase.

How can the compressed HAL representation get more efficient with more data? The answer is in the way the GZIP compression algorithm works. GZIP looks for repeated instances of strings and replaces them with a short placeholder code. Since the structure of the HAL links are repetitive (e.g., the beginning of each link in our jazz club API is the same), they can be highly compressed and optimised away.

Conclusion

Although this is a very simple example, we’ve shown that hypermedia APIs are not necessarily much larger than non-hypermedia APIs. Given the benefits of hypermedia, and the ubiquity of HTTP compression, it’s worth considering for your next API.

Hi, I'm Dan Cutting. I mainly write Swift for iOS but love working on Mac apps, web APIs, and programming languages too.

Follow me on Twitter, Micro.blog, or GitHub or subscribe to the feed.