I am not able to process Mono<List<Object>> to List<Object>. I have read somewhere that flatmap can be used, but I am not able to do that either. I don't want to use .block() method for this scenario.
A code example could be:
PagePublisher<Address> someAddressPage = someService.getPagePublisherForAddress();
Mono<List<Address>> addressListMono =
Flux.from(someAddressPage.items()).collectList();
I need to get List<Address> from Mono<List<Address>>. The method return type is List<Address>.
I am not sure how to go about that and I am relatively new to reactive. Need help in that regard.
Using block() is actually the only way to get the object from a Mono when it is emitted. However, you should avoid it at all costs because it defeats the purpose of using the reactive stack given that it actually blocks the thread waiting for the object to be emitted from the Mono.
This means that while working with reactive programming you should actually work with Mono and Flux so that your application is really reactive. The consequence of this is that you must change the return type of your method to return Mono<List<Address>> instead.
Its impossible to get List<Address> from Mono<List<Address>>. If the List isn't available yet since Mono and Flux are asynchronous/non-blocking by their nature, you can't get it except by waiting until it comes in and that's what blocking is.
Ideally, your method should return Mono<List<Address>> or just Flux<Address> and the caller of this method should subscribe to the results. Since you are using Spring Webflux, complete chain should be reactive.
You can subscribe to the Mono from the calling method and the Subscriber you pass will get the List when it becomes available. E.g.
addressListMono.subscribe(
addressList -> doSomething(addressList),
error -> error.printStackTrace(),
() -> Console.out.println("completed without a value")
)
Related
I still do not understand when to apply this method. In fact, it is similar to Mono.just, but I heard that callback is used for heavy operations if it needs to be performed separately from other flows. Now I use it like this, but is it correct.
Here is an example of use, I wrap sending a firebase notification in a callback since the operation is long
#Override
public Mono<NotificationDto> sendMessageAllDevice(NotificationDto notification) {
return Mono.fromCallable(() -> fcmProvider.sendPublicMessage(notification))
.thenReturn(notification);
}
maybe I still had to wrap up here in Mono.just ?
It depends which thread you want fcmProvider.sendPublicMessage(...) to be run on.
Either the one currently executing sendMessageAllDevice(...):
T result = fcmProvider.sendPublicMessage(notification);
return Mono.just(result);
Or the one(s) the underlying mono relies on:
Callable<T> callable = () -> fcmProvider.sendPublicMessage(notification);
return Mono.fromCallable(callable);
I would guess you need the latter approach.
If you use Mono.just(computeX()), computeX() is called immediately. No want you want(I guess).
If you use Mono.fromCallable(() -> computeX()), the computation is still not performed. I mean computeX() is only called when you subscribe to it. Maybe using .map, .flatMap, etc.
Important: if computeX() return Mono you doe not need to use Mono.fromCallable. It's only for blocking code
As you explained in the description, Mono.fromCallable is used when you want to compute a result with an async execution (mostly some heavy operation).
Since, you have already generated the Mono with Mono.fromCallable you do not have to wrap it again with Mono.just.
I have a scenario in which I will get a list of entities from DB using
repository.getAllByIds(ids)
which will return Flux<Entity>
in case of the Flux is empty then i need to call handleAllEntitiesNotFound() else i need to call handleNotFoundEntities()
repository.getAllByIds(ids)
.buffer()
.switchIfEmpty(__ -> handleAllEntitiesNotFound(ids, erroneousEntities))
.flatMap(list -> handleNotFoundEntities(list))
private Flux<Entity> handleAllEntitiesNotFound(List<String> ids, List<ResponseError> erroneousEntities) {
Flux.fromIterable(ids).subscribe(id -> erroneousEntities.add(new ResponseError("Not Found", "Not Found", id)));
return Flux.empty();
}
I'm using buffer() to collect the list into Flux<List<Entity>>
The problem is, when i call the service, it halts, no response no logs no anything, if i removed the line .switchIfEmpty(__ -> handleAllEntitiesNotFound(ids, erroneousEntities)) it works and return a response but without handling the handleAllEntitiesNotFound
What could be the problem using buffer() with switchIfEmpty()
I think you've come to the wrong conclusion here - buffer() and switchIfEmpty() work without a problem together:
Flux.empty()
.buffer()
.switchIfEmpty(Mono.just(List.of(1)))
.subscribe(System.out::println); //Prints "[1]"
However, your handleAllEntitiesNotFound() method is very suspicious. You seem to be passing in an existing list, creating a new Flux to add to it, and then returning an empty Flux. The example isn't runnable so it's impossible to narrow down the exact cause, but there's a few points that could well be the culprit (either individually or in tandem):
Mutating an existing object that's passed into a reactive stream is generally considered bad form. It's much easier and safer to return a new list (and you can merge that list with another if you want when the reactive stream completes.)
You're creating a Flux simply to read from one list, and add elements into another. That's confusing, and makes very little sense. Just use standard Java streams (i.e. ids.stream().map(id -> new ResponseError("Not Found", "Not Found", id)).collect(Collectors.toList()).)
You're returning Flux.empty(), which is almost certainly why there's no response. One would usually expect switchIfEmpty() to return a non-empty Flux, unless you're deliberately just using it as a side-effect.
handleNotFoundEntities seems like a strange choice of name for a method which would seem to be passed the entities that were found.
So, I'm trying to work with Webflux and I've got a scenario "check if an object exists; if so, do stuff, else - indicate error".
That can be written in reactor as:
public Mono<Void> handleObjectWithSomeId(Mono<IdType> id){
return id.
flatMap(repository::exists). //repository.exists returns Mono<Boolean>
flatMap(e -> e ? e : Mono.error(new DoesntExistException())).
then(
//can be replaced with just(someBusinessLogic())
Mono.fromCallable(this::someBusinessLogic)
);
}
or as:
public Mono<Void> handleObjectWithSomeId(Mono<IdType> id){
return id.
flatMap(repository::exists). //repository.exists returns Mono<Boolean>
flatMap(e -> e ? e : Mono.error(new DoesntExistException())).
map(e -> this.someBusinessLogic()));
}
Let's assume that return type of someBusinessLogic cannot be changed and it has to be simple void, not Mono<Void>.
In both cases if the object won't exist, appropriate Mono.error(...) will be produced.
While I understand that then and flatMap have different semantics, effectively I get the same result. Even though in second case I'm using flatMap against its meaning, I get to skip flatMap and fromCallable in favor of simple map with ignored argument (that seems more readable). My point is, both apporaches have advantages and disadvantages when it comes to readability and code quality.
So, here's a summary:
Using then
pros
is semantically correct
cons
in many cases (like above) requires wrapping in ad-hoc Mono/Flux
Using flatMap
pros
simplifies continued "happy scenario" code
cons
is semantically incorrect
What are other pros/cons of both approaches? What should I take under consideration when choosing an operator?
I've found this reactor issue that states that there is not real difference in speed.
TL, DR: If you care about the result of the previous computation, you can use map(), flatMap() or other map variant. Otherwise, if you just want the previous stream finished, use then().
You can see a detailed log of execution for yourself, by placing an .log() call in both methods:
public Mono<Void> handleObjectWithSomeId(Mono<IdType> id) {
return id.log()
.flatMap(...)
...;
}
Like all other operations in Project Reactor, the semantics for then() and flatMap() are already defined. The context mostly defines how these operators should work together to solve your problem.
Let's consider the context you provided in the question. What flatMap() does is, whenever it gets an event, it executes the mapping function asynchronously.
Since we have a Mono<> after the last flatMap() in the question, it will provide the result of previous single computation, which we ignore. Note that if we had a Flux<> instead, the computation would be done for every element.
On the other hand, then() doesn't care about the preceding sequence of events. It just cares about the completion event:
That's why, in your example it doesn't matter very much which one you use. However, in other contexts you might choose accordingly.
You might also find the Which operator do I need? section Project Reactor Reference helpful.
I'm using a Builder pattern to build upon a model object which combines data from different network calls and I'm having a hard time understanding the best way to take the model object from the first network call and combine the data from the second network call into the original model object.
My actual subscription:
myFirstApiRepository.getFirstModelObjectBuilder()
.flatmap(firstModelObjectBuilder -> mySecondApiRepository.getSomeExtraData(firstModelObjectBuilder))
.observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getMySubscriber());
First network call:
public Observable<FirstModelObject.Builder> getFirstModelObjectBuilder() {
return myFirstApiResource.getSomeData(...)
.flatMap(someData -> Observable.just(new FirstModelObject.Builder()
.setFirstAttribute(someData.getFirstAttribute())
.setSecondAttribute(someData.getSecondAttribute())));
}
Second network call:
public Observable<FirstModelObject> getSomeExtraData(FirstModelObject.Builder builder) {
return mySecondApiResource.getSomeData(...)
.flatMap(aString -> builder.setSomeStringValue(aString)
.build());
}
The problem here is that I have to pass the builder object into the second network call's observable. This makes it very rigid and I'd rather not have my SecondApiRepository rely on and reference a data type which it shouldn't need to reference. It also makes this second method here ".build()" the object, which is not good. So, how can I use the firstModelObject and add data to it from the second network call in a clean way?
If this is just bad design, please let me know. I'm still trying to learn more about RxJava best practices. :)
If your second request relies on first request's result, then check my answer - https://stackoverflow.com/a/41820372/7045114.
If not - just use zip operator:
Combines the emissions of multiple Observables together via a specified function and emit single items for each combination based on the results of this function.
Observable.zip(firstRequest, secondRequest, (firstResult, secondResult) -> {
//process results
})
Background
I have a number of RxJava Observables (either generated from Jersey clients, or stubs using Observable.just(someObject)). All of them should emit exactly one value. I have a component test that mocks out all the Jersey clients and uses Observable.just(someObject), and I see the same behaviour there as when running the production code.
I have several classes that act upon these observables, perform some calculations (& some side-effects - I might make them direct return values later) and return empty void observables.
At one point, in one such class, I'm trying to zip several of my source observables up and then map them - something like the below:
public Observable<Void> doCalculation() {
return Observable.zip(
getObservable1(),
getObservable2(),
getObservable3(),
UnifyingObject::new
).concatMap(unifyingObject -> unifyingObject.processToNewObservable())
}
// in Unifying Object
public Observable<Void> processToNewObservable() {
// ... do some calculation ...
return Observable.empty();
}
The calculating classes are then all combined and waited on:
// Wait for rule computations to complete
List<Observable<Void>> calculations = ...;
Observable.zip(calculations, results -> results)
.toBlocking().lastOrDefault(null);
The problem
The trouble is, processToNewObservable() is never being executed. By process of elimination, I can see it's getObservable1() that's the trouble - if I replace it with Observable.just(null), everything's executed as I'd imagine (but with a null value where I want a real one).
To reiterate, getObservable1() returns an Observable from a Jersey client in production code, but that client is a Mockito mock returning Observable.just(someValue) in my test.
Investigation
If I convert getObservable1() to blocking, then wrap the first value in just(), again, everything executes as I'd imagine (but I don't want to introduce the blocking step):
Observable.zip(
Observable.just(getObservable1().toBlocking().first()),
getObservable2(),
getObservable3(),
UnifyingObject::new
).concatMap(unifyingObject -> unifyingObject.processToNewObservable())
My first thought was that perhaps something else was consuming the value emitted from my observable, and zip was seeing that it was already complete, thus determining that the result of zipping them should be an empty observable. I've tried adding .cache() onto every observable source I can think is related, however, but that hasn't altered the behaviour.
I've also tried adding next / error / complete / finally handlers on getObservable1 (without converting it to blocking) just before the zip, but none of them executed either:
getObservable1()
.doOnNext(...)
.doOnCompleted(...)
.doOnError(...)
.finallyDo(...);
Observable.zip(
getObservable1(),
getObservable2(),
getObservable3(),
UnifyingObject::new
).concatMap(unifyingObject -> unifyingObject.processToNewObservable())
The question
I'm very new to RxJava, so I'm pretty sure I'm missing something fundamental. The question is: what stupid thing could I be doing? If that's not obvious from what I've said so far, what can I do to help diagnose the issue?
The Observable must emit to start the chain. You have to think of your pipeline as a declaration of what will happen when the Observable emits.
You didn't share what was actually being observed, but Observable.just() causes the Observable to emit the wrapped object immediately.
Based on the response in the comment, either one of the getObservable doesn't return any value but just completes or the Mockito mocking does something wrong. The following standalone example works for me. Could you check it and start slowly mutating it to see where things break?
Observable.zip(
Observable.just(1),
Observable.just(2),
Observable.just(3),
(a, b, c) -> new Integer[] { a, b, c })
.concatMap(a -> Observable.from(a))
.subscribe(System.out::println)
;
Note: I didn't find my answer here very satisfying, so I dug in a bit further and found a much smaller reproduction case, so I've asked a new question here: Why does my RxJava Observable emit only to the first consumer?
I've figured out at least part of my troubles (and, apologies to all who tried to answer, I don't think you stood much of a chance given my explanation).
The various classes which perform these calculations were all returning Observable.empty() (as per processToNewObservable() in my original example). As far as I can tell, Observable.zip() doesn't subscribe to the Nth observable it's zipping until the N-1th observable has emitted a value.
My original example claimed it was getObservable1() that was misbehaving - that was actually slight inaccurate, it was a later observable in the parameter list. As I understand it, the reason making it blocking, then turning that value into an Observable again worked is because making it blocking and calling first forced its execution, and I got the side-effects I wanted.
If I change all my calculating classing to return Observable.just(null) instead, everything works: the final zip() of all the calculation classes' observables works through them all, so all the expected side-effects happen.
Returning a null Void seems like I'm definitely Doing Something Wrong from a design point of view, but at least this particular question is answered.