© Balaji Varanasi and Maxim Bartkov 2022
B. Varanasi, M. BartkovSpring RESThttps://doi.org/10.1007/978-1-4842-7477-4_10

10. HATEOAS

Balaji Varanasi1   and Maxim Bartkov2
(1)
Salt Lake City, UT, USA
(2)
Kharkov, Ukraine
 
In this chapter we will discuss the following:
  • HATEOAS

  • JSON hypermedia types

  • QuickPoll HATEOAS implementation

Consider any interaction with an ecommerce website such as Amazon.com. You typically begin your interaction by visiting the site’s home page. The home page might contain texts, images, and videos describing different products and promotions. The page also contains hyperlinks that allow you to navigate from one page to another, allow you to read product details and reviews, and allow you to add products to shopping carts. These hyperlinks along with other controls such as buttons and input fields also guide you through workflows such as checking out an order.

Each web page in the workflow presents you with controls to go to the next step or go back to a previous step or even completely exit the workflow. This is a very powerful feature of the Web—you as a consumer use links to navigate through resources finding what you need without having to remember all of their corresponding URIs. You just needed to know the initial URI: http://www.amazon.com. If Amazon were to go through a rebranding exercise and change its URIs for products or add new steps in its checkout workflow, you will still be able to discover and perform all the operations.

In this chapter we will review HATEOAS, a constraint that allows us to build resilient REST services that function like a website.

HATEOAS

The Hypermedia As The Engine Of Application State, or HATEOAS, is a key constraint of REST architecture. The term “hypermedia” refers to any content that contains links to other forms of media such as images, movies, and texts. As you have experienced, the Web is a classic example of hypermedia. The idea behind HATEOAS is simple—a response would include links to other resources. Clients would use these links to interact with the server, and these interactions could result in possible state changes.

Similar to a human’s interaction with a website, a REST client hits an initial API URI and uses the server-provided links to dynamically discover available actions and access the resources it needs. The client need not have prior knowledge of the service or the different steps involved in a workflow. Additionally, the clients no longer have to hard-code the URI structures for different resources. This allows the server to make URI changes as the API evolves without breaking the clients.

To better understand HATEOAS, consider REST API for a hypothetical blog application. An example request to retrieve a blog post resource with identifier 1 and the associated response in JSON format is shown here:
GET     /posts/1        HTTP/1.1
Connection: keep-alive
Host: blog.example.com
{
        "id" : 1,
        "body" : "My first blog post",
        "postdate" : "2015-05-30T21:41:12.650Z"
}
As we would expect, the generated response from the server contains the data associated with the blog post resource. When a HATEOAS constraint is applied to this REST service, the generated response has links embedded in it. Listing 10-1 shows an example response with links.
{
        "id" : 1,
        "body" : "My first blog post",
        "postdate" : "2015-05-30T21:41:12.650Z",
        "links" : [
                {
                    "rel" : "self",
                    "href" : http://blog.example.com/posts/1,
                    "method" : "GET"
                }
        ]
}
Listing 10-1

Blog Post with Links

In this response, each link in the links array contains three parts:
  1. 1.

    Href—Contains the URI that you can use to retrieve a resource or change the state of the application

     
  2. 2.

    Rel—Describes the relationship that the href link has with the current resource

     
  3. 3.

    Method—Indicates the HTTP method required to interact with the URI

     

From the link’s href value, you can see that this is a self-referencing link. The rel element can contain arbitrary string values, and in this case, it has a value “self” to indicate this self-relationship. As discussed in Chapter 1, a resource can be identified by multiple URIs. In those situations, a self-link can be helpful to highlight the preferred canonical URI. In scenarios in which partial resource representations are returned (e.g., as part of a collection), including a self-link would allow a client to retrieve a full representation of the resource.

We can expand the blog post response to include other relationships. For example, each blog post has an author, the user that created the post. Each blog post also contains a set of related comments and tags. Listing 10-2 shows an example of a blog post representation with these additional link relationships.
{
        "id" : 1,
        "body" : "My first blog post",
        "postdate" : "2015-05-30T21:41:12.650Z",
        "self" : "http://blog.example.com/posts/1",
        "author" : "http://blog.example.com/profile/12345",
        "comments" : "http://blog.example.com/posts/1/comments",
        "tags" : "http://blog.example.com/posts/1/tags"
}
Listing 10-2

Blog Post with Additional Relationships

The resource representation in Listing 10-2 takes a different approach and doesn’t use a links array. Instead, links to related resources are represented as JSON object properties. For example, the property with key author links the blog post with its creator. Similarly, the properties with keys comments and tags link the post with related comments and tags collection resources.

We used two different approaches for embedding HATEOAS links in a representation to highlight a lack of standardized linking within a JSON document. In both scenarios, the consuming clients can use the rel value to identify and navigate to the related resources. As long as the rel value doesn’t change, the server can release new versions of the URI without breaking the client. It also makes it easy for consuming developers to explore the API without relying on heavy documentation.

HATEOAS Debate

The REST API for the QuickPoll application that we worked on so far doesn’t follow the HATEOAS principles. The same applies to many public/open-source REST APIs being consumed today. In 2008, Roy Fielding expressed frustration in his blog (http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven) at such APIs being called RESTful but that are not hypermedia-driven:

What needs to be done to make the REST architectural style clear on the notion that hypertext is a constraint? In other words, if the engine of application state (and hence the API) is not being driven by hypertext, then it cannot be RESTful and cannot be a REST API. Period. Is there some broken manual somewhere that needs to be fixed?

Seven years later, the debate around hypermedia’s role and what is considered RESTful still continues. The blogosphere is filled with mixed opinions and people taking passionate stances on both sides. The so-called hypermedia skeptics feel that hypermedia is too academic and feel that adding extra links would bloat the payload and adds unnecessary complexity to support clients that really don’t exist.

Kin Lane provides a good summary of the hypermedia debate in his blog post (http://apievangelist.com/2014/08/05/the-hypermedia-api-debate-sorry-reasonable-just-does-not-sell/).

JSON Hypermedia Types

To put it simply, a hypermedia type is a media type that contains well-defined semantics for linking resources. The HTML media type is a popular example of a hypermedia type. The JSON media type however doesn’t provide native hyperlinking semantics and therefore is not considered to be a hypermedia type. This has resulted in a variety of custom implementations for embedding links in a JSON document. We have seen two approaches in the previous section.

Note

REST services that produce XML responses often use an Atom/AtomPub (http://en.wikipedia.org/wiki/Atom_(standard)) format for structuring HATEOAS links.

JSON Hypermedia Types

To address this issue and provide hyperlinking semantics within JSON documents, several JSON hypermedia types have been created:

HAL is one of the most popular hypermedia types and is supported by the Spring Framework. In the next section, we will cover the basics of HAL.

HAL

The Hypertext Application Language, or HAL, is a lean hypermedia type created by Mike Kelly in 2011. This specification supports both XML (application/hal+xml) and JSON (application/hal+json) formats.

The HAL media type defines a resource as a container of state, a collection of links, and a set of embedded resources. Figure 10-1 shows the HAL resource structure.
../images/332520_2_En_10_Chapter/332520_2_En_10_Fig1_HTML.png
Figure 10-1

HAL resource structure

The resource state is expressed using JSON properties or key/value pairs. Listing 10-3 shows the state of a blog post resource.
{
        "id" : 1,
        "body" : "My first blog post",
        "postdate" : "2015-05-30T21:41:12.650Z"
}
Listing 10-3

Blog Post Resource State in HAL

The specification uses a reserved _links property to provide linking capabilities. The _links property is a JSON object that contains all of the links. Each link inside _links is keyed by their link relation with the value containing the URI and a set of optional properties. Listing 10-4 shows the blog post resource augmented with the _links property. Notice the usage of an additional property total count inside the comments link value.
{
        "id" : 1,
        "body" : "My first blog post",
        "postdate" : "2015-05-30T21:41:12.650Z",
        "_links" : {
                "self": { "href": "http://blog.example.com/posts/1" },
"comments": { "href": "http://blog.example.com/posts/1/comments", "totalcount" : 20 },
                "tags": { "href": "http://blog.example.com/posts/1/tags" }
        }
}
Listing 10-4

Blog Post Resource with Links in HAL

There are situations in which it is more efficient to embed a resource than link to it. This would prevent the client from taking an extra round trip, allowing it to access the embedded resource directly. HAL uses a reserved _embedded property to embed resources. Each embedded resource is keyed by their link relation to the value containing the resource object. Listing 10-5 shows the blog post resource with an embedded author resource.
{
        "id" : 1,
        "body" : "My first blog post",
        "postdate" : "2015-05-30T21:41:12.650Z",
        "_links" : {
                     "self": { "href": "http://blog.example.com/posts/1" },
"comments": { "href": "http://blog.example.com/posts/1/comments", "totalcount" : 20 },
                     "tags": { "href": "http://blog.example.com/posts/1/tags" }
         },
        "_embedded" : {
                        "author" : {
                             "_links" : {
                                "self": { "href": "http://blog.example.com/profile/12345" }
                              },
                              "id" : 12345,
                              "name" : "John Doe",
                              "displayName" : "JDoe"
                              }
        }
}
Listing 10-5

Blog Post Resource with Embedded Resource in HAL

HATEOAS in QuickPoll

The Spring Framework provides a Spring HATEOAS library that simplifies the creation of REST representations adhering to HATEOAS principles. Spring HATEOAS provides an API for creating links and assembling representations. In this section, we will use Spring HATEOAS to enrich the poll representation with the following three links:
  • Self-referencing link

  • Link to the votes collection resource

  • Link to the ComputeResult resource

Note

The QuickPoll project that we are using in this chapter is available in the Chapter10starter folder of the downloaded source code. To follow the instructions in this section, import the starter project into your IDE. The completed solution is available in the Chapter10final folder. Please refer to that solution for complete code listings.

We begin the Spring HATEOAS integration by adding the Maven dependency shown in Listing 10-6 to the QuickPoll project’s pom.xml file.
<dependency>
        <groupId>org.springframework.hateoas</groupId>
        <artifactId>spring-hateoas</artifactId>
        <version>1.3.3</version>
</dependency>
Listing 10-6

Spring HATEOAS Dependency

The next step is to modify the Poll Java class such that the generated representation has associated link information. To simplify embedding of hypermedia links, Spring HATEOAS provides an org.springframework.hateoas.RepresentationModel class that resource classes can extend. The RepresentationModel class contains several overloaded methods for adding and removing links. It also contains a getId method that returns the URI associated with the resource. The getId implementation adheres to the REST principle: the ID of a resource is its URI.

Listing 10-7 shows the modified Poll class extending ResourceSupport. If you remember, the Poll class already contains a getId method that returns the primary key associated with the corresponding database record. To accommodate the getId method introduced by the RepresentationModel base class, we refactored the getId and setId Poll class methods to getPollId and setPollId.
package com.apress.domain;
import org.springframework.hateoas.RepresentationModel;
@Entity
public class Poll extends RepresentationModel {
        @Id
        @GeneratedValue
        @Column(name="POLL_ID")
        private Long id;
        @Column(name="QUESTION")
        private String question;
        @OneToMany(cascade=CascadeType.ALL)
        @JoinColumn(name="POLL_ID")
        @OrderBy
        private Set<Option> options;
        public Long getPollId() {
                return id;
        }
        public void setPollId(Long id) {
                this.id = id;
        }
        // Other Getters and Setter removed
}
Listing 10-7

Modified Poll Class

In Chapter 4, we implemented PollController’s createPoll method so that it constructed a URI of the newly created Poll resource using the getId method. The getId to getPollId refactoring just described requires us to update the createPoll method. Listing 10-8 shows the modified createPoll method using the getPollId method.
@RequestMapping(value="/polls", method=RequestMethod.POST)
public ResponseEntity<?> createPoll(@RequestBody Poll poll) {
        poll = pollRepository.save(poll);
        // Set the location header for the newly created resource
        HttpHeaders responseHeaders = new HttpHeaders();
URI newPollUri = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(poll.getPollId()).toUri();
        responseHeaders.setLocation(newPollUri);
        return new ResponseEntity<>(null, responseHeaders, HttpStatus.CREATED);
}
Listing 10-8

Modified createPoll() Method

Note

We modified our domain Poll class and had it extend the RepresentationModel class . An alternative to this approach is to create a new PollResource class to hold the Poll’s representation and have it extend the RepresentationModel class. With this approach, the Poll Java class remains untouched. However, we need to modify the PollController so that it copies the representation from each Poll to a PollResource and returns the PollResource instances.

The final step in the Spring HATEOAS integration is to modify PollController endpoints so that we can build links and inject them into responses. Listing 10-9 shows the modified portions of the PollController .
package com.apress.controller;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
@RestController
public class PollController {
        @RequestMapping(value="/polls", method=RequestMethod.GET)
        public ResponseEntity<Iterable<Poll>> getAllPolls() {
                Iterable<Poll> allPolls = pollRepository.findAll();
                for(Poll p : allPolls) {
                        updatePollResourceWithLinks(p);
                }
                return new ResponseEntity<>(allPolls, HttpStatus.OK);
        }
        @RequestMapping(value="/polls/{pollId}", method=RequestMethod.GET)
        public ResponseEntity<?> getPoll(@PathVariable Long pollId) {
                Optional<Poll> p = pollRepository.findById(pollId);
                if(!p.isPresent()) {
                        throw new Exception("Pool was not found");
                }
                updatePollResourceWithLinks(p.get());
                return new ResponseEntity<> (p.get(), HttpStatus.OK);
        }
        private void updatePollResourceWithLinks(Poll poll) {
poll.add(linkTo(methodOn(PollController.class).getAllPolls()).slash(poll.getPollId()).withSelfRel());
poll.add(linkTo(methodOn(VoteController.class).getAllVotes(poll.getPollId())).withRel("votes"));
poll.add(linkTo(methodOn(ComputeResultController.class).computeResult(poll.getPollId())).withRel("compute-result"));
        }
}
Listing 10-9

PollController Modifications

Because links need to be generated and injected in multiple places, we created a updatePollResourceWithLinks method to hold the common code. Spring HATEOAS provides a convenient ControllerLinkBuilder class that can build links pointing to Spring MVC controllers. The updatePollResourceWithLinks method implementation uses the linkTo, methodOn, and slash utility methods. These methods are part of the Spring HATEOAS ControllerLinkBuilder class and can generate links pointing to Spring MVC controllers. The generated links are absolute URIs to resources. This relieves developers from having to look up server information such as protocol, hostname, port number, and so on, duplicating URI path strings (/polls) all over the place. To better understand these methods, let’s dissect this code:
linkTo(
        methodOn(PollController.class).getAllPolls()
          )
          .slash(poll.getPollId())
          .withSelfRel()

The linkTo method can take a Spring MVC controller class or one of its methods as an argument. It then inspects the class or method for the @RequestMapping annotation and retrieves the path value to build the link. The methodOn method creates a dynamic proxy of the passed-in PollController class. When the getAllPolls method is invoked on the dynamic proxy, its @RequestMapping information is inspected and the value "/polls" is extracted. For methods such as getAllVotes that expect a parameter, we can pass in a null. However, if the parameter is used to build the URI, then a real value should be passed in.

The slash method , as the name suggests, appends the Poll’s ID as a subresource to the current URI. Finally, the withSelfRel method instructs that the generated link should have a rel with value “self.” Under the hood, the ControllerLinkBuilder class uses the ServletUriComponentsBuilder to obtain basic URI information such as hostname and builds the final URI. Hence, for a poll with ID 1, this code will generate the URI: http://localhost:8080/polls/1.

With these changes in place, run the QuickPoll application, and, using Postman, perform a GET request on the http://localhost:8080/polls URI. You will see that the generated response includes three links for each poll. Figure 10-2 shows the Postman request and partial response.
../images/332520_2_En_10_Chapter/332520_2_En_10_Fig2_HTML.png
Figure 10-2

Postman response with links

Summary

In this chapter, we reviewed the HATEOAS constraint that enables developers to build flexible and loosely coupled APIs. We scratched the surface and used Spring HATEOAS to create QuickPoll REST representations that adhere to HATEOAS principles.

This brings us to the end of our journey. Throughout this book, you have learned the key features of REST and Spring technologies that simplify REST development. With this knowledge, you should be ready to start developing your own RESTful services. Happy coding!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.101.81