Joined up thinking: why your resources want links

Piers Cawley

Remember the good old days? The days before Google? The days before Altavista? The days when a 14k4bps modem was fast? Did I say good old days?

In those days, the web had to be discoverable ‘cos it sure as hell wasn’t searchable. The big, big enabling technology of the web was the humble <a href='http://somewhereelse.com'>Go somewhere else</a>. Placing the links right there in the body of the document turned out to be exactly the right thing to do.

And it continues to be the right thing to do. Consider the two pieces of YAML below the fold.

Remember the good old days? The days before Google? The days before Altavista? The days when a 14k4bps modem was fast? Did I say good old days?

In those days, the web had to be discoverable ‘cos it sure as hell wasn’t searchable. The big, big enabling technology of the web was the humble <a href='http://somewhereelse.com'>Go somewhere else</a>. Placing the links right there in the body of the document turned out to be exactly the right thing to do.

And it continues to be the right thing to do. Consider the two pieces of YAML below the fold.

Here’s something close to what I’ve been generating at work (We generate JSON, but YAML’s easier to write out by hand):

location: http://site/users/pdcawley/tunes/99
name: Bill Norrie
creator:
  name: Piers Cawley
  location: http://site/users/pdcawley
duration: 360
image: http://s3.amazonaws.com/...
stream: http://s3.amazonaws.com/...

Here’s that same snippet, recast in the URL free style that’s common in ‘RESTful’ APIs:

id: 99
name: Bill Norrie
creator:
  name: Piers Cawley
  id: pdcawley
duration: 360
image: http://s3.amazonaws.com/...
stream: http://s3.amazonaws.com/...

Which of those would you rather have served up to your API client? Obviously, the one with the URLs because then your client has to know so much less. If resources carry links within their body they’re discoverable, just like anything else on the web. A client only needs to know about a few resource directories and maybe a mechanism for searching, and most of the rest should follow from the RESTful principles.

For values of ‘the rest’ that are concerned with the how of the API. You’re still going to have to document the what and the why. You can get a long way down that road with careful use of <link rel“ServiceDoc” href="/servicedocs/model" />= though.

If you wanted to write a client to make use of resources in the second form, you’d need to know that a tune’s URL is of the form /users/==creator.id==/tunes/==tune_id, that user urls have the form /users/==creator.id and so on. You’d curse the API designer who designed that URL scheme. You’d curse again when, having written the Ruby API, you sat down to write the Javascript version (modern sites need their AJAX, right?) and had to teach it all about the URL scheme all over again.

Don’t Repeat Yourself

If there’s one thing worse than violating the DRY principle, it’s forcing other people to do it. Structuring your APIs representations around IDs rather than URIs is doing just that. The complexity that you dodge by punting URL generation to the user is multiplied by the number of API client implementations (and, arguably, the number of client that don’t get written ‘cos your API’s a PITA).

It’s worse than that though. You already have the code for mapping between resources and URLs. If you’re using Ruby on Rails, your mapper is bidirectional too.1 Generating URLs within your representations should be a snap.2 So, what are you waiting for? Start writing Joined Up APIs.

4 historic comments »

These are archived comments. To respond to this post, use a webmention

http://www.gravatar.com/avatar.php?gravatar_id=f1b0c8dfe96dad2606c99535803d12b8&size=48&url=http://husk.org/ By Paul Mison Fri, 22 Feb 2008 06:39:52 GMT

While I can see the sense in serving URLs for convenience, I can see cases where serving the chunks that build the URLs instead is more useful.

As an example, Flickr’s API tends to return a photo element complete with all the resources needed to build a URL for an image (indeed, multiple versions of that image) or the page showing that image. (To be fair, they also offer a flickr.photos.getSizes call that returns all the URLs for you, but that requires an “API join”: http://blech.vox.com/library/post/the-api-join-and-avoiding-it.html.)

So, more generally, if you serve a URL which contains information that may be more useful on its own, aren’t you then forcing the API user to parse that URL for its useful information? Perhaps the answer is to serve both the URL and the constituent chunks, but I accept that increases app overhead.

http://www.gravatar.com/avatar.php?gravatar_id=e17949267bbfe21a0fadf1bbf00592b4&size=48&url=http://plasmasturm.org/ By Aristotle Pagaltzis Fri, 22 Feb 2008 10:39:23 GMT

Piers:

I can rant for ages about the awfulness of RoR’s routing system, but the bidirectional nature of them trumps almost all my complaints.

Catalyst’s routing goes both ways too, and while my experience with it so far is limited, it does not seem at all awful to me. :-)

(PS.: your preview is broken. I hope this comment comes out OK. Why does everyone these days go pure Ajax for their previews?)

Paul Mison:

aren’t you then forcing the API user to parse that URL for its useful information?

No. No no no no no. Clients don’t parse URIs. Ever. If it’s not a URI you minted, you have no business peering at its pieces. It’s good for many reasons for URIs to be readable, but as far as the client is concerned, /topic/42/post/23 is completely equivalent to /0913-jdq981k1j187.

If the client needs to have some information, you give the client that information explicitly, not by implying it within the URI.

Flickr’s API tends to return a photo element complete with all the resources needed to build a URL for an image (indeed, multiple versions of that image) or the page showing that image.

Right; and if they also gave you a URI Template so you didn’t need to hardwire any knowledge of their URI space into your client, that would be fine. It’d be the equivalent of a form in HTML.

Clients constructing URIs is perfectly RESTful as long as their a priori knowledge is limited to the algorithm for doing so and does not include specifics of the server’s URI space.

http://www.gravatar.com/avatar.php?gravatar_id=f1b0c8dfe96dad2606c99535803d12b8&size=48&url=http://husk.org/ By Paul Mison Sat, 23 Feb 2008 10:00:22 GMT

Aristotle: thanks for the reply. I think we’re agreed that the API consumer parsing the URI is not desirable (although you seem to be a bit more hardcore about it than I do), hence my argument that if there’s information that may be useful that’s in the URI, it should be provided separately in the response. (Just because something’s wrong, doesn’t mean people won’t be tempted to do it, if you like.)

On the subject of URI templates, they seem to be newer than Flickr’s API, which instead provides a human-readable guide to constructing URLs from response information.

(Regarding the Textile preview, it didn’t work for me on Safari for Windows but does on the Mac. Slightly odd.)

http://www.gravatar.com/avatar.php?gravatar_id=d41d8cd98f00b204e9800998ecf8427e&size=48&url= By Piers Cawley Sat, 23 Feb 2008 11:38:14 GMT

That’s because there was a problem with the way the javascript was served when you wrote your first post, but it got fixed in the second. Now I just need to tweak the javascript to keep the text entry box in the viewport at all times and I’ll be laughing…


  1. Catch me in the right mood and I can rant for ages about the awfulness of RoR’s routing system, but the bidirectional nature of them trumps almost all my complaints. It’s like Jamie Zawinski’s line that Java doesn’t have free. Everything else about the routing system can suck almost as hard as it likes, but two way routing is a win. ↩︎

  2. Not quite the snap it could be in Rails because rails is of the opinion that URL generation is something the controller does. Which is fine as far as it goes until you find yourself writing Tune#to_json without a controller in sight and you can’t change to_json=‘s signature because it's a standard method. And you just want to cry. =Model.include ActionController::UrlWriter is wrong, but so tempting… In fact I’ve succumbed, just to get the test to pass. I’m in the process of refactoring by introducing a UrlPolicy singleton that will do all that stuff and at least isolate the bits where my models get to play like controllers. ↩︎

  • 0 likes
  • 0 reposts
  • 0 replies
  • 0 mentions