How to check if Mono is empty? - spring

I'm developing a app with Spring Boot 2.0 and Kotlin using the WebFlux framework.
I want to check if a user id exits before save a transaction. I'm stucked in a simple thing like validate if a Mono is empty.
fun createTransaction(serverRequest: ServerRequest) : Mono<ServerResponse> {
val transaction = serverRequest.body(BodyExtractors.toMono(Transaction::class.java))
transaction.flatMap {
val user = userRepository.findById(it.userId)
// If it's empty, return badRequest()
}
return transaction.flatMap { transactionRepository.save(it).then(created(URI.create("/transaction/" + it.id)).build()) }
}
It is possible to do what I want?

The techniques that allow checking whether Flux/Mono is empty
Using operators .switchIfEmpty/.defaultIfEmpty/Mono.repeatWhenEmpty
Using mentioned operators you will be able to react to the case when Stream has been completed without emitting any elements.
First of all, remember that operators such .map, .flatMap, .filter and many others will not be invoked at all if there no onNext has been invoked.
That means that in your case next code
transaction.flatMap {
val user = userRepository.findById(it.userId)
// If it's empty, return badRequest()
}
return transaction.flatMap { transactionRepository.save(it).then(created(URI.create("/transaction/" + it.id)).build()) }
will not be invoked at all, if transaction will be empty.
In case if there is a requirement for handling cases when your flow is empty, you should consider operators like next in the following manner:
transaction
.flatMap(it -> {
val user = userRepository.findById(it.userId)
})
.swithIfEmpty(Flux.defer(() -> Flux.just(badRequest())));
Actual solution
Also, I have noted that you created two sub-flows from the main transaction. Actually, following code will not be executed at all:
transaction.flatMap {
val user = userRepository.findById(it.userId)
// If it's empty, return badRequest()
}
and will be only executed the last one, which is returned from the method. That happens because you ain't subscribed using operator .subscribe(...).
The second point, you can't subscribe to the same request body more the one time (kind of limitation for WebClient's reponse). Thus you are required to share your request body in the next way, so completed example will be:
fun createTransaction(serverRequest: ServerRequest): Mono<ServerResponse> {
val transaction = serverRequest.body(BodyExtractors.toMono(Transaction::class.java)).cache()
transaction
.flatMap { userRepository.findById(it.userId) }
.flatMap { transaction.flatMap { transactionRepository.save(it) } }
.flatMap { ServerResponse.created(URI.create("/transaction/" + it.id)).build() }
.switchIfEmpty(transaction.flatMap { ServerResponse.badRequest().syncBody("missed User for transaction " + it.id) })
}
Or more simple case without sharing transaction flow but using Tuple:
fun createTransaction(serverRequest: ServerRequest): Mono<ServerResponse> {
val emptyUser = !User()
val transaction = serverRequest.body<Mono<Transaction>>(BodyExtractors.toMono(Transaction::class.java))
transaction
.flatMap { t ->
userRepository.findById(t.userId)
.map { Tuples.of(t, it) }
.defaultIfEmpty(Tuples.of(t, emptyUser))
}
.flatMap {
if (it.t2 != emptyUser) {
transactionRepository.save(it.t1)
.flatMap { ServerResponse.created(URI.create("/transaction/" + it.id)).build() }
} else {
ServerResponse.badRequest().syncBody("missed User for transaction " + it.t1.id)
}
}
}

You can check it using the Mono's provided method hasElement() which is analogous to Optional's isPresent(). The method definition is :
Mono<Boolean> hasElement()
for more details checkout : project reactor documentation
In case you have to perform some action based on this value you can further use switchIfEmpty() to provide with alternate publisher.

Let me start by saying I am a newbie on reactive (java) and on this forum.
I think you cannot really check in this code if a mono is empty because a mono represents code that will be executed later on, so in this code body you won't know yet if its is empty. Does that make sense?
I just wrote something similar in Java which seems to work (but not 100% this is the best approach either):
public Mono<ServerResponse> queryStore(ServerRequest request) {
Optional<String> postalCode = request.queryParam("postalCode");
Mono<ServerResponse> badQuery = ServerResponse.badRequest().build();
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
if (!postalCode.isPresent()) { return badQuery; }
Flux<Store> stores = this.repository
.getNearByStores(postalCode.get(), 5);
return ServerResponse.ok().contentType(APPLICATION_JSON)
.body(stores, Store.class)
.switchIfEmpty(notFound);
}

We can use switchIfEmpty method for this
Below example, I'm checking if the user exists with email if not then add it
userRepository.findByEmail(user.getEmail())
.switchIfEmpty(s -> {
user.setStatus("InActive");
String encodedPassword = DigestUtils.sha256Hex(user.getPassword());
user.setPassword(encodedPassword);
userRepository.save(user).subscribe();
s.onComplete();
}).then(Mono.just(user));

Use Mono with Optional:
return findExistingUserMono
.map(Optional::of)
.defaultIfEmpty(Optional.empty())
.flatMap(optionalUser -> {
if(optionalUser.isPresent()) {
return Mono.error('xxxx');
}
return this.userService.create(optionalUser.get());
});
This way it will always emit Optional value so that the stream will never break.

Related

Project Reactor: Obtain Size of List Contained Within a Mono

I'm trying to do something again here in project reactor that I'm sure is reeeeeal simple for any of you project reactor gurus out there!
I've been searching and scratching around with this one for a while now, and feel I'm once again hitting a wall with this stuff.
All I'm trying to do is determine if a List of objects contained within a Mono is empty or not.
This is what I have so far:
private Mono<Boolean> isLastCardForAccount(String accountId) {
return cardService.getAccountCards(accountId)
.hasElement();
}
I'm thinking the above might work, but I'm having difficulty figuring out how to extract/access the 'Boolean' contained within the returned Mono. I think I have to use 'subscribe' somehow right?
I've mucked around with this stuff for a while now, but still no luck.
Here is how 'getAccountCards' is defined:
public Mono<List<Card>> getAccountCards(final String accountId) {
return cardCrudRepository.getCardsByAccountId(accountId)
.collectList();
}
From CardCrudRepository:
// #Query("SELECT * FROM card WHERE account_id = :accountId") <-Not sure if I need this
Flux<Card> getCardsByAccountId(String accountId);
And lastly, how I'm using 'isLastCardForAccount':
public Mono<Void> updateCardStatus(String accountId, String cardId, String cardStatus) {
return accountService.getAccount(accountId)
.map(Account::getClientId)
.map(clientId -> createUpdateCardStatusServiceRequestData(clientId, cardId, cardStatus))
.flatMap(requestData -> cartaClient.updateCardStatus(requestData)
.then(Mono.defer(() -> isCardBeingCancelled(cardStatus) ? allCardsCancelledForAccount(accountId) ? removeAccount(accountId) :
(isLostOrStolen(cardStatus) ? replaceCard(cardId, cardStatus).flatMap(this::updateCardNumber) : Mono.empty()) : Mono.empty())));
}
As always, any and all help and insight is tremendously appreciated!
I am not sure if this would resolve the issue but this way you can try to write your logic
return accountService.getAccount(accountId)
.map(Account::getClientId)
.map(clientId -> createUpdateCardStatusServiceRequestData(clientId, cardId, cardStatus))
.flatMap(requestData -> cartaClient.updateCardStatus(requestData)
.then(Mono.defer(() ->
Mono.zip(
Mono.just(isCardBeingCancelled(cardStatus)),
isLastCardForAccount(accountId),
Mono.just( isLostOrStolen(cardStatus) )
)
.map(tuple -> {
WRITE YOUR IF ELSE LOGIC
})
The idea is to use zip and then use the tuple for writing logic. The Tuple would be of type Tuple3 of <Boolean, Boolean ,Boolean>. I made the assumption that isLostOrStolen(cardStatus) returns Boolean.
One way of doing that is by using filterWhen operator like this:
.then(Mono.defer(() -> {
if (isCardBeingCancelled(cardStatus)) {
return Mono.just(accountId)
.filterWhen(this::allCardsCancelledForAccount)
.flatMap(this::removeAccount);
} else if (isLostOrStolen(cardStatus)) {
return replaceCard(cardId, cardStatus).flatMap(this::updateCardNumber);
}
return Mono.empty();
}))
You can use filterWhen in the case of asynchronous filtering. Check this section of Which operator do I need? reference and this How to filter Flux asynchronously.
As a side note, this is not going to work as you expect:
private Mono<Boolean> isLastCardForAccount(String accountId) {
return cardService.getAccountCards(accountId)
.hasElement();
}
public Mono<List<Card>> getAccountCards(final String accountId) {
return cardCrudRepository.getCardsByAccountId(accountId)
.collectList();
}
The collectList() will emit an empty List if there is no card. I'd use exists query instead:
public Mono<Boolean> isLastCardForAccount(final String accountId) {
return cardCrudRepository.existsByAccountId(accountId);
}

Reactive way of returning `Map<Long,T>`

I am learning reactive. In an MVC project I have a service method (and the controller has the same signature) like this:
#Override
public Map<Long, Question> getQuestions() {
List<Question> questions = questionRepo.findAllByType(Type.A);
return questions.stream()
.collect(Collectors.toMap(q -> q.getId(), q -> q));
}
Resulting in something similar to this:
{
1: {id: 1, ...}
2: {id: 2, ...}
...
}
Now, switching to reactive and kotlin coroutines. What is the proper way to implement this in a reactive way?
This is the signature of the repository:
interface QuestionRepository : CoroutineCrudRepository<Question, Long> {
#Query("select * from Question q where type = :type")
fun findAllByType(type: Type): Flow<Question>
}
Approaches
From what I think so far using Mono<Map<Long,Question>> seems to make no sense as it would require to block for building the inner map.
Flow<Map<Long,Question>> Does not make sense either, because we do not populate multiple maps.
So my best approach for now is not using a Map...
override fun getQuestions(): Flow<Question> {
return questionRepo.findAllByType(Type.A)
}
...but this would require to change the frontend code (it now needs to convert the list to a map).
I also think of
override fun getQuestions(): Flow<Pair<Long?,Question>> {
return questionRepo.findAllByType(Type.A).map { it.id to it }
}
but this would require the frontend to change as well, because the output would look like
[{"first":1,"second":{"id":1, ...}]
Are there other, better approaches? How would you implement it?
UPDATE
added repository.
Assuming the Flow emits elements one at a time that you want to put into a single Map in the client code, then you can collect them into a MutableMap like this and return it.
suspend fun getQuestions(): Map<Long, Question> {
val map = mutableMapOf<Long, Question>()
questionRepo.findAllByType(Type.A)
.collect {
map[it.id] = it
}
return map
}
If your downstream client code is not expecting a suspend function, I guess you need to wrap this in runBlocking, and presumably the downstream code is already handling the fact that this is a long-running function call.
override fun getQuestions(): Map<Long, Question> = runBlocking {
val map = mutableMapOf<Long, Question>()
questionRepo.findAllByType(Type.A)
.collect {
map[it.id] = it
}
map
}

How to make multiple Spring Webclient calls in parallel and wait for the result?

I am new to Reactive programming and I would like to make two API calls in parallel and process the results and return a simple array or list of items.
I have two functions, one returns a Flux and the other returns a Mono and I make a very simple filtering logic on the Flux emitted items depending on the result of that Mono.
I tried to use zipWith but only one item made it to the end no matter what filtering logic. Also I tried with block but that is not allowed inside the controller :/
#GetMapping("/{id}/offers")
fun viewTaskOffers(
#PathVariable("id") id: String,
#AuthenticationPrincipal user: UserPrincipal
) : Flux<ViewOfferDTO> {
data class TaskOfferPair(
val task: TaskDTO,
val offer: ViewOfferDTO
)
return client.getTaskOffers(id).map {
it.toViewOfferDTO()
}.zipWith(client.getTask(id), BiFunction {
offer: ViewOfferDTO, task: TaskDTO -> TaskOfferPair(task, offer)
}).filter {
it.offer.workerUser.id == user.id || it.task.creatorUser == user.id
}.map {
it.offer
}
}
getTaskOffers returns a Flux of OfferDTO
getTask returns a Mono of TaskDTO
If you cannot answer my question please tell me atleast how to do multiple API calls in parallel and wait for the results in WebClient
Here is a use case for a parallel call.
public Mono<UserInfo> fetchCarrierUserInfo(User user) {
Mono<UserInfo> userInfoMono = fetchUserInfo(user.getGuid());
Mono<CarrierInfo> carrierInfoMono = fetchCarrierInfo(user.getCarrierGuid());
return Mono.zip(userInfoMono, carrierInfoMono).map(tuple -> {
UserInfo userInfo = tuple.getT1();
userInfo.setCarrier(tuple.getT2());
return userInfo;
});
}
Here:
fetchUserInfo makes http call to get user info from another service and returns Mono
fetchCarrierInfo method makes HTTP call to get carrierInfo from another service and returns Mono
Mono.zip() merges given monos into a new Mono that will be fulfilled when all of the given Monos have produced an item, aggregating their values into a Tuple2.
Then, call fetchCarrierUserInfo().block() it to get the final result.
As you already figured out, zipWith won't help you there, since it will produce min(a.size, b.size), which will always be 1, in case one of them is Mono.
But since those two are independent, you can simply split them:
val task: Mono<TaskDTO> = client.getTask(id)
val result: Flux<ViewOfferDTO> =
task.flatMapMany {t ->
client.getTaskOffers(id).map {offer ->
t to offer
}
}.filter {
it.second.workerUser.id == user.id || it.first.creatorUser == user.id
}.map {
it.second
}
Note that if you want to have pair of elements, you can use built-in Pair.
Also, this check doesn't make much sense, since you have only Mono: it.first.creatorUser
Convert your Mono to a Flux using repeat():
client.getTask(id).cache().repeat();
So your code would become
return client.getTaskOffers(id).map {
it.toViewOfferDTO()
}.zipWith(client.getTask(id).cache().repeat(), BiFunction {
offer: ViewOfferDTO, task: TaskDTO -> TaskOfferPair(task, offer)
}).filter {
it.offer.workerUser.id == user.id || it.task.creatorUser == user.id
}.map {
it.offer
}

How to check record existance in Rxjava2?

I have implementation with reactive programming and I'm using Springboot Framework, Rxjava2, reactive spring data framework. I have a scenario about saving record into mongodb when the record is not exist.
But when I check with the record reactively, I found the emitter stopped to proceed.
Below is my sample code. I have 4 datas and 2 of that were not in database. I found emitter proceed with the data which exists in database only.
val movies = mutableListOf("Secret Mother (Mainland) - 秘密媽媽","Life For Life - 命情真","Before Dawn - 愛在暴風的日子","The Threat Of Love 2 - Loving ou 我愛你2")
Observable
.fromIterable(movies)
.flatMapMaybe {
videoInfoService
.findVideoByTitle(it)
.switchIfEmpty(Maybe.empty())
}
.subscribe(object: Observer<VideoInfo>{
override fun onComplete() {
println("on complete ")
}
override fun onSubscribe(d: Disposable) {
println("on subscribe ")
}
override fun onNext(t: VideoInfo) {
println("on next: ${t.title}")
}
override fun onError(e: Throwable) {
e.printStackTrace()
}
})
Appreciate your guidance. I know Rxjava2 not able to proceed with null value from other post I wrote this morning, I guess it should be some way to handle this scenario.
Thanks,
assuming that your method to save the record in mongodb returns a Completable:
fun store(movie: Movie): Completable
and your videoInfoService.findVideoByTitle method returns a Movie instance, you can change your code to:
Observable
.fromIterable(movies)
.flatMapSingle { title ->
videoInfoService
.findVideoByTitle(title)
.switchIfEmpty {
Single.defer {
val movie = Movie(title)
store(movie)
.andThen(Single.just(movie))
}
}
}
This is the output I want.
Thanks #Gustavo provide the answer and I got the hint from #Gustavo's answer to come out this answer.
val movies = mutableListOf("Secret Mother (Mainland) - 秘密媽媽","Life For Life - 命情真","Before Dawn - 愛在暴風的日子","The Threat Of Love 2 - Loving ou 我愛你2")
Observable
.fromIterable(movies)
.flatMap {
videoInfoService
.findVideoByTitle(it)
.toObservable()
.switchIfEmpty{
// Save the video
save(it)
}
}
.subscribe(object: Observer<VideoInfo>{
override fun onComplete() {
println("on complete ")
}
override fun onSubscribe(d: Disposable) {
println("on subscribe ")
}
override fun onNext(t: VideoInfo) {
println("on next: ${t.title}")
}
override fun onError(e: Throwable) {
e.printStackTrace()
}
})

How can I apply Flux all to a predicate that returns Mono<Boolean>?

I have a list of filters for my Spring Boot application that check whether my logged in user should be enabled or not, and the filters return Mono<Boolean>
Now what I want is to to pass my object to all of them, and chain the filters. But the point is, I need to stop filtering if any of the filters returns false
Here's my Filter interface
interface Filter {
fun doFilter(obj: UserDetails): Mono<Boolean>
}
And here's my code for login method
private fun login(serverRequest: ServerRequest, securityFilters: List<SecurityFilter>): Mono<ServerResponse> {
return serverRequest
.bodyToMono(LoginPayload::class.java)
.flatMap {
userDetailsService.findByUsername(it.username)
.filter { userDetails -> passwordEncoder.matches(it.password, userDetails.password) }
}
./////////////////////////TODO///////////////////////
.flatMap {
val jwt = jwtService.generateJwtToken(it)
ServerResponse
.ok()
.cookie(ResponseCookie.from("jwt-access-token", jwt).build())
.build()
}
.switchIfEmpty(ServerResponse.status(HttpStatus.UNAUTHORIZED).build())
}
What I want is to filter my object by all the filters at TODO part, and
do the final flatMap only if all the filters return true, and stop filtering and emit empty if one of the filters fails
You could use a filterWhen() statement in combination with all(), for example (in Java code):
.filterWhen(userDetails -> Flux
.fromIterable(securityFilters)
.flatMap(securityFilter -> securityFilter.doFilter(userDetails))
.all(result -> result))
I don't think there's an all() statement that accepts a Mono<Boolean> though, only a Predicate<Boolean>, so that means you have to write a "weird" statement at the end that does result -> result.

Resources