Teimuraz
Teimuraz

Reputation: 9325

Project reactor: How to retry mono with different argument until some condition is met

I need to generate unique code asynchronously with project reactor.

Method signature looks like this:

public Mono<String> generateCode() 

So the flow should be like this:

  1. Generate random code
  2. Check if this code exists in the database
  3. If exists, re-generate the code (step 1) and check it again (step2)
  4. If the code is unique, return it

My current solution is to call generateCode recursively like this:

 Mono<String> generateCode() {
    String code = generateCodeValue();
    return emailConfirmationRepository
        .findByCode(code)
        .flatMap(codeOpt -> codeOpt.map(c -> generateCode()).orElseGet(() -> Mono.just(code)));
  }

But I don't like this, because each call creates its own stack and this can lead to StackOverflowError.

I know, there should be a very large amount of calls, it most likely will not happen, but still, I need a solution without recursion, like a plain while loop, but with async code.

How can I achieve this with reactor?

Upvotes: 1

Views: 2650

Answers (3)

RodneyZ
RodneyZ

Reputation: 94

To retry it UNTIL SOME CONDITION IS MET indefinitely you shall:

Mono<String> generateCode() {
return Mono.fromCallable(() -> generateCodeValue())
        .flatMap(code -> emailConfirmationRepository
                .findByCode(code)
                .flatMap(codeOpt -> codeOpt
                        .map(c -> Mono.<String>error(new CodeAlreadyExistsException()))
                        .orElseGet(() -> Mono.just(code))))
        .retry(CodeAlreadyExistsException.class::isInstance)
}

class CodeAlreadyExistsException extends RuntimeException {}

Thanks @alexander-pankin.

Upvotes: 2

RodneyZ
RodneyZ

Reputation: 94

Note, though, that retry will retry all of your steps. So, if you have a more complicated code, like:

Mono<String> generateCode() {
return Mono.fromCallable(() -> generateCodeValue())
        .flatMap(code -> doSomeExpensiveOperation1())
        .flatMap(code -> doSomeDangerousOperation2())
        .flatMap(code -> emailConfirmationRepository
                .findByCode(code)
                .flatMap(codeOpt -> codeOpt
                        .map(c -> Mono.<String>error(new CodeAlreadyExistsException()))
                        .orElseGet(() -> Mono.just(code))))
        .retry(5);
}
class CodeAlreadyExistsException extends RuntimeException {}

Then all of your steps before "findByCode" will be repeated again, including doSomeExpensiveOperation1 and doSomeDangerousOperation2.

Upvotes: 3

Alexander Pankin
Alexander Pankin

Reputation: 3955

You could return error Mono when given code exists and use retry operator.

Mono<String> generateCode() {
    return Mono.fromCallable(() -> generateCodeValue())
            .flatMap(code -> emailConfirmationRepository
                    .findByCode(code)
                    .flatMap(codeOpt -> codeOpt
                            .map(c -> Mono.<String>error(new CodeAlreadyExistsException()))
                            .orElseGet(() -> Mono.just(code))))
            .retry(5);
}

class CodeAlreadyExistsException extends RuntimeException {}

Upvotes: 0

Related Questions