Mono.flatMap is not getting invoked - java

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();
}

Related

Spring WebFlux - how to get data from DB to use in the next step

I use Spring WebFlux (Project Reactor) and I'm facing the following problem:
I have to get some data from db to use them to call another service - everything in one stream. How to do that?
public Mono<MyObj> saveObj(Mono<MyObj> obj) {
return obj
.flatMap(
ob->
Mono.zip(
repo1.save(
...),
repo2
.saveAll(...)
.collectList(),
repo3
.saveAll(...)
.collectList())
.map(this::createSpecificObject))
.doOnNext(item-> createObjAndCallAnotherService(item));
}
private void createObjAndCallAnotherService(Prot prot){
myRepository
.findById(
prot.getDomCred().stream()
.filter(Objects::nonNull)
.findFirst()
.map(ConfDomCred::getCredId)
.orElse(UUID.fromString("00000000-0000-0000-0000-000000000000")))
.doOnNext( //one value is returned from myRepository -> Flux<MyObjectWithNeededData>
confCred-> {//from this point the code is unreachable!!! - why????
Optional<ConfDomCred> confDomCred=
prot.getDomCreds().stream().filter(Objects::nonNull).findFirst();
confDomCred.ifPresent(
domCred -> {
ProtComDto com=
ProtComDto.builder()
.userName(confCred.getUsername())
.password(confCred.getPassword())
.build();
clientApiToAnotherService.callEndpintInAnotherService(com); //this is a client like Feign that invokes method in another service
});
});
}
UPDATE
When I invoke
Flux<MyObj> myFlux = myRepository
.findById(
prot.getDomCred().stream()
.filter(Objects::nonNull)
.findFirst()
.map(ConfDomCred::getCredId)
.orElse(UUID.fromString("00000000-0000-0000-0000-000000000000")));
myFlux.subscribe(e -> e.getPassword())
then the value is printed
UPDATE2
So as a recap - I think the code below is asynchronous/non-blocking - am I right?
In my
ProtectionCommandService
I had to use subscribe() twice - only then I can call my other service and store them my object: commandControllerApi.createNewCommand
public Mono<Protection> saveProtection(Mono<Protection> newProtection) {
return newProtection.flatMap(
protection ->
Mono.zip(
protectorRepository.save(//some code),
domainCredentialRepository
.saveAll(//some code)
.collectList(),
protectionSetRepository
.saveAll(//some code)
.collectList())
.map(this::createNewObjectWrapper)
.doOnNext(protectionCommandService::createProtectionCommand));
}
ProtectionCommandService class:
public class ProtectionCommandService {
private final ProtectionCommandStrategyFactory protectionCommandFactory;
private final CommandControllerApi commandControllerApi;
public Mono<ProtectionObjectsWrapper> createProtectionCommand(
ProtectionObjectsWrapper protection) {
ProductType productType = protection.getProtector().getProductType();
Optional<ProtectionCommandFactory> commandFactory = protectionCommandFactory.get(productType);
commandFactory
.get()
.createCommandFromProtection(protection)
.subscribe(command -> commandControllerApi.createNewCommand(command).subscribe());
return Mono.just(protection);
}
}
And one of 2 factories:
#Component
#AllArgsConstructor
#Slf4j
public class VmWareProtectionCommandFactory implements ProtectionCommandFactory {
private static final Map<ProductType, CommandTypeEnum> productTypeToCommandType =
ImmutableMap.of(...//some values);
private final ConfigurationCredentialRepository configurationCredentialRepository;
#Override
public Mono<CommandDetails> createCommandFromProtection(ProtectionObjectsWrapper protection) {
Optional<DomainCredential> domainCredential =
protection.getDomainCredentials().stream().findFirst();
return configurationCredentialRepository
.findByOwnerAndId(protection.getOwner(), domainCredential.get().getCredentialId())
.map(credential -> createCommand(protection, credential, domainCredential.get()));
}
and createCommand method returns Mono object as a result of this factory.
private Mono<CommandDetails> createCommand(Protection protection
//other parameters) {
CommandDto commandDto =
buildCommandDto(protection, confCredential, domainCredentials);
String commands = JsonUtils.toJson(commandDto);
CommandDetails details = new CommandDetails();
details.setAgentId(protection.getProtector().getAgentId().toString());
details.setCommandType(///some value);
details.setArguments(//some value);
return Mono.just(details);
UPDATE3
My main method that calls everything has been changed a little bit:
public Mono<MyObj> saveObj(Mono<MyObj> obj) {
return obj
.flatMap(
ob->
Mono.zip(
repo1.save(
...),
repo2
.saveAll(...)
.collectList(),
repo3
.saveAll(...)
.collectList())
.map(this::wrapIntoAnotherObject)
.flatMap(protectionCommandService::createProtectionCommand)
.map(this::createMyObj));
Stop breaking the chain
This is a pure function it returns something, and always returns the same something whatever we give it. It has no side effect.
public Mono<Integer> fooBar(int number) {
return Mono.just(number);
}
we can call it and chain on, because it returns something.
foobar(5).flatMap(number -> { ... }).subscribe();
This is a non pure function, we can't chain on, we are breaking the chain. We can't subscribe, and nothing happens until we subscribe.
public void fooBar(int number) {
Mono.just(number)
}
fooBar(5).subscribe(); // compiler error
but i want a void function, i want, i want i want.... wuuaaa wuaaaa
We always need something to be returned so that we can trigger the next part in the chain. How else would the program know when to run the next section? But lets say we want to ignore the return value and just trigger the next part. Well we can then return a Mono<Void>.
public Mono<Void> fooBar(int number) {
System.out.println("Number: " + number);
return Mono.empty();
}
foobar(5).subscribe(); // Will work we have not broken the chain
your example:
private void createObjAndCallAnotherService(Prot prot){
myRepository.findById( ... ) // breaking the chain, no return
}
And some other tips:
Name your objects correctly not MyObj and saveObj, myRepository
Avoid long names createObjAndCallAnotherService
Follow single responsibility createObjAndCallAnotherService this is doing 2 things, hence the name.
Create private functions, or helper functions to make your code more readable don't inline everything.
UPDATE
You are still making the same misstake.
commandFactory // Here you are breaking the chain because you are ignoring the return type
.get()
.createCommandFromProtection(protection)
.subscribe(command -> commandControllerApi.createNewCommand(command)
.subscribe()); // DONT SUBSCRIBE you are not the consumer, the client that initiated the call is the subscriber
return Mono.just(protection);
What you want to do is:
return commandFactory.get()
.createCommandFrom(protection)
.flatMap(command -> commandControllerApi.createNewCommand(command))
.thenReturn(protection);
Stop breaking the chain, and don't subscribe unless your service is the final consumer, or the one initiating a call.

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 switchIfEmpty() is always called

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.

Concat and return Observable

I'm trying to figure out how to get a result from the network, persist it and return the reponse body to the Observable.
Like this:
#Override
public Observable<DefaultUserResponse> createUser(CreateUserCommand command) {
return this.userService.createUser(command)
.map(defaultUserResponse -> {
User user = new User();
defaultUserResponse.setUser(user);
return defaultUserResponse;
}).flatMap(defaultUserResponse -> persist(defaultUserResponse.getUser()));
}
Observable<User> persist(User user) {return null;}
Steps:
I get a DefaultUserResponse from the server.
Transform the command to a User (command = DTO)
Persist the user locally.
Return the DefaultUserResponse.
How should I proceed ?
Thanks
Consider using Completable type for your persist method (if you don't care about the returned value(s)):
Completable persist(User user) {
return Completable.fromCallable(() -> {
//persisting
return null;
}
}
Then you could use andThen operator to wait until persisting completes and push your value further:
...
.flatMap(defaultUserResponse -> persist(defaultUserResponse.getUser()).andThen(Observable.just(defaultUserResponse)));
Or if you still want to use Observable for persisting, just use another flatMap instead of andThen.

Java - Break execution sequence using CompletableFuture chain

I am new to Java and am using CompletableFutures to perform async operations such as below:
public CompletionStage<Either<ErrorResponse, Response>> insertOrUpdate(String actor, String key) {
return this.objectDAO.getByKey(key)
.thenApply(mapDOToContainer(key))
.thenApply(mergeContainerToDO(key, actor))
.thenComposeAsync(this.objectDAO.UpdateFn())
.thenApply(DBResult::finished)
.thenApply(finished -> {
if (finished) {
Response response = Response.ok().build();
return Either.right(response);
} else {
return Either.left(ErrorResponse.create("Error", 400));
}
});
}
Now I need to modify this so that if the get fails then I perform the above chain, but if it succeeds then I need to break this chain and return from the function with an Either object containing an ErrorResponse.
How can I break this processing chain? I know I can pass a flag to each function in the chain and achieve this by performing the actions in the functions based on the value of the flag. I was hoping there is a better way to do this.
Thanks!!
I would rewrite your code.
Don't use Either for errors, Java has exception
Don't return a CompletionStage from your DAO
Use exceptionally from CompletableFuture, it is designed for this
Then do this:
public CompletionStage<Response> insertOrUpdate(String actor, String key) {
return CompletableFuture.supplyAsync(() -> this.objectDAO.getByKey(key))
.thenApply(mapDOToContainer(key))
.thenApply(mergeContainerToDO(key, actor))
.thenComposeAsync(this.objectDAO.UpdateFn())
.thenApply(DBResult::finished)
.thenApply(finished -> {
Response response = Response.ok().build();
return response;
})
.exceptionally(e -> ErrorResponse.create("Error", 400));
}
The DAO should be something like this:
class ObjectDAO {
public Object getByKey(String key) {
if (keyNotFound) {
throw new NoSuchElementException();
}
return new Object();
}
}
You may have to make sure that ErrorResponse is a subclass of Response to make this work.

Categories