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.
Related
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);
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();
}
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();
I am new to reactive programming and I want to transform the following code into non blocking way.
For the sake of simplicity, I created a sample pseudo code based from my original code. Any help will be appreciated.
public Mono<Response> getResponse(List<Provider> providers) {
for (Provider provider : providers) {
Response response = provider.invokeHttpCall().block();
if(response.getMessage() == "Success") {
return Mono.just(response);
}
continue;
}
return Mono.empty();
}
provider.invokeHttpCall() method
#Override
public Mono<Response> invokeHttpCall(){
WebClient webClient = WebClient.create();
return webClient.post()
.uri("/provider").accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Response.class);
}
I tried several tactics to implement this, but still no luck. Either all providers are invoked or I need to block the webclient thread.
Flux.fromIterable(providers)
.concatMap(Provider::invokeHttpCall) // ensures providers are called sequentially
.filter(response -> response.getMessage().equals("Success"))
.next()
reactive is a kind of Stream. Please think it as a Stream and program it reactively.
I give you such followed code.
Firstly, use Flux.fromIterable() to create a flux stream from a List.
Next, use flatmap() and Lambda fuction to emit the invoke into another new thread.
use method filterWhen() and Lambda to get the "Success" response and just get the first "Success" elements. See filterwhen api Doc.
Finally, just use Mono.from() to wrap the Flux and then return the Mono type.
public Mono<Response> getResponse(List<Provider> providers) {
return Mono.from(Flux.fromIterable(providers)
.flatmap(provider ->
Mono.defer(() -> provider.invokeHttpCall())
.filterWhen(response -> response.getMessage() == "Success");
}
if you want to see result and println().
Just use .subsribe() method to excute it.
getResponse.subsribe(System.out::println);
I am trying to find a way to skip CompletableFuture based on specific conditions.
For example
public CompletableFuture<Void> delete(Long id) {
CompletableFuture<T> preFetchCf = get(id);
CompletableFuture<Boolean> cf1 = execute();
/*This is where I want different execution path, if result of this future is true go further, else do not*/
// Execute this only if result of cf1 is true
CompletableFuture<T> deleteCf = _delete(id);
// Execute this only if result of cf1 is true
CompletableFuture<T> postDeleteProcess = postDelete(id);
}
What is a good way to achieve this ?
I will prepare a different example than the one you used in the question, because your code is not quite clear in intent from the readers perspective.
First suppose the existing of a CompletableFuture<String> that provides the name of a Star Wars characters.
CompletableFuture<String> character = CompletableFuture.completedFuture("Luke");
Now, imagine I have two other CompletableFuture that represent different paths I may want to follow depending on whether the first completable future provides a character that is a Jedi.
Supplier<CompletableFuture<String>> thunk1 = () -> CompletableFuture.completedFuture("This guy is a Jedi");
Supplier<CompletableFuture<String>> thunk2 = () -> CompletableFuture.completedFuture("This guy is not a Jedi");
Notice that I wrapped the CompletableFuture in a a Supplier, to avoid that they get eagerly evaluated (this is concept known as thunk).
Now, I go and to my asynchronous chain:
character.thenApply(c -> isJedi(c))
.thenCompose(isJedi -> isJedi ? thunk1.get() : thunk2.get())
.whenComplete((answer, error) -> System.out.println(answer));
The use of thenCompose let me choose a path based on the boolean result. There I evaluate one of the thunks and cause it to create a new CompletableFuture for the path I care about.
This will print to the screen "This guys is a Jedi".
So, I believe what you're looking for is the thenCompose method.
Not sure if I understand your objective, but why won't you just go with future chaining like you said in the comment? Something like this, just to illustrate:
public class AppTest {
#Test
public void testCompletableFutures() {
Integer id = (int) Math.random() * 1000;
CompletableFuture<Void> testing = AppTest.execute()
.thenAcceptAsync(result -> {
System.out.println("Result is: " + result);
if(result)
AppTest.delete(id);
else
throw new RuntimeException("Execution failed");
})
.thenApplyAsync(result -> AppTest.postDelete())
.thenAcceptAsync(postDeleteResult -> {
if(postDeleteResult)
System.out.println("Post delete cleanup success");
else
throw new RuntimeException("Post delete failed");
});
}
private static boolean postDelete() {
System.out.println("Post delete cleanup");
return Math.random() > 0.3;
}
private static CompletableFuture<Boolean> delete(int i) {
System.out.println("Deleting id = " + i);
return CompletableFuture.completedFuture(true);
}
private static CompletableFuture<Boolean> execute() {
return CompletableFuture.supplyAsync(() -> Math.random() > 0.5);
}
}
Of course that doesn't make much real-life sense, but I think it works to show a concept.
If you want to skip the second call after execute based on the result it's clearly not possible since you need that result. The point is that it should not matter for you whether you skipped that or not since it's asynchronous, you are not blocking to wait for that result.