November 1, 2015 | Microservices
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
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.
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.
A Resource in HAL may contain:
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.
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.
To use Spring HATEOAS in a Spring Boot application, add:
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.
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 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 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.
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.
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.
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()); }
To keep the scope simple, some real-world features are missing:
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.
My pragmatic approach (and age) makes me cautious in creating full-fledged hypermedia API in real world:
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.