Skip to content

Instantly share code, notes, and snippets.

@dyoder
Last active April 23, 2016 04:10
Show Gist options
  • Save dyoder/178b6896391bffe7ca7b9c0b636d415d to your computer and use it in GitHub Desktop.
Save dyoder/178b6896391bffe7ca7b9c0b636d415d to your computer and use it in GitHub Desktop.

An effective way to think about URLs as identifiers is to just imagine that we've got a URL from prior GET request.

First, just for kicks, let's put Fairmont's reactive power to work to write a helper so we can get the parsed body from responses to GET requests.

get = async (url) ->
  JSON.parse yield collect flow [
    stream http.get url: url, headers: accept: "json"
    map toString
    reduce add, ""
  ]

Okay, moving on, let's make a request to the Acme, Inc. API:

data = yield get "http://api.acme.com/"
console.log "Link to people: #{data.feed.url}"

Our code doesn't know anything about the URL. It's just a property of an object.

According to the Acme, Inc. API docs, we can narrow a feed to a specific person by simply appending nick and then a nickname. We figure the developers at Acme, Inc. must provide other ways we can query the API. To avoid name collisions with API urls and nicknames, they just include a qualifier, like nick, for the type of query we're making.

data = yield get "#{data.feed.url}/nick/#{nickname}"
console.log post for post in data.posts

Pretty cool! Okay, now we want to just display the posts since I last viewed them. We consult the Acme, Inc. API docs and this works similarly.

data = yield get "#{data.feed.url}/since/#{data.timestamp}"
console.log post for post in data.posts

Okay! Now we want to combine these two features and see only a given person's post since I last viewed them. No problem:

data = yield get "#{data.feed.url}/nick/#{nickname}/since/#{data.timestamp}"
console.log post for post in data.posts

According to the documentation, we can put these in any order, so we don't have to bother remembering the order.

data = yield get "#{data.feed.url}/since/#{data.timestamp}/nick/#{nickname}"
console.log post for post in data.posts

This is very clever, except, in thinking about it, we realize that the Acme, Inc. developers have just re-invented query parameters. We could have simply written this as:

data = yield get "#{data.feed.url}?since=#{data.timestamp}&nick=#{nickname}"
console.log post for post in data.posts

What's nice about this, of course, is that query parameters already don't care about the order, so the Acme development team could have saved themselves some trouble.

This is nice and all, but it's basically keyword arguments for URLs, right? Well, yes, but it turns out that this is also related to the whole idea of URL opacity. The crucial thing here is that we don't know what's in data.feed.url. It might itself contain query parameters. (I'm ignoring for a moment the fact that our construction above assumes that it doesn't. Otherwise, I'd have to include code to properly concatenate query parameters to a URL.)

The implication of this is that if you don't have query parameters, everyone needs to add them to their URL parsing in order to be able to append qualifiers to a URL, as we did in the code above. If the order of the path components matters, we're at risk of placing them out of order when we append components ot it. So either client needs to parse the URL and reassemble it in the correct order, or the server needs to be able to parse the components as key-value pairs.

The one weird thing is that you can't append query parameters without first determining if a URL already has a query string. I think the reason for this is so that query parameters can themselves contain URLs, but it's a bit odd, nonetheless. Anyway, that's much simpler than what would have otherwise happened. (I mean, judging from past history, we'd just have ended up with URLs where you couldn't append to them.)

The benefit of being able to append arguments is basically the same as being able to construct one URL from another, that is, obtain a reference to a resource from another such reference. Which is really what we were doing above.

I think this is super-confusing to people, because you can't do this with normal identifiers. For example, you can't use a database ID to derive a second ID to another row in another table. But that's also arguably the genius of the whole thing. Not only do URLs piggy back off DNS to implement a lookup to find the physical location of a given resource, but you can use them to derive other URLs, all without ever needing to know the (structure of the) original URL.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment