Building a client

To implement the client, we are going to create a standalone Java application that connects to our temperature service implemented in Chapter 1, From Monoliths to Microservices. We will consume the service with a JAX-RS-based client implementation. Let's start by defining the dependencies needed to implement this client application.

We are assuming that you've already created an empty Maven application of the jar packaging with your favorite IDE. You can also create the project with Maven Archetype, as shown in Chapter 2Creating Your First Microservice. Since we are using Jersey as the reference implementation, its Maven dependency should be added, as follows:

<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>2.26</version>
</dependency>

The Jersey client dependency transitively depends on javax.ws.rs-api, which is the dependency of the JAX-RS API version 2.1. We don't need to define the JAX-RS 2.1 dependency explicitly, since it will be resolved by Maven.

In order to use this dependency in a standalone console project, the Jersey-HK2 dependency should be also added, as follows:

<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>2.26</version>
</dependency>
Jersey uses HK2 (also known as Hundred-Kilobyte Kernel) by default for the dependency injection. You can find more details about HK2 at https://javaee.github.io/hk2.

The client implementation for consuming the Temperature service is implemented as follows:

public class TemperatureResourceClient {

public static void main(String... args) {
Client client = ClientBuilder.newClient();
WebTarget target = client.target("http://localhost:8080/" +
"weather-service/weather/temperature");

Invocation.Builder builder = target.request();
Temperature result = builder.get(Temperature.class);

System.out.println(result);

client.close();
}
}

This is a console application that can be run directly without providing any parameters. Within the implementation, we first create a container instance of javax.ws.rs.client.Client by simply invoking the newClient() method on the javax.ws.rs.client.ClientBuilder;class.;ClientBuilder is the main entry point for using the client API, which itself is an abstract class. For ClientBuilder an implementation is to be provided. In our case, Jersey provides the implentation in the form of JerseyClientBuilder. Then we create an instance of javax.ws.rs.client.WebTarget by setting the target URL for the client. By calling the request() method on the target, we're starting to build a request for the targeted web resource. After having an instance of javax.ws.rs.client.Invocation.Builder at hand, calling the get() method with the Temperature.class parameter invokes the HTTP GET method on the URL synchronously. This is the default behavior of the client container, and invoking the get() method is a blocking call until the response has been sent by the remote endpoint.

We will be covering asynchronous client implementation in the following section. The parameter passed to the get() method states the class of the response type, which is an instance of the Temperature class in our case. As a final step, we close the client instance and all of its associated resources.

The aforementioned synchronous client implementation can also be implemented in a non-blocking way, as follows:

public class TemperatureResourceAsyncClient1 {

public static void main(String... args)
throws ExecutionException, InterruptedException {

Client client = ClientBuilder.newClient();
WebTarget target = client.target("http://localhost:8080/" +
"weather-service/weather/temperature");

Invocation.Builder builder = target.request();
Future<Temperature> futureResult = builder
.async()
.get(Temperature.class);
System.out.println(futureResult.get());
client.close();
}
}

The preceding client implementation enables us to consume the REST endpoint asynchronously. The usage of the API is exactly the same as we did in the synchronous call, but we only specified the async() method call to the builder. As soon as we invoke the get() method, we use an asynchronous invoker, and the method will execute and return directly with an instance of java.util.concurrent.Future. So, in the next step, we are using that instance to actually retrieve the result or the async REST invocation.

One problem that can be faced when we invoke the get() method on futureResult  is that if the method takes a long time to respond, our client code will be blocked. A timeout could be set on the get() method call, as follows:

try {
System.out.println(futureResult.get(5, TimeUnit.SECONDS));
} catch (TimeoutException e) {
// Handle timeout gracefully
}

The client application will not be blocked forever if a problem occurs on the server side. But in the worst-case scenario, the client will be blocked for five seconds at most. A better approach for implementing a non-blocking client would be using the InvocationCallback interface from JAX-RS 2.0, as follows:

public class TemperatureResourceAsyncClient3 {

public static void main(String... args)
throws ExecutionException, InterruptedException {

Client client = ClientBuilder.newClient();
WebTarget target = client.target("http://localhost:8080/" +
"weather-service/weather/temperature");
Invocation.Builder builder = target.request();
builder.async().
get(new InvocationCallback<Temperature>() {
@Override
public void completed(Temperature t) {
System.out.println(t);
}
@Override
public void failed(Throwable throwable) {
// method that will be invoked
// when something goes wrong
}
});
client.close();
}
}

The InvocationCallback interface provides two methods, completed() and failed(), which will be invoked when the call to the endpoint is completed or failed, respectively. Having reactive-style programming for this scenario will allow you to have a simpler implementation to surmount such programming difficulties. So JAX-RS 2.1 takes the client implementation one step further by introducing this reactive-style programming. Here is an example implementation of a reactive client approach:

public class TemperatureResourceAsyncClient4 {

public static void main(String... args)
throws ExecutionException, InterruptedException {

Client client = ClientBuilder.newClient();
WebTarget target = client.target("http://localhost:8080/" +
"weather-service/weather/temperature");

Invocation.Builder builder = target.request();
CompletionStage<Response> response =
builder.rx().get();

response.thenAccept(res -> {
Temperature t = res.readEntity(Temperature.class);
System.out.println(t);
});

new Thread(() -> {
try {
for (int seconds = 3; seconds > 0; seconds--) {
System.out.println(seconds + " seconds left");
Thread.sleep(1000);
}
System.out.println("Finished!");
client.close();
}
catch (Exception ignored) {}
}).start();
}
}

The rx() method invoked on the builder is introduced with JAX-RS 2.1, and its job is to get a reactive invoker that exists on the client's runtime. Since we are using Jersey on the classpath, an instance of JerseyCompletionStageRxInvoker will be returned in this implementation. Invoking the get() method on the reactive invoker will result in an instance of CompletionStage<Response>. CompletionStage<T> is a new interface introduced with Java 8, and it represents a computation stage that needs to be executed when another CompletionStage completes. Then we can invoke the thenAccept() method on the response to retrieve the result of the service call as an argument, named res

We implemented a timer thread at the end of the client's main method to print out a countdown timer by starting from three seconds. So while counting down the seconds, we will also see the response of the service call printed out to the console. The whole output of the client would be as follows when executed:

3 seconds left
Temperature{temperature=35.0, temperatureScale=CELSIUS}
2 seconds left
1 seconds left
Finished
..................Content has been hidden....................

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