Reactor Flux mergeWith using previous values - java

Is there any way to create a Flux by merging multiple Mono, the merged Mono is reading the value of the previous response.
Something like that :
Flux<Integer> values = Mono.just(1).mergeWith( value -> Mono.just(value * 2));

You have flatMapIterable/flatMapMany that could help, for ex:
.flatMapIterable(v -> List.of(v, v*2))

For posterity and to illustrate sp00m answer, here is an example of how I use this technique in combination with recursion to call Youtube API and navigate throw all the response pages.
The next page id is given in the current page in the getNextPagetoken()
public Flux<VideoListResponse> getvideoListResponses() {
Videos.List request = ...
return videoListResponseFluxPaginated(request, null, true);
}
private Flux<VideoListResponse> videoListResponseFluxPaginated(Videos.List request, String pageToken, boolean firstCall) {
if(pageToken == null && !firstCall) return Flux.empty();
request.setPageToken(pageToken);
return Mono.fromCallable(request::execute)
.flatMapMany(videoListResponse -> Flux.merge(Flux.just(videoListResponse), videoListResponseFluxPaginated(request, videoListResponse.getNextPageToken(),false)))
;
}

Related

How to get Different Optional type of Object from CompletableFuture

I have one code snippet, which is calling 2 different services based on some a if condition. And both the services return CompletableFuture<Optional<SomeObject>>. Following is the code logic looks like
if(someCondition){
CompletableFuture<Optional<SomeObjectType1>> = service1.call();
}else{
CompletableFuture<Optional<SomeObjectType2>> = service2.call();
}
And both SomeObjectType1 and SomeObjectType2 have a String inside it, which is of my interest. My current code looks like this:
private ContentWrapper getContentWrapper(input1, input2, ....) {
String content = null;
if (some_condition is true) {
List<Object_Type_1> list = service1.fetchTheCompletableFuture(..... inputs...)
.join()
.map(ListOutput::getList)
.orElse(null);
if (CollectionUtils.isNotEmpty(list)) {
content = list.get(0).getContent();
}
} else {
content = service2
.fetchTheCompletableFuture(..... inputs...)
.join()
.map(RenderedContent::getContent)
.orElse(null);
}
return content != null ? new ContentWrapper(content) : null;
}
Now my question is, can this if-else clause be removed or make it more clear by using lambdas. I am new in lambdas and does not have very good idea on this.
I am not sure whether the code below even compiles due to the vagueness.
private ContentWrapper getContentWrapper(input1, input2, ....) {
Optional<RenderedContent> content = some_condition
? service1
.fetchTheCompletableFuture(..... inputs...)
.join()
.map(ListOutput::getList)
.stream()
.findFirst()
: service2
.fetchTheCompletableFuture(..... inputs...)
.join();
}
return content
.map(RenderedContent::getContent)
.map(ContentWrapper::new).orElse(null);
}
The first service seems to yield a list of RenderedContent of which to take the first if there is one.
The second service may yield a Rendered content immediately.
So you can join the if-else to an Optional<RenderedContent>.
The map(RenderedContent::getContent) will yield Optional.empty() if it was empty to begin with. Otherwise getContent is called and wrapped in an Optional.
If present new ContentWrapper(...) might be called.
Notice much may fail, like getContent returning null (though there is an Optional.ofNullable.
Nevertheless Streams may be very expressive.
I would avoid using null in favor of Optional as that plays better together.

Do then and finally with Flowable reactive x Java

Trying to use Flowable, do then, and finally using RxJava3.
public String post(Publisher<CompletedFileUpload> files) {
return Flowable.fromPublisher(files).doOnNext(file -> {
MultipartBody requestBody = MultipartBody.builder()
.addPart("file", file.getFilename(), MediaType.MULTIPART_FORM_DATA_TYPE, file.getBytes())
.addPart("id", "asdasdsds")
.build();
}).doOnComplete((value) -> {
return this.iProduct.post(requestBody);
});
}
The above code has error, But what I am trying to achieve is described in the below scenarios
Iterate on files
add file.getFilename() and bytes to requestBody
Then call the this.iProduct.post(requestBody) which returns the string
Finally return the string value
One way to approach this is to:
Gather all emissions that would come out of Publisher<CompletedFileUpload> files with the toList() operator
Construct the request by looping through the list created in in Step 1 using the map() operator.
Post the request and return the resulting String (also using the map() operator.
The scaffolding for this would look something like this:
public String post(Publisher<CompletedFileUpload> files) {
final Single<MultipartBody> requestSingle =
Flowable.fromPublisher(files)
.toList()
.map(list -> {
final MultipartBody.Builder builder = MultipartBody.Builder();
for(file : list) {
builder.addPart(...)
}
return builder.build();
})
.map(requestBody -> this.iProduct.post(requestBody));
return requestSingle.blockingGet();
}
There are two things worth noting here:
The toList() operator transforms the Flowable into a Single.
Your sample mixes asynchronous code (all the Rx stuff) and synchronous code (the post method returns a String as opposed to a deferred operation/value). The Rx operators are helpful ways of transforming from one reactive type to another, but in your case you need a way to bridge into the synchronous world by invoking those asynchronous operations and waiting for the resulting value. This is the reason for the final call to blockingGet().

Repeat request until condition is met in webflux, based on last response's value

Using spring boot reactive web's WebClient, I need to call an API that returns an XML response. The response may hold a NextToken - if it is present I want to call the webservice again using the last returned NextToken value until there's no NextToken present on the response.
The current code returns the correct result for the first and second request, but it doesn't concat the returned values and the third and each consequent requests are duplicates of the second.
How can I conditionally repeat a request until the condition I have below in takeUntil is met, while all the orders are concatenated?
client.request(EU, Orders, ListOrdersRequest.forShipped(userData, LocalDateTime.now().minusDays(7), LocalDateTime.now().minusHours(2).minusMinutes(2)), ListOrdersResponse.class)
.flatMap(e -> {
if (e.getListOrdersResult().getNextToken() != null) {
return client.request(EU, Orders, ListOrdersRequest.byNextToken(userData, e.getListOrdersResult().getNextToken()), ListOrdersByNextTokenResponse.class)
.mergeWith(x -> Flux.just(e));
}
return Flux.just(e);
})
.delayElements(Duration.ofMinutes(1))
.repeat()
.retryBackoff(10, Duration.ofMinutes(2), Duration.ofMinutes(20))
.takeWhile(r -> r.getListOrdersResult().getNextToken() != null)
.checkpoint("nextToken fetched", true)
I think you can use the expand operator - something like this:
client.request(EU, Orders, ListOrdersRequest.forShipped(userData, LocalDateTime.now().minusDays(7), LocalDateTime.now().minusHours(2).minusMinutes(2)), ListOrdersResponse.class)
.expand(e -> {
if(e.getListOrdersResult().getNextToken() != null) {
return client.request(EU, Orders, ListOrdersRequest.byNextToken(userData, e.getListOrdersResult().getNextToken()), ListOrdersByNextTokenResponse.class);
}
return Flux.empty();
});

java reactor filterAndMap?

I want to use reactor achieve:
for (val worker : getWorkers(request)) {
val response = worker.tryDo(work);
if (response != null) {
return response;
}
}
return null;
getWorkers can transfer to return Flux<Worker>, tryDo can also return a mono.
The key is I want exactly one or zero Response and only try the next if the current worker.tryDo fails.
Which operator do I need? I cannot find an answer in the document.
assuming you can rework tryWork to return a Mono that is empty when no work instead of returning null, you could use getWorkers(request).flatMap(worker -> worker.tryDo(work), 1).next()
the 1 parameter to flatMap instruct it to only consider workers 1 by 1.
workers that return an empty mono are effectively not influencing the output of flatMap.
Flux.next() converts to Mono by discarding elements past the first one and cancelling the source.
I found an answer in the gitter, which come from #OlegDokuka:
Mono.fromDirect(workersFlux.concatMap(worker ->
Mono.justOrEmpty(worker.tryDo(work))).take(1))
Update:
Thanks for #Simon Baslé: Use singleOrEmpty instead of fromDirect.
workersFlux.concatMap(worker -> Mono.justOrEmpty(worker.tryDo(work))).take(1).singleOrEmpty()

rxjava2 - if else on Maybe

I am looking for what is the recommended practice in rxjava2 to handle a case where one flowable leads to conditional behaviors.
More concretely, I have a Maybe<String> for which I want to Update the String on the database if the String exists or, if it doesn't exists I want to create a new String and save it on the database.
I thought of the below but obviously it is not what I am looking for:
Maybe<String> source = Maybe.just(new String("foo")); //oversimplified source
source.switchIfEmpty(Maybe.just(new String("bar"))).subscribe(result ->
System.out.println("save to database "+result));
source.subscribe(result -> System.out.println("update result "+result));
The above obviously produces
save to database foo
update result foo
I tried also the below which gives the expected result but still feel it's... weird.
Maybe<String> source = Maybe.just(new String("foo")); //oversimplified source
source.switchIfEmpty(Maybe.just(new String("bar")).doOnSuccess(result ->
System.out.println("save to database "+result))).subscribe();
source.doOnSuccess(result -> System.out.println("update result "+result)).subscribe();
How can I have an action for when the result exists and when it doesn't exists? How is that use case supposed to be handled in rxjava2?
Update 01
I tried the below and it looks cleaner than what I came up with above. Note sure it is recommended rxjava2 practice however...
Maybe.just(new String("foo"))
.map(value -> Optional.of(value))
.defaultIfEmpty(Optional.empty())
.subscribe(result -> {
if(result.isPresent()) {
System.out.println("update result "+result);
}
else {
System.out.println("save to database "+"bar");
}
});
You have the isEmpty() operator that will return you Boolean if the Maybe source is empty or not, and then you can flatMap it and write a if else statement depending on that Boolean
This is a common pattern in our code as well, though in our case the choices are themselves async. You can't get quite the right semantic by simply composing flatMapX and switchIfEmpty (in either order), so I am curious why this isn't part of the API.
Here's what we're doing for now (this example for when the 2 options are both Completables, we have similar things for the other types as well):
public static <T> Completable flatMapCompletable(Maybe<T> target,
#ClosureParams(FirstParam.FirstGenericType.class)
Closure<? extends CompletableSource> completableSupplier,
Supplier<CompletableSource> emptySupplier) {
Maybe<T> result = target.cache();
return result.isEmpty().flatMapCompletable(empty -> {
if (empty) {
return emptySupplier.get();
} else {
return result.flatMapCompletable(completableSupplier::call);
}
});
}
We're using Groovy, so we package these up as extension methods. I'm not thrilled with the need to use cache() so I'm wondering if there is a better alternative. From looking at the code, an operator which basically combines flatMapX and switch looks like it wouldn't be too hard (but I feel like I'm missing something).
Try something like this. checkDB can return a Maybe or Single or whatever which emits either an optional or a wrapper Object.
checkDB(String)
.flatMap(s -> {
if (s.isPresent()) {
return updateDB(s.get());
} else {
return insertDB("new String");
}
})
There is an solution using the flatMap call with 3 params
fun addOrUpdate(message: LocalMessage): Single<LocalMessage> {
return getById(message.id) // returns Maybe
.flatMap(
Function {
update(message) // onSuccess update call returns Single
},
Function {
Single.error(it) // onError
},
Callable {
add(message) // onComplete add call returns Single
}
)
}
}
Or shorter version
fun addOrUpdate(message: LocalMessage): Single<LocalMessage> {
return getById(message.id) // returns Maybe
.flatMap(
{
update(message) // onSuccess update call returns Single
},
{
Single.error(it) // onError
},
{
add(message) // onComplete add call returns Single
}
)
}
}

Categories

Resources