REST APIs are extremely attractive for their design elegance. You get something that Google’s Adam Bosworth describes as “simple, relaxed, sloppily extensible,” but you do not get something built for consistent performance.
Existing REST frameworks do a great job of converting your domain model to a JSON or XML response. However, they operate under the assumption that each resource has a single document view. Every request for a resource returns the entire document, even if only a subset of the data is required for the request.
More importantly, each GET call in a REST API returns only one resource type. Therefore, a request that needs to aggregate data from multiple resource types will need to perform at least one request for each resource that comprises the desired result data. Think of a join query from the relational database world. Under a pure REST model, each row returned from the join would require its own GET request issued over the network. This level of chattiness quickly becomes a performance bottleneck in an Internet application.
What’s the Yoga Solution?
Yoga is an open-source toolkit that integrates with your existing REST server implementation, and allows you to customize your published API’s. It adds a selector to your GET requests which specifies exactly what data you expect to get from your request, like a column projection clause in the relational world. It also includes the ability to specify relational expressions that can aggregate multiple documents from different related resource types into a single response.
This is not a new concept. We’ve seen it before in published proprietary API’s, such as earlier versions of the LinkedIn API and Google’s GData specification. But unlike these API’s, Yoga allows you to introduce this syntax into your own Java REST application.
The remainder of this section will demonstrate use-cases where Yoga will improve application performance, while preserving the ease-of-use of REST syntax.
Specifying Resource Fields
Here is a typical REST request, for retrieving an instance of a resource. It will return all of the fields associated with the User resource type:
GET /user/1.json
What if, for security or performance concerns you want to generate a request that only returns name and location data for a given user? For development purposes, or publishing to trusted clients, you can append a selector to your request:
GET /user/1.json?selector=(id,name,city,state,country)
For publishing public API’s, you probably don’t want to give end-users unlimited selector capabilities. Here, you would simply publish an alias to the defined selector:
GET /user/1.json?selector=$locationView
Retrieving Multiple Resource Types
Now, consider a situation where you want to navigate your domain model’s object graph, and return data from multiple classes in a single API call:
The above graph reflects the data entity model utilized by a mobile or Javascript client which aggregates a number of entities on a single informational screen. One such API request issued by the client should return data about a user, his friends, which musical artists his friends like, and the albums and songs produced by those artists (this path in the graph is highlighted in maroon).
Concepts like User, Artist, and Song are distinct REST resources, so using the standard REST approach requires many distinct network calls:
GET /user/1.json (Get user) GET /user/2.json (Get detailed friend entities) GET /user/3.json ... GET /artist/1.json (Get favorite artists) GET /artist/2.json ... GET /album/1.json (Get albums for artists) GET /album/2.json ... GET /song/1.json (Get songs for albums) GET /song/2.json ...
Clearly, this does not scale as we traverse the depth of the object graph. Network latency will quickly become a performance bottleneck.
With selectors, we can specify all of the data we need, and retrieve it in a single request. For development and trusted clients, we can specify the selector explicitly:
GET /user/1.json?selector=friends(favoriteArtists(albums(songs)))
For production deployment of public APIs, we can publish an Alias:
GET /user/1.json?selector=$friendsFavoriteMusic
Implementing Yoga
Adding Yoga to your existing application requires minimal configuration changes. Yoga cleanly integrates with Spring MVC REST, Jersey, and RESTEasy. In the example below, we will implement Yoga in a Spring MVC REST application.
Our REST application uses Spring’s MappingJacksonJsonView to serialize responses:
<property name="defaultViews"> <list> <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView"/> </list> </property>
Our User Controller uses parameterizable URIs to process GET requests for the User resource, and Spring’s @ResponseBody annotation to render the output with the MappingJacksonJsonView:
@RequestMapping("/user/{id}") public @ResponseBody User get( @PathVariable long id ) { return _userRepository.fetchUser( id ); }
If we need more control over how we render the User documents, we can migrate from a REST application to a Yoga application. First, we import our Maven dependencies:
<dependency> <groupId>org.skyscreamer</groupId> <artifactId>yoga-core</artifactId> <version>1.0.0</version> </dependency> <dependency> <groupId>org.skyscreamer</groupId> <artifactId>yoga-springmvc</artifactId> <version>1.0.0</version> </dependency>
Next, we replace Spring’s MappingJacksonJsonView with the YogaSpringView, which knows how to parse selectors:
<property name="defaultViews">
<list> <bean class="org.skyscreamer.yoga.springmvc.view.YogaSpringView"p:yogaView-ref="jsonView"/>
<!--<beanclass="org.springframework.web.servlet.view.json.MappingJacksonJsonView"/>-->
</list>
</property>
The injected dependency of jsonView tells SpringMVC that Yoga will be processing JSON requests, and rendering JSON output. We define the jsonView in our application context:
<bean name="jsonView" class="org.skyscreamer.yoga.view.JsonSelectorView"
p:selectorParser-ref="selectorParser" />
<bean id="selectorParser" class="org.skyscreamer.yoga.selector.parser.GDataSelectorParser"/>
Here, we also specify that the GData specification’s syntax will be used for the selectors. Yoga ships with an alternate SelectorParser implementation, LinkedInSelectorParser, which can be used by developers who prefer the LinkedIn API’s selector format.
Finally, we remove the @ResponseBody annotations from our UserController. The @ResponseBody annotations are dependent on MappingJacksonJsonView, which is now replaced with the YogaSpringView.
@RequestMapping("/user/{id}")
public User get( @PathVariable long id )
{
return_userRepository.fetchUser( id );
}
At this point, the developer can launch the web application, and append the appropriate selectors to the resource requests.
GET /user/1.json?selector=id,name
renders the output:
{ "name": "Carter Page", "id": 1
}
The developer can add favoriteArtists to the selector:
GET /user/1.json?selector=id,name,favoriteArtists(id,name)
and navigate down the object graph to view instances of the Artist resource:
{ "id": 1, "name": "Carter Page", "favoriteArtists": [ { "id": 1, "name": "Arcade Fire" }, { "id": 3, "name": "Neutral Milk Hotel" } ] }
Core Fields
In our previous example, let’s assume the id and name fields of User are mandatory and must be returned for every User resource request. These are cheap, small, integral fields, and naming them explicitly each time we create a selector could get verbose.
Yoga provides a @Core annotation that can be applied to your serialized domain model (or DTO) to identify fields that will always be returned on a Yoga request. Here, we annotate the getter methods for our User domain object:
@Core public long getId() { return _id; } @Core public String getName() { return _name; }
Now, we no longer have to explicitly request id and name in our selector. The following request:
GET /user/1.json?selector=favoriteArtists(id,name)
will return id, name, and anything else specified in the selector:
{ "id": 1, "name": "Carter Page", "favoriteArtists": [ { "id": 1, "name": "Arcade Fire" }, { "id": 3, "name": "Neutral Milk Hotel" } ] }
Aliases
Iteratively refining your selectors makes for a rapid development/debug cycle. However, when you get to a Production deployment of your API, you may not want external users composing arbitrary selectors to run in your environment. Unrestricted navigation of your object graph can quickly lead to security and performance issues.
Yoga allows you to define aliases for the selectors you want to publish, and only allow users to invoke selectors that have defined aliases. Let’s say we are happy with the following selector and want to publish it:
?selector=id,name,favoriteArtists(id,name)
First, in our Production configuration we will disable the use of explicit selectors, so that users in that environment will not be able to compose selectors using the GData (or LinkedIn) syntax.
<bean id="selectorParser" class="org.skyscreamer.yoga.selector.parser.GDataSelectorParser" p:disableExplicitSelectors="true"/>
Next, we define the alias. Yoga provides several mechanisms for specifying aliases; in this case, we will define them in a properties file.
<bean id="aliasSelectorResolver" class="org.skyscreamer.yoga.selector.parser.DynamicPropertyResolver" p:propertyFile="classpath:selectorAlias.properties"/>
In the properties file, we set up the alias and name it. By convention, Yoga aliases begin with a $:
$userFavoriteArtists=id,name,favoriteArtists(id,name)
Now, in the Production environment, users are able to invoke the alias, whose behavior is defined in our API docs:
GET /user/1.json?selector=$userFavoriteArtists
Conclusion
For many developers, the REST model is sufficient for providing web API access to your application’s domain. If you need more fine-grained control over the structure of your document responses, Yoga will integrate with your existing REST application, and allow you to add selectors to your web requests.
This week, Skyscreamer Software has released version 1.0 of Yoga. Yoga integrates directly with Java applications that run Spring MVC REST, Jersey, or RESTEasy implementations. You can find a live video demonstration of the material in this article at this link.
About The Author
Corby Page has been writing software for cash and fun for 20 years. He focuses on Java solutions through his company ASP Methods, and he collaborates with Carter Page and Solomon Duskis as part of the open source initiative Skyscreamer Software.