4

I want to make an asynchronous rest call for which I'm using spring webclient and getting back a Mono. I'm also doing some database calls in parallel but it can't be done reactively due to some reason.

    Map<String, Object> models = new HashMap<>();

    Mono<User> users = this.webClient...;
    users.map(resp -> new UserState(userRequest, resp))
            .subscribe(response -> {
                models.put("userState", response);
            });
    Iterable<Product> messages = this.productRepository.findAll();
    models.put("products", messages);
    //Wait for users.subscribe to finish <<<<<<<<<<<<<HERE
    return new ModelAndView("messages/list", models);

How do I wait for subscribe to finish before returning ModelAndView. This would have been easy if I was using a Future where I can do get() whenever I want.

Heisenberg
  • 5,514
  • 2
  • 32
  • 43
  • 1
    Doesn't this kind of defeat the purpose of asynchronous processing? If you 100% need this, use [`block()`](https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Mono.html#block--). – Paul Benn Jun 03 '19 at 07:05
  • @PaulBenn I'm making database call after subscribe which I understand is executing in parallel. Many times my REST call will be finished before the DB call returns. I want to handle the cases where it doesn't. – Heisenberg Jun 03 '19 at 07:13
  • You can create a `Lock` before and unlock it within the subscribe callback. – daniu Jun 04 '19 at 14:07
  • @daniu That might work I guess. But `Mono` is returned by me and my clients will be handling rest of the stuff. It'll be a bit tedious to make them deal with locks. May be I can callback directly from them, subscribe myself and return the lock in response directly. But I'm really surprised there is no inbuilt method for this. I guess I'll have to fallback to plain old `Future`. – Heisenberg Jun 04 '19 at 14:51

1 Answers1

1

You can wrap the blocking call in a Mono executed on a separate scheduler, zip it with the Mono containing UserState data and transform their combination into a Mono<ModelAndView> (which can be returned from Spring controller methods). The calls will be executed in parallel, results will be combined when both calls are completed.

You can define a single bounded scheduler per application specifically for blocking calls and provide it as a constructor argument to any class that makes blocking calls.

The code will look as follows:

@Configuration 
class SchedulersConfig {

  @Bean
  Scheduler parallelScheduler(@Value("${blocking-thread-pool-size}") int threadsCount) {
    return Schedulers.parallel(threadsCount);
  }
}

@RestController
class Controller {

  final Scheduler parallelScheduler;

  ...

  Mono<User> userResponse = // webClient...

  Mono<Iterable<Product>> productsResponse = Mono.fromSupplier(productRepository::findAll)
    .subscribeOn(parallelScheduler); 

  return Mono.zip(userResponse, productsResponse, (user, products) -> 
    new ModelAndView("messages/list", 
      ImmutableMap.of(
        "userState", new UserState(userRequest, user),
        "products", products
      ))
  );
}

Update based on the comment:
If you just need to execute HTTP call asynchronously and then join it with the database results you can do the following

Map<String, Object> models = new HashMap<>();
Mono<User> userMono = webClient...;
CompletableFuture<User> userFuture = userMono.toFuture();
Iterable<Product> messages = productRepository.findAll();
User user = userFuture.join();
models.put("products", messages);
models.put("userState", new UserState(userRequest, user));
return new ModelAndView("messages/list", models);
Ilya Zinkovich
  • 4,082
  • 4
  • 25
  • 43
  • Thank you for your answer. But the DB call is not in my hand. I've given reactive support in my sdk and it'll return a Mono and clients will be using it from there on. Your answer was my first suggestion but it's too complicated for clients as they have many more things going on in their code. They don't want to deal with reactive mess. They want a `Future` type functionality. – Heisenberg Jun 04 '19 at 14:42
  • @Heisenberg then just turn the `Mono` into `CompletableFuture` using `.toFuture()` method instead of using `.subscribe()` and `.join()` the future after the DB call. – Ilya Zinkovich Jun 04 '19 at 14:56
  • @IIya this looks great! One question though, since the `Mono` is not subscribed yet, do you think `toFuture()` will start the call? – Heisenberg Jun 06 '19 at 05:25
  • 1
    @Heisenberg yes, `toFuture()` calls `.subscribe()` internally. – Ilya Zinkovich Jun 06 '19 at 09:51