Chapter 7. Consuming REST services

This chapter covers

  • Using RestTemplate to consume REST APIs
  • Navigating hypermedia APIs with Traverson

Have you ever gone to a movie and, as the movie starts, discovered that you were the only person in the theater? It certainly is a wonderful experience to have what is essentially a private viewing of a movie. You can pick whatever seat you want, talk back to the characters onscreen, and maybe even open your phone and tweet about it without anyone getting angry for disrupting their movie-watching experience. And the best part is that nobody else is there ruining the movie for you, either!

This hasn’t happened to me often. But when it has, I have wondered what would have happened if I hadn’t shown up. Would they still have shown the film? Would the hero still have saved the day? Would the theater staff still have cleaned the theater after the movie was over?

A movie without an audience is kind of like an API without a client. It’s ready to accept and provide data, but if the API is never invoked, is it really an API? Like Schrödinger’s cat, we can’t know if the API is active or returning HTTP 404 responses until we issue a request to it.

In the previous chapter, we focused on defining REST endpoints that can be consumed by some client external to your application. Although the driving force for developing such an API was a single-page Angular application that served as the Taco Cloud website, the reality is that the client could be any application, in any language—even another Java application.

It’s not uncommon for Spring applications to both provide an API and make requests to another application’s API. In fact, this is becoming prevalent in the world of microservices. Therefore, it’s worthwhile to spend a moment looking at how to use Spring to interact with REST APIs.

A Spring application can consume a REST API with

  • RestTemplate A straightforward, synchronous REST client provided by the core Spring Framework.
  • Traverson A hyperlink-aware, synchronous REST client provided by Spring HATEOAS. Inspired from a JavaScript library of the same name.
  • WebClient A reactive, asynchronous REST client introduced in Spring 5.

I’ll defer discussion of WebClient until we cover Spring’s reactive web framework in chapter 11. For now, we’ll focus on the other two REST clients, starting with RestTemplate.

7.1. Consuming REST endpoints with RestTemplate

There’s a lot that goes into interacting with a REST resource from the client’s perspective—mostly tedium and boilerplate. Working with low-level HTTP libraries, the client needs to create a client instance and a request object, execute the request, interpret the response, map the response to domain objects, and handle any exceptions that may be thrown along the way. And all of this boilerplate is repeated, regardless of what HTTP request is sent.

To avoid such boilerplate code, Spring provides RestTemplate. Just as JDBCTemplate handles the ugly parts of working with JDBC, RestTemplate frees you from dealing with the tedium of consuming REST resources.

RestTemplate provides 41 methods for interacting with REST resources. Rather than examine all of the methods that it offers, it’s easier to consider only a dozen unique operations, each overloaded to equal the complete set of 41 methods. The 12 operations are described in table 7.1.

Table 7.1. RestTemplate defines 12 unique operations, each of which is overloaded, providing a total of 41 methods.

Method

Description

delete(...) Performs an HTTP DELETE request on a resource at a specified URL
exchange(...) Executes a specified HTTP method against a URL, returning a ResponseEntity containing an object mapped from the response body
execute(...) Executes a specified HTTP method against a URL, returning an object mapped from the response body
getForEntity(...) Sends an HTTP GET request, returning a ResponseEntity containing an object mapped from the response body
getForObject(...) Sends an HTTP GET request, returning an object mapped from a response body
headForHeaders(...) Sends an HTTP HEAD request, returning the HTTP headers for the specified resource URL
optionsForAllow(...) Sends an HTTP OPTIONS request, returning the Allow header for the specified URL
patchForObject(...) Sends an HTTP PATCH request, returning the resulting object mapped from the response body
postForEntity(...) POSTs data to a URL, returning a ResponseEntity containing an object mapped from the response body
postForLocation(...) POSTs data to a URL, returning the URL of the newly created resource
postForObject(...) POSTs data to a URL, returning an object mapped from the response body
put(...) PUTs resource data to the specified URL

With the exception of TRACE, RestTemplate has at least one method for each of the standard HTTP methods. In addition, execute() and exchange() provide lower-level, general-purpose methods for sending requests with any HTTP method.

Most of the methods in table 7.1 are overloaded into three method forms:

  • One accepts a String URL specification with URL parameters specified in a variable argument list.
  • One accepts a String URL specification with URL parameters specified in a Map<String,String>.
  • One accepts a java.net.URI as the URL specification, with no support for parameterized URLs.

Once you get to know the 12 operations provided by RestTemplate and how each of the variant forms works, you’ll be well on your way to writing resource-consuming REST clients.

To use RestTemplate, you’ll either need to create an instance at the point you need it

RestTemplate rest = new RestTemplate();

or you can declare it as a bean and inject it where you need it:

@Bean
public RestTemplate restTemplate() {
  return new RestTemplate();
}

Let’s survey RestTemplate’s operations by looking at those that support the four primary HTTP methods: GET, PUT, DELETE, and POST. We’ll start with getForObject() and getForEntity()—the GET methods.

7.1.1. GETting resources

Suppose that you want to fetch an ingredient from the Taco Cloud API. Assuming that the API isn’t HATEOAS-enabled, you can use getForObject() to fetch the ingredient. For example, the following code uses RestTemplate to fetch an Ingredient object by its ID:

public Ingredient getIngredientById(String ingredientId) {
  return rest.getForObject("http://localhost:8080/ingredients/{id}",
                           Ingredient.class, ingredientId);
}

Here you’re using the getForObject() variant that accepts a String URL and uses a variable list for URL variables. The ingredientId parameter passed into getForObject() is used to fill in the {id} placeholder in the given URL. Although there’s only one URL variable in this example, it’s important to know that the variable parameters are assigned to the placeholders in the order that they’re given.

The second parameter to getForObject() is the type that the response should be bound to. In this case, the response data (that’s likely in JSON format) should be deserialized into an Ingredient object that will be returned.

Alternatively, you can use a Map to specify the URL variables:

public Ingredient getIngredientById(String ingredientId) {
  Map<String,String> urlVariables = new HashMap<>();
  urlVariables.put("id", ingredientId);
  return rest.getForObject("http://localhost:8080/ingredients/{id}",
                           Ingredient.class, urlVariables);
}

In this case, the value of ingredientId is mapped to a key of id. When the request is made, the {id} placeholder is replaced by the map entry whose key is id.

Using a URI parameter is a bit more involved, requiring that you construct a URI object before calling getForObject(). Otherwise, it’s similar to both of the other variants:

public Ingredient getIngredientById(String ingredientId) {
  Map<String,String> urlVariables = new HashMap<>();
  urlVariables.put("id", ingredientId);
  URI url = UriComponentsBuilder
            .fromHttpUrl("http://localhost:8080/ingredients/{id}")
            .build(urlVariables);

  return rest.getForObject(url, Ingredient.class);
}

Here the URI object is defined from a String specification, and its placeholders filled in from entries in a Map, much like the previous variant of getForObject(). The getForObject() method is a no-nonsense way of fetching a resource. But if the client needs more than the payload body, you may want to consider using getForEntity().

getForEntity() works in much the same way as getForObject(), but instead of returning a domain object that represents the response’s payload, it returns a ResponseEntity object that wraps that domain object. The ResponseEntity gives access to additional response details, such as the response headers.

For example, suppose that in addition to the ingredient data, you want to inspect the Date header from the response. With getForEntity() that becomes straightforward:

public Ingredient getIngredientById(String ingredientId) {
  ResponseEntity<Ingredient> responseEntity =
      rest.getForEntity("http://localhost:8080/ingredients/{id}",
          Ingredient.class, ingredientId);

  log.info("Fetched time: " +
          responseEntity.getHeaders().getDate());

  return responseEntity.getBody();
}

The getForEntity() method is overloaded with the same parameters as getForObject(), so you can provide the URL variables as a variable list parameter or call getForEntity() with a URI object.

7.1.2. PUTting resources

For sending HTTP PUT requests, RestTemplate offers the put() method. All three overloaded variants of put() accept an Object that is to be serialized and sent to the given URL. As for the URL itself, it can be specified as a URI object or as a String. And like getForObject() and getForEntity(), the URL variables can be provided as either a variable argument list or as a Map.

Suppose that you want to replace an ingredient resource with the data from a new Ingredient object. The following code should do the trick:

public void updateIngredient(Ingredient ingredient) {
  rest.put("http://localhost:8080/ingredients/{id}",
           ingredient,
           ingredient.getId());
}

Here the URL is given as a String and has a placeholder that’s substituted by the given Ingredient object’s id property. The data to be sent is the Ingredient object itself. The put() method returns void, so there’s nothing you need to do to handle a return value.

7.1.3. DELETEing resources

Suppose that Taco Cloud no longer offers an ingredient and wants it completely removed as an option. To make that happen, you can call the delete() method from RestTemplate:

public void deleteIngredient(Ingredient ingredient) {
  rest.delete("http://localhost:8080/ingredients/{id}",
              ingredient.getId());
}

In this example, only the URL (specified as a String) and a URL variable value are given to delete(). But as with the other RestTemplate methods, the URL could be specified as a URI object or the URL parameters given as a Map.

7.1.4. POSTing resource data

Now let’s say that you add a new ingredient to the Taco Cloud menu. An HTTP POST request to the .../ingredients endpoint with ingredient data in the request body will make that happen. RestTemplate has three ways of sending a POST request, each of which has the same overloaded variants for specifying the URL. If you wanted to receive the newly created Ingredient resource after the POST request, you’d use postForObject() like this:

public Ingredient createIngredient(Ingredient ingredient) {
  return rest.postForObject("http://localhost:8080/ingredients",
                            ingredient,
                            Ingredient.class);
}

This variant of the postForObject() method takes a String URL specification, the object to be posted to the server, and the domain type that the response body should be bound to. Although you aren’t taking advantage of it in this case, a fourth parameter could be a Map of the URL variable value or a variable list of parameters to substitute into the URL.

If your client has more need for the location of the newly created resource, then you can call postForLocation() instead:

public URI createIngredient(Ingredient ingredient) {
  return rest.postForLocation("http://localhost:8080/ingredients",
                              ingredient);
}

Notice that postForLocation() works much like postForObject() with the exception that it returns a URI of the newly created resource instead of the resource object itself. The URI returned is derived from the response’s Location header. In the off chance that you need both the location and response payload, you can call postForEntity():

public Ingredient createIngredient(Ingredient ingredient) {
  ResponseEntity<Ingredient> responseEntity =
         rest.postForEntity("http://localhost:8080/ingredients",
                            ingredient,
                            Ingredient.class);

  log.info("New resource created at " +
           responseEntity.getHeaders().getLocation());

  return responseEntity.getBody();
}

Although the methods of RestTemplate differ in their purpose, they’re quite similar in how they’re used. This makes it easy to become proficient with RestTemplate and use it in your client code.

On the other hand, if the API you’re consuming includes hyperlinks in its response, RestTemplate isn’t as helpful. It’s certainly possible to fetch the more detailed resource data with RestTemplate and work with the content and links contained therein, but it’s not trivial to do so. Rather than struggle while consuming hypermedia APIs with RestTemplate, let’s turn our attention to a client library that’s made for such things—Traverson.

7.2. Navigating REST APIs with Traverson

Traverson comes with Spring Data HATEOAS as the out-of-the-box solution for consuming hypermedia APIs in Spring applications. This Java-based library is inspired by a similar JavaScript library of the same name (https://github.com/traverson/traverson).

You might have noticed that Traverson’s name kind of sounds like “traverse on”, which is a good way to describe how it’s used. In this section, you’ll consume an API by traversing the API on relation names.

Working with Traverson starts with instantiating a Traverson object with an API’s base URI:

Traverson traverson = new Traverson(
    URI.create("http://localhost:8080/api"), MediaTypes.HAL_JSON);

Here I’ve pointed Traverson to the Taco Cloud’s base URL (running locally). This is the only URL you’ll need to give to Traverson. From here on out, you’ll navigate the API by link relation names. You’ll also specify that the API will produce JSON responses with HAL-style hyperlinks so that Traverson knows how to parse the incoming resource data. Like RestTemplate, you can choose to instantiate a Traverson object prior to its use or declare it as a bean to be injected wherever it’s needed.

With a Traverson object in hand, you can start consuming an API by following links. For example, suppose that you’re interested in retrieving a list of all ingredients. You know from section 6.3.1 that the ingredients link has an href property that links to the ingredients resource. You’ll need to follow that link:

ParameterizedTypeReference<Resources<Ingredient>> ingredientType =
    new ParameterizedTypeReference<Resources<Ingredient>>() {};

Resources<Ingredient> ingredientRes =
    traverson
      .follow("ingredients")
      .toObject(ingredientType);

Collection<Ingredient> ingredients = ingredientRes.getContent();

By calling the follow() method on the Traverson object, you can navigate to the resource whose link’s relation name is ingredients. Now that the client has navigated to ingredients, you need to ingest the contents of that resource by calling toObject().

The toObject() method requires that you tell it what kind of object to read the data into. This can get a little tricky, considering that you need to read it in as a Resources<Ingredient> object, and Java type erasure makes it difficult to provide type information for a generic type. But creating a ParameterizedTypeReference helps with that.

As an analogy, imagine that instead of a REST API, this were a homepage on a website. And instead of REST client code, imagine that it’s you viewing that homepage in a browser. You see a link on the page that says Ingredients and you follow that link by clicking it. Upon arriving at the next page, you read the page, which is analogous to Traverson ingesting the content as a Resources<Ingredient> object.

Now let’s consider a slightly more interesting use case. Let’s say that you want to fetch the most recently created tacos. Starting at the home resource, you can navigate to the recent tacos resource like this:

ParameterizedTypeReference<Resources<Taco>> tacoType =
    new ParameterizedTypeReference<Resources<Taco>>() {};

Resources<Taco> tacoRes =
    traverson
      .follow("tacos")
      .follow("recents")
      .toObject(tacoType);

Collection<Taco> tacos = tacoRes.getContent();

Here you follow the Tacos link and then, from there, follow the Recents link. That brings you to the resource you’re interested in, so a call to toObject() with an appropriate ParameterizedTypeReference gets you what you want. The .follow() method can be simplified by listing a trail of relation names to follow:

Resources<Taco> tacoRes =
    traverson
      .follow("tacos", "recents")
      .toObject(tacoType);

As you can see, Traverson makes easy work of navigating a HATEOAS-enabled API and consuming its resources. But one thing it doesn’t do is offer any methods for writing to or deleting from those APIs. In contrast, RestTemplate can write and delete resources, but doesn’t make it easy to navigate an API.

When you need to both navigate an API and update or delete resources, you’ll need to use RestTemplate and Traverson together. Traverson can still be used to navigate to the link where a new resource will be created. Then RestTemplate can be given that link to do a POST, PUT, DELETE, or any other HTTP request you need.

For example, suppose you want to add a new Ingredient to the Taco Cloud menu. The following addIngredient() method teams up Traverson and RestTemplate to post a new Ingredient to the API:

private Ingredient addIngredient(Ingredient ingredient) {
  String ingredientsUrl = traverson
      .follow("ingredients")
      .asLink()
      .getHref();

  return rest.postForObject(ingredientsUrl,
                            ingredient,
                            Ingredient.class);
}

After following the Ingredients link, you ask for the link itself by calling asLink(). From that link, you ask for the link’s URL by calling getHref(). With a URL in hand, you have everything you need to call postForObject() on the RestTemplate instance and save the new ingredient.

Summary

  • Clients can use RestTemplate to make HTTP requests against REST APIs.
  • Traverson enables clients to navigate an API using hyperlinks embedded in the responses.
..................Content has been hidden....................

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