Webflux/Reactor makes two API calls instead of one - spring-webflux

I have a webflux application. In one place I make 3 API calls. Every call depends on result of previous call.
Mono<List<Book>> books = bookService.find(params); //1st API call
Mono<List<User>> users = books.flatMapMany(Flux::fromIterable)
.map(User::getId)
.collectList()
.map(allUserIds -> userService.findUsers(allUserIds)) //2nd API call
.flatMap(mono -> mono);
Mono<List<User>> parents = users.flatMapMany(Flux::fromIterable)
.filter(user -> user.getParentId() != null)
.map(User::getParentId)
.collectList()
.map(parentIds -> userService.findUsers(parentIds)) //3rd API call
.flatMap(mono -> mono);
Mono<List<User>> usersWithParents = Flux.merge(users, parents)
.flatMapIterable(Function.identity())
.collectList();
This code works, but I get 4 API calls in total. I get two API calls for 2nd step.
I assume that reactor makes 1 call to calculate parents variable and 2n call for Flux.merge(users, parents).
How can I update the call to avoid extra API call?

I think you should do smth like this
public Mono<List<User>> getAllUsers(){
Mono<List<String>> booksMono = bookService.find(params)
.flatMapMany(Flux::fromIterable)
.map(User::getId)
.flatMap(book->userService.findUsers(allUserIds))
.collectList()
.flatMap(users->Mono.zip(users, getParents(users)))
.flatMap(zippedUsers-> Stream.concat(zippedUsers.getT1(), zippedUsers.getT2()));
}
private Mono<List<User>> getParents(List<User> users){
return Flux.fromIterable(users)
.filter(user -> user.getParentId() != null)
.map(User::getParentId)
.collectList()
.map(parentIds -> userService.findUsers(parentIds));
}

Found a solution. We need to combine 2nd and 3rd API calls into one chain with flatmap:
Mono<List<Book>> books = bookService.find(params); //1st API call
Mono<List<User>> users = books.flatMapMany(Flux::fromIterable)
.map(User::getId)
.collectList()
.map(allUserIds -> userService.findUsers(allUserIds)) //2nd API call
.flatMap(mono -> mono);
.flatMap(children -> {
List<Long> parentIds = children.stream()
.filter(child -> child.getParentId() != null)
.map(User::getParentId)
.collect(toList());
return userService.findUsers(parentIds);
});

Related

Efficient way to update progress of long task while processing a task with Mono&Flux

I tried to make a method for long task which sends events of progress.
The method implemented by using Flux and Mono.
I figured out two ways to do it.
But I can't decide which approach is more efficient and elegant, or the other one.
Way 1.
var progressEvents = Sinks.Many<Progress> = Sinks.many().multicast()
fun doLongTask(taskFile : Mono<File>) : Mono<Another> {
return taskFile.map{ file ->
progressEvents.tryEmit(Progress("1"))
doSomething(file)
}.map{ something ->
progressEvents.tryEmit(Progress("2"))
doAnother(something)
}
}
Way2.
var progressEvents = Sinks.Many<Progress> = Sinks.many().multicast()
fun doLongTask(taskFile : Mono<File>) : Mono<Another> {
var doSomeMap = taskFile.map{ file ->
doSomething(file)
}.share()
doSomeMap.map{ something -> Progress("1")}.subscribe{progressEvents::tryEmit}
var doAnotherMap = doSomeMap.map{ something ->
doAnother(something)
}.share()
doAnotherMap.map{ another -> Progress("2")}.subscribe{progressEvents::tryEmit}
return doAnotherMap
}

Spring Reactor not working on handle operation while DB fetch

I'm using spring reactor. The code below is :
public Mono<ResponseEntity<SignUpResponse>> createSignUpForUser(SignUpRequest signUpRequest) {
return Mono.just(signUpRequest)
.map(sign -> {
Mono<UserDetailsEntity> userDetailsEntityMono = userDetailsRepository.findByPhoneNumber(sign.getMobileNumber());
userDetailsEntityMono.handle((user, sink) -> {
if (user != null) {
sink.error(new RuntimeException("Phone number already registered"));
}
});
return functionUserDetails.apply(sign);
})
.flatMap(userDetailsRepository::save)
.map(functionUser)
.flatMap(userRepository::save)
.map(usr -> ResponseEntity.ok(functionSignUpRes.apply(usr)))
.defaultIfEmpty(ResponseEntity.notFound().build())
.log();
}
Here the findByPhoneNumber(sign.getMobileNumber()) DB call is not working (the error is not throwing). The Rest of the operations are working and returning the response. Am i doing anything wrongly ? help me to fix this issue.
I think you need to rewrite your code a bit
public Mono<ResponseEntity<SignUpResponse>> createSignUpForUser(SignUpRequest signUpRequest) {
return userDetailsRepository.findByPhoneNumber(sign.getMobileNumber())
.flatMap(__ -> Mono.error(new RuntimeException("Phone number already registered")))
.switchIfEmpty(userDetailsRepository.save(functionUserDetails.apply(sign)))
.map(functionUser)
.flatMap(userRepository::save)
.map(usr -> ResponseEntity.ok(functionSignUpRes.apply(usr)))
.defaultIfEmpty(ResponseEntity.notFound().build())
.log()
;
}
Idea is that if you find that number then it should error, if result is empty then you would need to insert it so we need to use switch if empty

Optaplanner contraint streams - how to check mutliple resources

My domain is:
Requirement with some Variants of its realization
Any Variant need multiple resources with amount
Any Resource has limit
I try to write conflict method
constraintFactory
.from(Requirement::class.java)
.groupBy(Requirement::variant::resourceUsageList, sum(Resource::amount))
...
but is doesn't work
How can I get all used resource with its used amount and compare it with resources limit?
I think I need something like flatmap after from.
First make sure that your List<Resource> on your #PlanningSolution has a #ProblemFactCollectionProperty, so from(Resource.class) works.
Then I see multiple ways of doing it:
Proposal A)
from(Requirement.class)
.join(Resource.class) // Bi<Requirement, Resource>
.groupBy((requirement, resource) -> resource, sum((requirement, resource) -> requirement.variant.resourceUsage[resource.index]))
...
The downside is that this proposal A creates a Cartesian Product, so it can be costly memory wise if you have 100 resources and 10 000 requirements.
Propsal B)
.from(Requirement::class.java)
.groupBy(Requirement::variant::resourceUsageList, new MyResourceUsageSumCollector(...))
...
For MyResourceUsageSumCollector, which sums for each resource, look at this sum which sums for just one long for inspiration:
public static <A> UniConstraintCollector<A, ?, Long> sumLong(ToLongFunction<? super A> groupValueMapping) {
return new DefaultUniConstraintCollector<>(
() -> new long[1],
(resultContainer, a) -> {
long value = groupValueMapping.applyAsLong(a);
resultContainer[0] += value;
return () -> resultContainer[0] -= value;
},
resultContainer -> resultContainer[0]);
}

Running sequential operations in Webflux

I have 2 services that I want to call back to back in a sequential fashion. Neither of them produces any data. What would be the best way to chain them in a webflux pipeline.
I want to call subscribe on the last service call to trigger the whole flow. Something like:
serviceA.methodX()
.then()
serviceB.methodY().subscribe()
This is what I figured as an alternative:
Mono.just(Boolean.TRUE)
.flatMap( success -> { serviceA.methodX(); return true; } )
.flatMap( success -> { serviceB.methodY(); return true; } )
.subscribe();
Mono<OfSomeThing> executeFirst = ... ;
Mono<OfSomeThing> onceFirstIsCompletedExcecuteSecond = ... ;
Mono<OfSomeThing> plan = executeFirst.then(onceFirstIsCompletedExcecuteSecond);
plan.block();

Kotlin: function to stop iteration when predicate satisfied

I'm looking for a function in Kotlin which stops an iteration once a predicate is fulfilled:
val services = listOf(service1, service2)
...
var res: Result = null
services.stopIfPredicateFulFilled { service ->
res = service.doSomething()
res != null
}
While this example is not really nice since res is overwritten in each iteration I hope the intention is clear.
forEach doesn't do the job the way I expect it to be done. So, I was wondering if there isn't anything else.
You can use the functions find { ... } and firstOrNull { ... } (they are equivalent, just named differently). They find the first element satisfying the predicate and return that element, ignoring all the remaining elements.
services.find { service ->
res = service.doSomething()
res != null
}