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);
Related
I have been banging my head for this simple issue on this simple code :
#Bean
public RouterFunction<ServerResponse> refresh() {
return route(GET("/api/refresh"), (request) ->
request.principal().or(Mono.empty()).flatMap((it) -> {
System.out.println(it);
return ServerResponse.ok().body(Mono.just(new FreshStatus(true)), FreshStatus.class);
}));
}
record FreshStatus(boolean isFresh) {}
I have two issues:
When not authenticated, the code inside flatMap is not called, despite my .or clause
When authenticated, the flatMap code is called, but the server response is just blank.
In both cases response is 200 OK.
I have tried using SecurityContextHolder nothing changed.
New code is:
ServerResponse.ok().body(request.principal()
.map(it -> new FreshStatus(true))
.defaultIfEmpty(new FreshStatus(false)), FreshStatus.class));
If the principal() method returns an empty Mono, the flatMap() method will not be called and the response will be empty. Mono.empty() will return a Mono that completes without emitting any item.
You can try the defaultIfEmpty() method instead.
Not sure whether this is what you want, but something like:
return route(GET("/api/refresh"), (request) ->
request.principal().defaultIfEmpty(new FreshStatus(false))
.flatMap((it) -> {
System.out.println(it);
return ServerResponse.ok().body(Mono.just(it), FreshStatus.class);
}));
As an aside, Josh Bloch would claim that in most cases, you're better off using an enum instead of a boolean. I tend to agree. This could be relevant for your FreshStatus record potentially.
I want to perform two business operations, in a Webflux environment, in a way that the second operation happens only after the first one is succesfull. After the second one is done, I want to return the result of the first operation. The second operation calls a org.springframework.web.reactive.function.client.WebClient. This is what I have tried until now:
public Mono<ResponseEntity<Resource>> callOperations(){
return service.operation1()
.flatMap(resource -> {
service.operation2();
return resource;
})
.map(ResponseEntity::ok);
}
I also tried with then and subscribe but I can't get the webclient to perform the call and return the result of service.operation1. What must I do?
You need to construct a flow using reactive operators and let WebFlux subscribe to it. In your snippet there is no subscription to service.operation2()
public Mono<ResponseEntity<Resource>> callOperations(){
return service.operation1()
.flatMap(resource -> {
return service.operation2()
.thenReturn(resource);
})
...
}
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().
I have a list of following objects with method returning reactive type Mono<?>:
interface GuyWithReactiveReturnTypeMethod {
Mono<String> execute();
}
class ReactiveGuysInvocator {
Mono<String> executeAllGuys(List<GuyWithReactiveReturnTypeMethod> guysToInvoke) {
???
}
}
And I need to invoke all the guys one by one (n's guy result is n+1's guy argument), but I'm not sure how can I iterate over such list.
I thought of flatMaping next guy in a while loop:
public interface GuyWithReactiveReturnTypeMethod {
Mono<String> execute(String string);
}
class ReactiveGuysInvocator {
Mono<String> executeAllGuys(List<GuyWithReactiveReturnTypeMethod> guysToExecute) {
ListIterator<GuyWithReactiveReturnTypeMethod> iterator = guysToExecute.listIterator();
Mono<String> currentResult = Mono.just("start");
while (iterator.hasNext()) {
GuyWithReactiveReturnTypeMethod guyToInvoke = iterator.next();
currentResult = currentResult.flatMap(guyToInvoke::execute)
.doOnNext(object -> System.out.println("Executed!"))
.doOnError(error -> System.out.println("Error"));
}
return currentResult;
}
}
But this approach seems to be completely incorrect.
Does anyone know how could I implement something like this?
UPDATE: flatMap can be easily abused. Make sure that you are doing asynchronous work when using flatMap. Mostly, it seems to me, that you can do pretty well with a minimum of Mono.just.
Flatmap is what you have to do with the constraints you provide.
executeAllGuys(Arrays.asList(new GuyWithReactiveReturnTypeMethod[] {
(s)->Mono.just(s+"1"),
(s)->Mono.just(s+"2"),
(s)->Mono.just(s+"3")}))
.subscribe(System.out::println);
Mono<String> executeAllGuys(List<GuyWithReactiveReturnTypeMethod> guysToExecute) {
// your flow is starting here
Mono<String> stringMono = Mono.just("start");
for ( GuyWithReactiveReturnTypeMethod guyToInvoke: guysToExecute) {
stringMono = stringMono.flatMap(guyToInvoke::execute);
}
return stringMono;
}
Just look at all those Mono.just calls. Why do you want to create N+1 flows to do the job? The real problem is you're creating a new flow every time you execute the interface method. Flatmap stops the current flow and starts a new one with the publisher returned by the flatMap method. Try to think reactive and treat the whole business like a stream. There is no flatMap in Streams. A reactive execution should be done on only a single flow.
A Mono<String> execute(String string) is not a reactive component. It is a reactive producer. A Mono<String> execute(Mono<String> string) is a reactive component.
Make your interface more reactive by taking a Mono in and returning a Mono. Your application is doing a map conversion on at each step. This is "chaining reactive components".
executeAllGuys(Arrays.asList(new GuyWithReactiveReturnTypeMethod[] {
(s)->s.map(str->str+"1"),
(s)->s.map(str->str+"2"),
(s)->s.map(str->str+"3")}))
.subscribe(System.out::println);
Mono executeAllGuys(List guysToExecute) {
// your flow is starting here
Mono stringMono = Mono.just("start");
for ( GuyWithReactiveReturnTypeMethod guyToInvoke: guysToExecute) {
stringMono = guyToInvoke.execute(stringMono);
}
return stringMono;
}
interface GuyWithReactiveReturnTypeMethod {
Mono execute(Mono string);
}
Make your interface less reactive but make your application more reactive by using a Flux instead of a list. You will then have to use reduce to convert a Flux to a Mono. Your application is doing a Map/Reduce function. I don't think a Flux will guarantee execution order of the elements in the flow but it could executeAllGuys more efficiently.
// your flow is starting here
executeAllGuys(Flux.just(
(s)->s+"1",
(s)->s+"2",
(s)->s+"3"))
.subscribe(System.out::println);
Mono executeAllGuys(Flux guysToExecute) {
return guysToExecute.reduce("start", (str, guyToInvoke)->guyToInvoke.execute(str));
}
interface GuyWithReactiveReturnTypeMethod {
String execute(String string);
}
Reference: Reactive Programming: Spring WebFlux: How to build a chain of micro-service calls?
I want to get a string data from another server by webclient object, and put it to another Mono object. But in a webclient, only readable that in .subscribe().
Because responseBody.subscribe() method is async, method test() will be return result object with empty message field before responseBody.subscribe() executed.
Of course, I knew that if I return responseBody object instead of result object, there is no problem. But I want to return not a responseBody object but result object with not empty field of message.
I want to return result when responseBody's subscribe() is completed.
How to change my code?
Please help me.
public Mono<ResultVO> test() {
Mono<ResultVO> result = Mono.just(new ResultVO());
WebClient client = webClientBuilder.baseUrl("http://XXXXXX").build();
Mono<String> responseBody = client.get().uri("/aaaa/bbbbb").retrieve().bodyToMono(String.class);
responseBody.subscribe( s -> {
result.subscribe(g -> g.setMessage(s));
});
return result;
}
...
#Data
public class ResultVO {
private long timestamp;
private String ip;
private String message;
...
}
I expect like this
{
"timestamp": 1566662695203,
"ip": "192.168.1.1",
"message": "c0db76f6-4eb5-4f84-be8d-018d53b453bb"
}
But result data is,
{
"timestamp": 1566662695203,
"ip": "192.168.1.1",
"message": ""
}
Putting this kind of logic into the subscribe method is not recommended, it can easily lead to 'callback hell' and eventually unmaintainable code. Also, I don't see the caller of the shared test method, but chances are that one of the Monos is subscribed twice, which also leads to quite confusing behaviour.
Instead, to combine Monos you can use zip, zipWith, flatMap and a couple of other operators.
One solution with zipWith method:
public Mono<ResultVO> test()
{
WebClient client = WebClient.builder().baseUrl("http://XXXXXX").build();
// dummy representation of another data source (db query, web service call...)
Mono<ResultVO> result = Mono.just(new ResultVO());
Mono<String> responseBody = client.get().uri("/aaaa/bbbbb").retrieve().bodyToMono(String.class);
return result.zipWith(responseBody,
(resultObj, body) -> new ResultVO(resultObj.getTimestamp(), resultObj.getIp(), body));
}
Couple of other notes:
If you are returning JSON through a REST endpoint of your reactive WebFlux application, then you never need to subscribe manually, Spring will do that for you
Avoid using mutable objects (the ones which you modify after creation with setters), instead create new object, this will make your code easier to reason about and less prone to concurrency issues
Useful read about available Reactor operators
First of all, you hardly ever subscribe in your own application.
Think of it this way. Your server is a publisher, that means that your server fetches data and then publishes it to whomever wants it.
The subscriber is usually the end client, that could be a react application, an angular application or any client.
I think you need to read up on the basics of how to use webflux and reactive programming.
This is how to do what you are asking for, with as minimal changes to your code, we map what we fetched to what we want returned.
public Mono<ResultVO> test() {
final WebClient client = webClientBuilder
.baseUrl("http://XXXXXX").build();
return client.get()
.uri("/aaaa/bbbbb")
.retrieve()
.bodyToMono(String.class)
.map(message -> {
final ResultVO resultVO = new ResultVO();
resultVO.setMessage(message);
return resultVO;
}
);
}