Spring hateoas example


Goal of this example

Spring HATEOAS is the de facto standard to generate Hypermedia controls aware REST API using Spring MVC. Reading the official getting started makes the impression that domain from which json is generated has to extend HATEOAS specific ResourceSupport class, which is something I don’t like. Actually this is not the case, this example demonstrates multiple options to generate links and resources in a better way

Options to generate resources

Resuource Inheritence Encapsulation
POJOS without HATEOAS dependency Yes No Yes
No need for “Wrapper” classes Yes No No
List of resources supported out of the box Yes No No

Options to generate links

Reference entity Reference Controller Build URL
Automatic link generation Yes Yes No
Controllers don’t need to know about each other Yes No Yes
Compile time validation of links No Yes No

The two tables indicate that the best is to use Resources and build links by referencing entities or controllers. Interestingly 2 out of 3 techniques are not covered by the getting started guide.

Technology Used

  • Spring boot 1.2.7.RELEASE
  • Spring HATEOAS 1.16.0.RELEASE

A quick summary of what is HATEOAS

A well designed REST interface is built on

  1. Resources
  2. HTTP verbs
  3. Hypermedia controls.
  4. Optionally embedded resources

Hypermedia control is a fancy name for http links embedded into a resource to refer an another resource, something link this :

{  
   id:1,
   firstName:"Peter",
   lastName:"Test",
   _links:{  
      self:{  
         href:"http://localhost:8080/api/customer/1"
      },
      invoice:{  
         href:"http://localhost:8080/api/invoice/customer/1"
      },
      all-invoices:{  
         href:"http://localhost:8080/api/invoice"
      }
   }
}

Embedded resource means a resource (properties + links) embedded into the json. By convention under the “_embedded” property. Something like this :

{  
   _links:{  
      self:{  
         href:"http://localhost:8080/api/customer"
      }
   },
   _embedded:{  
      customerList:[  
         {  
            id:1,
            firstName:"Peter",
            lastName:"Test",
            _links:{  
               self:{  
                  href:"http://localhost:8080/api/customer/1"
               },
               invoice:{  
                  href:"http://localhost:8080/api/invoice/customer/1"
               },
               all-invoices:{  
                  href:"http://localhost:8080/api/invoice"
               }
            }
         },
         {  
            id:2,
            firstName:"Peter",
            lastName:"Test2",
            _links:{  
               self:{  
                  href:"http://localhost:8080/api/customer/2"
               },
               invoice:{  
                  href:"http://localhost:8080/api/invoice/customer/2"
               },
               all-invoices:{  
                  href:"http://localhost:8080/api/invoice"
               }
            }
         }
      ]
   }
}

Why is it good?

  • Helps understanding the API
  • UI can be data driven

The primary goal of Spring HATEOAS is to generate these links, but a prerequisite is to have resources first.

Generating resources

Options are the following

Doman object with HATEOAS dependency (Inheritance)

This is the solution described in the getting started :

Customer.java

public class Customer extends ResourceSupport {
private int customerId;
private String firstName;
private String lastName;

}

Generating links is easy because the domain object inherits the add() method from ResourceSupport
CustomerController.java


@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public Customer getCustomer(@PathVariable int id) {

return addLinks(customerService.getCustomer(id));

}

private static Customer addLinks(Customer customer) {
Link selfLink = linkTo(methodOn(CustomerController.class).getCustomer(customer.getCustomerId())).withSelfRel();
Link invoiceLink = linkTo(methodOn(InvoiceController.class).getInvoiceByCustomerId(customer.getCustomerId())).withRel("invoice");

customer.add(selfLink);
customer.add(invoiceLink);

return customer;

}

PROS :

  • Easy to implement

CONS :

  • extending ResourceSupport might not be possible if an another class is already extended
  • extending ResourceSupport might not be possible if we can’t modify the domain objects
  • extending ResourceSupport adds a dependency on Spring HATEOAS that might be not ideal for example when domain objects are shared among projects or layers

Create wrapper classes (Encapsulation)

This solution uses a wrapper class that extends ResourceSupport and wraps the domain object through an instance variable.

CustomerResource.java

public class CustomerResource extends ResourceSupport {

@JsonUnwrapped
private Customer customer;

public Customer getCustomer() {
return customer;
}

public void setCustomer(Customer customer) {
this.customer = customer;
}
}

The @JsonUnwrapped annotation will “flatten” the customer class and generate all of it’s properties on the same level as the properties of the wrapper class.

Generating the links is just a little bit more complex, we need to instantiate the wrapper class separately and set the domain class. Spring HATEOAS provides a convenience class that converts domain classes to wrapped classes.

CustomerResourceAssembler.java

public class CustomerResourceAssembler extends ResourceAssemblerSupport<Customer, CustomerResource> {

public CustomerResourceAssembler() {
super(CustomerController.class, CustomerResource.class);
}

@Override
public CustomerResource toResource(Customer customer) {
CustomerResource customerResource = createResourceWithId(customer.getId(), customer);

Link invoiceLink = ControllerLinkBuilder.linkTo(methodOn(InvoiceController.class).getInvoiceByCustomerId(customer.getId())).withRel("invoice");

customerResource.setCustomer(customer);
customerResource.add(invoiceLink);

return customerResource;
}

}

Then the assmebler can be called by the controller

CustomerController.java


@RequestMapping(value = "{id}", method = RequestMethod.GET)
public CustomerResource getCustomer(@PathVariable int id) {

return customerToResource(customerService.getCustomer(id));

}

private CustomerResource customerToResource(Customer customer) {

return customerResourceAssembler.toResource(customer);
}

PROS :

  • Domain classes are independent of Spring HATEOAS

CONS :

  • Need to create an extra wrapper for every domain class

Use the Resource and Resources class


Spring HATEOAS provides two classes : Resource and Resources. They are quite similar to the hand written wrapper class in the previous example, except for they use generics and contains a few more methods.

CustomerController.java


@RestController
@RequestMapping(value = "/api/customer", produces = "application/hal+json")
public class CustomerController {

@Autowired
private CustomerService customerService;

@Autowired
EntityLinks entityLinks;

@RequestMapping(method = RequestMethod.GET)
public Resources<Resource<Customer>> getCustomers() {

return customerToResource(customerService.getCustomers());

}

@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public Resource<Customer> getCustomer(@PathVariable int id) {

return customerToResource(customerService.getCustomer(id));

}

private Resources<Resource<Customer>> customerToResource(List<Customer> customers) {

Link selfLink = linkTo(methodOn(CustomerController.class).getCustomers()).withSelfRel();

List<Resource<Customer>> customerResources = customers.stream().map(customer -> customerToResource(customer)).collect(Collectors.toList());

return new Resources<>(customerResources, selfLink);

}

private Resource<Customer> customerToResource(Customer customer) {
Link selfLink = linkTo(methodOn(CustomerController.class).getCustomer(customer.getId())).withSelfRel();

Link allInvoiceLink = entityLinks.linkToCollectionResource(Invoice.class).withRel("all-invoice");
Link invoiceLink = linkTo(methodOn(InvoiceController.class).getInvoiceByCustomerId(customer.getId())).withRel("invoice");

return new Resource<>(customer, selfLink, invoiceLink, allInvoiceLink);

}
}

PROS:

  • No need for extra wrapper class
  • Single resources and list of resources are supported out of the box

Generating links

Spring HATEOAS provides the following options to generate links

Referencing controllers

The real power of Spring HATEOAS lies in the way links are generated. Given a controller having a method with standard @RequestMapping annotation

InvoiceController.java

@RestController
@RequestMapping(value = "/api/invoice", produces = "application/hal+json")
public class InvoiceController {

@Autowired
private InvoiceService invoiceService;

@RequestMapping(method = RequestMethod.GET, value = "/customer/{customerId}")
public Resources<Resource<Invoice>> getInvoiceByCustomerId(@PathVariable int customerId) {

Link selfLink = linkTo(methodOn(InvoiceController.class).getInvoiceByCustomerId(customerId)).withSelfRel();

return invoiceToResource(invoiceService.getInvoiceByCustomerId(customerId), selfLink);

}

A link can be generated by calling it’s method with the actual parameters.

Link invoiceLink = linkTo(methodOn(InvoiceController.class).getInvoiceByCustomerId(customer.getId())).withRel("invoice");

PROS:

  • Links are guaranteed to be valid due to compile time checking

CONS :

  • Controllers need to know about each other

Referencing entities

A different approach is to indicate that a controller produces a domain object using the @ExposesResourceFor annotation like this :

InvoiceController.java

@RestController
@ExposesResourceFor(Invoice.class)
@RequestMapping(value = "/api/invoice", produces = "application/hal+json")
public class InvoiceController {

@Autowired
private InvoiceService invoiceService;

@RequestMapping(method = RequestMethod.GET, value = "/customer/{customerId}")
public Resources<Resource<Invoice>> getInvoiceByCustomerId(@PathVariable int customerId) {

Link selfLink = linkTo(methodOn(InvoiceController.class).getInvoiceByCustomerId(customerId)).withSelfRel();

return invoiceToResource(invoiceService.getInvoiceByCustomerId(customerId), selfLink);

}

Then a link can be generated by referencing the domain object’s class

Link allInvoiceLink = entityLinks.linkToCollectionResource(Invoice.class).withRel("all-invoices");

PROS:

  • Controllers don’t need to cross reference each other

CONS:

  • There is no compile time checking if the link is really valid
  • It becomes difficult to track which method will be actually called by a link
  • This kind of referencing is less flexible with parameters

Bonus

If you managed to read so far, here is your bonus : have you ever wondered what HATEOAS means? The solution is “Hypertext As The Engine Of Application State”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: