5

I'm a newbie in reactive programming and also Spring Webflux I have a method to get some key from Redis and if this key is null or is not equals to the specified string i want to throw an exception but the nested donOnNext method won't get called and customerRepository.save(customer) get triggered while the exception must be thrown and break the chain. Can someone explain to me how the reactor API behaves in my case?

this is my method:

@Override
public Mono<RegistrationVerificationResDTO> verifyCustomerAndGenerateToken(Mono<VerifyOtpReqDTO> verifyOtpReqDTO) {
    return verifyOtpReqDTO
            .doOnNext(verifyDTO -> reactiveRedisOperations
                    .opsForValue()
                    .get(RedisDictionary.OTP_KEY + verifyDTO.getPhoneNumber())
                    .filter(otp -> otp.equalsIgnoreCase(verifyDTO.getOtp()))
                    .switchIfEmpty(Mono.error(ForbiddenException::new)))
            .map(verifyDTO -> customerRepository.findById(verifyDTO.getId())
                    .orElseThrow(() -> new NotFoundException("Customer not found")))
            .doOnNext(customer -> {
                customer.setVerified(true);
                customerRepository.save(customer);
            })
            .map(customer -> new RegistrationVerificationResDTO().setAccessToken("accessToken")
                    .setRefreshToken("refreshToken")
                    .setCustomer(customer));
}

UPDATE: I realized if we create another publisher inside the doOnNext method because the spring just subscribes to the most outer publisher the inner one won't get triggered I have updated my code but it still doesn't work.

Sia
  • 139
  • 2
  • 10
  • You are calling `subscribe ()` at some point? – daniu Mar 03 '20 at 19:56
  • @daniu Spring calls it automatically – Sia Mar 03 '20 at 19:58
  • On the returned `Mono`? How? – daniu Mar 03 '20 at 19:59
  • 1
    @daniu https://stackoverflow.com/questions/50795071/how-rest-endpoints-are-auto-subscribed-while-calling-from-browser-rest-client – Sia Mar 03 '20 at 20:03
  • doOnNext is just callback that says what to do when Mono above completed successfully,.. usually we log something or update some value, but it is not used to return something or throw exception.. so to summarize, your exception in doOnNext is swallowed If you want to throw exception then do it different, i.e. check here: https://stackoverflow.com/questions/53595420/correct-way-of-throwing-exceptions-with-reactor – vanillaSugar Mar 05 '20 at 12:02
  • @vanillaSugar I have changed it to: verifyOtpReqDTO .doOnNext(verifyDTO -> reactiveRedisOperations .opsForValue() .get(RedisDictionary.OTP_KEY + verifyDTO.getPhoneNumber()) .switchIfEmpty(Mono.error(ForbiddenException::new)) .filter(otp -> otp.equalsIgnoreCase(verifyDTO.getOtp())) .switchIfEmpty(Mono.error(ForbiddenException::new))) .map(... but it still doesn't work! – Sia Mar 05 '20 at 14:38
  • can you update the question with the new code that doesn't check for `null`s ? your comment above is not very readable. – Simon Baslé Mar 06 '20 at 09:17
  • @SimonBaslé done – Sia Mar 07 '20 at 17:21

1 Answers1

4

I'm guessing you're saying this "doesn't work" because you can't observe the saved customer in DB, even after the (correct) changes you've made to the second (innermost) doOnNext?

The third doOnNext is problematic: customerRepository.save(customer) is a NO-OP assuming customerRepository is a reactive repository, because the (lazy) Mono is neither attached to the main sequence nor subscribed to.

Simply replace that doOnNext with flatMap (and keep your changes to the innermost doOnNext with switchIfEmpty as well) to make it part of the reactive chain that Spring will subscribe to.

Simon Baslé
  • 27,105
  • 5
  • 69
  • 70
  • No, your guess is not correct the problem is the customer saves in the DB but my expectation is thrown the exception before that, consider ”nested” word in my question. But thank you for your suggestion in the third paragraph I will try it and I'll keep you updated. My other question is about this sentence that you have mentioned: to make it part of the reactive chain that spring will subscribe to. Based on my knowledge spring subscribes to the chain automatically and there's no difference between doonnext or flatmap could you please clarify your meaning by that sentence? – Sia Mar 06 '20 at 09:38
  • 4
    there is a HUGE difference between handling a Mono/Flux inside a `doOnNext` and inside a `flatMap`: Spring does subscribe to the outer Mono or Flux that your controller returns, but that subscription only propagates to publishers that are links in the chain. Generating a Publisher inside a `doOnNext` doesn't make it a link the chain, while returning a Publisher from `flatMap` does. – Simon Baslé Mar 06 '20 at 09:41
  • I updated my code in the question description could you please review it? – Sia Mar 07 '20 at 17:36