A RESTful API exposes a list of resource representations. This architecture often creates APIs that are quite close to the business model. While this can be fine to avoid duplication, it forces clients of the APIs to make several requests in order to get all the data they need to fulfill a given task. On mobile networks, where latency is constantly high, this is a big drawback. So how can you design an API following REST principles, without forcing the clients to make too many requests?
Let's take an example: a mobile bookstore application offers a search feature, where the user can look for books by author name. The mobile application uses a REST API provided by a bookstore web service, exposing data on authors and books. Here is the list of API calls the application needs to show the list of books written by authors named "Tol*":
The data set is simplified, but you get the idea: the number of HTTP requests necessary to build the response on the application side is proportional to the number of responses to the first request.
If the application was talking with a database, the solution would be simple: use a JOIN to retrieve data from the
book table associated with the results from the
author table. But this is REST, not SQL, and there is no such thing as join. What convention would you follow to allow denormalization of related results?
REST is not a normalized standard, and only answers partially to the requirements of web service design. There has been other attempts at normalizing data exchange between servers. One of them, backed by Microsoft, is called OData, or Open Data Protocol. OData goes way beyond the simple architectural principles of REST, and gives a convention to the solve the denormalization problem:
$expand. Examples from the OData documentation show this in practice:
$ prefix is another convention from the OData protocol to denote "System Query Options". It is widely used to provide filtering, ordering and more, somehow following the SQL path. For instance, an OData HTTP request can look like this:
http://services.odata.org/Northwind/Northwind.svc/Customers?$filter=tolower(CompanyName) eq 'foobar' &select=FirstName,LastName&$orderby=Name desc&$format=json
This is not a RESTful representation (note the
&$format=json), and it's probably exposing too much filtering options for a simple REST service. But the
$expand option fits the need.
Some true RESTful web services providers also offer an
expand option. NetFlix, for instance, uses it for title expansion. An example from the NetFlix documentation is shown below:
JIRA uses the same convention for its REST API:
expand convention to do a JOIN equivalent in a REST API seems like a good idea. So, for the bookstore example, that would give the following:
And now the mobile application only needs one HTTP request to retrieve all the necessary information to display the search results page.
Note that the expand parameter is not really a consensus. Some architects suggest using the Accept HTTP header to achieve the same goal.
You're probably aware that designing URIs like
/authors/1234/books isn't sufficient to make an API RESTFUL. To reach the glory of REST and get to level 3 of Richardson Maturity Model, you must achieve Hypertext As The Engine Of Application State (HATEOAS). Check David Zuelke's excellent presentation about RESTful web services for details.
Let's switch to XML as there is now a broadly accepted convention on implementing HATEOAS in this language (there is not yet one in JSON). It may be more verbose than JSON, but every client library can decode XML natively nowadays. The initial REST API, exposing resources using HTTP verbs, reaches Level 2 of the RMM:
HATEOAS suggests using links to allow clients to discover locations and operations. That way, URLs can change without breaking every client application.
author resource should therefore expose links about itself, and related resources:
expand fit in this syntax? Just like NetFlix, you should expand the
<link> tag using the same
rel attribute as the
expand parameter value, and put the related data inside it:
Note the use of the
<link rel="self"> tag even on
book to always reference the canonical URL for a given resource.
Expanding one sub-element is quite simple, but how would you ask for a list of authors embedding author books, reviews and sales on author books, and biographical data on authors? The
expand parameter can accept several fields separated by commas (","), and the object hierarchy can be traversed using dot notation ("."). So the request would look like:
With this convention, virtually every page of a mobile application can be reconstituted based on the response from one single request to the API, minimizing network traffic and latency. It's the API application responsibility to know what type of JOIN and
expand must be translated to.
REST and Mobile are not enemies. Instead of leaving REST to craft your own custom denormalized API for mobile applications, offer the
expand option. That way, both web and mobile may use the same API, with optimal performance. As for the implementation, as long as relationships between model objects are clearly defined in your code, it's a piece of cake. And if you happen to use an ORM or an ODM, offering the
expand options won't be a problem.
Published on 09 Aug 2012
with tags development rest