Asynchronous and reactive JAX-RS

JAX-RS supports asynchronous behavior to not unnecessarily block request threads on the server-side. Even if an HTTP connection is currently waiting for a response, the request thread could potentially handle other requests while the long-running process on the server is handled. Request threads are pooled by the container and this pool only has a certain size. In order to not unnecessarily occupy a request thread, JAX-RS asynchronous resource methods submit tasks that are executed while the request thread returns and is free to be reused again. The HTTP connection is being resumed and responded after the asynchronous task has been finished or when a timeout occurs. The following code shows an asynchronous JAX-RS resource method:

@Path("users")
@Consumes(MediaType.APPLICATION_JSON)
public class UsersResource {

    @Resource
    ManagedExecutorService mes;

    ...

    @POST
    public CompletionStage<Response> createUserAsync(User user) {
        return CompletableFuture.supplyAsync(() -> createUser(user), mes);
    }

    private Response createUser(User user) {
        userStore.create(user);

        return Response.accepted().build();
    }
}

For the request thread not to be occupied for too long, the JAX-RS method needs to return fast. This is due to the resource method being called from the container using inversion of control. The completion stage's result will be used to resume the client connection once processing has finished.

Returning completion stages is a fairly recent approach in the JAX-RS API. If a timeout declaration, together with more flexibility on the asynchronous response, is required, the AsyncResponse type can be injected into the method. The following code snippet demonstrates this approach.

import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;

@Path("users")
@Consumes(MediaType.APPLICATION_JSON)
public class UsersResource {

    @Resource
    ManagedExecutorService mes;

    ...

    @POST
    public void createUserAsync(User user, @Suspended AsyncResponse response) {

        response.setTimeout(5, TimeUnit.SECONDS);
        response.setTimeoutHandler(r ->
                r.resume(Response.status(Response.Status.SERVICE_UNAVAILABLE).build()));

        mes.execute(() -> response.resume(createUser(user)));
    }
}

Using custom timeouts, the client request will not wait infinitely, only until either the result is completed or the invocation timed out. The calculation, however, will continue since it's executed asynchronously.

For JAX-RS resources being implemented as EJBs, @Asynchronous business methods can be used to omit the asynchronous invocation using an executor service.

The JAX-RS client also supports asynchronous behavior. Depending on the requirements, it makes sense to not block during HTTP invocations. A previous example showed how to set timeouts on client requests. For long running and especially parallel external system calls, asynchronous and reactive behavior provides benefits.

Imagine several backends that provide weather information. The client component accesses all of them and provides the average weather forecast. Accessing the systems ideally happens in parallel.

import java.util.stream.Collectors;

@ApplicationScoped
public class WeatherForecast {

    private Client client;
    private List<WebTarget> targets;

    @Resource
    ManagedExecutorService mes;

    @PostConstruct
    private void initClient() {
        client = ClientBuilder.newClient();
        targets = ...
    }

    public Forecast getAverageForecast() {
        return invokeTargetsAsync()
                .stream()
                .map(CompletableFuture::join)
                .reduce(this::calculateAverage)
                .orElseThrow(() -> new IllegalStateException("No weather service available"));
    }

    private List<CompletableFuture<Forecast>> invokeTargetsAsync() {
        return targets.stream()
                .map(t -> CompletableFuture.supplyAsync(() -> t
                        .request(MediaType.APPLICATION_JSON_TYPE)
                        .get(Forecast.class), mes))
                .collect(Collectors.toList());
    }

    private Forecast calculateAverage(Forecast first, Forecast second) {
        ...
    }

    @PreDestroy
    public void closeClient() {
        client.close();
    }
}

The invokeTargetsAsync() method invokes the available targets asynchronously, using the managed executor service. The CompletableFuture handles are returned and used to calculate the average results. Calling the join() method will block until the invocation has finished and will deliver the individual results.

By invoking the available targets asynchronously, they call and wait for the potentially slow resource in parallel. Waiting for the weather service resources then only takes as long as the slowest response, not the sum of all responses.

The latest version of JAX-RS natively supports completion stages, which reduces boilerplate code in the applications. Similar to using completable futures, the invocation immediately returns a completion stage instance for further usage. The following demonstrates reactive JAX-RS client functionality using the rx() invocation:

public Forecast getAverageForecast() {
    return invokeTargetsAsync()
            .stream()
            .reduce((l, r) -> l.thenCombine(r, this::calculateAverage))
            .map(s -> s.toCompletableFuture().join())
            .orElseThrow(() -> new IllegalStateException("No weather service available"));
}

private List<CompletionStage<Forecast>> invokeTargetsAsync() {
    return targets.stream()
            .map(t -> t
                    .request(MediaType.APPLICATION_JSON_TYPE)
                    .rx()
                    .get(Forecast.class))
            .collect(Collectors.toList());
}

The preceding example doesn't require to lookup the managed executor service. The JAX-RS client will manage this internally.

Before the rx() method was introduced, the client contained an explicit async() invocation that behaves similarly, but only returns Futures. The reactive client approach usually fits the need in projects better.

As seen before, we are using the container-managed executor service since we're in a Java EE environment.

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

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