the cure for URI construction in your Web API is URI-Templates

2010-05-04 @ 19:30#

regular readers know that i've been ranting a bit lately about Web APIs. i thought i'd take a moment to single out one of the more frustrating habits of Web API designers: too much dependence on URI construction.

as much as i squawk about the importance of using opaque identifiers in your media-type APIs, i know there are times when it seems "just natural" to include one or more URI construction rules to help clients access data using your Web API. it seems that almost everyone does it. there are several problems w/ URI construction rules. and the only benefits to them accrue to server programmers, not the folks who need to write clients against them.

finally, they're rarely needed at all and there is a simple replacement that has positive value for both server and client programmers: URI templates.

and this time i promise to be a bit more constructive and curb my "you-kids-get-off-my-porch-and-go-home" attitude. (i'm really a nice guy, ya know.)

you probably don't need them

as i've mentioned before, most all links should be supplied by the server with a media-type response and these links should be decorated with a link relation value that is well-documented and understood by the client:

  <link href="{...}" rel="user-list" />

any time the client sees a link with this relation value, then that client knows the URI can be used to retrieve a list of users. if, in the future, the application needs to change the way servers generate URIs for user lists or even has to migrate to a new server domain, any clients that understand the above rule will be just fine. nothing breaks, the Web is wonderful.

unfortunately, too often i see documentation that looks like this:

  // use the following URI convention to get a list of users 
  http://www.example.org/users/

client programmers code this URI into thier app (either directly or via config settings) and all goes fine until someone somehwere sometime in the future changes some part of the URI value on the server side and breaks all the pre-configured clients. ("See? That's why I hate writing clients for Web APIs!")

but sometimes you really want to give clients a hint on how to build their own URIs. maybe there are too many possibilties to cover with link relation values. maybe you need some kind of convention, right? ("Oh, my! This is blasphemy!")

you're probably halfway there already

the simple example i gave above was really a cheat. more commonly, i see Web API documentation that looks like this:

  // to get user-related content, use the following URIs:
  http://www.example.org/{user-id}/updates
  http://www.example.org/{user-id}/votes
  http://www.example.org/{user-id}/tags
  http://www.example.org/{user-id}/donations

this is essentially documenting a single URI template; one that has two variables: the {user-id} and a content keyword. this could be written as a template that clients can look for and populate based on pre-defined rules:

  <link-template href="http://www.example.org/{user-id}/{keyword}" rel="user-content" />

the important difference here is that clients can be coded to identify and resolve the link templates at runtime using existing data. even better, the server can change the details of each template and, as long as the rules for resolving link templates are not changed, future client applications will work just fine ("Oh, I get it now, you teach the client up front and 'future-proof' it against changes for even complex URI patterns!").

but that doesn't cover all the options, right?

while URI templating is a great way to add abstraction and resilience to your API design, it can't solve all your URI problems. there may be times when you want to allow some client applications to perform more generalized queries against your data service. in that case link templates and relation names can get to be pretty tricky:

  http://www.example.org/messages?d2={date-start}&d2={date-start}&
     c={category}&o={owner-user-id}&t={title-text}....." rel="query" />

you get the idea. it turns into a mess. and, in cases where you really do want to allow administrators or other high-access users to be able to do ad-hoc queries, it might be impossible to work out the details of a URI template that covers all the bases.

thankfully, there is a better way.

you can always use a query language

if you really want to open up your server to involved queries, it's best to document support for one or more standard query languages such as those supported by Google Base, YQL, OData, or even generic query languages such as SPARQL or T-SQL.

the best approach is to send the query as an encoded parameter within the URI:

  // refer to YQL documentation for details on how to construct user queries
  <link-template http://www.example.org/users?q={encoded-yql-statement}" rel="user-yql" />

instead, i see too many Web APIs trying to invent their own query language using URI construction rules that are a mix of URI segments, query parameters and other values that are difficult to document and a real pain to maintain over time:

  http://www.example.org/{containter-name}/{entity-name}?sort={direction}&
     filter={where-clause}&max={max-rows-returned}&page={page-number}...
OK, i kept this comment until last, just to annoy some readers:
in general, it's a dicey proposition to offer ad-hoc query options for your data service. you risk opening yourself up to incredibly inventive hackers looking to bash your data and you likely blast any cache-ability of your service if everyone is building their own requests on the fly each time. but sometimes, it's needed. and when it is, you need to make sure your server is resilient and your data is protected. if not, you'll rue the day you opened your data up for queries like this.

in summary

there is no need to use URI construction rules in your Web APIs. usually you just need to use well-documented link relation values in your media-type responses. in cases where URIs can have serveral options, use URI templates with appropriate link relation values for commonly-used requests. in cases where you want to support more complex and/or ad-hoc queries, support one of the many existing query languages instead of trying to invent your own URI-style ad-hoc queries.

see, that wasn't so bad, right?

REST