Tuesday, 23 February 2016

Better Hypermedia Through Obfuscation

Here’s a fun and completely twisted idea that came to me on the train this morning. One of the constraints of ReSTful architectures is that they use hypermedia as the engine of application state – in other words, clients shouldn’t maintain their own information about where they “are” and what resources and actions are available to them right now; they should rely on hypermedia embedded in the current resource representation they’ve retrieved from the server. A common example of this is pagination – navigating big lists of resources, using a representation something like this example:


  GET /people

  "_embedded": {
    "people": [ /* array of 10 people */ ]
  },
  "_links": {
    "first": { "href": "/people?page=0" },
    "last": { "href": "/people?page=12" },
    "next": { "href": "/people?page=1" },
    "self": { "href": "/people?page=0" }
  },
  "count": 10,
  "total": 115
}
Now, with an API like this, it’s all too easy for the client – or rather, the person building the client – to go “ok, page number is a zero-based integer; let’s cut a few corners here” and just program something like

for(var i = 0; i < 12; i++) {
    http.get(“https://api.foo.com/people?page=”+page);
}
Now, I’m a big fan of something we call the "pit of success" – the idea being that we "build platforms such that […] developers just fall into doing the 'right thing'", and more generally, the idea that the easiest way to achieve something is also the ‘correct’ way to achieve that thing. So what if we intentionally obfuscate our APIs so that hypermedia navigation becomes easier than building a for() loop? By, for example, requiring that our page number is written out longform, instead of numerically? And, just for fun, we’ll require that it’s in a language that isn’t in the regular ASCII character set. Like Ukrainian:
{
    "first": { "href": "/people?page=один" },
    "last": { "href": "/people?page=дванадцять" },
    "prev": { "href": "/people?page=два" },
    "next": { "href": "/people?page=чотири" },
    "self": { "href": "/people?page=три" }
}
Suddenly, your ‘shortcut’ isn’t a short cut any more. For starters, you’ll probably need to install a special keyboard in order to type those characters – not to mention suddenly your source code files will need to be in UTF8, and I’ll wager that somewhere in your build pipeline there’s a tool that can’t handle UTF8 source code. And you’ll need a routine which can translate integers into Ukrainian string representations… No, the easiest thing to do now is to retrieve the first resource, parse the next.href property, retrieve that resource, and so on until you hit a resource with no next link. Which, of course, is exactly how hypermedia is supposed to work in the first place.

3 comments:

Richard Smith said...

While it's a neat idea and wonderfully Heath-Robinson, much the same result could be achieved by returning "last": { "href": "/people?page=-1" } ? It's only the upper bound that allows the for loop.

Harrison said...

Why not just use a GUID? I'm think if I found an int => long form Ukrainian string converter function in a project I was working on, I'd have to take the rest of the day off for mental health.

Dylan Beattie said...

@Harrison The point of the exercise was to show how in a properly-designed hypermedia system, your hrefs don't even need to be something you can type. I wanted to stick with the idea of having sequential page numbers rather than something like GUIDs, which aren't stateless since the server needs to track which GUID goes with which page. And I picked Ukrainian because most developers don't have a Cyrillic keyboard handy so it's hard to type. :)