I'm using WebFlux, and I want to log with AOP as follow:
Class LogAop {
#AfterReturning("execution ...")
public void configSetted() {
// Getting username by token
Mono<Sting> username = ReactiveSecurityContextHolder.getContext()
.map { ... };
username.subscribe ({it -> loggerin.info(it);});
}
}
In the above code, I want to log username, but there is no log. How can I subscribe to Mono or Flux without returning to the method?
Note: Some times I want to do differnt things on subscribe data, such as saving data in db.
This should work unless ReactiveSecurityContextHolder.getContext() doesn't emit any item.
Mono<String> username = ReactiveSecurityContextHolder.getContext()
.map { ... };
username.subscribe (it -> loggerin.info(it));
I'd suggest adding a log to find out:
username.log().subscribe (it -> loggerin.info(it));
Related
I have a DTO class like this :
public class User {
#Field("id")
private String id;
private String userName;
private String emailId;
}
I have to provide an update and delete feature through API.
I have written the following code to delete the record:
public Mono<String> userData(User body) {
repo.removeUserDetails(userObj).subscribe();
return Mono.just("Remove Successful");
}
RemoveUserDetails method is something like this :
public Mono<User> removeUserDetails(User userObj) {
return findByUsername(userObj.getUsername())
.flatMap(existingUser -> {
// logic to delete the data from database which working as expected
}).switchIfEmpty(
Mono.defer(() -> {
return Mono.error(new Exception("User Name " + userObj.getUsername() + " doesn't exist."));
})
);
}
The problem with this code is even if the user is not existing, it is not showing the Mono error I'm returning. In every case, this always returns "Remove Successful".
How can I change my service layer method so that it can return whatever is received by the repo method? I'm new to Reactor code, so unable to figure out how to write it.
Whenever you call subscribe, consider it an immediate red flag. Subscription is something that should be handled by the framework you're using (Webflux in this case.)
If you subscribe yourself, such as in this example:
public Mono<String> userData(User body) {
repo.removeUserDetails(userObj).subscribe();
return Mono.just("Remove Successful");
}
...then you've essentially created a "fire and forget" type subscription, where you have no way of knowing if that publisher completed successfully, if it caused an error, how long it took to complete, whether it completed at all, or whether it emitted an element. So in this case, you're saying "send a request to remove user details, forget you sent it, and then before waiting for any kind of result, always return 'Remove successful'." This is almost never what you want.
You could use something like:
public Mono<String> userData(User body) {
return repo.removeUserDetails(userObj)
.then(Mono.just("Remove Successful"));
}
...which is much better as it includes everything as part of the reactive chain. In this case, you'll either get an error signal, or you'll get "Remove Successful".
However, chances are you don't need that String to be returned at all - you just need to know if it's successful or not. The standard way of doing that (I just need to know that it's completed successfully or not, I don't need it to return a value) is to use Mono<Void> as the return type and then(), something like:
public Mono<Void> userData(User body) {
return repo.removeUserDetails(userObj).then();
}
...which will give you a standard completion if the deletion was successful, and an error signal otherwise.
A common pattern you find when using reactive java code is handling nulls when collecting a list.
The following code is a simple example showing how to handle nulls returned by a Location by wrapping getLocation in a Mono.defer then handling a null using onErrorReturn.
The test code
List<String> items = inventory.testList().block();
items.forEach(System.out::println);
USA
Not Found
SPAIN
private List<Integer> clusters;
private List<Mono<Location>> locations;
private List<String> countryCodes;
public Mono<List<String>> testList() {
clusters = Arrays.asList(0, 1, 2);
locations = Arrays.asList(Mono.just(new Location(0)), null, Mono.just(new Location(2)));
countryCodes = Arrays.asList("USA", "FRANCE", "SPAIN");
return Flux.fromIterable(clusters)
.flatMap(cluster -> getLocation(cluster))
.collectList();
}
public Mono<String> getLocation(int clusterID) {
return Mono.defer(() -> locations.get(clusterID))
.flatMap(location -> Mono.just(location.id))
.flatMap(id -> Mono.just(countryCodes.get(id)))
.onErrorReturn(Exception.class, "Not Found");
}
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'm using Reactive Spring Cloud Stream and I'm having trouble creating a StreamListener without an Output. The following code works as long as no malformed messages are received. When a malformed message is received, the flux closes.
#StreamListener
public void handleMessage(#Input(MessagingConfig.INPUT) Flux<String> payloads) {
payloads.flatMap(objectToSave -> reactiveMongoTemplate.insert(objectToSave)).subscribe();
}
If I understand correctly, it is preferable to let the framework subscribe to the flux instead of subscribing to it manually. This isn't a problem when a listener has an output, because I can simply return the flux like so:
#StreamListener
#Output(MessagingConfig.OUTPUT)
public Flux<String> handleMessage(#Input(MessagingConfig.INPUT) Flux<String> payloads) {
return payloads.flatMap(objectToSave -> reactiveMongoTemplate.insert(objectToSave));
}
The framework seems to handle bad messages in a way that doesn't close the flux when it is returned. Is there any way to let the framework handle the flux when the listener doesn't specify an output?
Consider switching to using Spring Cloud Function (SCF) programming model which we have recently adopted.
Basically, as long as you have the latest code base (2.1.0.RC4 is the latest and RELEASE is few days away) you're fine. Here is the example of your code using SCF programming model:
#SpringBootApplication
#EnableBinding(Sink.class)
public class SampleReactiveConsumer {
public static void main(String[] args) {
SpringApplication.run(SampleReactiveConsumer.class,
"--spring.cloud.stream.function.definition=consume");
}
#Bean
public Consumer<Flux<String>> consume(){
return payloads -> payloads.flatMap(objectToSave -> reactiveMongoTemplate.insert(objectToSave)).subscribe();
}
}
You can also remove reactive module from your classpath as we're also considering deprecating it all together
If you really want to avoid SCF mentioned in Oleg's answer you could try below, hacky approach.
const val IN = "input"
const val OUT = "dummy-output"
interface Channels {
#Input(IN)
fun input(): MessageChannel
#Output(OUT)
fun output(): MessageChannel
}
#EnableBinding(Channels::class)
class MsgList {
#StreamListener
#Output(OUT)
fun receive(#Input(IN) messages: Flux<String>): Flux<Void> {
return messages
.doOnNext { if (it == "err") throw IllegalStateException("err") }
.doOnNext { println(it) }
.flatMap { Mono.empty<Void>() }
}
}
Output binding will be created but no messages will go through. In case of RabbitMQ that means - dummy exchange will appear, but queue won't get created.
Also errors would be handled as you expected. With above example, you may send 3 messages, "ok", "err", "ok2", and you will see "ok", then exception, then "ok2" on the screen. An "ok2" and any subsequent valid message will be handled properly.
I'm working on microservice based Java application using JDK 1.8, Spring 5 and SpringBoot 2.0. I'm using JPA in application for CRUD. I have following method to update an existing record with cart id in database (PostGres) :-
#Override
#CachePut(value = CommerceConnectorConstants.CommerceCache.COMMERCE_CACHE_TENANT_USER_DATA_MAP)
public Mono<CommerceTenantUser> updateCartId(String tenantId, String userId, String cartId) {
logger.info("cartId->"+cartId);
final TenantUserKey commerce = getCommerceObj(tenantId, userId);
Optional <CommerceTenantUser> tenantUser = commerceTenantUserRepository.findById(commerce);
return tenantUser.map(tenantUserObj -> updateCartIdAndRefreshMap(tenantUserObj, cartId)).orElse(Mono.empty());
}
private Mono<CommerceTenantUser> updateCartIdAndRefreshMap(CommerceTenantUser tenantUserObj, String cartId) {
tenantUserObj.setCartId(cartId);
final Mono<CommerceTenantUser> commerceTenantUser = asyncRunner
.one(() -> commerceTenantUserRepository.saveAndFlush(tenantUserObj))
.doOnNext(value -> commerceCacheService.refreshMap())
.doOnError(error -> logger.error("Error while persisting Cart Id: {}", error))
.map(commerceTenantUserObj -> commerceTenantUserObj);
return commerceTenantUser;
}
My issue is in one of the application flow, I'm unable to update the cart id. I tried to debug the flow many times, I can clearly see that the call is happening to this method and I can log cart id value in console as well but in the end the update of cart id never actually happens in database.
Please note this same method for updating the cart id is happening in other flows of application where cart id eventually gets saved in database (except my flow) so I guess there is no issue with the implementation logic of saving.
Following is the flow where this method invocation is happening but not saving in database :-
#Override
public Mono verifyCredentialsforBasicAuthorization(Map requestInfo) {
Tuple4<String, String, WebClient, String> serviceConnectionDetails = commerceConnectorHelper.verifyCredentialsforBasicAuthorization(requestInfo);
if(!StringUtils.isBlank(serviceConnectionDetails._1)) {
logger.info("Calling cart id update service...");
return serviceConnectionDetails._3
.post()
.uri(builder -> builder.path(serviceConnectionDetails._1)
.queryParam(CommerceConnectorConstants.CartParams.OLD_CART_ID, serviceConnectionDetails._4)
.build())
.accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)
.retrieve()
.bodyToMono(CartModel.class)
.map(response ->
{
return repositoryDetails.updateCartId(requestInfo.get(CommerceConnectorConstants.HttpHeaders.TENANT_ID).get(),
requestInfo.get(CommerceConnectorConstants.HttpHeaders.TENANT_USER_ID).get(), response.getGuid()).then(Mono.just(response));
}) .then(repositoryDetails.updateCommerceUserId(requestInfo.get(CommerceConnectorConstants.HttpHeaders.TENANT_ID).get(),
requestInfo.get(CommerceConnectorConstants.HttpHeaders.TENANT_USER_ID).get(),
requestInfo.get(CommerceConnectorConstants.AuthorizationParams.AUTHORIZATION_USERNAME).get()))
.then(serviceConnectionDetails._3
.get()
.uri(serviceConnectionDetails._2)
.accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)
.retrieve()
.bodyToMono(UserProfile.class)
.doOnNext(value -> logger.info("Called User Profile service {}", value.getCustomerId()))
.doOnError(error -> logger.error("Error while calling User Profile service: {}", error)));
}
else
//Update Commerce User Id
;
//User Profile Call
return serviceConnectionDetails._3
.get()
.uri(serviceConnectionDetails._2)
.accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)
.retrieve()
.bodyToMono(UserProfile.class)
.doOnNext(value -> logger.info("Called User Profile service {}", value.getCustomerId()))
.doOnError(error -> logger.error("Error while calling User Profile service: {}", error));
}
If you notice, I'm calling this method repositoryDetails.updateCartId and passing all the required parameters including the cart id by using response.getGuid(). The method is executed in the end but eventually it does not saves the data in table. I tried really hard but not able to figure out the issue. This is the problem with asynchronous calls using the Java Reactive programming. I would really appreciate if you can help me in figuring out issue. Thanks
I found the root cause of it. I was suppose to call update once only on same Entity using JPA. In my case I was calling twice individually to update 2 different columns in same Entity.
I have the following request handler
fun x(req: ServerRequest) = req.toMono()
.flatMap {
...
val oldest = myRepository.findOldest(...) // this is the object I want to modify
...
val v= anotherMongoReactiveRepository.save(Y(...)) // this saves successfully
myRepository.save(oldest.copy(
remaining = (oldest.remaining - 1)
)) // this is not saved
ok().body(...)
}
and the following mongodb reactive repository
#Repository
interface MyRepository : ReactiveMongoRepository<X, String>, ... {
}
The problem is that after the save() method is executed there is no changed in the object. I managed to fix the problem with save().block() but I don't know why the first save on the other repository works and this one isn't. Why is this block() required?
Nothing happens until someone subscribes to reactive Publisher. That's why it started to work when you used block(). If you need to make a call to DB and use the result in another DB request than use Mono / Flux operators like map(), flatMap(),... to build a pipeline of all the operations you need and after that return resulting Mono / Flux as controller’s response. Spring will subscribe to that Mono / Flux and will return the request. You don't need to block it. And it is not recommended to do it (to use block() method).
Short example how to work with MongoDB reactive repositories in Java:
#GetMapping("/users")
public Mono<User> getPopulation() {
return userRepository.findOldest()
.flatMap(user -> { // process the response from DB
user.setTheOldest(true);
return userRepository.save(user);
})
.map(user -> {...}); // another processing
}