HATEOAS with Spring MVC REST

REST has been getting momentum for the last few years. Roy Fielding was the first to come up with the term « REST » in the 5th chapter of his thesis. When describing REST, we usually say this style of architecture builds on top of 5 principles: resources, uniform interface, representation, stateless architecture, hypermedia. Today we’ll study the hypermedia principle and see how to implement HATEOAS – hypermedia as the engine of application state – with Spring MVC.

HATE… what?

HATEOAS! As a developer you have perhaps no idea of what HATEOAS is. Even worse, your children or your grandmother are probably HATEOAS-ing everyday, assuming they surf on the internet. To make it simple, HATEOAS is just about clicking on links. Behind this simple definition, a very powerful paradigm is hiding. The application (e.g. website) presents the next choices the user has thanks to links in the page (i.e. responses). When you want to cancel or confirm your order, you click on the corresponding link, when you want to see the next page of results, you click on the « next » link. You don’t have to guess how to get to the next step. In this case, guess would mean directly typing the URL in the address panel of your browser.
This explanation is enough to understand this article, but keep in mind HATEOAS is an essential part of the design of a REST application.

HATEOAS in my applications

Many people are using REST to expose web services to their applications. This is the case of Github and Twitter among others. So the clients of a REST application will be other applications. How can a client know which URL, which HTTP method to call? There are usually 2 solutions: the client knows the REST interface by advance or the client is smart enough to follow links.
For the first solution to work, the REST application provides a description of its interface. This is handy and quite common.
For the second solution to work, the client must be quite smart. Some conventions or even standards to expose links also exist (ATOM and its link tag is an example).
Real-world applications are usually based on a combination of both solutions.

An example, please

Imagine a list of resources (e.g. persons). The interface is a GET on /persons and the representation is JSON. The following representation doesn’t comply to HATEOAS:

[
   {
      "id":"1",
      "firstname":"Joe",
      "lastname":"Dalton",
   },
   {
      "id":"2",
      "firstname":"William",
      "lastname":"Dalton",
   },
   {
      "id":"3",
      "firstname":"Jack",
      "lastname":"Dalton",
   },
   ... more resources
]

This is a list and each record is only a summary of the underlying resource. How to get to the details of a resource? Well, this is easy if we know about conventions: for the list, GET on /persons, so for the detail, GET on /persons/{id}.
Something more HATEOAS-compliant would be:

[
   {
      "id":{
         "rel":"self",
         "href":"http://localhost:8080/hateoas/zen-contact/contacts/1"
      },
      "firstname":"Joe",
      "lastname":"Dalton",
      "links":[
         {
            "rel":"self",
            "href":"http://localhost:8080/hateoas/zen-contact/contacts/1"
         }
      ]
   },
   {
      "id":{
         "rel":"self",
         "href":"http://localhost:8080/hateoas/zen-contact/contacts/2"
      },
      "firstname":"William",
      "lastname":"Dalton",
      "links":[
         {
            "rel":"self",
            "href":"http://localhost:8080/hateoas/zen-contact/contacts/2"
         }
      ]
   },
   {
     "id":{
         "rel":"self",
         "href":"http://localhost:8080/hateoas/zen-contact/contacts/3"
      },
      "firstname":"Jack",
      "lastname":"Dalton",
      "links":[
         {
            "rel":"self",
            "href":"http://localhost:8080/hateoas/zen-contact/contacts/3"
         }
      ]
    }
   ... more resources
]

Note in this second representation that each resource summary exposes the ID as a URI (better and more RESTfull than a simple integer, even if a the row ID is implemented as a database sequence) and some links. There’s only one link in the links list and it leads to the resource detail (it’s the same URI as the ID).

HATEOAS with Spring MVC and Spring HATEOAS

Spring MVC is a popular Java web framework and provides REST support since Spring 3.0 (December 2009). It doesn’t provide support for HATEOAS per se, but makes it quite easy to implement. Let’s start from a simple controller and see how to add HATEOAS.

The first version of the web controller

Here is a web controller that returns entities straight from the database:

@Controller
@RequestMapping("/contacts")
public class ContactController {
  @Autowired ContactRepository contactRepository;
  @RequestMapping(method=RequestMethod.GET)
  @ResponseBody
  public List<Contact> contacts() {
    List<Contact> contacts = contactRepository.findAll();
    return contacts;
  }
  @RequestMapping(value="/{id}",method=RequestMethod.GET)
  public Contact contact(@PathVariable Long id) {
    Contact contact = contactRepository.findOne(id);
    return contact;
  }
}

The controller has one method to return the list of contacts and one method for the detail of a contact. Our implementation works and is quite straightforward, but as the contacts method returns directly instances of Contact, there’s no link in the generated representation.
To make this more HATEOAS-compliant, we will use a wrapper for Contact instances to limit the fields exposed and also add links. We will only change the contacts method (for the list), the contact method will remain the same.

Wrapping the contacts

We don’t want to expose all the fields of our contacts in the list. This would make the representation way to large and waste some band
width. We will wrap Contact into instances of the following class:

// inside the controller class...
public static class ShortContact extends ResourceSupport {
  private String firstname,lastname;
  (...)
}

Note the ShortContact extends the ResourceSupport class from Spring HATEOAS. Spring HATEOAS is a brand new project of the Spring portfolio and aims at providing support for HATEOAS in Spring MVC. It’s still in a early stage of development, but I decided to use it in this article. What does the ResourceSupport bring us? It allows to add links to the object and declares also a id property, which is itself a link. Nothing much, but less work for us. But what is a link?

Spring HATEOAS and links

Spring HATEOAS declares a Link class. A link is an URL (href property) and a qualifier for the link (rel) property. The Link class follows the semantics of the link tag from ATOM.
So once I have a ShortContact instance, I can easily add links to it:

import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;
...
ShortContact resource = new ShortContact();
Link detail = linkTo(ContactController.class).slash(contact.getId()).withSelfRel();
resource.add(detail);

One can create a Link instance by calling the default constructor, but Spring HATEOAS provides some support to automatically create and map the link to a given controller (linkTo static method that grabs the RequestMapping annotation on the controller). Note also the method to add a slash to the URL followed by a value (slash method) and another method to qualify the link (withSelfRel method). We end up with an URL that matches the following URI template: /contacts/{id}.

The entire controller

Here is the code of the controller:

@Controller
@RequestMapping("/contacts")
public class ContactController {
  @Autowired ContactRepository contactRepository;
  @RequestMapping(method=RequestMethod.GET)
  @ResponseBody
  public List<ShortContact> contacts() {
    List<Contact> contacts = contactRepository.findAll();
    List<ShortContact> resources = new ArrayList<ShortContact>(contacts.size());
    for(Contact contact : contacts) {
      ShortContact resource = new ShortContact();
      resource.setFirstname(contact.getFirstname());
      resource.setLastname(contact.getLastname());
      Link detail = linkTo(ContactController.class).slash(contact.getId()).withSelfRel();
      resource.add(detail);
      resources.add(resource);
    }
    return resources;
  }
  @RequestMapping(value="/{id}",method=RequestMethod.GET)
  public Contact contact(@PathVariable Long id) {
    Contact contact = contactRepository.findOne(id);
    return contact;
  }
}

Each record in the list will be sent to the client like this:

{
     "id":{
         "rel":"self",
         "href":"http://localhost:8080/hateoas/zen-contact/contacts/3"
      },
      "firstname":"Jack",
      "lastname":"Dalton",
      "links":[
         {
            "rel":"self",
            "href":"http://localhost:8080/hateoas/zen-contact/contacts/3"
         }
      ]
 }

The client will then be able to follow the link to get to the detail of the contact. The client won’t have to guess.

Summary

HATEOAS is one of the building block of REST. We showed in this post how to implement HATEOAS with Spring MVC and some help from the brand new Spring HATEOAS project. I hope this would give some ideas to make your REST applications even more RESTfull, easier to use for your clients, and more interoperable. Another post will follow to apply HATEOAS to paging, with Spring MVC, Spring Data JPA, and Spring HATEOAS.
Source code

2 pensées sur “HATEOAS with Spring MVC REST

  • 15 juin 2012 à 13 h 13 min
    Permalink

    Very interesting Arnaud.
    Thanks for sharing it.

    Répondre
  • 4 décembre 2012 à 21 h 03 min
    Permalink

    Thank you, a great kick start for one who likes to implement HATEOAS style web layer.
    I do have a question though, As far as I can tell with this kind of implementation if the client asks for a client, it should be ready the get back a single contact with the form of:

      {      "id":"1",      "firstname":"Joe",      "lastname":"Dalton",   }

    But when asking for multiple contacts getting a different structure (for each single contact). This means that the client should now be able to get 2 types of contacts? Meaning it should have 2 types of JSON to Model converters (I’m guessing.. I’m not so familiar with client side development), causing some overhead. Do I get it right?

    Répondre

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

%d blogueurs aiment cette page :