I'm trying to implement this code with Spring web flux:
Can you guide me how I can call the two methods parseRawSuccessResponse and parseRawFailedResponse based on the client response codes because I have different return values? Also how this code can be implemented without using block()?
I tried this:
Mono<AuthorizeRequest> transactionMono = Mono.just(transaction);
return client.post().uri(checkTrailingSlash(gatewayUrl) + token)
.body(transactionMono, AuthorizeRequest.class)
.exchange()
.flatMap(clientResponse -> {
if (clientResponse.statusCode().is4xxClientError()) {
return Mono.error(RuntimeException::new);
}
return clientResponse.bodyToMono(AuthorizeResponse.class);
});
// parse response:
result.map(fooBar -> {
return parseRawSuccessResponse(fooBar);
}).doOnError(throwable -> {
// return parseRawFailedResponse(throwable);
}).block();
You can use one of the available disjunction type implementations available for java.
val result: Mono<Either<ClientError, AuthorizeResponse>> =
client.post().uri(checkTrailingSlash(gatewayUrl) + token)
.body(transactionMono, AuthorizeRequest.class)
.exchange()
.flatMap(response -> {
if (clientResponse.statusCode().is2xxSuccessful()) {
return response.bodyToMono(AuthorizeResponse.class).map(Either::right);
} else if (clientResponse.statusCode().is4xxClientError()) {
return response.bodyToMono(ClientError.class).map(Either::left);
} else {
return Mono.error(new RuntimeException("Unexpected response type"));
}
});
You can implement disjunction type yourself of just use existing implementations. One of the popular options would be: vavr.io
Answering the second part of your question, use Mono.subscribe(). It's not idiomatic to escape reactive context with .block() you just need to build your computation chain in terms of Monos and Fluxes and then do a single subscribe at the end.
Related
I need to construct a JSON from 3 api calls.
Call webclient1.get() -> response1
Use reponse1 to call second api
like this: webclient2.getDetails(response1.getId())
Use reponse1 to call third api
like this: webclient3.getDetails(response1.getId())
Sample code
private Mono<RequestPayload> buildRequestPayload(Request request) {
RequestPayload requestPayload = null;
Mono<T> response1 = webclient1.get(request.getId());
response1.flatMap(res -> {
return webclient2.getDetails(res.getId());
//not sure how to call webclient3.getDetails(response1.getId()) after return statement
});
//I think the final one should be something like this (not sure though) but did not get until here
return Mono.zip(respons1, resppnse2, response3).flatMap(finalResponse -> {
requestPayload.setResponse1(finalResponse.getT1());
requestPayload.setResponse2(finalResponse.getT2());
requestPayload.setResponse3(finalResponse.getT3());
});
}
I'm getting null in the pipeline not sure where the error is
You should chain all of your operations in order to create a reactive stream. You can accomplish this using flatmap and map operators:
private Mono<RequestPayload> buildRequestPayload(Request request) {
return executeRequest1()
.flatMap(request1Result ->
executeRequest2()
.map(request2Result -> RequestPayload.builder()
.response1(request1Result)
.response2(request2Result)
.build()))
.flatMap(requestPayload ->
executeRequest3()
.map(request3Result -> {
requestPayload.response3(request3Result);
return requestPayload;
})
);
}
I am trying to do two API calls, the second API call is dependent on the first API response. The following piece of code gives response for first weblient call.Here I am not getting the response from second API call. On log I could see that the request for the second webclient call is not even started with onSubscribe(). Can you please tell me what mistake am I doing.
#Autowired
Issue issue;
List issueList = new ArrayList<>();
public Mono<Response> getResponse(Request request) {
return webClient.post()
.uri("myURI")
.body(Mono.just(request),Request.class)
.retrieve()
.bodyToMono(Response.class)
.flatMap(resp->{
resp.getIssues().stream()
.forEach(issueTemp -> {
issue = issueTemp;
webClient.get()
.uri("mySecondURI" + issueTemp.getId())
.retrieve()
.bodyToMono(Issue.class)
.flatMap(issueTemp2-> {
issue.setSummary(issueTemp2.getSummary());
return Mono.just(issue);
}).log();
issueList.add(issue);
});
Response responseFinal = new Response();
responseFinal.setIssues(issueList);
return Mono.just(responseFinal);
}).log();
}
UPDATE 2:
I have changed my code to Functions and used Flux instead of stream iterations.What I am facing now is , all the iterations are get filtered out in doSecondCall method. Please refer my comment in doSecondCall method. Due to which the second call is not triggered. If i dont apply the filter, there are requests triggered like "issue/null" which also causes my service to go down.
public Mono<Response> getResponse(Request request) {
return webClient.post()
.uri("myURI")
.body(Mono.just(request),Request.class)
.retrieve()
.bodyToMono(Response.class)
.flatMap(r->
doSecondCall(r).flatMap(issueList->{
r.setIssues(issueList);
return Mono.just(r);
})
);
}
public Mono<Issue> doSecondCall(Response r) {
return Flux.fromIterable(r.getIssues())
.filter(rf->rf.getId()!=null) //everything gets filtered out
.flatMap(issue->getSummary(issue.getId()))
.collectList();
}
public Mono<Issue> getSummary(Response r) {
return webClient.get()
.uri("issue/"+id)
.retrieve()
.bodyToMono(Issue.class).log();
}
[ How does Reactive programming using WebFlux handles dependent external api calls ] #Thomas- Also ,Just found this thread. He basically says unless you block the first call, there is no way to declare the second one. Is that the case?
Why you are not triggering the second calls is because you are breaking the chain as i have mentioned in this answer (with examples).
Stop breaking the chain
// here...
.forEach(issueTemp -> {
issue = issueTemp; // and this is just silly? why?
webClient.get() // Here you are calling the webClient but ignoring the return value, so you are breaking the chain.
.uri("mySecondURI" + issueTemp.getId())
.retrieve()
.bodyToMono(Issue.class)
.flatMap(issueTemp2-> {
issue.setSummary(issueTemp2.getSummary());
return Mono.just(issue); // Return here but you are ignoring this return value
}).log();
issueList.add(issue);
});
You should use more functions to divide up your code. Make it a habit by writing a function and always start with the return statement. You code is very hard to read.
I think you should instead use a FLux instead of iterating a stream.
// something like the following i'm writing by free hand without IDE
// i have no idea what your logic looks like but you should get the point.
Flux.fromIterable(response.getIssues())
.flatMap(issue -> {
return getIssue(issue.getId())
.flatMap(response -> {
return issue.setSummary(reponse.getSummary());
});
}).collectList();
I have two methods.
Main method:
#PostMapping("/login")
public Mono<ResponseEntity<ApiResponseLogin>> loginUser(#RequestBody final LoginUser loginUser) {
return socialService.verifyAccount(loginUser)
.flatMap(socialAccountIsValid -> {
if (socialAccountIsValid) {
return this.userService.getUserByEmail(loginUser.getEmail())
.switchIfEmpty(insertUser(loginUser))
.flatMap(foundUser -> updateUser(loginUser, foundUser))
.map(savedUser -> {
String jwts = jwt.createJwts(savedUser.get_id(), savedUser.getFirstName(), "user");
return new ResponseEntity<>(HttpStatus.OK);
});
} else {
return Mono.just(new ResponseEntity<>(HttpStatus.UNAUTHORIZED));
}
});
}
And this invoked method (the service calls an external api):
public Mono<User> getUserByEmail(String email) {
UriComponentsBuilder builder = UriComponentsBuilder
.fromHttpUrl(USER_API_BASE_URI)
.queryParam("email", email);
return this.webClient.get()
.uri(builder.toUriString())
.exchange()
.flatMap(resp -> {
if (Integer.valueOf(404).equals(resp.statusCode().value())) {
return Mono.empty();
} else {
return resp.bodyToMono(User.class);
}
});
}
In the above example, switchIfEmpty() is always called from the main method, even when a result with Mono.empty() is returned.
I cannot find a solution for this simple problem.
The following also doesn't work:
Mono.just(null)
Because the method will throw a NullPointerException.
What I also can't use is the flatMap method to check that foundUser is null.
Sadly, flatMap doesn't get called at all in case I return Mono.empty(), so I cannot add a condition here either.
#SimY4
#PostMapping("/login")
public Mono<ResponseEntity<ApiResponseLogin>> loginUser(#RequestBody final LoginUser loginUser) {
userExists = false;
return socialService.verifyAccount(loginUser)
.flatMap(socialAccountIsValid -> {
if (socialAccountIsValid) {
return this.userService.getUserByEmail(loginUser.getEmail())
.flatMap(foundUser -> {
return updateUser(loginUser, foundUser);
})
.switchIfEmpty(Mono.defer(() -> insertUser(loginUser)))
.map(savedUser -> {
String jwts = jwt.createJwts(savedUser.get_id(), savedUser.getFirstName(), "user");
return new ResponseEntity<>(HttpStatus.OK);
});
} else {
return Mono.just(new ResponseEntity<>(HttpStatus.UNAUTHORIZED));
}
});
}
It's because switchIfEmpty accepts Mono "by value". Meaning that even before you subscribe to your mono, this alternative mono's evaluation is already triggered.
Imagine a method like this:
Mono<String> asyncAlternative() {
return Mono.fromFuture(CompletableFuture.supplyAsync(() -> {
System.out.println("Hi there");
return "Alternative";
}));
}
If you define your code like this:
Mono<String> result = Mono.just("Some payload").switchIfEmpty(asyncAlternative());
It'll always trigger alternative no matter what during stream construction. To address this you can defer evaluation of a second mono by using Mono.defer
Mono<String> result = Mono.just("Some payload")
.switchIfEmpty(Mono.defer(() -> asyncAlternative()));
This way it will only print "Hi there" when alternative is requested
UPD:
Elaborating a little on my answer. The problem you're facing is not related to Reactor but to Java language itself and how it resolves method parameters. Let's examine the code from the first example I provided.
Mono<String> result = Mono.just("Some payload").switchIfEmpty(asyncAlternative());
We can rewrite this into:
Mono<String> firstMono = Mono.just("Some payload");
Mono<String> alternativeMono = asyncAlternative();
Mono<String> result = firstMono.switchIfEmpty(alternativeMono);
These two code snippets are semantically equivalent. We can continue unwrapping them to see where the problem lies:
Mono<String> firstMono = Mono.just("Some payload");
CompletableFuture<String> alternativePromise = CompletableFuture.supplyAsync(() -> {
System.out.println("Hi there");
return "Alternative";
}); // future computation already tiggered
Mono<String> alternativeMono = Mono.fromFuture(alternativePromise);
Mono<String> result = firstMono.switchIfEmpty(alternativeMono);
As you can see future computation was already triggered at the point when we start composing our Mono types. To prevent unwanted computations we can wrap our future into a defered evaluation:
Mono<String> result = Mono.just("Some payload")
.switchIfEmpty(Mono.defer(() -> asyncAlternative()));
Which will unwrap into
Mono<String> firstMono = Mono.just("Some payload");
Mono<String> alternativeMono = Mono.defer(() -> Mono.fromFuture(CompletableFuture.supplyAsync(() -> {
System.out.println("Hi there");
return "Alternative";
}))); // future computation defered
Mono<String> result = firstMono.switchIfEmpty(alternativeMono);
In second example the future is trapped in a lazy supplier and is scheduled for execution only when it will be requested.
UPD: 2022:
Since some time project reactor comes with an alternative API for wrapping eagerly computed futures which results in the same - trapping eager computation in a lazy supplier:
Mono<String> result = Mono.just("Some payload")
.switchIfEmpty(Mono.fromCompletionStage(() -> alternativePromise()));
For those who, despite the well voted answer, do not still understand why such a behaviour:
Reactor sources (Mono.xxx & Flux.xxx) are either:
Lazily evaluated : the content of the source is evaluated/triggered only when a subscriber subscribes to it;
or eagerly evaluated : the content of the source is immediately evaluated even before the subscriber subscribes.
Expressions like Mono.just(xxx), Flux.just(xxx), Flux.fromIterable(x,y,z) are eager.
By using defer(), you force the source to be lazily evaluated. That's why the accepted answer works.
So doing this:
someMethodReturningAMono()
.switchIfEmpty(buildError());
with buildError() relying on an eager source to create an alternative Mono will ALWAYS be evaluated before the subscription:
Mono<String> buildError(){
return Mono.just("An error occured!"); //<-- evaluated as soon as read
}
To prevent that, do this:
someMethodReturningAMono()
.switchIfEmpty(Mono.defer(() -> buildError()));
Read this answer for more.
I have a reactive rest api (webflux), also using the spring WebClient class, to request data from other rest services.
Simplified design:
#PostMapping(value = "/document")
public Mono<Document> save(#RequestBody Mono<Document> document){
//1st Problem: I do not know how to get the documentoOwner ID
//that is inside the Document class from the request body without using .block()
Mono<DocumentOwner> documentOwner = documentOwnerWebClient()
.get().uri("/document-owner/{id}", document.getDocumentOwner().getId())
.accept(MediaType.APPLICATION_STREAM_JSON)
.exchange()
.flatMap(do -> do.bodyToMono(DocumentOwner.class));
//2nd Problem: I need to check (validate) if the documentOwner object is "active", for instance
//documentOwner and document instances below should be the object per se, not the Mono returned from the external API
if (!documentOwner.isActive) throw SomeBusinessException();
document.setDocumentOwner(documentOwner);
//Now I can save the document in some reactive repository,
//and return the one saved with the given ID.
return documentRepository.save(document)
}
In other words: I understand (almost) all of the reactive examples individually, but I am not able to put it all together and build a simple use case (get -> validate -> save -> return) without blocking the objects.
The closer I could get is:
#PostMapping(value = "/document")
public Mono<Document> salvar(#RequestBody Mono<Document> documentRequest){
return documentRequest
.transform(this::getDocumentOwner)
.transform(this::validateDocumentOwner)
.and(documentRequest, this::setDocumentOwner)
.transform(this::saveDocument);
}
Auxiliar methods are:
private Mono<DocumentOwner> getDocumentOwner(Mono<Document> document) {
return document.flatMap(p -> documentOwnerConsumer.getDocumentOwner(p.getDocumentOwnerId()));
}
private Mono<DocumentOwner> validateDocumentOwner(Mono<DocumentOwner> documentOwner) {
return documentOwner.flatMap(do -> {
if (do.getActive()) {
return Mono.error(new BusinessException("Document Owner is Inactive"));
}
return Mono.just(do);
});
}
private DocumentOwnersetDocumentOwner(DocumentOwner documentOwner, Document document) {
document.setDocumentOwner(documentOwner);
return document;
}
private Mono<Document> saveDocument(Mono<Document> documentMono) {
return documentMono.flatMap(documentRepository::save);
}
I am using Netty, SpringBoot, Spring WebFlux and Reactive Mongo Repository. But there are some problems:
1) I am getting the error:
java.lang.IllegalStateException: Only one connection receive subscriber allowed. Maybe because I am using the same documentRequest to transform and to setDocumentOwner. I really don't know.
2) setDocumentOwner method is not being called. So the document object to be saved is not updated. I believe could have a better way to implement this setDocumentOwner().
Thanks
I don't really get the point with the validation aspect of that question.
But it looks like you're trying to compose reactive types together. This is something that reactor handles perfectly with operators.
There are certainly better ways to handler that case, but a quick search in the Mono API makes me think about this:
Mono<Document> document = ...
Mono<DocumentOwner> docOwner = ...
another = Mono.when(document, docOwner)
.map(tuple -> {
Document doc = tuple.getT1();
DocumentOwner owner = tuple.getT2();
if(owner.getActive()) {
return Mono.error(new BusinessException("Document Owner is Inactive"));
}
doc.setDocumentOwner(owner);
return doc;
})
.flatMap(documentRepository::save);
I am new to Java and am using CompletableFutures to perform async operations such as below:
public CompletionStage<Either<ErrorResponse, Response>> insertOrUpdate(String actor, String key) {
return this.objectDAO.getByKey(key)
.thenApply(mapDOToContainer(key))
.thenApply(mergeContainerToDO(key, actor))
.thenComposeAsync(this.objectDAO.UpdateFn())
.thenApply(DBResult::finished)
.thenApply(finished -> {
if (finished) {
Response response = Response.ok().build();
return Either.right(response);
} else {
return Either.left(ErrorResponse.create("Error", 400));
}
});
}
Now I need to modify this so that if the get fails then I perform the above chain, but if it succeeds then I need to break this chain and return from the function with an Either object containing an ErrorResponse.
How can I break this processing chain? I know I can pass a flag to each function in the chain and achieve this by performing the actions in the functions based on the value of the flag. I was hoping there is a better way to do this.
Thanks!!
I would rewrite your code.
Don't use Either for errors, Java has exception
Don't return a CompletionStage from your DAO
Use exceptionally from CompletableFuture, it is designed for this
Then do this:
public CompletionStage<Response> insertOrUpdate(String actor, String key) {
return CompletableFuture.supplyAsync(() -> this.objectDAO.getByKey(key))
.thenApply(mapDOToContainer(key))
.thenApply(mergeContainerToDO(key, actor))
.thenComposeAsync(this.objectDAO.UpdateFn())
.thenApply(DBResult::finished)
.thenApply(finished -> {
Response response = Response.ok().build();
return response;
})
.exceptionally(e -> ErrorResponse.create("Error", 400));
}
The DAO should be something like this:
class ObjectDAO {
public Object getByKey(String key) {
if (keyNotFound) {
throw new NoSuchElementException();
}
return new Object();
}
}
You may have to make sure that ErrorResponse is a subclass of Response to make this work.