Designing URIs

Last modified

This page captures thoughts and ideas on how to design URIs for WOA/RESTful web-services and applications.  Technically, URI design is irrelevant as all URIs should be discovered through request-response exchanges between the user agent and the server.  However, in practice, well designed URIs have been conducive to good architecture.

Example 1: URIs for versioned multilingual documents

Problem statement: imagine having a model where you have a document identified by an docId that can have different translations (one of them being the default) and revisions (each translation has its own independent revision history).

Approach 1: using URI segments

One could model these resources in the following way.

Path
Meaning
/{docId} current version in default translation
/{docId}/versions list of versions in default translation (Q: should this redirect to /{docId}/translations/{lang}/versions using the default language?)
/{docId}/versions/{version} specific version for default translation (NOTE: this makes no sense since each translation has its own version list)
/{docId}/translations list of translations
/{docId}/translations/{lang} current version for a given translation
/{docId}/translations/{lang}/versions list of versions for a given translation
/{docId}/translations/{lang}/versions/{version} specific version for a given translation
Benefits
  • Usage is fairly straightforward.
Drawbacks
  • URIs are longer than for other approaches.
  • Has one degenerate case where a version of a document can be requested without setting the preferred translation first.
  • No mechanism for URI discovery, meaning the user agent has hard-coded knowledge of URI structure.
  • No mechanism for user agent to specify alternates if requested translation does not exist.  Instead the alternate translation appears to be determined by the server.

Approach 2: Using query parameters

Or, with the same expressive power, one might do:

Path
Meaning
/{docId} current version in default translation
/{docId}/versions list of versions in default translation (Q: should this redirect to /{docId}/versions?translation={default} using the default language?)
/{docId}?version={v} specific version for default translation (NOTE: this makes no sense since each translation has its own version list)
/{docId}/translations list of translations
/{docId}?translation={lang} current version for a given translation
/{docId}/versions?translation={lang} list of versions for a given translation
/{docId}?translation={lang}&version={v} specific version for a given translation
Benefits
  • URIs are shorter than Approach 1.
Drawbacks
  • Has one degenerate case where a version of a document can be requested without setting the preferred translation first.
  • No mechanism for URI discovery, meaning the user agent has hard-coded knowledge of URI structure.
  • No mechanism for user agent to specify alternates if requested translation does not exist.  Instead the alternate translation appears to be determined by the server.

Approach 3: Using Accept-Language header and query parameters

The Accept-Language header can be used by a user agent to request resources in various language and even provide hints at suitable alternatives when the requested language is not available.

Path
Meaning
/{docId} current version of preferred translation
/{docId}/versions list of versions in preferred translation
/{docId}?version={v} specific version for preferred translation
/{docId}/translations list of translations

For user agents that cannot provide a custom HTTP Accept-Language request header, the API could define an override query parameter (e.g. ?accept-language=...).

Benefits
  • URIs are shorter than Approach 1 & 2.
  • Leverages the built-in HTTP mechanism for negotiating the preferred translation with a user agent.
  • No degenerate case since server relies on the presence of the Accept-Language request header.
Drawbacks
  • No mechanism for URI discovery, meaning the user agent has hard-coded knowledge of URI structure.
  • Responses must include Vary response header to indicate proper caching behavior.

Addendum 1: URI discoverability

All above approaches fail to address one important aspect: URI discoverability.  Without the means for the user agent to "discover" the various URIs that can be used to interact with the document, the user agent must hard-code the URI design.  Such hard-coding prevents future changes the URI design or requires the server administrator to manage redirect from old designs to the latest design.

The solution is to return a hypermedia or XML document instead of the contents of the document for path /{docId}.  The returned document provides descriptions for the various URIs that a document can be requested by.

Path
Meaning
/{docId} hypermedia or XML document describing the resource

Let's assume for this example that the returned response is an XML document.  It may look as follows:

<doc translation="en">
  <link rel="contents">http://...</link>
  <link rel="versions">http://...</link>
  <link rel="translations">http://...</link>
</doc>
Benefits
  • Server remains in control of the URI design.
  • User agent is completly decoupled from server design up to the shared semantics of the hypermedia/XML document used to discover the other links.
Drawbacks
  • An additional request-response exchange is required to go from the /{docId} to the contents of the document.

Note: the request-response exchange could be avoided by defining a custom MIME type (e.g. application/x.doc-meta+xml) and using the Accept request header, where the default behavior for /{docId} would be to return the current verion of the translation specified by the Accept-Language header.

Page statistics
19595 view(s) and 21 edit(s)
Social share
Share this page?

Tags

This page has no custom tags.
This page has no classifications.

Comments

Attachments