How to wait until List<Mono<List<Object>>> finishes? - java

It's my first time working with webClient and I am wondering how to wait until List<Mono<>> finishes. I have the following code:
List<Address> addresses = collectAllAddresses(someObject);
List<Mono<List<AnotherAddress>>> monoResponses =
addresses.stream()
.map(address -> webClientGateway.findAddresses(userData, address.getFullAddress()))
.collect(Collectors.toList());
Mono.when(monoResponses).block();
log.info("mono responses");
monoResponses.stream()
.flatMap(it -> Objects.requireNonNull(it.block()).stream()).forEach(it -> log.info("mono responses + {}", it));
and the following findAddresses method:
public Mono<List<AnotherAddress>> findAddresses(UserData userData, String fullAddress) {
if (StringUtils.isEmpty(fullAddress)) {
log.info("Address is empty that why we return Mono.just(Collections.emptyList()");
return Mono.just(Collections.emptyList());
}
return webClient.get()
.uri(path, uri -> uri.queryParam("query", fullAddress).queryParam("count", 1).build())
.header("someHeader", someHeader)
.retrieve()
.bodyToMono(new ParameterizedTypeReference<List<AnotherAddress>>() {
})
.doOnError(e -> log.error("Error occurred!", e));
}
but every time I execute it I always get list of empty objects, I mean I get List but every object in that list is empty (every field of class AnotherAddress is null). What can be wrong?
UDP: more explanations:
I have two microservices. In one microservice (that return another address) there is RestController that sends anotherAddress. In another microservice I want to use WebClient (instead of using threadPool with many threads) to call the RestController from previous microservice. When I have previous implementation for function webClientGateway.findAddresses(userData, address.getFullAddress()) and it returns Mono<List> I tested it and immediately after calling function I call block on result and it works. But now I have following situation, I have many addresses (maybe 5 or 10) and I want send async request for every address and wait until latest finishes and after that I want to do another operation, but instead of getting fullfielded AnotherAddress instance, I am getting 5 empty AnotherAddress instances (every field is null)

Use a Flux instead of a Mono, e.g. something like (untested):
public Flux<AnotherAddress> findAddresses(UserData userData, String fullAddress) {
if (StringUtils.isEmpty(fullAddress)) {
log.info("Address is empty that why we return Mono.just(Collections.emptyList()");
return Flux.empty();
}
return webClient.get()
.uri(path, uri -> uri.queryParam("query", fullAddress).queryParam("count", 1).build())
.header("someHeader", someHeader)
.retrieve()
.bodyToFlux(AnotherAddress.class)
.doOnError(e -> log.error("Error occurred!", e));
}
If you don't need the AnotherAddress list grouped by address the you could use something like (untested):
Flux<AnotherAddress> anotherAddressFlux= Flux.fromIterable(addresses)
.flatMap(address -> webClientGateway.findAddresses(userData, address.getFullAddress()));
If you want to block you can use:
List<AnotherAddress> anotherAddressList = anotherAddressFlux.collectList().block();

Related

Await response and use in another method

I need to make two calls to External APIs. In the first one, get a response to use it and invoke another API. I'm creating threads at the time of subscription but when debugging I don't get a correct response from the first API. I should be getting integers greater than 0, but I'm getting 0, indicating that nothing was created in the database.
public class MyApi {
// call first api
public Maybe<ResponseOne> crearOne(OneRequest OneRequest){
return oneApi.createOne(..)
.subscribeOn(Schedulers.io())
.doFinally(...)
.doOnError(...);
}
// call second api
public Completable crearTwo(...){
return TwoApi.createTwo(..)
.subscribeOn(Schedulers.io())
.doFinally(...)
.doOnError(...);
}
// call two methods
public Maybe<MyApiResponse> create(..){
return createOne(..)
.flatMap(response -> Maybe.fromCompletable(
createTwo(response.getValue(), ..) // need response values
))
.map(
// ..
// build MyApiResponse
);
}
}

How can catch MonoError?

I need to catch MonoError and stop an application with ErrorResponse, but the application works as I did not expect.
My code:
return checkText(text)
.then(getWordsFromText(text))
.map(keyWords -> new SuccessfulResponse(keyWords))
.onErrorResume(
throwable -> {
return Mono.just(new ErrorResponse(throwable.getMessage()));
});
public Mono<Void> checkText(String text) {
if (text == null) {
return Mono.error(new Exception("wrong text"));
}
return Mono.empty();
}
my problem is that if text param is null -> I fall into getWordsFromText method. This is an incorrect execution, because if the text parameter is equal to null, then the application must exit with an error (with ErrorResponse).
I fixed it as (replacing 'then' to 'flatMap'):
return checkText(text)
.flatMap(voidParam -> getWordsFromText(text)) //replaced 'then' to 'flatMap'
.map(keyWords -> new SuccessfulResponse(keyWords))
.onErrorResume(
throwable -> {
return Mono.just(new ErrorResponse(throwable.getMessage()));
});
and now it's working correctly. If text param is null I miss the call getWordsFromText method and fall in error handling (onErrorResume).
But I think using flatMap in my case is not a good idea, I don't like how it looks: .flatMap(voidParam -> ...
Can you have any ideas how possible to do better? (without 'flatMap')
In the first snippet, the call to getWordsFromText() is made while building your main reactive pipeline, before it is even subscribed to (i.e. at assembly time). The reason it works as intended in the second snippet is that flatMap only creates the inner publishers (and subsequently subscribes to them) as it receives elements from upstream (i.e. at subscription time).
In this case if you want to replace the flatMap you could try this: .then(Mono.fromCallable(() -> getWordsFromText(text)))

How to prevent Mono from being cancelled?

I am trying to implement something as a "race condition". This race condition must follow these situations:
Fire two simultaneous HTTP calls.
Return the response from the first call that was completed successfully.
Handle the last call. (The most important thing here is that I can not discard the last call, I do need to handle the result of it: whatever its status, success or fail).
This sample of code is the most close of the solution that I have achieved:
Mono<StatusMock> monoA = webClient.get()
.uri("https://some.url.a")
.retrieve()
.bodyToMono(StatusMock.class)
.subscribeOn(Schedulers.boundedElastic());
Mono<StatusMock> monoB = webClient.get()
.uri("https://some.url.b")
.retrieve()
.bodyToMono(StatusMock.class)
.doOnSuccess(this::verifyBody)
.onErrorStop()
.subscribeOn(Schedulers.boundedElastic());
StatusMock statusMock = Flux.first(monoA, monoB)
.blockFirst();
if (statusMock != null) {
return statusMock.getStatus();
}
return "empty";
}
private void verifyBody(StatusMock statusMock) {
if (statusMock.getStatus().contains("error")) {
log.error("throwing an exception");
throw new RuntimeException("error");
}
}
public class StatusMock {
private String status; // getters and setters implicit
}
In this example I used the Flux.first method, and it helps me a lot returning the first call, but it discards (cancel) the second one which is a problem since I need the result of the last call as well.
Is there any solution to this logic? Here I am using Spring Project Reactor, but I accept any library or framework that could help me with this situation.
You can use cache operator on the Monos to prevent them from being cancelled:
Mono<StatusMock> monoA = webClient.get()
// ...
.cache();
Mono<StatusMock> monoB = webClient.get()
// ...
.cache();
Mono.firstWithSignal(monoA, monoB);

Is it possible to create a dynamic filter/predicate in Mono/Flux?

The app is a simple processing - reading data, filtering it, process and write filtered data.
Here is a simple code which runs without any issue:
void run() {
Flux.interval(Duration.ofMillis(200))
.filter(value -> getPredicate().test(value))
.flatMap(this::writeData)
.subscribe();
}
private Predicate<Long> getPredicate() {
return value -> value % 2 == 0;
}
Is it possible to have dynamic predicate which will be retrieved from remote web service with periodic requests?
If possible - how to use Mono<Predicate> inside .filter() and keep it non-blocking
For example replacing getPredicate() with below:
private Mono<Predicate<Long>> getPredicateFromRemoteServer() {
return webClient.get()
.uri("/filters/1")
.retrieve()
.bodyToMono(Filter.class)
.map(this::mapToPredicate)
.cache(v -> Duration.ofMinutes(10), ex -> Duration.ZERO, () -> Duration.ZERO);
}
private Predicate<Long> mapToPredicate(Filter filter) {
// here will be converting filter object into predicate
return value -> value > 5;
}
Ideally I would like to avoid cache(Duration.ofMinutes(10)) because filter could be updated each minute, or each day... and once filter is updated my service get notified, but I didn't find a way to invalidate cache externally, that's why Duration.ofMinutes(10) is used for some approximate invalidation.
Well, perhaps you could write the pipeline a bit differently. Instead of aspiring to return a new Predicate every time your process an item in your stream by calling getPredicateFromRemoteServer(), you could make the function itself your predicate. Pass the value you are processing from the stream and make it return a Mono<Boolean> with the answer and use that in a filterWhen pipe in your pipeline.
For example, somewhat like this:
private Mono<Boolean> isWithinThreshold(int value) {
return webClient.get()
.uri("/filters/1")
.retrieve()
.bodyToMono(Filter.class)
.map(filter -> filter.threshold <= value)
.cache(v -> Duration.ofMinutes(10), ex -> Duration.ZERO, () -> Duration.ZERO);
}
Then in your main pipeline you can do:
Flux.interval(Duration.ofMillis(200))
.filterWhen(value -> isWithinThreshold(value))
.flatMap(this::writeData)
.subscribe();
}

How do I conditionally chain webclient calls in spring webflux/webclient

I am trying to achieve the following scenario using WebClient. It is trivial using RestTemplate, but I can't do it anymore.
Relevant parts of a Spring controller in pseudo-java code:
Mono<T1> t1 = webClient.get()...retrieve()...;
Mono<T2> t2;
if (t1.getResult().getValue() > 0) {
t2 = webClient.get().buildUsing(t1.getResult().getValue())...retrieve()...);
} else {
t2 = Mono.empty();
}
return(Mono.zip(t1, t2, mergeFunction));
I am not asking how to use Webflux. I can also add error handling myself. My problem is how to pass data to the second call if the first call is successful and where to merge results of both calls one of which may or may not happen. The task is absolutely trivial if I could use RestTemplate.
There is a question with a very similar title, but it was not answered.
I think zipWhen fits well for this purpose. zipWhen waits for the result from first mono and then combines both results into a Tuple2
WebClient.builder().baseUrl("https://jsonplaceholder.typicode.com/todos/1")
.build()
.get()
.retrieve()
.bodyToMono(User.class)
.zipWhen(r -> {
if (r.getId() == 1) {
return WebClient.builder().baseUrl("https://jsonplaceholder.typicode.com/todos/2")
.build()
.get()
.retrieve()
.bodyToMono(User.class);
} else {
return Mono.empty();
}
});
The result is a Mono<Tuple2<T, T2>> holding both values.
as far as I could understand your problem, this is my reactive solution to this:
private static Mono<String> mono() {
Mono<Integer> t1 = Mono.just(0);
return t1.flatMap(outerResult -> outerResult > 0
? Mono.just("VALUE").map(innerResult -> outerResult + "" + innerResult)
: Mono.just(outerResult.toString())
);
}
So what's happening here:
With .flatMap you subscribe to a new Mono and take the result of that.
Inside the lambda of the .flatMap you still have the result of your t1, so you can use .map on t2, if you need to subscribe, or just do whatever you need to do with the result of t1 to bring it to the wanted return value.

Categories

Resources