I'm trying to reactively fetch data from external API using two methods from some Service class.
I'm new to reactive and Spring in general, so it could be a very obvious mistake but I just can't find it
These are the two methods:
public Mono<SomeClass> get(int value) {
return webClient.get()
.uri("/" + value)
.retrieve()
.onRawStatus(HttpStatus.CONFLICT::equals, clientResponse -> {
return Mono.error(new SomeException1("Some message", clientResponse.rawStatusCode()));
})
.onRawStatus(HttpStatus.NOT_FOUND::equals, clientResponse -> {
return requestGeneration(value)
.flatMap(res -> Mono.error(new SomeException1("Some message", clientResponse.rawStatusCode())));
})
.bodyToMono(SomeClass.class)
.retryWhen(Retry.backoff(5, Duration.ofSeconds(8))
.filter(throwable -> throwable instanceof SomeException1));
}
private Mono<Void> requestGeneration(int value) {
return webClient.post()
.uri("/" + value)
.retrieve()
.onRawStatus(HttpStatus.BAD_REQUEST::equals, clientResponse -> {
return Mono.error(new SomeException2("Wrong value", clientResponse.rawStatusCode()));
})
.bodyToMono(Void.class);
}
Baasically what I'm trying to achieve is:
first GET from http://api.examplepage.com/{value}
if that API returns HTTP404 it means I need to first call POST to the same URL, because the data is not yet generated
the second function does the POST call and returns Mono<Void> because it is just HTTP200 or HTTP400 on bad generation seed (i don't need to process the response)
first function (GET call) could also return HTTP429 which means the data is generating right now, so I need to call again after some time period (5-300 seconds) and check if data has been generated already
then after some time it results in HTTP200 with generated data which I want to map to SomeClass and then return mapped data in controller below
#PostMapping("/follow/{value}")
public Mono<ResponseEntity<?>> someFunction(#PathVariable int value) {
return Mono.just(ResponseEntity.ok(service.get(value)));
}
all the code I posted is very simplified to the issues I'm struggling with and doesn't contain some things I think are not important in this question
and now the actual question:
it doesn't actually make the call? i really don't know what is happening
program doesn't enter onRawStatus, even if i change it to onStatus 2xx or whatever other httpstatus and log inside i see nothing as if it doesn't even enter the chains
when i manually call with postman it seems like the program calls have never been made because the GET call returns 404 (the program didn't request to generate data)
the controller only returns "scanAvailable": true, when i expect it to return mapped SomeClass json
// edit
i changed the code to be a full chain as suggested and it didn't solve the problem. all the status are still unreachable (code inside any onStatus nor onRawStatus never executes)
Related
I'm a bit lost when playing with Mutiny. I have a testfunction that Creates a String uni and when that is succesful I want that it return a 200 OK or when it fails(which it shouldn´t with this simple string creation) a 500 Internal Server Error for now.
I can get it to work by using the onItemOrOnFailure() of mutiny but I'm trying to split up the handling of the success and failure scenario. I see my sout of the onItem() but I still get to the onFailure() and get a 500 response in postman. Why do I go into the onFailure? What am I not understanding?
So I expect one or the other but I get into both.
#GET
#Path("/test")
#Produces(MediaType.APPLICATION_JSON)
public Uni<RestResponse<?>> test() {
return Uni.createFrom().item("Hello world")
.onItem().transform(str -> {
var resp = RestResponse.ok(str);
System.out.println("In onItem");
return resp;
})
.onFailure().recoverWithNull().replaceWith(() -> {
System.out.println("In onFailure");
return RestResponse.status(500);
});
}
I think I figured it out. Its not the fault of the onFailure, but its just that the replaceWith isnt part of the onFailure. Its just always called as the next part.
So on success it goes onItem -> transform -> replaceWith and onFailure it goes onFailure -> recoverWithNull -> replaceWith.
So If i understand correctly; when using a recoverWith... function you step out of the failure event and continue with the rest of the steps Is this a correct assumption?
Ah well what a bit of sleep can do:)
I have three WebClients that look something like this:
WebClient
public Mono<MyObject> getResponseOne() {
return webClient.get()
.uri(URI)
.header("header", header)
.bodyValue(body)
.retrieve()
.bodyToMono(MyObject.class);
}
Then I have a controller which calls multiple WebClients:
Controller
#ResponseBody
#PostMapping("/get")
public Mono<MyObject> processResponse() {
MyObject obj = getResponseOne().toFuture().get();
system.out.println("Got first response");
String str = getResponseTwo().toFuture().get();
system.out.println("Got second response");
//process and send with third WebClient
MyObject newObj = getResponseThree(obj, str).toFuture().get();
//process response from third WebClient and send to fourth WebClient
//return statement
}
When I call /get, the console only prints "Got first response" then it just stops there and anything below doesn't seem to be executing. I'm using Postman to send the request, so it keeps on waiting without getting any response.
This may or may not be relevant, but I'm using blocking calls because I need the both responses to be processed before sending it to a third WebClient, the response from the third WebClient will also go through additional processing before being returned as the response of processResponse().
Solution
I used Mono.zip() like Alex suggested:
#ResponseBody
#PostMapping("/get")
public Mono<MyObject> processResponse() {
//TupleN depends on the amount of Monos you want to process
Mono<Tuple2<MyObject,String>> output = Mono.zip(getResponseOne(),getResponseTwo());
return output.map(result ->{
// getT1() & getT2() is automatically generated by the tuple
MyObject obj = result.getT1();
String str = result.getT2();
getResponseThree(obj, str);
//process and return
});
}
More about Mono.zip()
In reactive API nothing happens until you subscribe. Your method returns Mono and you need to construct a flow combining publishers. There are multiple ways to combine depending on the required logic.
For example, if you need the result of the predecessor you could use flatMap to resolve Mono sequentially
return getResponseOne()
.flatMap(res -> getResponseTwo(res))
.flatMap(res -> getResponseThree(res));
In case calls are independent you could use then
return getResponseOne()
.then(getResponseTwo())
.then(getResponseThree());
You could also execute in parallel using Mono.when(getResponseOne(), getResponseTwo(), getResponseThree()) or Mono.zip(getResponseOne(), getResponseTwo(), getResponseThree()).
There are many other operators but the key here is to construct the flow and return Mono or Flux.
I am using Java 11 and project Reactor (from Spring). I need to make a http call to a rest api (I can only make it once in the whole flow).
With the response I need to compute two things:
Check if a document exists in the database (mongodb). If it does not exists then create it and return it. Otherwise just return it.
Compute some logic on the response and we are done.
In pseudo code it is something like this:
public void computeData(String id) {
httpClient.getData(id) // Returns a Mono<Data>
.flatMap(data -> getDocument(data.getDocumenId()))
// Issue here is we need access to the data object consumed in the previous flatMap but at the same time we also need the document object we get from the previous flatMap
.flatMap(document -> calculateValue(document, data))
.subscribe();
}
public Mono<Document> getDocument(String id) {
// Check if document exists
// If not create document
return document;
}
public Mono<Value> calculateValue(Document doc, Data data) {
// Do something...
return value;
}
The issue is that calculateValue needs the return value from http.getData but this was already consumed on the first flatMap but we also need the document object we get from the previous flatMap.
I tried to solve this issue using Mono.zip like below:
public void computeData(String id) {
final Mono<Data> dataMono = httpClient.getData(id);
Mono.zip(
new Mono<Mono<Document>>() {
#Override
public void subscribe(CoreSubscriber<? super Mono<Document>> actual) {
final Mono<Document> documentMono = dataMono.flatMap(data -> getDocument(data.getDocumentId()))
actual.onNext(documentMono);
}
},
new Mono<Mono<Value>>() {
#Override
public void subscribe(CoreSubscriber<? super Mono<Value>> actual) {
actual.onNext(dataMono);
}
}
)
.flatMap(objects -> {
final Mono<Document> documentMono = objects.getT1();
final Mono<Data> dataMono = objects.getT2();
return Mono.zip(documentMono, dataMono, (document, data) -> calculateValue(document, data))
})
}
But this is executing the httpClient.getData(id) twice which goes against my constrain of only calling it once. I understand why it is being executed twice (I subscribe to it twice).
Maybe my solution design can be improved somewhere but I do not see where. To me this sounds like a "normal" issue when designing reactive code but I could not find a suitable solution to it so far.
My question is, how can accomplish this flow in a reactive and non blocking way and only making one call to the rest api?
PS; I could add all the logic inside one single map but that would force me to subscribe to one of the Mono inside the map which is not recommended and I want to avoid following this approach.
EDIT regarding #caco3 comment
I need to subscribe inside the map because both getDocument and calculateValue methods return a Mono.
So, if I wanted to put all the logic inside one single map it would be something like:
public void computeData(String id) {
httpClient.getData(id)
.map(data -> getDocument(data).subscribe(s -> calculateValue(s, data)))
.subscribe();
}
You do not have to subscribe inside map, just continue building the reactive chain inside the flatMap:
getData(id) // Mono<Data>
.flatMap(data -> getDocument(data.getDocumentId()) // Mono<Document>
.switchIfEmpty(createDocument(data.getDocumentId())) // Mono<Document>
.flatMap(document -> calculateValue(document, data)) // Mono<Value>
)
.subscribe()
Boiling it down, your problem is analogous to:
Mono.just(1)
.flatMap(original -> process(original))
.flatMap(processed -> I need access to the original value and the processed value!
System.out.println(original); //Won't work
);
private static Mono<String> process(int in) {
return Mono.just(in + " is an integer").delayElement(Duration.ofSeconds(2));
}
(Silly example, I know.)
The problem is that map() (and by extension, flatMap()) are transformations - you get access to the new value, and the old one goes away. So in your second flatMap() call, you've got access to 1 is an integer, but not the original value (1.)
The solution here is to, instead of mapping to the new value, map to some kind of merged result that contains both the original and new values. Reactor provides a built in type for that - a Tuple. So editing our original example, we'd have:
Mono.just(1)
.flatMap(original -> operation(original))
.flatMap(processed -> //Help - I need access to the original value and the processed value!
System.out.println(processed.getT1()); //Original
System.out.println(processed.getT2()); //Processed
///etc.
);
private static Mono<Tuple2<Integer, String>> operation(int in) {
return Mono.just(in + " is an integer").delayElement(Duration.ofSeconds(2))
.map(newValue -> Tuples.of(in, newValue));
}
You can use the same strategy to "hold on" to both document and data - no need for inner subscribes or anything of the sort :-)
I am trying to build an application A (like an adaptor) that will:
1) Receive POST requests with some key (JSON format)
2) It should modify that key somehow and create POST request to another system B.
3) Application A should parse the response from application B and modify that response.
4) After that my application A should answer to the initial POST request.
#RestController
#RequestMapping("/A")
public class Controller {
#ResponseStatus(HttpStatus.OK)
#PostMapping(value = "B", consumes = APPLICATION_JSON_VALUE)
// to return nested Flux is a bad idea here
private Flux<Flux<Map<String, ResultClass>>> testUpdAcc(#RequestBody Flux<Map<String, SomeClass>> keys) {
return someMethod(keys);
}
// the problem comes here when I will get Flux<Flux<T>> in the return
public Flux<Flux<Map<String, ResultClass>>> someMethod(Flux<Map<String, SomeClass>> keysFlux) {
return keysFlux.map(keysMap -> {
// do something with keys and create URL
// also will batch keys here
<...>
// for each batch of keys:
WebClient.create(hostAndPort)
.method(HttpMethod.POST)
.uri(url)
.body(BodyInserters.fromObject(body))
.header(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(schema) // response will be parsed into some schema here
.retryWhen (// will make a retry mechanism here)
// ===== will join all Mono batches into single Flux
Flux.concat(...);
}
);
}
}
Of course this can be fixed by not reading keysFlux as Flux and read that as Map. But that should make everything less reactive, no? :)
#ResponseStatus(HttpStatus.OK)
#PostMapping(value = "B", consumes = APPLICATION_JSON_VALUE)
// to return nested Flux is a bad idea here
private Flux<Map<String, ResultClass>> testUpdAcc(#RequestBody Map<String, SomeClass> keys) {
return someMethod(keys);
}
Also I have tried to use block()/blockFirst() in the last moment before returning the request, but I have got an error:
block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor...
Thank you for your ideas!
Forget about my question - we can easily use "flatMap" instead of "map".
That will solve a problem with Flux inside Flux.
Try to zip all the flux like this
Flux.zip(flux1,flux2)
It will create Tuple2 so that you can do flatMap
Thanks,
Vimalesh
The application i'm writing performs an initial API call with Retrofit which returns a URL. That response is then .flatMap'd into another API call depending on the text contained in the URL. However, the two secondary API calls are defined to return different response models.
To make things clearer, here is some code:
APIService service = retrofit.create(APIService.class);
service.getURL() // returns response model containing a URL.
.flatMap(new Function<GetURLResponse, ObservableSource<?>>() {
#Override
public ObservableSource<?> apply(GetURLResponse getURLResponse) throws Exception {
// Determine whether the returned url is for "first.com".
if (getURLResponse.url.contains("first.com")) {
return service.first(getURLResponse.url);
}
// Otherwise, the URL is not for "first.com", so use our other service method.
return service.second(getURLResponse.url);
}
})
Here are the interface definitions for service.first() and service.second():
#GET
Observable<retrofit2.Response<ResponseBody>> first(#Url String url);
#GET
Observable<SecondModel> second(#Url String url);
How can I better handle these two different possible types (retrofit2.Response<ResponseBody> and SecondModel) for the rest of the stream? Eg. If the initial URL contains first.com then the service.first() API call should fire, and operators down the stream should received a retrofit2.Response<ResponseBody>. Conversely, if the initial URL does not contain first.com, the service.second() API call should fire and operators down the stream should receive a SecondModel.
The easiest way would be to have both your model classes implement an interface and return that interface, alternatively the models could both extend an abstract class to achieve the same effect. You would then do an instanceOf check to see which model it is and continue with your preferred transformations.
That having said you mentioning downstream operators, makes me think that this would cause an annoying amount of checks. So what I would do is split the stream using the publish operator, and then apply your further transformations to each sub-stream. Finally you should merge the two streams back together and return a single model encompassing both models.
Below a code example to get you started.
Observable<Integer> fooObservableSecondaryRequest(String foo) {
return Observable.just(1);
}
Observable<Integer> booObservableSecondaryRequest(String boo) {
return Observable.just(2);
}
Observable<String> stringObservable = Observable.just("foo", "boo");
stringObservable.publish(shared -> Observable.merge(
shared.filter(a -> a.equals("foo"))
.flatMap(fooString -> fooObservableSecondaryRequest(fooString))
.map(num -> num * 2),
shared.filter(a -> a.equals("boo"))
.flatMap(booString -> booObservableSecondaryRequest(booString))
.map(num -> num * 10)
)).subscribe(result -> System.out.println(result)); // will print 2 and 20