How to use Spring WebClient to make multiple calls simultaneously? - spring

I want to execute 3 calls simultaneously and process the results once they're all done.
I know this can be achieved using AsyncRestTemplate as it is mentioned here How to use AsyncRestTemplate to make multiple calls simultaneously?
However, AsyncRestTemplate is deprecated in favor of WebClient. I have to use Spring MVC in the project but interested if I can use a WebClient just to execute simultaneous calls. Can someone advise how this should be done properly with WebClient?

Assuming a WebClient wrapper (like in reference doc):
#Service
public class MyService {
private final WebClient webClient;
public MyService(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder.baseUrl("http://example.org").build();
}
public Mono<Details> someRestCall(String name) {
return this.webClient.get().url("/{name}/details", name)
.retrieve().bodyToMono(Details.class);
}
}
..., you could invoke it asynchronously via:
// ...
#Autowired
MyService myService
// ...
Mono<Details> foo = myService.someRestCall("foo");
Mono<Details> bar = myService.someRestCall("bar");
Mono<Details> baz = myService.someRestCall("baz");
// ..and use the results (thx to: [2] & [3]!):
// Subscribes sequentially:
// System.out.println("=== Flux.concat(foo, bar, baz) ===");
// Flux.concat(foo, bar, baz).subscribe(System.out::print);
// System.out.println("\n=== combine the value of foo then bar then baz ===");
// foo.concatWith(bar).concatWith(baz).subscribe(System.out::print);
// ----------------------------------------------------------------------
// Subscribe eagerly (& simultaneously):
System.out.println("\n=== Flux.merge(foo, bar, baz) ===");
Flux.merge(foo, bar, baz).subscribe(System.out::print);
Mono javadoc
Flux javadoc
Spring WebClient reference doc
Spring Boot WebClient reference doc
Projectreactor reference doc
Which (reactive) operator to use!
Thanks, Welcome & Kind Regards,

You can make HTTP calls concurrently using simple RestTemplate and ExecutorService:
RestTemplate restTemplate = new RestTemplate();
ExecutorService executorService = Executors.newCachedThreadPool();
Future<String> firstCallFuture = executorService.submit(() -> restTemplate.getForObject("http://first-call-example.com", String.class));
Future<String> secondCallFuture = executorService.submit(() -> restTemplate.getForObject("http://second-call-example.com", String.class));
String firstResponse = firstCallFuture.get();
String secondResponse = secondCallFuture.get();
executorService.shutdown();
Or
Future<String> firstCallFuture = CompletableFuture.supplyAsync(() -> restTemplate.getForObject("http://first-call-example.com", String.class));
Future<String> secondCallFuture = CompletableFuture.supplyAsync(() -> restTemplate.getForObject("http://second-call-example.com", String.class));
String firstResponse = firstCallFuture.get();
String secondResponse = secondCallFuture.get();

You can use Spring reactive client WebClient to send parallel requests.
In this example,
public Mono<UserInfo> getUserInfo(User user) {
Mono<UserInfo> userInfoMono = getUserInfo(user.getId());
Mono<OrgInfo> organizationInfoMono = getOrgInfo(user.getOrgId());
return Mono.zip(userInfoMono, organizationInfoMono).map(tuple -> {
UserInfo userInfo = tuple.getT1();
userInfo.setOrganization(tuple.getT2());
return userInfo;
});
}
Here:
getUserInfo makes an HTTP call to get user info from another service and returns Mono
getOrgInfo method makes HTTP call to get organization info from another service and returns Mono
Mono.zip() waits all the results from all monos and merges into a new mono and returns it.
Then, call getOrgUserInfo().block() to get the final result.

Another way:
public Mono<Boolean> areVersionsOK(){
final Mono<Boolean> isPCFVersionOK = getPCFInfo2();
final Mono<Boolean> isBlueMixVersionOK = getBluemixInfo2();
return isPCFVersionOK.mergeWith(isBlueMixVersionOK)
.filter(aBoolean -> {
return aBoolean;
})
.collectList().map(booleans -> {
return booleans.size() == 2;
});
}

Related

How to assign values, call method in Subscribe method in Spring boot web client(non blocking)

I have a Spring boot application. End point A calls three different REST endpoints X, Y, Z. All the calls were using RestTemplate. I am trying to change from RestTemplate to Webclient. As a part of this I changed endpoint Y from RestTemplate to Webclient.
I had a blocking code. It was working as expected. But when I changed it to non-blocking using subscribe things are not working as expected.
With Blocking code
public class SomeImplClass {
#Autowired
private WebClient webClient;
public someReturnType someMethodName()
{
List myList = new ArrayList<>();
Mono<SomeResponse> result = this.webclient.post().uri(url).header(…).bodyValue(….).retrieve().bodyToMone(responseType);
someResponse = result.block(someDuration);
if(someResponse.getId().equals(“000”)
{
myList.addAll(this.somemethod(someResponse));
}else{
log.error(“some error”);
throw new SomeCustomException(“some error”)
}
return myList;
}
With Non Blocking Code
public class SomeImplClass {
#Autowired
private WebClient webClient;
public someReturnType someMethodName()
{
List myList = new ArrayList<>();
Mono<SomeResponse> result = this.webclient.post().uri(url).header(…).bodyValue(….).retrieve().bodyToMone(responseType);
result.subscribe(someResponse -> {
if(someResponse.getId().equals(“000”)
{
myList.addAll(this.somemethod(someResponse));
}
else{
log.error(“some error”);
throw new SomeCustomException(“some error”) //Not able to throw custom exception here.
}
});
return myList;
}
I am getting 2 issues
With non-blocking code the list which I am returning is empty. I guess return is called before subscribe consumes the data. How to resolve this? I tried result.doOnSuccess and doOnNext but both are not working. If I ad d Thread.sleep(5000) before return, everything is working as expected. How to achieve this without adding Thread.sleep.
I am able to throw RunTimeExceptions alone from subscribe. How to throw customeExceptions.

Setting one mono within another using webflux

I am retrieving a publisher Mono<ProductOrder> from mongo db
I have another publisher Mono<CommandResponse>
I want to set ProductOrder object into CommandResponse as it is a part of that class. like commandResponse.setProductOrder(po instance)
CommandResponse will also have different Mono or String or Int apart from ProductOrder instance
Finally want to return Mono<ServerResponse> which would contain body of CommandResponse
i am not being able to set the Mono<ProductOrder> object into Mono<CommandResponse>
Pleas help. Thanks.
CODE SNIPPET
#Component
public class Handler {
#Autowired
private MongoService service;
public Mono<ServerResponse> get(ServerRequest request) {
Mono<ProductOrder> p = service.queryById();
> Here, in the return statement i want to return Mono<CommandResponse>
> instead of Mono<ProductOrder> in the response body
> remember: CommandResponse has a reference to ProductOrder
return
ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(p, ProductOrder.class).map(null);
);
}
}
Mono<CommandResponse> commandMono = ... // I don't how it is set
Mono<ProductOrder> productMono = service.queryById();
Mono.zip(commandMono, productMono)
.map(tuple -> {
var command = tuple.getT1();
var product = tuple.getT2();
command.setProductOrder(product) // => .setProductOrder(po instance)
return command;
})
.flatMap(command -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(command)
)
I haven't run this code, so I'm not sure if it compiles, but with it you should be able to figure out how your code could work

Running Tasks in different thread in Spring Webflux Annotated controller

I have a spring Webflux Annotated controller as below,
#RestController
public class TestBlockingController {
Logger log = LoggerFactory.getLogger(this.getClass().getName());
#GetMapping()
public Mono<String> blockForXSeconds(#RequestParam("block-seconds") Integer blockSeconds) {
return getStringMono();
}
private Mono<String> getStringMono() {
Integer blockSeconds = 5;
String type = new String();
try {
if (blockSeconds % 2 == 0) {
Thread.sleep(blockSeconds * 1000);
type = "EVEN";
} else {
Thread.sleep(blockSeconds * 1000);
type = "ODD";
}
} catch (Exception e) {
log.info("Got Exception");
}
log.info("Type of block-seconds: " + blockSeconds);
return Mono.just(type);
}
}
How do I make getStringMono run in a different thread than Netty server threads. The problem I am facing is that as I am running in server thread I am getting basically less throughput (2 requests per second). How do I go about making running getStringMono in a separate thread.
You can use subscribeOn operator to delegate the task to a different threadpool:
Mono.defer(() -> getStringMono()).subscribeOn(Schedulers.elastic());
Although, you have to note that this type of blocking should be avoided in a reactive application at any cost. If possible, use a client which supports non-blocking IO and returns a promise type (Mono, CompletableFuture, etc.). If you just want to have an artificial delay, then use Mono.delay instead.
You can use Mono.defer() method.
The method signature is as:
public static <T> Mono<T> defer(Supplier<? extends Mono<? extends T>> supplier)
Your Rest API should look like this.
#GetMapping()
public Mono<String> blockForXSeconds(#RequestParam("block-seconds") Integer blockSeconds) {
return Mono.defer(() -> getStringMono());
}
The defer operator is there to make this source lazy, re-evaluating the content of the lambda each time there is a new subscriber. This will increase your API throughput.
Here you can view the detailed analysis.

Validating Spring Kafka payloads

I am trying to set up a service that has both a REST (POST) endpoint and a Kafka endpoint, both of which should take a JSON representation of the request object (let's call it Foo). I would want to make sure that the Foo object is valid (via JSR-303 or whatever). So Foo might look like:
public class Foo {
#Max(10)
private int bar;
// Getter and setter boilerplate
}
Setting up the REST endpoint is easy:
#PostMapping(value = "/", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> restEndpoint(#Valid #RequestBody Foo foo) {
// Do stuff here
}
and if I POST, { "bar": 9 } it processes the request, but if I post: { "bar": 99 } I get a BAD REQUEST. All good so far!
The Kafka endpoint is easy to create (along with adding a StringJsonMessageConverter() to my KafkaListenerContainerFactory so that I get JSON->Object conversion:
#KafkaListener(topics = "fooTopic")
public void kafkaEndpoint(#Valid #Payload Foo foo) {
// I shouldn't get here with an invalid object!!!
logger.debug("Successfully processed the object" + foo);
// But just to make sure, let's see if hand-validating it works
Validator validator = localValidatorFactoryBean.getValidator();
Set<ConstraintViolation<SlackMessage>> errors = validator.validate(foo);
if (errors.size() > 0) {
logger.debug("But there were validation errors!" + errors);
}
}
But no matter what I try, I can still pass invalid requests in and they process without error.
I've tried both #Valid and #Validated. I've tried adding a MethodValidationPostProcessor bean. I've tried adding a Validator to the KafkaListenerEndpointRegistrar (a la the EnableKafka javadoc):
#Configuration
public class MiscellaneousConfiguration implements KafkaListenerConfigurer {
private Logger logger = LoggerFactory.getLogger(this.getClass());
#Autowired
LocalValidatorFactoryBean validatorFactory;
#Override
public void configureKafkaListeners(KafkaListenerEndpointRegistrar registrar) {
logger.debug("Configuring " + registrar);
registrar.setMessageHandlerMethodFactory(kafkaHandlerMethodFactory());
}
#Bean
public MessageHandlerMethodFactory kafkaHandlerMethodFactory() {
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
factory.setValidator(validatorFactory);
return factory;
}
}
I've now spent a few days on this, and I'm running out of other ideas. Is this even possible (without writing validation into every one of my kakfa endpoints)?
Sorry for the delay; we are at SpringOne Platform this week.
The infrastructure currently does not pass a Validator into the payload argument resolver. Please open an issue on GitHub.
Spring kafka listener by default do not scan for #Valid for non Rest controller classes. For more details please refer this answer
https://stackoverflow.com/a/71859991/13898185

Example for spring MVC DeferredResult with long running task

Can anyone tell me, how to do long running transactional task using spring DeferredResult ? went through a lot of tutorial available on net but neither documentation nor examples clear on non Rest based application, which doesn't need long polling but running the task at background and immediately returning the HTTP response and subsequent calls to the same controller method just return the result. With some assumption i have created like the following
private static final Map<String, DeferredResult<ModelAndView>> deferredResults = new ConcurrentHashMap<>();
#RequestMapping(value = "longRunning", method = RequestMethod.POST)
public DeferredResult<ModelAndView> longRunning(#ModelAttribute LongRunningJob longRunningJob) {
String resultKey = longRunningJob.getKey();
DeferredResult<ModelAndView> result = deferredResults.get(resultKey);
if (result == null ) {
deferredResults.put(resultKey, result = new DeferredResult<ModelAndView>());
new Thread(runLongRunning(longRunningJob, result)).start();
}
result.onCompletion(() -> {
deferredResults.remove(resultKey);});
return result;
}
public Runnable runLongRunning((LongRunningJob newLongRunningJob, DeferredResult deferredResult) {
return () -> {
LongRunningJob returnJobValue = this.longRunningJobService.startLongRunningJob(newLongRunningJob); //startLongRunningJob is a transactional method
ModelMap modelMap = new ModelMap();
modelMap.put("returnJobValue", returnJobValue);
modelMap.put("message", "Success");
deferredResult.setResult(new ModelAndView("job-view", modelMap));
};
}
Will it work or is there any other better way to handle it ? Will it be threadsafe is there any chances of getting into a race condition ?

Resources