In our Spring application we have the situation that we consumer several cloud-based REST endpoints to retrieve data that are combined into one single data object (let's call it Employee.java
), which is then passed to the business logic.
I would like to encapsulate all these calls into one method of a service bean, something like this:
@Service
public class EmployeeService {
public Employee retrieveEmployee(String id) {
Employee e = new Employee();
callRESTToFillWithBasicData(e);
callRESTToFillWithExtendedData(e);
callRESTToFillWithAdditionalData(e);
return e;
}
}
As the REST calls are rather time consuming, we want to make this method asynchronous, so the application can do other stuff in the meantime. For additional difficulty, the methods in the service need access to request-scoped Spring beans. The problem is that Spring doesn't by default pass the request context to threads spawned through async methods. Therefore we've written a custom implementation of ThreadPoolTaskExecutor, similar to what is suggested here. The asynchronous code looks like this:
@Service
public class EmployeeService {
@Async("requestAwareTaskExecutor")
public CompletableFuture<Employee> retrieveEmployee(String id) {
Employee e = new Employee();
callRESTToFillWithBasicData(e);
callRESTToFillWithExtendedData(e);
callRESTToFillWithAdditionalData(e);
return CompletableFuture.completedFuture(e);
}
}
Now, to speed the whole thing up some more, we also want to parallelize the 3 REST calls in the method. This leaves us with some problems to solve:
The problem is, we can't just put
@Async
on thecallRESTToFill...
methods, because method calls from one object to itself are not routed through the Spring proxy and thus not executed asynchronously.We also can't just wrap the method calls in
CompletableFuture.runAsync(...)
, because then the threads would, again, be unable to access request scoped beans.The last resort would be to have the request-aware Executor bean
@Autowired
into the service bean and pass it to all theCompletableFuture
calls.
But we don't feel that this is the Spring way to go. Directly using the Executor feels kind of superfluous, considering that we've already specified the executor bean in the @Async
annotation.
What would be the best practice here?