Inter-microservice communication has always brought questions and challenges to software architects. For example, when it comes to propagating certain information (via HTTP headers for instance) through a whole chain of calls in the scope of one transaction, we want this to happen outside of the microservices’ business logic. We do not want to tackle and work with these headers in the presentation or service layers of the application, especially if they are not important to the microservice for completing some business logic task. I would like to show you how you can automate this process by using Java thread locals and Tomcat/Spring capabilities, showing a simple microservice architecture.
Architecture overview
This is the sample architecture we will be looking at. We have a Zuul Proxy Server
that will act as a gateway towards our two microservices: the Licensing Microservice
and the Organization Service
. Those three will be the main focus of this article. Let’s say that a single License
belongs to a single Organization
and a single Organization
can deal with multiple Licenses
. Additionally, our services are registered to a Eureka Service Discovery
and they pull their application config from a separate Configuration Server
.
Simple enough, right? Now, what is the goal we want to achieve?
Let’s say that we have some sort of HTTP headers related to authentication or tracking of the chain of calls the application goes through. These headers arrive at the proxy along with each request from the client-side and they need to be propagated towards each microservice participating in the action of completing the user’s request. For simplicity’s sake, let’s introduce two made up HTTP headers that we need to send: correlation-id
and authentication-token
. You may say: “Well, the Zuul proxy gateway will automatically propagate those to the corresponding microservices, if not stated otherwise in its config”. And you are correct because this is a gateway that has an out-of-the-box feature for achieving that. But, what happens when we have an inter-microservice communication, for example, between the Licensing Microservice
and the Organization Microservice
. The Licensing Microservice
needs to make a call to the Organization Microservice
in order to complete some task. The Organization Microservice
needs to have the headers sent to it somehow. The “go-to, technical debt” solution would be to read these headers in the controller/presentation layer of our application, then pass them down to the business logic in the service layer, which in turn is gonna pass them to our configured HTTP client, which in the end is gonna send them to the Organization Microservice
. Ugly right? What if we have dozens of microservices and need to do this in each and every single one of them? Luckily, there is a lot prettier solution that includes using a neat Java feature: ThreadLocal
.
Java thread locals
The Java ThreadLocal class provides us with thread-local variables. What does this mean? Simply put, it enables setting a context (tying all kinds of objects) to a certain thread, that can later be accessed no matter where we are in the application, as long as we access them within the thread that set them up initially. Let’s look at an example:
public class Main {
public static final ThreadLocal<String> threadLocalContext = new ThreadLocal<>();
public static void main(String[] args) {
threadLocalContext.set("Hello from parent thread!");
Thread childThread = new Thread(() -> {
System.out.println("Child thread: " + threadLocalContext.get()); // Prints null
});
childThread.start();
childThread.join(); // Waiting for the child thread to finish
System.out.println("Parent thread: " + threadLocalContext.get()); // Prints "Hello from parent thread!"
}
}
We have a single static final ThreadLocal<String>
reference that we use for setting some information to the thread (in this case, the string “Hello from parent thread!” to the main thread). Accessing this variable via threadLocalContext.get()
(no matter in which class we are, as long as we are on the same main thread) produces the expected string we have set previously. Accessing it in a child thread produces a null
result. What if we set some context to the child thread as well:
threadLocalContext.set("Hello from parent thread!");
Thread childThread = new Thread(() -> {
threadLocalContext.set("Hello from child thread!");
System.out.println("Child thread: " + threadLocalContext.get()); // Prints "Hello from child thread!"
});
childThread.start();
childThread.join(); // Waiting for the child thread to finish
System.out.println("Parent thread: " + threadLocalContext.get()); // Prints "Hello from parent thread!"
We can notice that the two threads have completely separate contexts. Even though they access the same threadLocalContext
reference, in the background, the context is relative to the calling thread. What if we wanted the child thread to inherit its parent context:
public class Main {
private static final ThreadLocal<String> threadLocalContext = new InheritableThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
threadLocalContext.set("Hello from parent thread!");
Thread childThread = new Thread(() -> {
System.out.println("Child thread: " + threadLocalContext.get()); // Prints "Hello from parent thread!"
threadLocalContext.set("Hello from child thread!");
System.out.println("Child thread: " + threadLocalContext.get()); // Prints "Hello from child thread!"
});
childThread.start();
childThread.join(); // Waiting for the child thread to finish
System.out.println("Parent thread: " + threadLocalContext.get()); // Prints "Hello from parent thread!"
}
}
We only changed the ThreadLocal
to an InheritableThreadLocal
in order to achieve that. We can notice that the first print inside the child thread does not render null
anymore. The moment we set another context to the child thread, the two contexts become disconnected and the parent keeps its old one. Note that by using the InheritableThreadLocal
, the reference to the parent context gets copied to the child, meaning: this is not a deep copy, but two references pointing to the same object (in this case, the string “Hello from parent thread!”). If, for example, we used InheritableThreadLocal<SomeCustomObject>
and tackled directly some of the properties of the object inside the child thread (threadLocalContext.get().setSomeProperty("some value")
), then this would also be reflected in the parent thread and vice versa. If we want to disconnect the two contexts completely, we just call .set(new SomeCustomObject())
on one of the threads, which will turn its local reference to point to the newly created object.
Now, you may be wondering: “What does this have to do with automatically propagating headers to a microservice?”. Well, by using Servlet containers such as Tomcat (which Spring Boot has it embedded by default), we handle each new HTTP request (whether we like/know it or not :-)) in a separate thread. The servlet container picks an idle thread from its dedicated thread pool each time a new call is made. This thread is then used by Spring Boot throughout the processing of the request and the return of the response. Now, it is only a matter of setting up Spring filters and HTTP client interceptors that will set
and get
the local thread context that will contain the HTTP headers.
Solution
First off, let’s create a simple POJO class that is going to contain both of the headers that need propagating:
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class RequestHeadersContext {
public static final String CORRELATION_ID = "correlation-id";
public static final String AUTHENTICATION_TOKEN = "authentication-token";
private String correlationId;
private String authenticationToken;
}
Next, we will create a utility class for setting and retrieving the thread-local context:
public final class RequestHeadersContextHolder {
private static final ThreadLocal<RequestHeadersContext> requestHeaderContext = new ThreadLocal<>();
public static void clearContext() {
requestHeaderContext.remove();
}
public static RequestHeadersContext getContext() {
RequestHeadersContext context = requestHeaderContext.get();
if (context == null) {
context = createEmptyContext();
requestHeaderContext.set(context);
}
return context;
}
public static void setContext(RequestHeadersContext context) {
Assert.notNull(context, "Only not-null RequestHeadersContext instances are permitted");
requestHeaderContext.set(context);
}
public static RequestHeadersContext createEmptyContext() {
return new RequestHeadersContext();
}
}
The idea is to have a Spring filter, that is going to read the HTTP headers from the incoming request and place them in the RequestHeadersContextHolder
:
@Configuration
public class RequestHeadersServiceConfiguration {
@Bean
public Filter getFilter() {
return new RequestHeadersContextFilter();
}
private static class RequestHeadersContextFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
RequestHeadersContext context = new RequestHeadersContext(
httpServletRequest.getHeader(RequestHeadersContext.CORRELATION_ID),
httpServletRequest.getHeader(RequestHeadersContext.AUTHENTICATION_TOKEN)
);
RequestHeadersContextHolder.setContext(context);
filterChain.doFilter(servletRequest, servletResponse);
}
}
}
We created a RequestHeadersServiceConfiguration
class which, at the moment, has a single Spring filter bean defined. This filter is going to read the needed headers from the incoming request and set them in the RequestHeadersContextHolder
(we will need to propagate those later when we make an outgoing request to another microservice). Afterwards, it will resume the processing of the request and will give control to the other filters that might be present in the filter chain. Keep in mind that, all the while, this code executes within the boundaries of the dedicated Tomcat thread, which the container had assigned to us.
Next, we need to define an HTTP client interceptor which we are going to link to a RestTemplate client, which in turn is going to execute the interceptor’s code each time before it makes a request to an outer microservice. We can add this new RestTemplate
bean inside the same configuration file:
@Configuration
public class RequestHeadersServiceConfiguration {
// .....
@LoadBalanced
@Bean
public RestTemplate getRestTemplate() {
RestTemplate restTemplate = new RestTemplate();
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
interceptors.add(new RequestHeadersContextInterceptor());
return restTemplate;
}
private static class RequestHeadersContextInterceptor implements ClientHttpRequestInterceptor {
@Override
@NonNull
public ClientHttpResponse intercept(@NonNull HttpRequest httpRequest,
@NonNull byte[] body,
@NonNull ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
RequestHeadersContext context = RequestHeadersContextHolder.getContext();
HttpHeaders headers = httpRequest.getHeaders();
headers.add(RequestHeadersContext.CORRELATION_ID, context.getCorrelationId());
headers.add(RequestHeadersContext.AUTHENTICATION_TOKEN, context.getAuthenticationToken());
return clientHttpRequestExecution.execute(httpRequest, body);
}
}
}
As you might have guessed, the interceptor reads the header values from the thread-local context and sets them up for the outgoing request. The RestTemplate
just adds this interceptor to the list of its already existing ones.
A good-to-have thing will be to eventually clear/remove the thread-local variables from the thread. When we have an embedded Tomcat container, missing out on this point will not impose a problem, since along with the Spring application, the Tomcat container dies as well. This means that all of the threads will altogether be destroyed and the thread-local memory released. However, if we happen to have a separate servlet container and we deploy our app as a .war
instead of a standalone .jar
, not clearing the context might introduce some memory leaks. Imagine having multiple applications on our standalone servlet container and each of them messing around with thread locals. The container shares its threads with all of the applications. When one of the applications is turned off, the container is going to continue to run and the threads which it borrowed to the application will not cease to exist. Hence, the thread-local variables will not be garbage collected, since there are still references to them. That is why we are going to define and add an interceptor to the Spring interceptor registry, which will clear the context after a request finishes and the thread can be assigned to other tasks:
@Configuration
public class WebMvcInterceptorsConfiguration implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RequestHeadersContextClearInterceptor()).addPathPatterns("/**");
}
private static class RequestHeadersContextClearInterceptor implements HandlerInterceptor {
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) {
RequestHeadersContextHolder.clearContext();
}
}
}
All we need to do now is wire these configurations into our microservices. We can create a separate library extracting the config (and maybe upload it to an online repository, such as Maven Central, or our own Nexus) so that we do not need to copy-paste all of the code into each of our microservices. Whatever the case, it is good to make this library easy to use. That is why we are going to create a custom annotation for enabling it:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({RequestHeadersServiceConfiguration.class, WebMvcInterceptorsConfiguration.class})
public @interface EnableRequestHeadersService {
}
Usage
Let’s see how we can leverage and use this library from inside a microservice. Only a couple of things are needed.
First, we need to annotate our application with the @EnableRequestHeadersService
:
@SpringBootApplication
@EnableRequestHeadersService
public class LicensingServiceApplication {
public static void main(String[] args) {
SpringApplication.run(LicensingServiceApplication.class, args);
}
}
Second, we need to inject the already defined RestTemplate
in our microservice and use it as given:
@Component
public class OrganizationRestTemplateClient {
private final RestTemplate restTemplate;
public OrganizationRestTemplateClient(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public Organization getOrganization(String organizationId) {
ResponseEntity<Organization> restExchange = restTemplate.exchange(
"http://organizationservice/v1/organizations/{organizationId}",
HttpMethod.GET,
null,
Organization.class,
organizationId
);
return restExchange.getBody();
}
}
We can notice that the getOrganization(String organizationId)
method does not handle any HTTP headers whatsoever. It just passes the URL and the HTTP method and lets the imported configuration do its magic. As simple as that! We can now call the getOrganization
method wherever we like, without having any sort of knowledge about the headers that are being sent in the background. If we have the need to read them somewhere in our code, or even change them, then we can use the RequestHeadersContextHolder.getContext()/setContext()
static methods wherever we like in our microservice, without the need to parse them from the request object.
Feign HTTP Client
If we want to leverage a more declarative type of coding we can always use the Feign HTTP Client. There are ways to configure interceptors here as well, so, using the RestTemplate
is not strictly required. We can add the new interceptor configuration to the already existing RequestHeadersServiceConfiguration
class:
@Configuration
public class RequestHeadersServiceConfiguration {
// .....
@Bean
public RequestInterceptor getFeignRequestInterceptor() {
return new RequestHeadersContextFeignInterceptor();
}
private static class RequestHeadersContextFeignInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
RequestHeadersContext context = RequestHeadersContextHolder.getContext();
requestTemplate.header(RequestHeadersContext.CORRELATION_ID, context.getCorrelationId());
requestTemplate.header(RequestHeadersContext.AUTHENTICATION_TOKEN, context.getAuthenticationToken());
}
}
}
The new bean we created is going to automatically be wired as a new Feign interceptor for our client.
Next, in our microservice, we can annotate our application class with @EnableFeignClients
and then create our Feign client:
@FeignClient("organizationservice")
public interface OrganizationFeignClient {
@GetMapping(value = "/v1/organizations/{organizationId}")
Organization getOrganization(@PathVariable("organizationId") String organizationId);
}
All that we need to do now, is just inject our new client anywhere in our services and use it from there. In comparison to the RestTemplate
, this is a more concise way of making HTTP calls.
Asynchronous HTTP requests
What if we do not want to wait for the request to the Organization Microservice
to finish and want to execute it asynchronously and concurrently (using the @EnableAsync
and @Async
annotations from Spring, for example). How are we going to access the headers that need to be propagated in this case? You might have guessed it: by using InheritableThreadLocal
instead of ThreadLocal
. As mentioned earlier above, the child threads we create separately (aside from the Tomcat ones which will be the parents) can inherit their parent’s context. That way we can send header-populated requests in an asynchronous manner. There is no need to clear the context for the child threads (side note: clearing it will not affect the parent thread, and clearing the parent thread will not affect the child thread; it will only set the current thread’s local context reference to null
), since these will be created from a separate thread pool that has nothing to do with the container’s one. The child threads’ memory will be cleared after execution or after the Spring application exits because eventually, they die off.
Summary
I hope you will find this neat little trick useful while refactoring your microservices. A lot of Spring’s functionality is actually based on thread locals. Looking into its source code, you will find a lot of similar/same concepts as the ones mentioned above. Spring Security is one example, Zuul Proxy is another.
The full code for this article can be found here.
Налаженный бизнес или успешная франшиза?
Рейтинг франшиз