Completing a REST response asynchronously

After all parallel executions are synchronized to a single stage, and the results combined into a list, we can chain more lambda callbacks to process the final list of forecasts and send it as a REST response. Because asynchronous callbacks are executed in a different thread, we need a way to finalize the REST response other than returning a final value from the REST method. Although we could put the initial thread to sleep and wait until the asynchronous result is available, it has serious disadvantages. One of them is that we need two threads to finalize the response, while only one of them is working at a given time. Another problem is that we need to synchronize the threads and pass the list of forecasts from one to the other. Synchronization between threads is an additional overhead, which slows things down and should be avoided if possible. A better solution is to return from the initial method and complete the response later in another thread. This is called asynchronous request processing.

The JAX-RS REST API supports asynchronous processing and allows us to provide a response even after a resource method returns. If we return CompletionStage directly from a resource method, the container will understand it, complete it, and send the HTTP response after the returned CompletionStage completes.

CompletionStage as a return value in resource methods has only been supported since Java EE 8. JAX-RS also supports a lower-level way of returning responses asynchronously, using an AsyncResponse argument in resource methods, which can also be used  in older versions of Java EE.

Therefore, to make our method asynchronous, we just replace the Response return type with CompletionStage<Response>:

@GET
@Produces(MediaType.APPLICATION_JSON)
public CompletionStage<Response> getLocationsWithTemperature()

Our returned CompletionStage can hold any type that's allowed as a return value of a synchronous method. It can hold any Java type that can be mapped to a REST response. Therefore, instead of Response, we will use a custom ServiceResponse class, which will be automatically mapped to JSON using the JSON-Binding mechanism available in Java EE 8:

@GET
@Produces(MediaType.APPLICATION_JSON)
public CompletionStage<ServiceResponse> getLocationsWithTemperature()

Lastly, we need to return the final stage of our asynchronous computation. In our previous example, this is stored in the finalStage variable:

@GET
@Produces(MediaType.APPLICATION_JSON)
public CompletionStage<ServiceResponse> getLocationsWithTemperature() {
...
return finalStage;
}

In our case, the type of the finalStage variable is different than the type of CompletionStage returned from the method. Therefore, we're going to convert finalStage with its apply method:

    return finalStage.thenApply(forecasts -> {
ServiceResponse response = new ServiceResponse();
response.getForecasts().addAll(forecasts);
return response;
});

We closed the loop by finishing our asynchronous callback chain and returning the final CompletionStage from the REST resource method. The container does the rest; it waits for CompletionStage to complete and either sends an expected response when it's completed (OK), or it sends an error response and logs the error when it's completed with an exception.

As an alternative to returning CompletionStage, we can use a supplied AsyncResponse object. This is useful if we need to access the JAX-RS API that isn't available with plain CompletionStage. It may also be convenient if we don't use CompletionStage during our processing (at all), and we don't want to create an instance of CompletionStage (for example, CompletableFuture) just so that we can return it.

An alternative asynchronous definition of our getLocationsWithTemperature method with an AsyncResponse argument looks as follows:

@GET
@Produces(MediaType.APPLICATION_JSON)
public void getLocationsWithTemperature(@Suspended AsyncResponse ar) {
}

This asynchronous method should do the following:

  • Return no value (the return type is void)
  • Contain an argument of the javax.ws.rs.container.AsyncResponse type
  • Make sure that the AsyncResponse argument is annotated with javax.ws.rs.container.Suspended

The method doesn't return anything immediately, hence the calling method is not blocked and it continues with other commands. The container understands that the method is asynchronous and that a response will be provided via the AsyncResponse object later, from another thread. In our case, the response will be provided from a thread that is created for us automatically to handle asynchronous responses from remote REST services.

It's possible to complete the response using AsyncResponse within the method call on the same thread before the method returns. In this case, the container will complete the response right after the resource method returns. However, the method then behaves like a usual synchronous method and there's no advantage to using the asynchronous API.

When the final result is ready, we complete the response by calling the resume method of our AsyncResponse object. This method accepts the same values, which can be returned from a usual synchronous REST method. Therefore, we can call it with the same value, as in our previous synchronous version of the Forecast service:

response.getForecasts().addAll(forecasts);
asyncResponse.resume(Response.ok(response).build());

To summarize, asynchronous methods are supposed to return quickly, to allow further execution on the current thread. Since I/O operations and some other actions may take a while, the result may not be available without waiting. The result is available later and very often in another thread. Therefore, asynchronous methods can't return the result directly as the return value. They either don't return any value and provide the result using an additional argument (callback), or they return an object (future or promise) that will allow us to handle the result when it's available in the future.

An asynchronous JAX-RS method can have both forms. It either doesn't return any value and a final result or exception is provided by methods of the AsyncResponse argument, which is annotated with @Suspended to turn on asynchronous processing. This is suitable if we need more control over when and how we complete processing a request with a result or an exception, or if we need to specify a timeout or register life cycle callbacks. Alternatively, an asynchronous method simply returns CompletionStage, which either completes with a response or results in an exception.

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

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