Stability when consuming HTTP

This example, however, lacks some aspects in regard to resilience. Calling this client control without further consideration could lead to unwanted behavior.

The client request blocks until the HTTP invocation either returned successfully or the connection timed-out. The HTTP connection timeout configuration depends on the JAX-RS implementation, which is set to infinite blocking in some technologies. For resilient clients, this is obviously not acceptable. A connection could wait forever, blocking the thread and, in a worst-case scenario, could block the whole application if all available threads are stuck at that location, waiting for their individual HTTP connection to finish. To prevent this scenario, we configure the client to use custom connection timeouts.

The timeout values depend on the application, especially the network configuration to the external system. It varies what are reasonable values for HTTP timeouts. To get sensible timeout values, it is advisable to gather statistics about the latency to the external system. For systems where load and network latency vary a lot, for example e-commerce systems with selective high utilization during certain seasons, the nature of variations should be considered.

The HTTP connect timeout is the maximum time allowed until a connection has been established. Its value should be small. The HTTP read timeout specifies how long to wait to read data. Its value depends on the nature of the external service being consumed. Following the gathered statistics, a good starting point for configuring the read timeout is to calculate the mean response times plus three times the standard deviation. We will cover the topic of performance and service backpressure in Chapter 9, Monitoring, Performance, and Logging.

The following shows how to configure both the HTTP connect and read timeout:

@ApplicationScoped
public class CoffeePurchaser {

    ...

    @PostConstruct
    private void initClient() {
        client = ClientBuilder.newBuilder()
                .connectTimeout(100, TimeUnit.MILLISECONDS)
                .readTimeout(2, TimeUnit.SECONDS)
                .build();
        target = client.target("http://coffee.example.com/beans/purchases/");
    }

    ...
}

Client invocations can result in potential errors. The external service could respond with an unexpected status code, an unexpected response, or no response at all.This needs to be considered when implementing client components.

The readResponse() client call expects the response to be of the HTTP status code SUCCESSFUL family and the response body to be mappable into the given Java type from the requested content type. If something goes wrong, a RuntimeException is thrown. Runtime exceptions enable engineers to write code without obfuscating try-catch blocks, but also require them to be aware of the potential errors.

The client method could catch the runtime exceptions in order to prevent them from being thrown to the calling domain service. There is also another, leaner possibility using interceptors. Interceptors provide cross-cutting functionalities that are applied without being tightly coupled to the decorated functionality. For example, this client method should intentionally return null when the external system could not deliver a reasonable response.

The following demonstrates an interceptor that intercepts method invocations and applies this behavior on occurred exceptions. This interceptor is integrated by annotating the method of the CoffeePurchaser control:

import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;

@Interceptor
public class FailureToNullInterceptor {

    @AroundInvoke
    public Object aroundInvoke(InvocationContext context) {
        try {
            return context.proceed();
        } catch (Exception e) {
            ...
            return null;
        }
    }
}

The purchaseBean() method is annotated with @Interceptors(FailureToNullInterceptor.class). This activates the cross-cutting concerns for that method.

In regard to resilience, the client functionality could include further logic. If several systems are available, the client can retry failed invocations on a different system. Then, only as a last resort, the invocation would fail without a result.

In the topic, Cross-cutting concerns, we will see how to implement further cross-cutting concerns.

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

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