Filtering a list of anything, as well as sorting and paging, requires the client to know which parameters must use. Furthermore, if some parameters are not compatible (like filters that are not supposed to be used together), the knowledge must be even deeper. This generates a coupling to the server, as any change on these parameters will have an impact in the client.
HATEOAS can be used to reduce the coupling between a client and a server, specifically in the way the client obtains resources, by following links. This can be applied in some scenarios for filtering, sorting and paging lists. As lists are resources, links for this operations can be provided.
Let's take as an example an API for selling cars. The client has the following URL /shop-api/cars, obtained through a link while navigating the API or previously bookmarked. This URL lists the first ten available cars for sale.
In order to sort the cars by price, the client must know which sorting parameters has to use, but with HATEOAS, the list resource can include a link for this:
{
"rel" : "sortedByPriceAsc",
"href" : "http://shop-api/cars?sortBy=price&sortDirection=ASC",
"title" : "Price: Low to High"
}
By locating the link with rel “sortedByPriceAsc” and following it, the client sorts the list by price ascending and never has to deal with sortBy and sortDirection parameters. If these parameters change (though in real cases a bit unlikely but possible) to something like sort=price-ASC, the client is not affected as it keeps following the same link now with the new URL:
{
"rel" : "sortedByPriceAsc",
"href" : "http://shop-api/cars?sort=price-ASC",
"title" : "Price: Low to High"
}
Once sorted, if the next ten cars are desired, the list resource will provide a link to get them, that not only handles pagination parameters but also keeps the navigation consistent, as a sort has been applied previously:
{
"rel" : "next",
"href" : "http://shop-api/cars?sort=price-ASC&page=2&pageSize=10",
"title" : "Next"
}
The client is not only decoupled from sorting and paging but also is released from keeping track of all parameters, already used, for further requests.
Filtering follows the same mechanism. In order to get only convertible cars, the list resource includes this link:
{
"rel" : "convertibles",
"href" : "http://shop-api/cars?category=convertible",
"title" : "Convertibles"
}
In all cases, the navigation coherence of the API is driven by the server. Once a sort, pagination or filter is applied, the obtained resource will provide links for further navigation, including any parameter already used, unless it is not compatible (like several sorts at the same time) which in that case is excluded from the link URL.
The tree operations, then, are transformed into following links, requiring the client only to know which one to follow. Also, incorporating any new filter or sort, is just a matter of using a new link.
Conclusion
At some extent, filtering, sorting and paging are withdrawn as operations for manipulating lists in the sense that there is no filter, what there is, is a link to a resource that turns out to be a "reduced" list of resources, or there is no sort, what there is is a link to a resource that turns out to be a "sorted" list of resources. The whole API navigation is limited to one mechanism and that is following links; lists are not special cases. The further the client can keep the interaction in this way, the less coupled will be.
The first concern regarding this approach is the proliferation of links. For each possible value in a filter, a link must be provided. Seems like a lot of links, and they are, but is this the negative enough to eclipse the benefits?
It could be argued that this proliferation of links increases the size of the response, but it's just text and can be efficiently compressed if need it (HTTP compression: gzip). Also programmatically speaking, building those links, at the server, doesn't present much of a challenge.
There are two scenarios (there always are) where this approach doesn't fit like a glove.
The first one is when the filter values are not discrete or there are too many options, making it unrealistic to offer a link for each one. A possible solution, that could work in some cases, is to offer links representing ranges, like for example price ranges.
The second scenario is the ability to apply several filters and a sort at the same time, in one interaction. This is the case where the user chooses several options at once and then triggers a search, expecting all criteria to be applied. The link concept here is not flexible enough for tackling the problem, as each link models the application of one criteria. Some possible solutions are to use URI templates in the href value of the link or to use forms (like HTML forms). In both cases, though the client would need to know how to complete those structures (generating certain level of coupling), some of the advantages, like decoupling part of the URL and navigation coherence, can still be obtained.
There is a third scenario worth to mention. If the client is an intermediary backend, that enhances or aggregates the API with more functionality, the hypermedia mechanism (at least for filtering, sorting and paging) looks a bit cumbersome, as it may have to define some sort of mapping between the links it offers in its API, and those provided by the API it uses. More brain on this in further posts as it seems to be a long discussion.
Not all the scenarios are the same and there is no unique solution for all of them. HATEOAS presents an elegant and simple solution for decoupling filters, sorts and paging operations for final API clients, reducing the knowledge they must have regarding parameters and how to combine them leveraging an easy interaction between the client and the server.