Spring Boot Reactive: How to call the multiple services at the same time from performance optimization perspective? - spring

I am new to Spring Boot Reactive Microservices world, I've a below code which I am looking to call at the same time. Currently both the calls are blocked. How can we make the below code more performant?
#RestController
public class CustomerController {
#Autowired
private WebClient wc;
#GetMapping("/servicestatus")
public String getStatus() {
return "up";
}
#RequestMapping(value="/customer/{cid}", method=RequestMethod.GET)
public CustomerDetails getCustomer(#PathVariable String cid) {
CustomerDetails customer = new CustomerDetails();
//WebClient client = WebClient.create();
ContactDetails svc1 = wc.get()
.uri("http://localhost:8091/customer/" + cid + "/contactdetails")
.retrieve()
.bodyToMono(ContactDetails.class)
.block();
VehicleDetails svc2 = wc.get()
.uri("http://localhost:8092/customer/" + cid + "/vehicledetails")
.retrieve()
.bodyToMono(VehicleDetails.class)
.block();
customer.setContactId(cid);
customer.setContactName(svc1.getContactName());
customer.setPostalCode(svc1.getPostalCode());
customer.setLicensePlate(svc2.getLicensePlate());
customer.setCarType(svc2.getCarType());
return customer;
}
}

You should be able to something like this (although you should move service calls to another layer away from controllers). Just for demonstration:
#RequestMapping(value = "/customer/{cid}", method = RequestMethod.GET)
Flux<CustomerDetails> getCustomer(#PathVariable String cid) {
var fetchContactDetails = wc.get()
.uri("http://localhost:8091/customer/" + cid + "/contactdetails")
.retrieve()
.bodyToMono(ContactDetails.class);
var fetchVehicleDetails = wc.get()
.uri("http://localhost:8092/customer/" + cid + "/vehicledetails")
.retrieve()
.bodyToMono(VehicleDetails.class);
return Flux.zip(fetchContactDetails, fetchVehicleDetails)
.parallel()
.runOn(Schedulers.parallel())
.map(result -> {
CustomerDetails customer = new CustomerDetails();
customer.setContactId(cid);
customer.setContactName(result.getT1().getContactName());
customer.setPostalCode(result.getT1().getPostalCode());
customer.setLicensePlate(result.getT2().getLicensePlate());
customer.setCarType(result.getT2().getCarType());
return customer;
})
.sequential();
}

Related

FeignException com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.springframework.http.ResponseEntity`

Any Help please !!
I receive this error when I'm calling my endpoint which call Feign in the background :
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of
`org.springframework.http.ResponseEntity` (no Creators, like default constructor, exist): cannot deserialize
from Object value (no delegate- or property-based Creator)
at [Source: (BufferedReader); line: 1, column: 2]
This is my endpoint inside Controller :
#RestController
#RequestMapping(Routes.URI_PREFIX)
public class CartoController {
#Autowired
private ReadCartographyApiDelegate readCartographyApiDelegate;
#GetMapping(value = "/cartographies/{uid}", produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseWrapper<ReadCartographyResponse> readCarto(HttpServletRequest request,
#PathVariable(name = "uid") String uid) {
ResponseEntity<ReadCartographyResponse> result ;
try {
result = readCartographyApiDelegate.readCartography(uid);
}catch (Exception e){
throw new TechnicalException("Error during read Carto");
}
return responseWrapperWithIdBuilder.of(result.getBody());
}
}
Interface ReadCartographyApiDelegate generated automatically by openApi from yaml file :
#javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "...")
public interface ReadCartographyApiDelegate {
default Optional<NativeWebRequest> getRequest() {
return Optional.empty();
}
default ResponseEntity<ReadCartographyResponse> readCartography(String uid) {
getRequest().ifPresent(request -> {
for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {
if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
String exampleString = "null";
ApiUtil.setExampleResponse(request, "application/json", exampleString);
break;
}
}
});
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
}
This my ReadCartoApiDelegateImpl which implements ReadCartographyApiDelegate interface :
#Service
public class ReadCartographyApiDelegateImpl implements ReadCartographyApiDelegate {
private EcomGtmClient ecomGtmClient;
public ReadCartographyApiDelegateImpl(EcomGtmClient ecomGtmClient) {
this.ecomGtmClient = ecomGtmClient;
}
#Override
public ResponseEntity<ReadCartographyResponse> readCartography(String uid) {
ResponseEntity<ReadCartographyResponse> response = ecomGtmClient.readCartography(uid);
return response;
}
}
This is the feign client :
#FeignClient(name = "ecomGtmSvc", url = "http://localhost/")
public interface EcomGtmClient {
#GetMapping(value = "/read-carto/{uid}")
ResponseEntity<ReadCartographyResponse> readCartography(#PathVariable("uid") String uid);
}
The problem is that ResponseEntity (spring class) class doesn't contain default constructor which is needed during creating of instance. is there Any config to resolve this issue ?
If you want access to the body or headers on feign responses, you should use the feign.Response class. ResponseEntity does not work with feign because it is not meant to. I think it is best if you just return Response from your feign client method. You should then be able to pass the body to the ResponseEntity instance in the Controller.
What is your reason to even use the response-wrapper, i can't really figure that out from your code?
Sadly I couldn't find any documentation on the Response class, but here's the link to the source on GitHub.
https://github.com/OpenFeign/feign/blob/master/core/src/main/java/feign/Response.java
My Suggestion would be
#FeignClient(name = "ecomGtmSvc", url = "http://localhost/")
public interface EcomGtmClient {
#GetMapping(value = "/read-carto/{uid}")
ReadCartographyResponse readCartography(#PathVariable("uid") String uid);
}
#RestController
#RequestMapping(Routes.URI_PREFIX)
public class CartoController {
#Autowired
private ReadCartographyApiDelegate readCartographyApiDelegate;
#GetMapping(value = "/cartographies/{uid}", produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseWrapper<ReadCartographyResponse> readCarto(HttpServletRequest request,
#PathVariable(name = "uid") String uid) {
ReadCartographyResponse result ;
try {
result = readCartographyApiDelegate.readCartography(uid);
}catch (Exception e){
throw new TechnicalException("Error during read Carto");
}
// I don't know where you get the builder from, so I assume it does something import and is needed
return responseWrapperWithIdBuilder.of(result);
}
}
Of course you'd also have to change all intermediate classes.
The Response Output was the correct Object that I have to put, cause every time I need to check the status from my feign client endpoint to do différent logic
#FeignClient(name = "ecomGtmSvc", url = "http://localhost/")
public interface EcomGtmClient {
#GetMapping(value = "/read-carto/{uid}")
ReadCartographyResponse readCartography(#PathVariable("uid") String uid);
}

How to write custom insert Query Spring Webflux, and return custom response class?

I am new to Reactive programming and I got stuck writing a custom Insert query.
So far I have a FriendshipRepository.java class.
public interface FriendshipRepository extends R2dbcRepository<Friendship, String> {
#Query(value = "INSERT INTO public.friendship(requester_id, addressee_id) values (:requesterid::uuid, :addresseeid::uuid)")
public Mono<Void> insertFriendRequest(
#Param("requesterid") String requesterId,
#Param("addresseeid") String addresseeId
);
}
And a FriendshipController.java class.
#RestController
public class FriendsController {
private final FriendshipRepository friendshipRepository;
public FriendsController(FriendshipRepository friendshipRepository) {
this.friendshipRepository = friendshipRepository;
}
#PostMapping(value = "/request", produces = "application/json")
public Mono<ResponseEntity<RequestResponse>> sendFriendRequest(#RequestBody FriendRequest friendRequest, #AuthenticationPrincipal Mono<User> principal) throws Exception {
String id = principal.map(User::getId).toFuture().get();
return friendshipRepository.insertFriendRequest(id, friendRequest.getUserId()).log()
.then(Mono.just("NEXT"))
.map(e -> {
return ResponseEntity.status(HttpStatus.CREATED).body(new RequestResponse("Success", ResponseCode.SUCCESS));
}).onErrorResume(e -> {
return Mono.just(ResponseEntity.status(HttpStatus.FORBIDDEN).body(new RequestResponse("Friend request was unsuccessful ", ResponseCode.REFUSED)));
});
}
}
This is a working example.
But I dont understand why I have to call .then(Mono.just("NEXT")) and create a new Mono to be able to return a custom ResponseEntity<RequestResponse>>. I also tried merge the the whole process. I meen by this at the begining when I get the Id from the ReactiveSpringSecutiryContext that is a blocking line of code and If I know it correctly that is a bad approach in Reactive programming.
I tried this approach but in this case, I can only retrun the Id of the user.
Mono<String> userId = principal.map(User::getId);
return userId.doOnNext(id -> {
friendshipRepository.insertFriendRequest(id, friendRequest.getUserId()).log()
.map(e -> {
return ResponseEntity.status(HttpStatus.CREATED).body(new RequestResponse("SIKER", ResponseCode.SUCCESS));
})
.onErrorResume(e -> {
return Mono.just(ResponseEntity.status(HttpStatus.FORBIDDEN).body(new RequestResponse("Friend request was unsuccessful ", ResponseCode.REFUSED)));
}).subscribe();
// .doOnSuccess(e -> ServerResponse.noContent().build((new RequestResponse("SIKER", ResponseCode.SUCCESS)), Void.class));
});
How could I rewrite this endpoint? Or does my whole approach inaproptirate?
Thank you in advance for your help.
Probably not the nicest solution, but better then it was.
My query:
public interface FriendshipRepository extends R2dbcRepository<Friendship, String> {
#Query(value = "INSERT INTO public.friendship(requester_id, addressee_id) values (:requesterid::uuid, :addresseeid::uuid) RETURNING id")
public Mono<String> insertFriendRequest(
#Param("requesterid") String requesterId,
#Param("addresseeid") String addresseeId
);
}
My api:
#PostMapping(value = "/request", produces = "application/json")
public Mono<ResponseEntity<RequestResponse>> sendFriendRequest(#RequestBody FriendRequest friendRequest, #AuthenticationPrincipal Mono<User> principal) throws Exception {
String id = principal.map(User::getId).toFuture().get();
return friendshipRepository.insertFriendRequest(id, friendRequest.getUserId()).log()
.map(e -> {
return ResponseEntity.status(HttpStatus.CREATED).body(new RequestResponse("Success", ResponseCode.SUCCESS));
}).onErrorResume(e -> {
return Mono.just(ResponseEntity.status(HttpStatus.FORBIDDEN).body(new RequestResponse("Friend request was unsuccessful ", ResponseCode.REFUSED)));
});
}

REST endpoint works when directly accessed, but not when accessed via WebClient

Note: The following is an edit/revision to a similar post/question, in an attempt to better identify my issue/question, and to provide better code samples demonstrating my issue.
Add Note: Code example has been revised to include working code.
I have two endpoints in two routers in the same spring reactive application. The first (/v2/DemoPOJO) appears to work correctly. The second (/v2/DemoClient/DemoPOJO), which uses WebClient to delegate to /v2/DemoPOJO appears to "do nothing" (although the logged output shows that DemoClientHandler.add() and DemoClient.add() are being invoked).
When I do a POST request to the /v2/DemoPOJO endpoint, the doFirst(), doOnSuccess(), and doFinally() are invoked and output the appropriate text (in "real life", a row is added to the repository).
When I do a POST request to the /v2/DemoClient/DemoPOJO endpoint, it returns a 200 OK status, but none of the expected text is output (in "real life", nothing gets added to the repository).
The following files support the /v2/DemoPOJO endpoint...
Router class implementation for DemoPOJO...
#Configuration
public class DemoPOJORouter {
private Logger logger = LoggerFactory.getLogger(DemoPOJORouter.class);
#Bean
public RouterFunction<ServerResponse> demoPOJORoute(DemoPOJOHandler requestHandler) {
logger.debug("DemoPOJORouter.demoPOJORoute( DemoPOJOHandler )");
return nest(path("/v2"),
nest(accept(APPLICATION_JSON),
RouterFunctions.route(RequestPredicates.POST("/DemoPOJO"), requestHandler::add)));
}
}
Handler class implementation for DemoPOJO...
#Component
public class DemoPOJOHandler {
private Logger logger = LoggerFactory.getLogger(DemoPOJOHandler.class);
public Mono<ServerResponse> add(ServerRequest request) {
logger.debug("DemoPOJOHandler.add( ServerRequest )");
return request.bodyToMono(DemoPOJO.class).doFirst(() -> System.out.println("-> doFirst()."))
.doOnSuccess(demoPOJO -> System.out.println("Received >> " + demoPOJO.toString()))
.then(ServerResponse.accepted().build())
.doOnError(e -> System.out.println("-> doOnError()"))
.doFinally(demoPOJO -> System.out.println("-> doFinally()"));
}
}
DemoPOJO implementationj...
public class DemoPOJO {
private Logger logger = LoggerFactory.getLogger(DemoPOJO.class);
public static final String DEF_NAME = "DEFAULT NAME";
public static final int DEF_VALUE = 99;
private int id;
private String name;
private int value;
public DemoPOJO(#JsonProperty("id") int id, #JsonProperty("name") String name, #JsonProperty("value") int value) {
logger.debug("DemoPOJO.DemoPOJO( {}, {}, {} )", id, name, value);
this.id = id;
this.name = name;
this.value = value;
}
/*
* setters and getters go here
*/
public String toString() {
logger.debug("DemoPOJO.toString()");
StringBuilder builder = new StringBuilder();
builder.append(id);
builder.append(" :: ");
builder.append(name);
builder.append(" :: ");
builder.append(value);
return builder.toString();
}
}
The following files support the /v2/DemoClient/DemoPOJO endpoint...
Router implementation for DemoClient...
#Configuration
public class DemoClientRouter {
private Logger logger = LoggerFactory.getLogger(DemoClientRouter.class);
#Bean
public RouterFunction<ServerResponse> clientRoutes(DemoClientHandler requestHandler) {
logger.debug("DemoClientRouter.route( DemoClientHandler )");
return nest(path("/v2/DemoClient"),
nest(accept(APPLICATION_JSON),
RouterFunctions.route(RequestPredicates.POST("/DemoPOJO"), requestHandler::add)));
}
}
Handler implementation for DemoClient...
#Component
public class DemoClientHandler {
private Logger logger = LoggerFactory.getLogger(mil.navy.demo.demopojo.DemoPOJOHandler.class);
#Autowired
DemoClient demoClient;
public Mono<ServerResponse> add(ServerRequest request) {
logger.debug("DemoClientOHandler.add( ServerRequest )");
// THIS CODE
return request.bodyToMono(DemoPOJO.class).flatMap(demoPOJO -> demoClient.add(demoPOJO))
.then(ServerResponse.accepted().build());
// REPLACES THIS CODE
/*
return request.bodyToMono(DemoPOJO.class).doOnSuccess( demoPOJO -> demoClient.add(demoPOJO))
.then(ServerResponse.ok().build())
.switchIfEmpty(ServerResponse.badRequest().build());
*/
}
}
WebClient implementation for DemoClient...
#Component
public class DemoClient {
private Logger logger = LoggerFactory.getLogger(DemoClient.class);
private final WebClient client;
public DemoClient() {
client = WebClient.create();
}
public Mono<Boolean> add(DemoPOJO demoPOJO) {
logger.debug("DemoClient.add( DemoPOJO )");
logger.debug("DemoClient.add() >> DemoPOJO -> {}", demoPOJO.toString());
return client.post().uri("http://localhost:8080/v2/DemoPOJO")
.accept(MediaType.APPLICATION_JSON)
.syncBody(demoPOJO)
.exchange()
.flatMap(response -> response.bodyToMono(Boolean.class));
}
}
here is where i guess your problem is.
return request.bodyToMono(DemoPOJO.class)
.doOnSuccess( demoPOJO -> demoClient.add(demoPOJO))
doOnSuccess takes a consumer, a consumer returns void not Mono<Void>.
Here is the verbose usage of a Consumer.
Mono.just("hello")
.doOnSuccess(new Consumer<String>() {
#Override
public void accept(String s) {
// See here, it returns void
}
});
Lets look at some examples:
Mono<String> helloWorld = Mono.just("Hello")
.doOnSuccess(string -> {
// This will never be executed because
// it is just declared and never subscribed to
Mono.just(string + " world");
});
helloWorld.doOnSuccess(string -> {
// This will print out Hello
System.out.println(string);
});
Mono<String> hello = Mono.just("Hello")
.doOnSuccess(string -> {
// This will print out Hello World
System.out.println(string + " World");
});
// hello hasn't been changed
hello.map(string -> {
// This will also print out Hello World
System.out.println(string + " World");
});
// This prints hello world to after we mapped it
Mono<String> helloworld = Mono.just("hello")
.map(s -> s + " World")
.doOnSuccess(System.out::println);
// Now this is what you are essentially doing
// See how this is wrong?
Mono<DemoPOJO> demoPOJO = request.bodyToMono(DemoPOJO.class)
.doOnSuccess( demoPOJO -> Mono.empty() );
you are calling demoClient#add that returns a Mono<Void> here you are breaking the chain because nothing is chained onto the Mono that it is returning so it never gets subscribed to because it is not in the event chain.
return request.bodyToMono(DemoPOJO.class)
.map( demoPOJO -> {
return demoClient.add(demoPOJO);
});
If you change it to map it will probably work. What is happening then is that it is taking a Mono<DemoPojo> and "mapping" it to a Mono<Void> that is returned by your add function. And suddenly it is in the event chain (callback).

406 error always happening with spring rest controller

I'd like to have your opinion on a error always throwed in my spring boot rest controller. I got the following first controller accepting reading requests :
#RequestMapping(value="/read/{file:.+}" , method = RequestMethod.GET)
public ResponseEntity myFunction(#PathVariable("file") String file) {
String[] parts = file.split("\\.");
String extension = parts[1];
List<SousBloc> resWord;
List<SousBloc> resPdf;
List<CvAvecBlocs> resExcel;
RestTemplate rt = new RestTemplate();
rt.getMessageConverters().add(new StringHttpMessageConverter());
if(extension.equals("xlsx")){
resExcel = rt.getForObject("http://localhost:8080/readExcel/"+file, List.class, 200);
return new ResponseEntity<>(resExcel, HttpStatus.OK);
}
else if(extension.equals("pdf")){
resPdf = rt.getForObject("http://localhost:8080/readPdf/"+file, List.class, 200);
return new ResponseEntity<>(resPdf, HttpStatus.OK);
}
else if(extension.equals("docx")){
resWord = rt.getForObject("http://localhost:8080/readWord/"+file, List.class, 200);
return new ResponseEntity<>(resWord, HttpStatus.OK);
}
return null;
}
There is my Reading Word Controller :
#Controller
public class ReadWordController {
private static String UPLOADED_FOLDER = "C:\\cvsUploades\\";
#Autowired
ReadWord readWord;
#RequestMapping(value="/readWord/{file:.+}" , method = RequestMethod.GET)
public ResponseEntity readingWord(#PathVariable("file") String file) throws IOException {
String path = UPLOADED_FOLDER+file;
List<SousBloc> sousBlocs = readWord.extract(path);
return new ResponseEntity<>(sousBlocs, HttpStatus.OK);
}
}
Well this controller works fine and does the job.
Now there is my Reading Pdf Controller :
#Controller
public class ReadPdfController {
private static String UPLOADED_FOLDER = "C:\\cvsUploades\\";
#Autowired
ReadPdf readPdf;
#RequestMapping(value="/readPdf/{file:.+}" , method = RequestMethod.GET)
public ResponseEntity readingPdf(#PathVariable("file") String file) throws IOException {
String path = UPLOADED_FOLDER+file;
List<SousBloc> blocs = readPdf.extract(path);
return new ResponseEntity<>(blocs, HttpStatus.OK);
}
}
It is contructed on the same model of the Reading Word Controller but it does not work. In debug, the program works fine until the return new ResponseEntity<>(blocs, HttpStatus.OK); that throws a 406 error null...
Do you know why ?
EDIT: I tried something strange and it worked... I put the following code :
#Controller
public class ReadWordController {
private static String UPLOADED_FOLDER = "C:\\cvsUploades\\";
#Autowired
ReadWord readWord;
#Autowired
ReadPdf readPdf;
#RequestMapping(value="/readWord/{file:.+}" , method = RequestMethod.GET)
public ResponseEntity readingWord(#PathVariable("file") String file) throws IOException {
/*String path = UPLOADED_FOLDER+file;
List<SousBloc> sousBlocs = readWord.extract(path);
return new ResponseEntity<>(sousBlocs, HttpStatus.OK);*/
String path = "C:\\cvsUploades\\file.pdf";
List<SousBloc> blocs = readPdf.extract(path);
return new ResponseEntity<>(blocs, HttpStatus.OK);
}
}

#PathVariable Validation in Spring 4

How can i validate my path variable in spring. I want to validate id field, since its only single field i do not want to move to a Pojo
#RestController
public class MyController {
#RequestMapping(value = "/{id}", method = RequestMethod.PUT)
public ResponseEntity method_name(#PathVariable String id) {
/// Some code
}
}
I tried doing adding validation to the path variable but its still not working
#RestController
#Validated
public class MyController {
#RequestMapping(value = "/{id}", method = RequestMethod.PUT)
public ResponseEntity method_name(
#Valid
#Nonnull
#Size(max = 2, min = 1, message = "name should have between 1 and 10 characters")
#PathVariable String id) {
/// Some code
}
}
You need to create a bean in your Spring configuration:
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
You should leave the #Validated annotation on your controller.
And you need an Exceptionhandler in your MyController class to handle theConstraintViolationException :
#ExceptionHandler(value = { ConstraintViolationException.class })
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public String handleResourceNotFoundException(ConstraintViolationException e) {
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
StringBuilder strBuilder = new StringBuilder();
for (ConstraintViolation<?> violation : violations ) {
strBuilder.append(violation.getMessage() + "\n");
}
return strBuilder.toString();
}
After those changes you should see your message when the validation hits.
P.S.: I just tried it with your #Size validation.
To archive this goal I have apply this workaround for getting a response message equals to a real Validator:
#GetMapping("/check/email/{email:" + Constants.LOGIN_REGEX + "}")
#Timed
public ResponseEntity isValidEmail(#Email #PathVariable(value = "email") String email) {
return userService.getUserByEmail(email).map(user -> {
Problem problem = Problem.builder()
.withType(ErrorConstants.CONSTRAINT_VIOLATION_TYPE)
.withTitle("Method argument not valid")
.withStatus(Status.BAD_REQUEST)
.with("message", ErrorConstants.ERR_VALIDATION)
.with("fieldErrors", Arrays.asList(new FieldErrorVM("", "isValidEmail.email", "not unique")))
.build();
return new ResponseEntity(problem, HttpStatus.BAD_REQUEST);
}).orElse(
new ResponseEntity(new UtilsValidatorResponse(EMAIL_VALIDA), HttpStatus.OK)
);
}

Resources