Open Credo

November 1, 2015 | Microservices

Implementing HAL hypermedia REST API using Spring HATEOAS

To use or not to use hypermedia (HATEOAS) in a REST API, to attain the Level 3 of the famous Richardson Maturity Model. This is one of the most discussed subjects about API design.
The many objections make sense (“Why I hate HATEOAS“, “More objections to HATEOAS“…)
. The goal of having fully dynamic, auto-discovering clients is still unrealistic (…waiting for AI client libraries).

However, there are good examples of successful HATEOAS API. Among them, PayPal.

WRITTEN BY

Lorenzo Nicora

Lorenzo Nicora

Implementing HAL hypermedia REST API using Spring HATEOAS

The real benefit

Often literature points out the ability to include links to related resources, but the really revolutionary idea is the ability to include stateful links: Links depending on the state and the business workflow; link depending on some business rule.

Ideally, all the business logic should reside on the server, but often you have to move partly into the client.
Imagine an e-commerce platform: Orders can be cancelled only when ‘pending’ or ‘paid’, but not when ‘dispatched’ and so on…
How could the client application decide when to show the ‘Cancel’ button? It has to embed some business logic.
Something like…

if ( order.status == PENDING || order.status == PAID ) {
 showCancelButton
}

What happens if the process changes? A new status allows cancelling the order (e.g. ‘preparing’).
The client code has to be changed, tested, redeployed or redistributed (painful for native mobile apps). The old clients are no more compatible.

A hypermedia API may include a link to a ‘cancel’ action (a link with rel = ‘cancel’ or something similar).
The server decides when adding the link. The client has to know the meaning of ‘cancel’ rel (must be documented) and displaying the button when that link is there: No business logic. If the business logic changes, the client requires no change.

Hypermedia formats and frameworks

Among the number of emerging hypermedia formats, HAL (Hypermedia Application Language) is becoming a de-facto standard.

If you are using Spring MVC for developing a REST API and plan to introduce hypermedia support with HAL, using Spring HATEOAS would make sense. The Spring HATEOAS project is still immature (as the 0.xx version express) despite being started in 2012, but this is the same with the HATEOAS concept itself. Most importantly, the usage patterns are still not well defined. The examples are trivial and use the library in different ways.

Logical Resource model, from HAL specifications

Logical Resource model, from HAL specifications.

 

A Resource in HAL may contain:

  • Embedded related resources; properties of embedded resources are included in the representation
  • Links to related resources (possibly representing operations); contains the URI of the linked resource and a rel that define the meaning

The sample application

Trying to find a good pattern for using Spring HATEOAS, I wrote a sample application available here: https://github.com/opencredo/spring-hateoas-sample.
The goal was to add some real world features – though in a simplified way – often not included in trivial examples you may find googling, and having them implemented using Spring HATEOAS.

  • Spring Boot application
  • REST Spring MVC + Spring HATEOAS
  • HAL-compliant API. Specifically using both links and embedded resources
  • Use different resource archetypes: Documents, Collections and Controllers (actions that don’t fit HTTP verbs).
  • Add a discovery page (AKA index or docRoot)
  • Give the client the option to switch between different representation flavour (beyond content-type) using query parameters; e.g. simple or detailed resource.
  • Integration tests leveraging Spring Boot support, including testing link traversal.

The Domain

Having a little imagination, I used the most abused example: A library; a collection of books, classified by author and publisher. A fictional librarian may add books to the collection, buy new copies that will be borrowed and returned.

Setup

To use Spring HATEOAS in a Spring Boot application, add:

  1. Spring Boot parent dependency (org.springframework.boot:spring-boot-starter-parent:1.2.6.RELEASE)
  2. Spring Boot HATEOAS starter (org.springframework.boot:spring-boot-starter-hateoas)

My project also include the Spring Data JPA – Spring Boot starter and H2 database, for a simple persistent layer. I also added Spring Boot Actuator. But these are not strictly required by the API implementation.

JSON-Path (com.jayway.jsonpath:json-path) and Rest-Assured (com.jayway.restassured:rest-assured:2.5.0) are for integration tests.

Design notes

One of the values of Spring HATEOAS is trying to reduce the tangling between controllers and resource classes,  common in hypermedia API implementations. I have implemented ResourceAssemblers, to move the knowledge on how to build and link resources out of the Resource and Controller implementations. To keep link and rel definitions in a single place I leveraged @ExposeResourceFor and @Relation annotations, RelProvider and EntityLinks to generate links.

The Resource Assemblers implement methods to build resources in all the required flavours.
The base ResourceSupport had to be extended, to add support for embedded resources. Surprisingly enough this is not provided out of the box by Spring HATEOAS.

Resources

Resources are simple POJOs, extending ResourceSupport (actually, my extended version with support for ’embeddeds’). This is required by the custom Jackson serialisation used by Spring HATEOAS.

@Relation(value="author", collectionRelation="authors")
public class AuthorResource extends ResourceWithEmbeddeds {
  private final String firstName;
  private final String lastName;

  @JsonCreator
  public AuthorResource(
    @JsonProperty("firstName") String firstName,
    @JsonProperty("lastName") String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
  }
  // ...accessors omitted..   
}

Note the @Relation annotation: It defines the property name when embedded and the rel when linked.

The Controller

The Controller methods are as simple as possible, just implementing the simple logic to interface with the simplistic repository layer. In a more realistic application this logic should be handled by a service layer wrapping integration with any external ‘service’ (persistence, backend services, etc…).

@RestController @RequestMapping("/authors")
@ExposesResourceFor(AuthorResource.class)
@Transactional
public class AuthorController {
  // ...dependecies omitted...

  @RequestMapping(value="/{author_handle}", method=RequestMethod.GET)
  public ResponseEntity showAuthor(@PathVariable("author_handle") final String authorHandle){
    final Author author = entityOrNotFoundException(authorRepository.findOneByHandle(authorHandle) );
    final AuthorResource resource = authorResourceAssembler.toResource(author);
    return ResponseEntity.ok(resource);
  }
}

Note that Controllers are annotated with @ExposesResourceFor. It enables EntityLinks generating ‘singular’ and ‘plural’ links.
EntityLinks and ControllerLinkBuilder somehow overlaps in their goal, but are slightly different. EntityLinks generates link to single Document and Collections, ControllerLinkBuilder may link any other Controller method. So you may decide to use only the latter or both.

@Transactional annotations are just a consequence of using Spring Data JPA not wrapped in a service layer. They are not related to the API implementation.

Sample responses

This is a sample response representing a Book with links to related resources (http://localhost:8080/books/0321127420, GET)

{
   "isbn": "0321127420",
   "title": "Patterns of Enterprise Application Architecture",
   "available": 1,
   "_links": {
       "self": {
           "href": "http://localhost:8080/books/0321127420"
       },
       "purchase": {
           "href": "http://localhost:8080/books/0321127420/purchase"
       },
       "borrow": {
           "href": "http://localhost:8080/books/0321127420/borrow-a-copy"
       },
       "return": {
           "href": "http://localhost:8080/books/0321127420/return-a-copy"
       },
       "authors": [
           {
               "href": "http://localhost:8080/authors/martin_fowler"
           },
           {
               "href": "http://localhost:8080/authors/kendall_scott"
           }
       ],
       "publisher": {
           "href": "http://localhost:8080/publishers/2"
       }
   }
}

In this example you may see both the power and the weakness of HAL hypermedia implementation.

A client may follow links to related resources, without hard wiring URIs in the client code.
The backend may include some links conditionally, depending on the state and the business rules. In this example, the ‘borrow’ link is included only if the library has at least one copy of the book.

The weak point: The client developer has to know both the semantics of ‘borrow’ and what to do with it (which http method? what payload?…). These details have to be documented in a human readable way. HAL gives no guidance about that.
Other hypermedia specifications try to give an answer. Siren is an example. There is a pending request of adding support for Siren to Spring HATEOAS.

CURIEs

Compact URIs (CURIEs) are an interesting optional feature of HAL (not implemented in the sample application).
The main goal is to have fully qualified, globally unique, user defined rels (using URLs) and not having to repeat it on every use.
They are also a good place for documenting the rels.

The following example come from HAL specifications:

{
    "_links": {
        "self": { "href": "/orders" },
        "curies": [{  
           "name": "ea", 
           "href": "http://example.com/docs/rels/{rel}", 
           "templated": true 
        }],
        "next": { 
           "href": "/orders?page=2" 
        },
        "ea:find": {
           "href": "/orders{?id}", 
           "templated": true
        }
    },
...
}

Standard IANA rels (‘next’ in this example) do not need to be fully qualified. The custom ‘find’ rel has to.

http://example.com/docs/rels/{rel} is just a unique identifier, but it is also a good place to publish documentation pages for all the defined rels.

Testing

An important part of API testing is validating the links. Spring HATEOAS provide the Traverson class, inspired by the Traverson Javascript library, to test link traversal.

The following is part of an Integration Test, leveraging Spring Boot support for starting the full application context, containerless.

@Test
public void canTraverseBookPublisherLink() throws Exception {
  final ParameterizedTypeReference<Resource> 
      resourceParameterizedTypeReference =
      new ParameterizedTypeReference<Resource>() {};
  final Resource actual = 
      new Traverson(new URI(
         "http://localhost:" + port + "/books/0596007736"), 
         MediaTypes.HAL_JSON)
      .follow( "publisher" )
      .toObject(resourceParameterizedTypeReference);
  assertNotNull(actual.getContent());
  assertEquals("O'Reilly", actual.getContent().getName());
}

What’s missing

To keep the scope simple, some real-world features are missing:

  • CURIEs support. Spring HATEOAS support for CURIEs is not fully working, at least in the version I used.
  • API Documentation. Most of the API documentation tools lack of support for hypermedia (see this post: “Documenting REST APIs“).
    I experimented with Swagger, through SpringFox, but the result was very disappointing.
    Also, the common way of documenting APIs, supported by most of the tools, is URI-centric. This drives the client developer to hardwire URIs in their code.
  • Full error handling. Only the ‘resource not found’ is handled in the example, though Spring Boot has basic REST error handling by default.
  • Input validation. It could be easily added using Spring MVC validation support, annotating request resources, checking BindingResult and returning an appropriate status code along with some error details, in response headers or body.
  • Security. The API implements no security at all.

Spring HATEOAS Limitations

Currently, Spring HATEOAS has some limitations.

The current stable Spring Boot version (1.2.6-RELEASE) supports Spring HATEOAS version 0.16-RELEASE. This is a very old version. Replacing it in the newest 0.19-RELEASE (or 0.20-SNAPSHOT) is tricky, for some known compatibility issues.

The support for embedding resource is limited. You have to add support for it. I got inspired by this answer.

No support for templated links (e.g. http:/localhost:8080/books/{isbn}/borrow, where {isbn} is a variable to be filled by client).
About templated links: One could argue that they break the REST principles; Resources must (…should…) be identified by the FULL URI, not part of it (a ID or something similar). So it would not be correct to add templated link to resources.

Building links and rels have a lot of space for improvement. I tested different patterns, but none gave sufficient separation of concerns. As a result, the Controller+Resource(+Resource Assembler) layer is a bit entangled. But if you are using a microservice approach, with a relatively small API, this is not a big issue.

The ability to add a “title” attribute to a link is another important missing feature. The title is useful when the client has to show a label (a clickable text) for the link. For example, a list of authors should show their names before retrieving their details, so the API must provide them within the links:

"_links": {
  "authors": [
    {  
      "href": "http://localhost:8080/authors/martin_fowler", 
      "title" : "M.Fowler"  
    },
    {  
      "href": "http://localhost:8080/authors/kendall_scott",
      "title" : "K.Scott"  
    }
  ]
}

 

There is a pending request for supporting links title.

The support of CURIEs is partial and faulty. CURIE links are not added to the root resource. This is a known issue.

Conclusions

My pragmatic approach (and age) makes me cautious in creating full-fledged hypermedia API in real world:

  • Support for HATEOAS by mainstream frameworks is sparse; both on the client and server side.
  • The client developer’s culture is hard to adapt, and they tend to hardwire URIs in their code, making links mostly useless.
  • Fully discoverable APIs are still utopian (and probably useless).
  • Lack of appropriate documentation tools doesn’t help.

Nevertheless some links, mainly links that depends on state and business rules, help moving intelligence out of the client and definitely worth some additional work.

Document the rels to push client developers relying on links. Do not believe to the myth hypermedia will unburden you from documenting the API. Be prepared to find yourself hand-writing some documentation on a wiki or in AsciiDoc.

I’m not sure HAL is the best hypermedia API standard. Nevertheless, it is a good choice being one of the most adopted at the moment.

If you are using Spring, Spring HATEOAS requires some work, but is still a good alternative to reinventing the wheel.

Some additional thought is required for finding a workaround the missing ‘title’ attribute in links (could be a blocking issue in some applications).

 

This blog is written exclusively by the OpenCredo team. We do not accept external contributions.

RETURN TO BLOG

SHARE

Twitter LinkedIn Facebook Email

SIMILAR POSTS

Blog