Mono switchIfEmpty() is always called - java

I have two methods.
Main method:
#PostMapping("/login")
public Mono<ResponseEntity<ApiResponseLogin>> loginUser(#RequestBody final LoginUser loginUser) {
return socialService.verifyAccount(loginUser)
.flatMap(socialAccountIsValid -> {
if (socialAccountIsValid) {
return this.userService.getUserByEmail(loginUser.getEmail())
.switchIfEmpty(insertUser(loginUser))
.flatMap(foundUser -> updateUser(loginUser, foundUser))
.map(savedUser -> {
String jwts = jwt.createJwts(savedUser.get_id(), savedUser.getFirstName(), "user");
return new ResponseEntity<>(HttpStatus.OK);
});
} else {
return Mono.just(new ResponseEntity<>(HttpStatus.UNAUTHORIZED));
}
});
}
And this invoked method (the service calls an external api):
public Mono<User> getUserByEmail(String email) {
UriComponentsBuilder builder = UriComponentsBuilder
.fromHttpUrl(USER_API_BASE_URI)
.queryParam("email", email);
return this.webClient.get()
.uri(builder.toUriString())
.exchange()
.flatMap(resp -> {
if (Integer.valueOf(404).equals(resp.statusCode().value())) {
return Mono.empty();
} else {
return resp.bodyToMono(User.class);
}
});
}
In the above example, switchIfEmpty() is always called from the main method, even when a result with Mono.empty() is returned.
I cannot find a solution for this simple problem.
The following also doesn't work:
Mono.just(null)
Because the method will throw a NullPointerException.
What I also can't use is the flatMap method to check that foundUser is null.
Sadly, flatMap doesn't get called at all in case I return Mono.empty(), so I cannot add a condition here either.
#SimY4
#PostMapping("/login")
public Mono<ResponseEntity<ApiResponseLogin>> loginUser(#RequestBody final LoginUser loginUser) {
userExists = false;
return socialService.verifyAccount(loginUser)
.flatMap(socialAccountIsValid -> {
if (socialAccountIsValid) {
return this.userService.getUserByEmail(loginUser.getEmail())
.flatMap(foundUser -> {
return updateUser(loginUser, foundUser);
})
.switchIfEmpty(Mono.defer(() -> insertUser(loginUser)))
.map(savedUser -> {
String jwts = jwt.createJwts(savedUser.get_id(), savedUser.getFirstName(), "user");
return new ResponseEntity<>(HttpStatus.OK);
});
} else {
return Mono.just(new ResponseEntity<>(HttpStatus.UNAUTHORIZED));
}
});
}

It's because switchIfEmpty accepts Mono "by value". Meaning that even before you subscribe to your mono, this alternative mono's evaluation is already triggered.
Imagine a method like this:
Mono<String> asyncAlternative() {
return Mono.fromFuture(CompletableFuture.supplyAsync(() -> {
System.out.println("Hi there");
return "Alternative";
}));
}
If you define your code like this:
Mono<String> result = Mono.just("Some payload").switchIfEmpty(asyncAlternative());
It'll always trigger alternative no matter what during stream construction. To address this you can defer evaluation of a second mono by using Mono.defer
Mono<String> result = Mono.just("Some payload")
.switchIfEmpty(Mono.defer(() -> asyncAlternative()));
This way it will only print "Hi there" when alternative is requested
UPD:
Elaborating a little on my answer. The problem you're facing is not related to Reactor but to Java language itself and how it resolves method parameters. Let's examine the code from the first example I provided.
Mono<String> result = Mono.just("Some payload").switchIfEmpty(asyncAlternative());
We can rewrite this into:
Mono<String> firstMono = Mono.just("Some payload");
Mono<String> alternativeMono = asyncAlternative();
Mono<String> result = firstMono.switchIfEmpty(alternativeMono);
These two code snippets are semantically equivalent. We can continue unwrapping them to see where the problem lies:
Mono<String> firstMono = Mono.just("Some payload");
CompletableFuture<String> alternativePromise = CompletableFuture.supplyAsync(() -> {
System.out.println("Hi there");
return "Alternative";
}); // future computation already tiggered
Mono<String> alternativeMono = Mono.fromFuture(alternativePromise);
Mono<String> result = firstMono.switchIfEmpty(alternativeMono);
As you can see future computation was already triggered at the point when we start composing our Mono types. To prevent unwanted computations we can wrap our future into a defered evaluation:
Mono<String> result = Mono.just("Some payload")
.switchIfEmpty(Mono.defer(() -> asyncAlternative()));
Which will unwrap into
Mono<String> firstMono = Mono.just("Some payload");
Mono<String> alternativeMono = Mono.defer(() -> Mono.fromFuture(CompletableFuture.supplyAsync(() -> {
System.out.println("Hi there");
return "Alternative";
}))); // future computation defered
Mono<String> result = firstMono.switchIfEmpty(alternativeMono);
In second example the future is trapped in a lazy supplier and is scheduled for execution only when it will be requested.
UPD: 2022:
Since some time project reactor comes with an alternative API for wrapping eagerly computed futures which results in the same - trapping eager computation in a lazy supplier:
Mono<String> result = Mono.just("Some payload")
.switchIfEmpty(Mono.fromCompletionStage(() -> alternativePromise()));

For those who, despite the well voted answer, do not still understand why such a behaviour:
Reactor sources (Mono.xxx & Flux.xxx) are either:
Lazily evaluated : the content of the source is evaluated/triggered only when a subscriber subscribes to it;
or eagerly evaluated : the content of the source is immediately evaluated even before the subscriber subscribes.
Expressions like Mono.just(xxx), Flux.just(xxx), Flux.fromIterable(x,y,z) are eager.
By using defer(), you force the source to be lazily evaluated. That's why the accepted answer works.
So doing this:
someMethodReturningAMono()
.switchIfEmpty(buildError());
with buildError() relying on an eager source to create an alternative Mono will ALWAYS be evaluated before the subscription:
Mono<String> buildError(){
return Mono.just("An error occured!"); //<-- evaluated as soon as read
}
To prevent that, do this:
someMethodReturningAMono()
.switchIfEmpty(Mono.defer(() -> buildError()));
Read this answer for more.

Related

Mapping imperative conditional statements (with exception throwing) to Reactive process

I am struggling to make sense of Reactive process flow. My understanding is that, when manipulating data within a Mono or Flux, type consistency must be maintained. However, when problems occur within that process, something resembling an exception must be thrown, and that breaks the type consistency.
Currently, I have an imperative process for taking a verification token ID and processing it. In two instances, it throws exceptions (InvalidTokenException is 404 response; ExpiredTokenException is a 410), and in the last modifies and saves the account, and deletes the token. I would like to convert this to a reactive process:
public AccountDto verifyAccountByToken (UUID tokenId) {
VerifyToken vToken = verifyTokenRepository.findByToken(tokenId);
if (vToken == null) {
throw new InvalidTokenException();
}
if (vToken.isExpired()) {
verifyTokenRepository.delete(vToken);
throw new ExpiredTokenException();
}
Account account = vToken.getAccount();
account.addRole(AccountRole.VERIFIED);
account.deleteRole(AccountRole.UNVERIFIED);
accountRepository.save(account);
verifyTokenRepository.delete(vToken);
return new AccountDto(account);
}
I believe the below is essentially correct for the data-manipulation process when everything is as it should be, but how do I handle the conditional branching? – How do I say: do-this-if-empty? (I figure I can use .filter() as a way of capturing the expired case.) And how do I convert it to an error type?
public Mono<AccountDto> verifyAccountByToken (UUID tokenId) {
return verifyTokenRepository.findByToken(tokenId)
// the bits I can't figure out to deal with Invalid and Expired tokens;
.flatMap(vToken -> {
verifyTokenRepository.delete(vToken);
return vToken.getAccount();
})
.flatMap(account -> {
account.addRole(AccountRole.VERIFIED);
account.deleteRole(AccountRole.UNVERIFIED);
return accountRepository.save(account);
})
.map(account -> new AccountDto(account));
}
How do I say: do-this-if-empty?
There's an operator for that:
.switchIfEmpty(Mono.error(new InvalidTokenException()));
Have a look at comments below:
public Mono<AccountDto> verifyAccountByToken(UUID tokenId) {
return verifyTokenRepository.findByToken(tokenId) //Mono<VerifyToken> or Mono<Void>
.switchIfEmpty(Mono.error(new InvalidTokenException())); //token does not exist, empty Mono is returned, switch to an error signal
.flatMap(this::validateToken)
.map(vToken -> vToken.getAccount())
.flatMap(account -> {
//token is valid...
account.addRole(AccountRole.VERIFIED);
account.deleteRole(AccountRole.UNVERIFIED);
return accountRepository.save(account);
})
.map(account -> new AccountDto(account));
}
private Mono<VerifyToken> validateToken(VerifyToken vToken) {
if (vToken.isExpired()) {
//Token is expired... delete it and then signal an error
return verifyTokenRepository.delete(vToken)
.then(Mono.error(new ExpiredTokenException()));
}
return Mono.just(vToken);//token is valid...
}

How do I chain Singles in Rxjava

I had this:
#Override
public Single<JsonObject> getUser(String token) {
return tokenManager.getTokenInfo(token)
.map(userInfo -> userRepo.findOne(userInfo));
}
But the compiler complains that Single<Single<JsonObject>> cannot be converted to Single<JsonObject>. The problem is, userRepo.findOne returns a single, so the .map() returns a single that resolves to a single, rather than the value. Is there a way to flatten this? Like with JavaScript promises where if you had a promise that resolves to a promise, a subscriber to the first promise recieves the value of the second promise, rather than the promise itself.
I am currently settled with this:
public Single<JsonObject> getUser(String token) {
return Single.create(emitter -> {
tokenManager.getTokenInfo(token)
.subscribe(userInfo -> userRepo.findOne(userInfo)
.subscribe(user -> emitter.onSuccess(user),
emitter::onError),
emitter::onError);
});
}

Print response from doOnError

I'm trying to implement this code with Spring web flux:
Can you guide me how I can call the two methods parseRawSuccessResponse and parseRawFailedResponse based on the client response codes because I have different return values? Also how this code can be implemented without using block()?
I tried this:
Mono<AuthorizeRequest> transactionMono = Mono.just(transaction);
return client.post().uri(checkTrailingSlash(gatewayUrl) + token)
.body(transactionMono, AuthorizeRequest.class)
.exchange()
.flatMap(clientResponse -> {
if (clientResponse.statusCode().is4xxClientError()) {
return Mono.error(RuntimeException::new);
}
return clientResponse.bodyToMono(AuthorizeResponse.class);
});
// parse response:
result.map(fooBar -> {
return parseRawSuccessResponse(fooBar);
}).doOnError(throwable -> {
// return parseRawFailedResponse(throwable);
}).block();
You can use one of the available disjunction type implementations available for java.
val result: Mono<Either<ClientError, AuthorizeResponse>> =
client.post().uri(checkTrailingSlash(gatewayUrl) + token)
.body(transactionMono, AuthorizeRequest.class)
.exchange()
.flatMap(response -> {
if (clientResponse.statusCode().is2xxSuccessful()) {
return response.bodyToMono(AuthorizeResponse.class).map(Either::right);
} else if (clientResponse.statusCode().is4xxClientError()) {
return response.bodyToMono(ClientError.class).map(Either::left);
} else {
return Mono.error(new RuntimeException("Unexpected response type"));
}
});
You can implement disjunction type yourself of just use existing implementations. One of the popular options would be: vavr.io
Answering the second part of your question, use Mono.subscribe(). It's not idiomatic to escape reactive context with .block() you just need to build your computation chain in terms of Monos and Fluxes and then do a single subscribe at the end.

Build a Flux object in while cycle

I have an interesting problem which I don't know how to solve without calling a block() method.
my method receives a user as an argument.
it calls an external service and receives a Mono
if Mono does not contain an error and user.getDepartment().startsWith("Development") I want to add this to the Flux
Flux should be a result of my method
For now I unfortunatelly should initialize Flux from the list before calling the block()-method for Mono that I receive:
Flux<User> getUsers(User user) {
List<Users> developmentUsers = new ArrayList<User>();
while (user.containsManager()) {
val resultUser = externalUserServiceClient.getManager(user).block(); //externalUserServiceClient.getManager(user) should return a Mono<User>
if (resultUser.getDepartment().startsWith("Development"))
developemtnUsers.add(resultUser);
user = resultUser;
}
return Flux.fromIterable(developmentUsers);
}
I am sure there should be a way not to interrupt the async processes chain. Do you know how?
You can use Mono#expand that recursively applies getManager function and combines results into Flux<User>:
Flux<User> getUsers(User user) {
return getManager(user).expand(manager -> getManager(manager));
}
Mono<User> getManager(User user) {
if (user.containsManager()) {
return externalUserServiceClient.getManager(user)
.filter(manager -> manager.getDepartment().startsWith("Development"));
} else {
return Mono.empty();
}
}
By using
externalUserServiceClient.getManager(user).map(manager -> ...);
//or
externalUserServiceClient.getManager(user).flatMap(manager -> ...);

Mono.flatMap is not getting invoked

I am still new to Spring Webflux and flatMap on Mono doesn't seem to work.
I have the following function and call to kafkaPublisher.publishToTopic is not working. I inserted the print statement to test if it prints anything and it doesn't even execute the print statement. publishToTopic returns Mono<Void>.
private Mono<Void> test(Long gId, UUID pId) {
Mono<UUID> nId = pDao.findNId(pId);
Mono<List<String>> channels = nId.flatMapMany(pDao::findChannels).collectList();
return Mono.zip(nId, channels)
.flatMap(t -> {
System.out.println(t.getT1());
return kafkaPublisher.publishToTopic(gId, t.getT1().toString(), t.getT2());
});
}
It gets invoked if .block is called on flatMap as shown below.
private Mono<Void> test(Long gId, UUID pId) {
Mono<UUID> nId = pDao.findNId(pId);
Mono<List<String>> channels = nId.flatMapMany(pDao::findChannels).collectList();
Mono.zip(nId, channels)
.flatMap(t -> {
System.out.println(t.getT1());
return kafkaPublisher.publishToTopic(gId, t.getT1().toString(), t.getT2());
}).block();
return Mono.empty();
}
I found my mistake. I wasn't not using the result of test anywhere in the function where I was calling this test method. Here is the code I was using to call test
public Mono<Void> saveNew(NewPre pre) {
preDao.insert(pre)
.flatMap(p -> test(p.pId(), p.nId()));
return Mono.empty();
}
I changed it to following and it works.
public Mono<Void> saveNew(NewPre pre) {
return preDao.insert(preference)
.flatMap(p -> test(p.p(), p.n())
.then(Mono.empty()));
}
flatMap hangs indefinitely, Best way to close the Asynchronous operation by converting it into future object .
public Mono<Void> saveNew(NewPre pre) {
return preDao.insert(preference)
.flatMap({
p -> test(p.p(), p.n())
}).toFuture();
}

Categories