Search code examples
spring-bootspring-webfluxproject-reactorspring-webclient

How to avoid Mono#block() when using reactive in rest API logic


So the problem is as follows:

  1. I am exposing API to register user within my service
  2. Before I am going to register user, I want to validate him in external service (rest API)
  3. I want to inform user with proper response status whether he was created (201 or some error code)

My code to create user looks like that:

  @Transactional
  public void createUser(UserDto userDto) {
    UserEntity newUser = userFactory.createUser(userDto);
    String userId = userDto.getUserId();

    externalApiService
        .validateUser(userId)
        .subscribe(a-> userRepository.save(newUser));
  }

and validation of user is as follows:

  public Mono<Void> validateUser(String userId) {
    return webClient
        .get(...)
        .retrieve()
        .toEntity(Void.class);
    //Exact request is bit more complicated, but those details shouldn't be crucial in this example
  }

My endpoint that is utilizing this looks like that:

  @PostMapping
  public ResponseEntity<Void> createUser(@Valid @RequestBody UserDto dto) {
    userService.createUser(dto);
    return new ResponseEntity<>(HttpStatus.CREATED);
  }

This is quite easy when I use blocking RestTemplate or use block() on Mono that I just got from WebClient. If there is an exception from REST call to external service my global exception handler will do the job and respond user with proper response code.

But what if I want to utilize full power of reactive programming and don't want to block(). I cannot figure out how to properly do this. Can anyone give me some advice?

And please excuse me if my question is trivial, I am quite new to WebClient and reactive programming at all.


Solution

  • WebClient is a reactive client and the only way to use it with non-reactive Spring MVC is to block.

    To make the flow fully non-blocking, all components/functions should be reactive. You would need to switch to Spring WebFlux and also make createUser reactive.

    @PostMapping
    public Mono<ResponseEntity<Void>> createUser(@RequestBody UserDto dto) {
        return userService.createUser(dto)
                .map(user -> ResponseEntity.status(HttpStatus.CREATED).build());
    }
    

    This code assumes that createUser is reactive

    public static Mono<UserEntity> createUser(UserDto dto) {
        ...
    }
    

    In case userRepository is not reactive you could wrap non-reactive code and run it on a separate scheduler How Do I Wrap a Synchronous, Blocking Call?.