How to handle nested subscriptions in Spring webflux - java

I have a requirement in which I need to perform a sequence of 3 method calls each of which returns a Mono.
Mono<WeightResponse> weightService()
Mono<PriceResponse> priceService(WeightResponse wr)
Mono<DispatchResponse> dispatchService(PriceResponse pr)
These 3 method calls need to be done in chronological order.
This is what I have tried to come up with. I am yet to compete and test this functionality end to end. I am looking for advice on how to handle this type of scenario in Spring Reactor?
There is subscription inside subscription inside subscription. Is this the right approach to handle such a scenario?
Could there be any side effects of such nested subscriptions?
weightService().subscribe(wr -> {
priceService(wr).subscribe (pr -> {
dispatchService(pr).subscribe (dr -> {
System.out.println("Dispatch Done!");
},
e -> log.error("error in dispatch {}", e);
);
},
e -> log.error("error in pricing {}", e);
);
},
e -> log.error("error in weight calculation {}", e);
);

You should not subscribe explicitly. Typically you need to construct reactive flow and the framework like spring-webflux will subscribe to it.
In the following example flatMap would subscribe internally and you could chain response to use it in the next operator.
Mono<DispatchResponse> execute() {
return weightService()
.flatMap(wr -> priceService(wr))
.flatMap(pr -> dispatchService(pr));
}

Related

how to handle errors in RxJava?

It may sound funny or maybe novice, but I don't understand what it means that something "failed" in Reactive Programming. Do you mean a null? An empty object? someone could please give me examples.
Some of the scenarios I wish to realize are:
Assuming I send a query parameter and get either a listing with values or an empty listing; and lastly, I do not send the query parameter.
If an empty listing is issued I want to return an exception and a 404 status code.
If they do not send the query parameter I want to return an exception and some status code.
And of course, if a list with values is found, return it. Will it be possible to make these cases in a single method? how do I do it?
First, a reactor operator can ends in different ways:
Completes successfully after emitting one (Mono) or more (Flux) value(s)
Completes empty: The pipeline sends completion signal, but no value has been emitted
Completes in error: somewhere in the pipeline, an error happened. By default, as in imperative code, it stops the chain of operations, and is propagated.
Cancelled: the pipeline might be interrupted by a manual action or a system shutdown. It then ends in error (a special kind of error, but an error nonetheless)
Secondly, reactive-stream, whatever the implementation (RxJava or Reactor) does not accept null values. It means that trying to produce a null value in/from a reactive stream will either cause an error or an undefined behavior. This is stated in reactive-stream specification, rule 2.13:
Calling [...] onNext [...] MUST return normally except when any provided parameter is null in which case it MUST throw a java.lang.NullPointerException to the caller
Let's try to produce some simple examples first.
This program shows the possible ways a pipeline can complete:
// Reactive streams does not accept null values:
try {
Mono.just(null);
} catch (NullPointerException e) {
System.out.println("NULL VALUE NOT ACCEPTED !");
}
// Mono/Flux operations stop if an error occurs internally, and send it downstream
try {
Mono.just("Something")
.map(it -> { throw new IllegalStateException("Bouh !"); })
.block();
} catch (IllegalStateException e) {
System.out.println("Error propagated: "+e.getMessage());
}
// A mono or a flux can end "empty". It means that no value or error happened.
// The operation just finished without any result
var result = Mono.just("Hello !")
.filter(it -> !it.endsWith("!"))
// Materialize allow to receive the type of signal produced by the pipeline (next value, error, completion, etc.)
.materialize()
.block();
System.out.println("Input value has been filtered out. No 'next' value " +
"received, just 'completion' signal:" + result.getType());
Its output:
NULL VALUE NOT ACCEPTED !
Error propagated: Bouh !
Input value has been filtered out. No 'next' value received, just 'completion' signal:onNext
Then, let's look at a program that intercept empty pipelines and errors, and handle them gracefully:
// Errors can be intercepted and replaced by a value:
var result = Mono.error(new IllegalStateException("No !"))
.onErrorResume(err -> Mono.just("Override error: Hello again !"))
.block();
System.out.println(result);
// Empty pipelines can also be replaced by another one that produce a value:
result = Mono.just("Hello !")
.filter(it -> !it.endsWith("!"))
.switchIfEmpty(Mono.just("Override empty: Hello again !"))
.block();
System.out.println(result);
It produces:
Override error: Hello again !
Override empty: Hello again !
With all this tools, we can solve the problem you describe with your query.
Let's mimic it:
public static Flux<String> processRequest(String queryParam) {
if (queryParam == null || queryParam.isEmpty()) return Flux.error(new IllegalArgumentException("Bad request"));
return Mono.just(queryParam)
.flatMapMany(param -> Flux.fromArray(param.split("_")))
.switchIfEmpty(Mono.error(new IllegalStateException("No data")));
}
public static void main(String[] args) {
String[] inputs = { null, "hello_world", "___" };
for (var input : inputs) {
try {
String result = processRequest(input)
.collect(Collectors.joining(", ", "[", "]"))
.block();
System.out.println("COMPLETED: " + result);
} catch (Exception e) {
System.out.println("ERROR: " + e.getMessage());
}
}
}
It prints:
ERROR: Bad request
COMPLETED: [hello, world]
ERROR: No data

Java Reactor - conditional stream execution

I was wondering how to create "logical stream" using Reactor.
Lets assume that I want to implement following scenario:
As input I have object to save in database. As output I would like to get Mono representing execution message.
Option 1: if object to save has all fields filled then I perform additionall operations, save it to database and finally return "Success" message
Option 2: if object to save has at least one field not filled I return "Error"
I have created such code:
Mono<String> message = Mono.just(new User("Bob the Reactor master")) // user with name = can be saved
.flatMap(user -> {
if(user.getName() != null && user.getName().length() > 1){
// Perform additional operations e.g. user.setCreatedDate(new Date())
// Save to repository e.g. repository.save(user)
return Mono.just("Success!");
}
else{
return Mono.just("Error!");
}
})
.doOnNext(System.out::println); // print stream result
message.subscribe();
Is this code 100% reactive (has all its benefits)? If no then what it will look like?
The answer depends on your commented repository.
Repository is non-blocking and returns Mono or Flux
You should subscribe it then return Success Mono. In your if statement:
return repository.save(user).then(Mono.just("Success!"));
Repository is blocking
You should make your repository call non-blocking moving its execution to separate thread. Reactor way is to wrap it with Mono and subscribe on elastic scheduler or your custom scheduler.
Mono<String> message = Mono.just(new User("Bob the Reactor master"))
.filter(user -> user.getName() != null && user.getName().length() > 1)
.map(user -> user) // Perform additional operations e.g. user.setCreatedDate(new Date())
.flatMap(user -> Mono.fromRunnable(() -> repository.save(user))
.subscribeOn(Schedulers.elastic())
.then(Mono.just("Success!")))
.switchIfEmpty(Mono.just("Error!"));

RxJava Combine Sequence Of Requests

The Problem
I have two Apis. Api 1 gives me a List of Items and Api 2 gives me more detailed Information for each of the items I got from Api 1. The way I solved it so far results in bad Performance.
The Question
Efficent and fast solution to this Problem with the help of Retrofit and RxJava.
My Approach
At the Moment my Solution Looks like this:
Step 1: Retrofit executes Single<ArrayList<Information>> from Api 1.
Step 2: I iterate through this Items and make a request for each to Api 2.
Step 3: Retrofit Returns Sequentially executes Single<ExtendedInformation> for
each item
Step 4: After all calls form Api 2 completely executed I create a new Object for all Items combining the Information and Extended Information.
My Code
public void addExtendedInformations(final Information[] informations) {
final ArrayList<InformationDetail> informationDetailArrayList = new ArrayList<>();
final JSONRequestRatingHelper.RatingRequestListener ratingRequestListener = new JSONRequestRatingHelper.RatingRequestListener() {
#Override
public void onDownloadFinished(Information baseInformation, ExtendedInformation extendedInformation) {
informationDetailArrayList.add(new InformationDetail(baseInformation, extendedInformation));
if (informationDetailArrayList.size() >= informations.length){
listener.onAllExtendedInformationLoadedAndCombined(informationDetailArrayList);
}
}
};
for (Information information : informations) {
getExtendedInformation(ratingRequestListener, information);
}
}
public void getRatingsByTitle(final JSONRequestRatingHelper.RatingRequestListener ratingRequestListener, final Information information) {
Single<ExtendedInformation> repos = service.findForTitle(information.title);
disposable.add(repos.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribeWith(new DisposableSingleObserver<ExtendedInformation>() {
#Override
public void onSuccess(ExtendedInformation extendedInformation) {
ratingRequestListener.onDownloadFinished(information, extendedInformation);
}
#Override
public void onError(Throwable e) {
ExtendedInformation extendedInformation = new ExtendedInformation();
ratingRequestListener.onDownloadFinished(extendedInformation, information);
}
}));
}
public interface RatingRequestListener {
void onDownloadFinished(Information information, ExtendedInformation extendedInformation);
}
tl;dr use concatMapEager or flatMap and execute sub-calls asynchronously or on a schedulers.
long story
I'm not an android developer, so my question will be limited to pure RxJava (version 1 and version 2).
If I get the picture right the needed flow is :
some query param
\--> Execute query on API_1 -> list of items
|-> Execute query for item 1 on API_2 -> extended info of item1
|-> Execute query for item 2 on API_2 -> extended info of item1
|-> Execute query for item 3 on API_2 -> extended info of item1
...
\-> Execute query for item n on API_2 -> extended info of item1
\----------------------------------------------------------------------/
|
\--> stream (or list) of extended item info for the query param
Assuming Retrofit generated the clients for
interface Api1 {
#GET("/api1") Observable<List<Item>> items(#Query("param") String param);
}
interface Api2 {
#GET("/api2/{item_id}") Observable<ItemExtended> extendedInfo(#Path("item_id") String item_id);
}
If the order of the item is not important, then it is possible to use flatMap only:
api1.items(queryParam)
.flatMap(itemList -> Observable.fromIterable(itemList)))
.flatMap(item -> api2.extendedInfo(item.id()))
.subscribe(...)
But only if the retrofit builder is configured with
Either with the async adapter (calls will be queued in the okhttp internal executor). I personally think this is not a good idea, because you don't have control over this executor.
.addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync()
Or with the scheduler based adapter (calls will be scheduled on the RxJava scheduler). It would my preferred option, because you explicitly choose which scheduler is used, it will be most likely the IO scheduler, but you are free to try a different one.
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
The reason is that flatMap will subscribe to each observable created by api2.extendedInfo(...) and merge them in the resulting observable. So results will appear in the order they are received.
If the retrofit client is not set to be async or set to run on a scheduler, it is possible to set one :
api1.items(queryParam)
.flatMap(itemList -> Observable.fromIterable(itemList)))
.flatMap(item -> api2.extendedInfo(item.id()).subscribeOn(Schedulers.io()))
.subscribe(...)
This structure is almost identical to the previous one execpts it indicates locally on which scheduler each api2.extendedInfo is supposed to run.
It is possible to tune the maxConcurrency parameter of flatMap to control how many request you want to perform at the same time. Although I'd be cautious on this one, you don't want run all queries at the same time. Usually the default maxConcurrency is good enough (128).
Now if order of the original query matter. concatMap is usually the operator that does the same thing as flatMap in order but sequentially, which turns out to be slow if the code need to wait for all sub-queries to be performed. The solution though is one step further with concatMapEager, this one will subscribe to observable in order, and buffer the results as needed.
Assuming retrofit clients are async or ran on a specific scheduler :
api1.items(queryParam)
.flatMap(itemList -> Observable.fromIterable(itemList)))
.concatMapEager(item -> api2.extendedInfo(item.id()))
.subscribe(...)
Or if the scheduler has to be set locally :
api1.items(queryParam)
.flatMap(itemList -> Observable.fromIterable(itemList)))
.concatMapEager(item -> api2.extendedInfo(item.id()).subscribeOn(Schedulers.io()))
.subscribe(...)
It is also possible to tune the concurrency in this operator.
Additionally if the Api is returning Flowable, it is possible to use .parallel that is still in beta at this time in RxJava 2.1.7. But then results are not in order and I don't know a way (yet?) to order them without sorting after.
api.items(queryParam) // Flowable<Item>
.parallel(10)
.runOn(Schedulers.io())
.map(item -> api2.extendedInfo(item.id()))
.sequential(); // Flowable<ItemExtended>
the flatMap operator is designed to cater to these types of workflows.
i'll outline the broad strokes with a simple five step example. hopefully you can easily reconstruct the same principles in your code:
#Test fun flatMapExample() {
// (1) constructing a fake stream that emits a list of values
Observable.just(listOf(1, 2, 3, 4, 5))
// (2) convert our List emission into a stream of its constituent values
.flatMap { numbers -> Observable.fromIterable(numbers) }
// (3) subsequently convert each individual value emission into an Observable of some
// newly calculated type
.flatMap { number ->
when(number) {
1 -> Observable.just("A1")
2 -> Observable.just("B2")
3 -> Observable.just("C3")
4 -> Observable.just("D4")
5 -> Observable.just("E5")
else -> throw RuntimeException("Unexpected value for number [$number]")
}
}
// (4) collect all the final emissions into a list
.toList()
.subscribeBy(
onSuccess = {
// (5) handle all the combined results (in list form) here
println("## onNext($it)")
},
onError = { error ->
println("## onError(${error.message})")
}
)
}
(incidentally, if the order of the emissions matter, look at using concatMap instead).
i hope that helps.
Check below it's working.
Say you have multiple network calls you need to make–cals to get Github user information and Github user events for example.
And you want to wait for each to return before updating the UI. RxJava can help you here.
Let’s first define our Retrofit object to access Github’s API, then setup two observables for the two network requests call.
Retrofit repo = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
Observable<JsonObject> userObservable = repo
.create(GitHubUser.class)
.getUser(loginName)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread());
Observable<JsonArray> eventsObservable = repo
.create(GitHubEvents.class)
.listEvents(loginName)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread());
Used Interface for it like below:
public interface GitHubUser {
#GET("users/{user}")
Observable<JsonObject> getUser(#Path("user") String user);
}
public interface GitHubEvents {
#GET("users/{user}/events")
Observable<JsonArray> listEvents(#Path("user") String user);
}
After we use RxJava’s zip method to combine our two Observables and wait for them to complete before creating a new Observable.
Observable<UserAndEvents> combined = Observable.zip(userObservable, eventsObservable, new Func2<JsonObject, JsonArray, UserAndEvents>() {
#Override
public UserAndEvents call(JsonObject jsonObject, JsonArray jsonElements) {
return new UserAndEvents(jsonObject, jsonElements);
}
});
Finally let’s call the subscribe method on our new combined Observable:
combined.subscribe(new Subscriber<UserAndEvents>() {
...
#Override
public void onNext(UserAndEvents o) {
// You can access the results of the
// two observabes via the POJO now
}
});
No more waiting in threads etc for network calls to finish. RxJava has done all that for you in zip().
hope my answer helps you.
I solved a similar problem with RxJava2. Execution of requests for Api 2 in parallel slightly speeds up the work.
private InformationRepository informationRepository;
//init....
public Single<List<FullInformation>> getFullInformation() {
return informationRepository.getInformationList()
.subscribeOn(Schedulers.io())//I usually write subscribeOn() in the repository, here - for clarity
.flatMapObservable(Observable::fromIterable)
.flatMapSingle(this::getFullInformation)
.collect(ArrayList::new, List::add);
}
private Single<FullInformation> getFullInformation(Information information) {
return informationRepository.getExtendedInformation(information)
.map(extendedInformation -> new FullInformation(information, extendedInformation))
.subscribeOn(Schedulers.io());//execute requests in parallel
}
InformationRepository - just interface. Its implementation is not interesting for us.
public interface InformationRepository {
Single<List<Information>> getInformationList();
Single<ExtendedInformation> getExtendedInformation(Information information);
}
FullInformation - container for result.
public class FullInformation {
private Information information;
private ExtendedInformation extendedInformation;
public FullInformation(Information information, ExtendedInformation extendedInformation) {
this.information = information;
this.extendedInformation = extendedInformation;
}
}
Try using Observable.zip() operator. It will wait until both Api calls are finished before continuing the stream. Then you can insert some logic by calling flatMap() afterwards.
http://reactivex.io/documentation/operators/zip.html

Using initial value of observable in subscription along with the mapped one

Take a look at the following code snippet:
vertx.eventBus().consumer<JsonObject>(CREATEMEETING_SERVICE_ID).toObservable()
.map { objectMapper.readValue(it.body().getJsonObject("body").encode(), Meeting::class.java) }
.flatMap (meetingService::createMeeting)
.subscribe(
{ Json.encodePrettily(it) },
{ throw it }
)
I need to call message.reply method from the initial observable in onNext method of subscribe. One way of doing this is to use Pairs of rather than Meeting alone what makes this ugly. Is there any other option to make this work not having to use Pairs?
I know one more way which is like this:
vertx.eventBus().consumer<JsonObject>(CREATEMEETING_SERVICE_ID).toObservable()
.subscribe {
message ->
Observable.just(objectMapper.readValue(message.body().getJsonObject("body").encode(), Meeting::class.java))
.flatMap(meetingService::createMeeting)
.subscribe(
{ message.reply(Json.encodePrettily(it)) },
{ throw it }
)
}
But it also looks wrong.
Maybe it also explains the problem.

RxJava combining observables without repeating execution

Short story:
I have a situation where I have 2 Observables that have a single purpose:
they receive some data
they return modified data
throw an error if the data cannot be processed
They are each in charge of handling different types of data. Additionally I want to do something when both data has been processed.
My current best implementation is as follows, these are my Observables:
Single<BlueData> blueObservable = Single.create(singleSubscriber -> {
if (BlueDataProcessor.isDataValid(myBlueData)) {
singleSubscriber.onSuccess(BlueDataProcessor.process(myBlueData));
}
else {
singleSubscriber.onError(new BlueDataIsInvalidThrow());
}
});
Single<RedData> redObservable = Single.create(singleSubscriber -> {
if (RedDataProcessor.isDataValid(myRedData)) {
singleSubscriber.onSuccess(RedDataProcessor.process(myRedData));
}
else {
singleSubscriber.onError(new RedDataIsInvalidThrowable());
}
});
Single<PurpleData> composedSingle = Single.zip(blueObservable, redObservable,
(blueData, redData) -> PurpleGenerator.combine(blueData, redData));
I also have the following subscriptions:
blueObservable.subscribe(
result -> {
saveBlueProcessStats(result);
},
throwable -> {
logError(throwable);
});
redObservable.subscribe(
result -> {
saveRedProcessStats(result);
},
throwable -> {
logError(throwable);
});
composedSingle.subscribe(
combinedResult -> {
savePurpleProcessStats(combinedResult)
},
throwable -> {
logError(throwable);
});
MY PROBLEM:
The blue & red data is processed twice, because both subscriptions are run again with I subscribe to the combined observable created with Observable.zip().
How can I have this behaviour without running both operations twice?
This is not possible with Single in 1.x because there is no notion of a ConnectableSingle and thus Single.publish. You can achieve the effect via 2.x and the RxJava2Extensions library:
SingleSubject<RedType> red = SingleSubject.create();
SingleSubject<BlueType> blue = SingleSubject.create();
// subscribe interested parties
red.subscribe(...);
blue.subscribe(...);
Single.zip(red, blue, (r, b) -> ...).subscribe(...);
// connect()
blueObservable.subscribe(blue);
redObservable.subscribe(red);

Categories