REST Client
An atypical scenario in a Microservices architecture is the remote invocation of remote REST HTTP endpoints. Quarkus provides a typed REST client that follows the MicroProfile REST Client specification.
Let’s create a REST client that accesses https://fruityvice.com to get nutrition information about our fruits. The endpoint we’re interested in is this one:
-
api/fruit/{name}
, which returns specific info about the given fruit name.
{
"genus": "Musa",
"name": "Banana",
"id": 1,
"family": "Musaceae",
"order": "Zingiberales",
"nutritions": {
"carbohydrates": 22,
"protein": 1,
"fat": 0.2,
"calories": 96,
"sugar": 17.2
}
}
Add the REST Client extension
Just open a new terminal window, and make sure you’re at the root of your tutorial-app
project, then run:
mvn quarkus:add-extension -Dextensions=quarkus-rest-client
quarkus extension add quarkus-rest-client
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------< com.redhat.developers:tutorial-app >-----------------
[INFO] Building tutorial-app 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- quarkus-maven-plugin:1.4.2.Final:add-extension (default-cli) @ tutorial-app ---
✅ Adding extension io.quarkus:quarkus-rest-client
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.773 s
[INFO] Finished at: 2020-05-11T21:43:38-04:00
[INFO] ------------------------------------------------------------------------
Create FruityVice POJO
We need to create a POJO object that is used to unmarshal a JSON message from http://fruityvice.com.
Create a new FruityVice
Java class in src/main/java
in the com.redhat.developers
package with the following contents:
package com.redhat.developers;
import javax.json.bind.annotation.JsonbCreator;
public class FruityVice {
private String name;
private Nutritions nutritions;
FruityVice(String name, Nutritions nutritions) {
this.name = name;
this.nutritions = nutritions;
}
@JsonbCreator
public static FruityVice of(String name, Nutritions nutritions) {
return new FruityVice(name, nutritions);
}
public String getName() {
return name;
}
public Nutritions getNutritions() {
return nutritions;
}
public static class Nutritions {
private double carbohydrates;
private double calories;
Nutritions(double carbohydrates, double calories) {
this.carbohydrates = carbohydrates;
this.calories = calories;
}
@JsonbCreator
public static Nutritions of(double carbohydrates, double calories) {
return new Nutritions(carbohydrates, calories);
}
public double getCarbohydrates() {
return carbohydrates;
}
public double getCalories() {
return calories;
}
}
}
Create FruityViceService
Now we’re going to implement a Java interface that mimics the remote REST endpoint.
Create a new FruityViceService
Java interface in src/main/java
in the com.redhat.developers
package with the following contents:
package com.redhat.developers;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
@Path("/api/fruit")
@RegisterRestClient
public interface FruityViceService {
@GET
@Path("/{name}")
@Produces(MediaType.APPLICATION_JSON)
FruityVice getFruitByName(@PathParam("name") String name);
}
Configure REST Client properties
Add the following properties to your application.properties
in src/main/resources
:
com.redhat.developers.FruityViceService/mp-rest/url=https://fruityvice.com
Create FruitDTO
We’re going to enhance our FruitResource
endpoint by creating a new FruitDTO
POJO and add the additional information provided by the FruitViceService
.
Create a new FruitDTO
Java class in src/main/java
in the com.redhat.developers
package with the following contents:
package com.redhat.developers;
public class FruitDTO {
private String name;
private String season;
private double carbohydrates;
private double calories;
private FruitDTO(String name, String season, double carbohydrates, double calories) {
this.name = name;
this.season = season;
this.carbohydrates = carbohydrates;
this.calories = calories;
}
public static FruitDTO of(Fruit fruit, FruityVice fruityVice) {
return new FruitDTO(
fruit.name,
fruit.season,
fruityVice.getNutritions().getCarbohydrates(),
fruityVice.getNutritions().getCalories());
}
public String getName() {
return name;
}
public String getSeason() {
return season;
}
public double getCarbohydrates() {
return carbohydrates;
}
public double getCalories() {
return calories;
}
}
Change FruitResource to use FruityViceService
Now that we have all the required classes, we can change FruitResource
to get fruits by season and use our FruityViceService
REST client via @RestClient
annotation.
Change the FruitResource
Java class in src/main/java
in the com.redhat.developers
package with the following contents:
package com.redhat.developers;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import java.util.List;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.transaction.Transactional;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
@Path("/fruit")
public class FruitResource {
@RestClient
FruityViceService fruityViceService;
@Transactional
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response newFruit(Fruit fruit) {
fruit.id = null;
fruit.persist();
return Response.status(Status.CREATED).entity(fruit).build();
}
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<FruitDTO> fruits(@QueryParam("season") String season) {
if (season != null) {
return Fruit.findBySeason(season).stream()
.map(fruit -> FruitDTO.of(fruit, fruityViceService.getFruitByName(fruit.name)))
.collect(Collectors.toList());
}
return Fruit.<Fruit>listAll().stream()
.map(fruit -> FruitDTO.of(fruit, fruityViceService.getFruitByName(fruit.name)))
.collect(Collectors.toList());
}
}
Invoke the endpoint
You can check your new implementation using a REST client by pointing your browser to http://localhost:8080/fruit?season=Summer
You can also run the following command:
curl localhost:8080/fruit?season=Summer
[
{
"calories": 0,
"carbohydrates": 29,
"name": "Blueberry",
"season": "Summer"
},
{
"calories": 0,
"carbohydrates": 96,
"name": "Banana",
"season": "Summer"
},
{
"calories": 0,
"carbohydrates": 30,
"name": "Watermelon",
"season": "Summer"
}
]