Multiple asynchronous calls using result of first call using spring webflux - java

I need to make an asynchronous call and use some values present in it to make multiple calls to same service. Combine the response on these calls with the first one and return.
For example When I make the first call I get below JSON, which has a list of ids. Now I have to make multiple calls to a service with these ids and make a list of their response and send it to downstream by appending it in same JSON.
{“id”: 145,
“object”:[{“id”:111}]
}
I have tried using zipWhen and
Flux.fromIterable(userIds).parallel().runOn(Schedulers.elastic()).flatMap()
But resulting list always comes as empty or null. How can we achieve this? Am I missing something here?
Edit1:
Resolved it by using Flux.fromIterable. Read more about it and finally understood the use and resolved it. The below method takes up one item from list and will call the inner method which will call multiple APIs:
return Flux.fromIterable(somelist).flatMap(listItem -> {
return someMethodToCallAnotherAPIUsingZipWith(listItem);
}).collectList();
Inner Method:
It calls 1st API, passed its result to zipWith and using this result we can call another API or we can simply use it with its response.
private Mono<Object> someMethodToCallAnotherAPIUsingZipWith(String listItem) {
return authService.getAccessToken().flatMap(accessToken ->
webClient.get().uri(builder -> builder.path("/path").build(listItem))
.header(HttpHeaders.AUTHORIZATION, accessToken)
.retrieve()
.toEntity(Entity.class).log()
.flatMap(entity -> {
//manipulate your response or create new object using it
return Mono.just(entity);
}).zipWhen(consent -> webClient.get().uri(builder -> builder.path("/otherpath").build(listItem))
.header(HttpHeaders.AUTHORIZATION, accessToken)
.retrieve().bodyToMono(Entity.class).log()
.flatMap(entity -> {
//example
listItem = entity.getString();
return Mono.just(listItem);
}), (string1, string2) -> string1 + string2));
}

private Mono<Object> someMethodToCallAnotherAPIUsingZipWith(String listItem) {
return authService.getAccessToken().flatMap(accessToken ->
webClient.get().uri(builder -> builder.path("/path").build(listItem))
.header(HttpHeaders.AUTHORIZATION, accessToken)
.retrieve()
.toEntity(Entity.class).log()
.flatMap(entity -> {
//manipulate your response or create new object using it
return Mono.just(entity);
}).zipWhen(consent -> webClient.get().uri(builder -> builder.path("/otherpath").build(listItem))
.header(HttpHeaders.AUTHORIZATION, accessToken)
.retrieve().bodyToMono(Entity.class).log()
.flatMap(entity -> {
//example
listItem = entity.getString();
return Mono.just(listItem);
}), (string1, string2) -> string1 + string2));
}

Related

Java List of CompletableFutures to CompletableFuture List with allOf()

I have a piece of asynchronous code which contains more methods and I need to make it return CompletableFuture<List> in the end.
I need to use 2 methods:
the first method getConfigsByType() returns a Flux of type Config
the second one, which needs to be applied to every individual Config object, returns CompletableFuture of type Config.
I want to use allOf() in order to get the expected result, but I have an error and I do not know why: "no instance(s) of type variable(s) U exist so that Boolean conforms to CompletionStage". The error is at this line: .thenCompose(segmentedConfig -> finalEvents.add(segmentedConfig));
private CompletableFuture<List<Config>> getConfigs(User user) {
Queue<Config> finalEvents = new ConcurrentLinkedQueue<>();
List<CompletableFuture<Config>> completableFutureList = admin.getConfigsByType(configurationProperties.getEvents()) // returns Flux<Config>
.map(config -> {
return segmentConfig(config, user) // returns CompletableFuture<Config>
.thenCompose(segmentedConfig -> finalEvents.add(segmentedConfig));
})
.collect(Collectors.toList());
return allOf(completableFutureList)
.thenApply(list -> finalEvents);
private CompletableFuture<Void> allOf(List<CompletableFuture<Config>> futuresList) {
return CompletableFuture.allOf(futuresList.toArray(new CompletableFuture[0]));
}
private CompletableFuture<Config> segmentConfig(Config config, User user) {
return configurationApi.getSegmentedConfig(new DefaultCombinedConfigProvider<>(config), user);
}
What am I doing wrong?
You can not produce the list of results before the future created by allOf(completableFutureList) has been completed. Further, a Queue<Config> won’t become a List<Config>.
So, remove your attempt to produce the result list from the stream operation that produces the List<CompletableFuture<Config>>. Then, add an actual operation producing the result list to allOf(completableFutureList).
private CompletableFuture<List<Config>> getConfigs(User user) {
List<CompletableFuture<Config>> completableFutureList
= admin.getConfigsByType(configurationProperties.getEvents())
.map(config -> segmentConfig(config, user))
.collect(Collectors.toList());
return CompletableFuture.allOf(completableFutureList.toArray(new CompletableFuture[0]))
.thenApply(voidArg -> completableFutureList.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList()));
}

RxJava2 combine multiple observables to make them return single result

How to combine multiple results emmited by observables into one result and emit it once?
I have a Retrofit service:
public interface MyService {
#GET("url")
Observable<UserPostsResult> getUserPosts(#Query("userId") int id);
#GET("url")
Observable<UserPostsResult> getUserPosts(#Query("userId") int id, #Query("page") int pageId);
}
And I have a model:
public class UserPostsResult {
#SerializedName("posts")
List<UserPost> mPosts;
#SerializedName("nextPage")
int mPageId;
}
Also I have ids List<Integer> friendsIds;
My goal is to have a method like this one:
public Observable<Feed> /*or Single<Feed>*/ getFeed(List<Integer> ids) {
...
}
It returns one Observable or Single that does the following:
Combines all getUserPosts(idFromList) to one observable
For each UserPostsResult must do:
if (userPostResult.mPageId > -1)
getUserPosts(currentUserId, userPostResult.mPageId);
And merge this result to the previous userPostResult
Return one single model as result of all operations.
Result class:
public class Feed {
List<UserPost> mAllPostsForEachUser;
}
EDIT (More details):
My client specifications was that I must take from social network user posts with no logging in, no token requesting. So I must parse HTML pages. That's why I have this complex structure.
EDIT (Partial solution)
public Single<List<Post>> getFeed(List<User> users) {
return Observable.fromIterable(users)
.flatMap(user-> mService.getUserPosts(user.getId())
.flatMap(Observable::fromIterable))
.toList()
.doOnSuccess(list -> Collections.sort(list, (o1, o2) ->
Long.compare(o1.getTimestamp(), o2.getTimestamp())
));
}
This solution doesn't include pages problem. Thats why it is only partial solution
There are a number of operators which transform things into other things. fromIterable() will emit each item in the iterable, and flatMap() will convert one type of observable into another type of observable and emit those results.
Observable.fromIterable( friendsIds )
.flatMap( id -> getUserPosts( id ) )
.flatMap( userPostResult -> userPostResult.mPageId
? getUserPosts(currentUserId, userPostResult.mPageId)
: Observable.empty() )
.toList()
.subscribe( posts -> mAllPostsForEachUser = posts);
If you need join two response in one you should use Single.zip
Single.zip(firsSingle.execute(inputParams), secondSingle.execute(inputPrams),
BiFunction<FirstResponse, SecondResponse, ResponseEmitted> { firstResponse, secondResponse ->
//here you put your code
return responseEmmitted
}
}).subscribe({ response -> },{ })

Rxjava chain more than one request

I am new in concept of RxJava.
I would like to chain some calls:
Observable<RoomList> listRoomsCall = mRoomServiceApi.listRooms();
//This call will get me RoomIds
Next step is to call for all RoomIds - request after request
mMeetingServiceApi.listMeetings(roomID, startsAtString, endsAtString, free))
How should I chain first call with next calls?
I thinkt that I should use flatMap and loop to call all requets but how to connect all responses on the end?
listRoomsCall.flatMap(v -> {
for (ExchangeRoom exchangeRoom : v.getExchangeRoomList()) {
mMeetingServiceApi.listMeetings(roomID, startsAtString, endsAtString, free);
}
})
Turn the inner list into an Observable and flatMap over it again:
listRoomsCall
.flatMapIterable(v -> v.getExchangeRoomList())
.flatMap(exchangeRoom -> {
mMeetingServiceApi.listMeetings(roomID, startsAtString, endsAtString, free);
})
.subscribe(/* */);
or
listRoomsCall
.flatMap(v ->
Observable.fromIterable(v.getExchangeRoomList())
.flatMap(exchangeRoom -> {
mMeetingServiceApi.listMeetings(roomID, startsAtString, endsAtString, free);
})
)
.subscribe(/* */);

RxJava: dynamically create Observables and send the final resut as Observable

I am using RxJava in which I want to dynamically create a number of Observables based on some condition. Once I'm done with creating, I want to do some processing on the different values returned by the observables and then send as a single Observable to which I can subscribe on. Here is how my code is :
List<String> valueList = ....
List<Observable<String>> listOfObservables = new ArrayList<Observable<String>>();
for(int i =; i <valueList.size(); i++){
listOfObservables.add(new SomeClass.doOperation(valueList(i)));
// SomeClass.doOperation will return an Observable<String>
}
return Observable.merge(listOfObservables);
But here , I want to do some operation on the values emitted by different Observables in the listOfObservable and finally return it as a single Observable<String>
Like in Observable.zip() , I can do this like
return Observable.zip(observable1, observable2, (string1, string2) -> {
// joining final string here
return string1 + string2;
But I know the number of arguments here. Please let me know how I can achieve this.
Use the zip overload that takes a variable number of arguments, it has a signature of
<R> Observable<R> zip(Iterable<? extends Observable<?>> ws,
FuncN<? extends R> zipFunction)
Example usage:
List<String> valueList = ....
return Observable.from(valueList)
.map(string -> SomeClass.doOperationThatReturnsObservable(string))
.toList()
.flatMap(listOfObs -> Observable.zip(listOfObs, (Object[] results) -> {
// do something with the strings in the array.
return Arrays.stream(results)
.map(Object::toString)
.collect(Collectors.joining(","));
}));

In RxJava, how to pass a variable along when chaining observables?

I am chaining async operations using RxJava, and I'd like to pass some variable downstream:
Observable
.from(modifications)
.flatmap( (data1) -> { return op1(data1); })
...
.flatmap( (data2) -> {
// How to access data1 here ?
return op2(data2);
})
It seems like a common pattern but I couldn't find information about it.
The advice I got from the Couchbase forum is to use nested observables:
Observable
.from(modifications)
.flatmap( (data1) -> {
return op1(data1)
...
.flatmap( (data2) -> {
// I can access data1 here
return op2(data2);
})
});
EDIT: I'll mark this as the accepted answer as it seems to be the most recommended. If your processing is too complex to nest everything you can also check the solution with function calls.
Another possibility is to map the result of op1 to a org.apache.commons.lang3.tuple.Pair that contains the variable and pass that along:
Observable
.from(modifications)
.flatmap( (data1) -> {
return op1(data1).map( obj -> { return Pair.of(data1,obj); });
})
...
.flatmap( (dataPair) -> {
// data1 is dataPair.getLeft()
return op2(dataPair.getRight());
})
It works but it feels a bit uncomfortable to have variables hidden inside a Pair/Triple/... and it gets very verbose if you use the Java 6 notation.
I wonder if there is a better solution, maybe some RxJava operator could help?
flatmap can take a second arg:
Observable.just("foo")
.flatMap(foo -> Observable.range(1, 5), Pair::of)
.subscribe(pair -> System.out.println("Result: " + pair.getFirst() + " Foo: " + pair.getSecond()));
source: https://medium.com/rxjava-tidbits/rxjava-tidbits-1-use-flatmap-and-retain-original-source-value-4ec6a2de52d4
One possibility would be to use a function call:
private static Observable<T> myFunc(final Object data1) {
return op1(data1)
...
.flatmap( (data2) -> {
// I can access data1 here
return op2(data2);
});
}
Observable
.from(modifications)
.flatmap( (data1) -> { return myFunc(data1); })
BUT: correct me if I'm wrong but it doesn't feel like the reactive-programming way of doing it
Actually we have library, that simplify call chains.
https://github.com/pakoito/Komprehensions
Adding as Gradle dependency:
implementation 'io.reactivex.rxjava2:rxjava:2.2.1'
implementation 'com.github.pakoito.Komprehensions:komprehensions-rx2:1.3.2'
Usage (Kotlin):
val observable = doFlatMap(
{ Observable.from(modifications) },
{ data1 -> op1(data1) },
{ data1, data2 -> op2(data2) },
{ data1, data2, data3 -> op3(data1, data2, data3) }
)
I know this is an old question, but using RxJava2 & lambda,
You can use something like:
Observable
.from(modifications)
.flatMap((Function<Data1, ObservableSource<Data2>>) data1 -> {
//Get data 2 obeservable
return Observable.just(new Data2())
}
}, Pair::of)
On the next flow (flatmap/map) your output pair will be (data1, data2)
solution on this thread works, but for complex chains it makes code difficult to read, I had to pass multiple values and what i did was create a private class with all parameters, I find code to be more readable this way,
private class CommonData{
private string data1;
private string data2;
*getters and setters*
}
...
final CommonData data = new CommonData();
Observable
.from(modifications)
.flatmap( (data1) -> {
data.setData1(data1);
return op1(data1);
})
...
.flatmap( (data2) -> {
data2 = data.getData1() + "data 2... ";
data.setData2(data2);
return op2(data2);
})
hope it helps
you can use resultSelector BiFunction<? super T, ? super U, ? extends R> resultSelector the second parameter in flatmap, you can choose which result to return.
You can use "global" variable to achive this:
Object[] data1Wrapper = new Object[]{null};
Object[] data2Wrapper = new Object[]{null};
Observable
.from(modifications)
.flatmap(data1 -> {
data1Wrapper[0] = data1;
return op1(data1)
})
...
.flatmap(data2 -> {
// I can access data1 here use data1Wrapper[0]
Object data1 = data1Wrapper[0];
data2Wrapper[0] = data2;
return op2(data2);
})

Categories