Recently I blogged about how to develop reactive REST APIs with Quarkus. Developing the actual endpoints asynchronously is the first step. To leverage reactive capabilities maximally though, the complete code path should be asynchronous, especially long lasting operations like database accesses and REST API invocations. This article describes two options how to invoke REST APIs asynchronously with Quarkus.
Two Options to invoke REST APIs asynchronously
Here are the two options:
The question is when to use which option. Rather than trying to answer this myself, let me refer to Clement Escoffier, the reactive expert in the Quarkus team. Clement answered this question on StackOverflow:
- The MicroProfile Rest Client is not non-blocking. The Vert.x web client is.
- If the rest of your code uses RX Java, the Vert.x client has a neat RX Java API.
- The MicroProfile Rest Client is using an annotation-driven approach, the Vert.x client is API-driven.
You can use both options in the same application which is what I’ve done in the sample application that is included in the cloud-native-starter project. The sample application uses several microservices. The web-api service invokes the articles service which I’ll describe below.
Eclipse Vert.x Axle Web Client
The Quarkus guide Using Eclipse Vert.x describes how to use the Vert.x client. Let’s take a look at the code to invoke the articles service from the web-api service.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import io.vertx.axle.core.Vertx;
import io.vertx.axle.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientOptions;
import io.vertx.core.json.JsonObject;
import io.vertx.core.json.JsonArray;
import javax.annotation.PostConstruct;
import java.util.concurrent.CompletableFuture;
...
@Inject
Vertx vertx;
private WebClient client;
@PostConstruct
void initialize() {
this.client = WebClient.create(vertx, new WebClientOptions().setDefaultHost(ARTICLES_DNS).setDefaultPort(ARTICLES_PORT).setSsl(false));
}
public CompletableFuture<List<CoreArticle>> getArticlesReactiveVertxWebClient(int amount) {
CompletableFuture<List<CoreArticle>> future = new CompletableFuture<>();
this.client.get("/v2/articles?amount=" + amount)
.send()
.toCompletableFuture()
.orTimeout(MAXIMAL_DURATION, TimeUnit.MILLISECONDS)
.thenAccept(resp -> {
if (resp.statusCode() == 200) {
List<CoreArticle> articles = this.convertJsonToCoreArticleList(resp.bodyAsJsonArray());
future.complete(articles);
} else {
future.completeExceptionally(new NoConnectivity());
}
})
.exceptionally(throwable -> {
future.completeExceptionally(new NoConnectivity());
return null;
});
return future;
}
The methods ‘get’ and ‘send’ invoke the HTTP request. I’ve explained in my previous article why ‘toCompletableFuture’ and ‘orTimeout’ or used.
Once the request has been completed, the HTTP status code has to be checked and converted into a Java exception. If the request has been successful, the response will be a io.vertx.core.json.JsonArray or io.vertx.core.json.JsonObject. These objects need to be converted manually into Java objects, in this example in a list of articles.
Eclipse MicroProfile REST Client
The second option is to use the MicroProfile REST Client. Check out my previous blog Invoking REST APIs from Java Microservices which explains how to invoke REST APIs synchronously.
The MicroProfile REST Client has been extended to also support asynchronous invocations. There is an OpenLiberty guide Consuming RESTful services asynchronously that describes this functionality.
Again, let’s take a look at the code. First you need to define an interface to the service you want to invoke:
1
2
3
4
5
6
7
8
9
import java.util.concurrent.CompletionStage;
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
...
@RegisterProvider(ExceptionMapperArticles.class)
public interface ArticlesServiceReactive {
@GET
@Produces(MediaType.APPLICATION_JSON)
public CompletionStage<List<CoreArticle>> getArticlesFromService();
}
In this interface an exception mapper class is defined. This class maps HTTP errors to Java exceptions (see code):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper;
...
@Provider
public class ExceptionMapperArticles implements ResponseExceptionMapper<InvalidArticle> {
@Override
public boolean handles(int status, MultivaluedMap<String, Object> headers) {
return status == 204;
}
@Override
public InvalidArticle toThrowable(Response response) {
switch (response.getStatus()) {
case 204:
return new InvalidArticle();
}
return null;
}
}
In order to invoke the REST API, a normal Java method can be called. The implementation of this method handles MicroProfile magically (see code):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public CompletableFuture<List<CoreArticle>> getArticlesReactive(int amount) {
CompletableFuture<List<CoreArticle>> future = new CompletableFuture<>();
URL apiUrl;
try {
apiUrl = new URL("http://" + ARTICLES_DNS + ":" + ARTICLES_PORT + "/v2/articles?amount=" + amount);
ArticlesServiceReactive articlesServiceReative = RestClientBuilder.newBuilder().baseUrl(apiUrl).build(ArticlesServiceReactive.class);
articlesServiceReative.getArticlesFromService()
.toCompletableFuture()
.orTimeout(MAXIMAL_DURATION, TimeUnit.MILLISECONDS)
.thenAccept((articles) -> {
future.complete(articles);
})
.exceptionally((throwable) -> {
future.completeExceptionally(new NoConnectivity());
return null;
});
} catch (MalformedURLException e) {
future.completeExceptionally(new NoConnectivity());
}
return future;
}
Note that the deserialization of the JSON response into a list of articles is done automatically.
Closing Thoughts
My two cents: If you are not an experienced Rx Java developer, I’d go with MicroProfile for asynchronous REST invocations. The MicroProfile model is nicely designed, you don’t have to convert objects manually and it’s not blocking.
All samples of this article are included in the open source project cloud-native-starter. Check it out to see the code in action.
This article is part of a series. Read the other articles of this series to learn about reactive programming: