13

I have a spring boot microservice where we call multiple services(Lets say Service A and Service B). I am trying to call these two services Asynchronously on multiple threads based on some conditions and once processing is completed I would like to merge the response from Service A and ServiceB.

I know we can use @Async to run a process asynchronously and use ExecutorService to start multiple threads for a service.

But i am not Sure How to keep all things together. So looking for any suggestions here?

              @Async
              Service A(thread1,thread2) \
MicroService /                             (Merge from Response of ServiceA and ServiceB)
             \ @Async
              Service B(thread1,thread2) /

I know this is mostly theoretically explained above, but i tried following / going through multiple websites but Most of the articles either explains about Aync or Multithreading but not sure how to wait and run two process in Async in multiple threads and continue execution after these two service calls are completed!

Any Suggestions or leads are appreciated! TIA :)

JingJong
  • 207
  • 1
  • 3
  • 14

2 Answers2

20

You need to use spring's AsyncResult class to wrap your result and then use its method .completable() to return CompletableFuture object.

When merging future object use CompletableFuture.thenCompose() and CompletableFuture.thenApply() method to merge the data like this:

CompletableFuture<Integer> result = futureData1.thenCompose(fd1Value -> 
                futureData2.thenApply(fd2Value -> 
                        merge(fd1Value, fd2Value)));

Here is a basic example:

Annotate Spring boot main class with @EnableAsync annotation

@SpringBootApplication
@EnableAsync
public class StackOverflowApplication {

    public static void main(String[] args) {
        SpringApplication.run(StackOverflowApplication.class, args);
    }

}

Create a sample service which will return CompletableFuture

Aservice.java

@Service
public class Aservice {

    @Async
    public CompletableFuture<Integer> getData() throws InterruptedException {
        Thread.sleep(3000); // sleep for 3 sec
        return new AsyncResult<Integer>(2).completable(); // wrap integer 2
    }
}

Bservice.java

@Service
public class Bservice {

    @Async
    public CompletableFuture<Integer> getData() throws InterruptedException {
        Thread.sleep(2000); // sleep for 2 sec
        return new AsyncResult<Integer>(1).completable(); // wrap integer 1
    }
}

Create another service which will merge other two service data

ResultService.java

@Service
public class ResultService {

    @Autowired
    private Aservice aservice;
    @Autowired
    private Bservice bservice;

    public CompletableFuture<Integer> mergeResult() throws InterruptedException, ExecutionException {
        CompletableFuture<Integer> futureData1 = aservice.getData();
        CompletableFuture<Integer> futureData2 = bservice.getData();

        // Merge futures from Aservice and Bservice
        return futureData1.thenCompose(
            fd1Value -> futureData2.thenApply(fd2Value -> fd1Value + fd2Value));
    }
}

Create a sample controller for testing

ResultController.java

@RestController
public class ResultController {

    @Autowired
    private ResultService resultService;

    @GetMapping("/result")
    CompletableFuture<Integer> getResult() throws InterruptedException, ExecutionException {
        return resultService.mergeResult();
    }

}
srk
  • 4,857
  • 12
  • 65
  • 109
Ajit Soman
  • 3,926
  • 3
  • 22
  • 41
  • Why `Thread.join`? Why not make the `ResultController` just return another `CompletableFuture` with the aggregation? – Edwin Dalorzo Mar 05 '20 at 15:51
  • @EdwinDalorzo, i have updated answer as per your comment – Ajit Soman Mar 05 '20 at 18:06
  • @AjitSoman and Edwin Thanks for the response. I will try to understand whats happening here and will give a try – JingJong Mar 06 '20 at 01:11
  • @AjitSoman thanks again for the complete example. But looks like above is just for Asynchronous Processing correct? For opening multiple threads can i use ExecutorService? – JingJong Mar 08 '20 at 19:11
  • 1
    @JingJong, Yes, you need to create bean for `Execotor` like this: ` @Bean public Executor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(3); executor.setMaxPoolSize(15); executor.setQueueCapacity(500); executor.setThreadNamePrefix("GithubLookup-"); executor.initialize(); return executor; }`. check this link for more information: https://spring.io/guides/gs/async-method/ – Ajit Soman Mar 09 '20 at 04:37
2

You can look to a java's CompletableFuture. The ComplitableFuture allows to union several async tasks (which also are CompletableFuture) and waits for a result of all CompletableFutures.I am not sure that it is exactly fit for your case, but it may be helpful. https://www.baeldung.com/java-completablefuture#Multiple

Maxim Popov
  • 1,167
  • 5
  • 9