How to wait for subscribe to finish? - java

I want to make an asynchronous rest call for which I'm using spring webclient and getting back a Mono. I'm also doing some database calls in parallel but it can't be done reactively due to some reason.
Map<String, Object> models = new HashMap<>();
Mono<User> users = this.webClient...;
users.map(resp -> new UserState(userRequest, resp))
.subscribe(response -> {
models.put("userState", response);
});
Iterable<Product> messages = this.productRepository.findAll();
models.put("products", messages);
//Wait for users.subscribe to finish <<<<<<<<<<<<<HERE
return new ModelAndView("messages/list", models);
How do I wait for subscribe to finish before returning ModelAndView. This would have been easy if I was using a Future where I can do get() whenever I want.

You can wrap the blocking call in a Mono executed on a separate scheduler, zip it with the Mono containing UserState data and transform their combination into a Mono<ModelAndView> (which can be returned from Spring controller methods). The calls will be executed in parallel, results will be combined when both calls are completed.
You can define a single bounded scheduler per application specifically for blocking calls and provide it as a constructor argument to any class that makes blocking calls.
The code will look as follows:
#Configuration
class SchedulersConfig {
#Bean
Scheduler parallelScheduler(#Value("${blocking-thread-pool-size}") int threadsCount) {
return Schedulers.parallel(threadsCount);
}
}
#RestController
class Controller {
final Scheduler parallelScheduler;
...
Mono<User> userResponse = // webClient...
Mono<Iterable<Product>> productsResponse = Mono.fromSupplier(productRepository::findAll)
.subscribeOn(parallelScheduler);
return Mono.zip(userResponse, productsResponse, (user, products) ->
new ModelAndView("messages/list",
ImmutableMap.of(
"userState", new UserState(userRequest, user),
"products", products
))
);
}
Update based on the comment:
If you just need to execute HTTP call asynchronously and then join it with the database results you can do the following
Map<String, Object> models = new HashMap<>();
Mono<User> userMono = webClient...;
CompletableFuture<User> userFuture = userMono.toFuture();
Iterable<Product> messages = productRepository.findAll();
User user = userFuture.join();
models.put("products", messages);
models.put("userState", new UserState(userRequest, user));
return new ModelAndView("messages/list", models);

Related

Webflux returning Mono and using info in Object in Mono to set header

Let's say my Webflux handler returns a Mono on a product creation
That's easy to do.
But now, I want to complete the response with a location in the header.
To do so, I need to get the created product ID.
In my example, I used a block() which fails the reactive idea of the handler.
public Mono<ServerResponse> handleRequest(ServerRequest serverRequest) {
...
Mono<Product> monoProduct = // Service call to get the Mono<Product>
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.location(URI.create(String.format("/api/products/%s",
monoProduct.block().getId()))))
.body(monoProduct), ProductResponse.class);
}
How can I perform such a task without breaking the reactive principles?
You don't really need to block. You need to build reactive flow combining different operators.
In your case it could look like
public Mono<ServerResponse> handleRequest(ServerRequest serverRequest) {
return getProduct() // Service call to get the Mono<Product>
.map(product -> mapToResponse(product)) // Product -> ProductResponse
.flatMap(response ->
ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.location(URI.create(String.format("/api/products/%s", response.getId())))
.body(BodyInserters.fromValue(response))
);
}

Spring webflux some methods not work without subscribe or block

New to reactive programming and also Spring Webflux, I have a method to get value from redis and expire the key under certain conditions. But the code expire key always not work.
my current implmention:
private Mono<MyObject> getMyObjectFromCache(String url) {
RMapReactive<String, String> rMap = redissonReactiveClient.getMap(url);
return rMap.readAllMap()
.flatMap(m ->
rMap.remainTimeToLive()
.flatMap(ttl -> {
final long renewalThreshold = 60 * 60 * 1000;
if (ttl <= renewalThreshold) {
System.out.println("start expiring");
// it doesn't work without subscribe()
rMap.expire(2, TimeUnit.HOURS);
}
return Mono.just(JSONObject.parseObject(JSON.toJSONString(m), MyObject.class));
}
)
);
}
expire method returns Mono<Boolean>
public Mono<MyObject> getMyObjInfo(String url) {
// something else
return getMyObjectFromFromCache(url).switchIfEmpty(Mono.defer(() -> getMyObjectFromRemoteService(url)));
}
CustomGatewayFilter
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
URI uri = request.getURI();
return getMyObjInfo(uri.getPath())
.flatMap(api -> {
// something else
return chain.filter(exchange.mutate().request(request).build());
});
when I test the filter , only print start expiring , but it doesn't work.
if i add subscribe or block, it can work. obviously this is not a good idea, I shouldn't break the reactor chain.
please could I have a correctly way to write this?
thanks
In reactive you need to combine all async operations into a flow, chaining publishers (Mono/Flux) using various reactive operators (assembly time) and then subscribe to it (subscription time). You are right that calling subscribe explicitly is a bad practice and should be avoided. Spring WebFlux subscribes to the provided flow behind the scene.
In your code you are breaking the flow by not chaining rMap.expire(2, TimeUnit.HOURS);. You could rewrite the code like this
private Mono<MyObject> getMyObjectFromCache(String url) {
RMapReactive<String, String> rMap = redissonReactiveClient.getMap(url);
return rMap.readAllMap()
.flatMap(m ->
rMap.remainTimeToLive()
.flatMap(ttl -> {
final long renewalThreshold = 60 * 60 * 1000;
if (ttl <= renewalThreshold) {
System.out.println("start expiring");
// it doesn't work without subscribe()
return rMap.expire(2, TimeUnit.HOURS);
}
return Mono.just(false);
})
.then(JSONObject.parseObject(JSON.toJSONString(m), MyObject.class))
);
}

How to make parallel calls to the same service using spring Flux

I am working on spring reactive and need to call multiple calls sequentially to other REST API using webclient.
The issue is I am able to call multiple calls to other Rest API but response am not able to read without subscribe or block.
I can't use subscribe or block due to non reactive programming. Is there any way, i can merge while reading the response and send it as flux.
Below is the piece of code where I am stuck.
public Mono<DownloadDataLog> getDownload(Token dto, Mono<DataLogRequest> request) {
Mono<GraphQlCustomerResponse> profileResponse = customerProfileHandler.getMyUsageHomeMethods(dto, null);
DownloadDataLog responseObj = new DownloadDataLog();
ArrayList<Mono<List<dataUsageLogs>>> al = new ArrayList<>();
return Mono.zip(profileResponse, request).flatMap(tuple2 -> {
Flux<List<Mono<DataLogGqlRequest>>> userequest = prepareUserRequest(getListOfMdns(tuple2.getT1()),
tuple2.getT2());
Flux.from(userequest).flatMap(req -> {
for (Mono<DataLogGqlRequest> logReq : req) {
al.add(service.execute(logReq, dto));
}
responseObj.setAl(al);
return Mono.empty();
}).subscribe();
return Mono.just(responseObj);
});
}
private Mono<DataLogGqlRequest> prepareInnerRequest(Mono<DataLogGqlRequest> itemRequest, int v1,int v2){
return itemRequest.flatMap(req -> {
DataLogGqlRequest userRequest = new DataLogGqlRequest();
userRequest.setBillDate(req.getBillDate());
userRequest.setMdnNumber(req.getMdnNumber());
userRequest.setCposition(v1+"");
userRequest.setPposition(v2+"");
return Mono.just(userRequest);
});
}

Concurrent asynchronous HTTP requests in Java and/or Spring Boot

The issue I need to solve (in Java) is:
Fetch an array of games from an API.
Iterate over games N times:
(async / concurrent)
Make an individual HTTP request for every game in order to get its
details.
Store the details of it in a gamesWithDetails array.
Done. I have my array gamesWithDetails.
I cannot fetch the details of all the games with a single request, I have to hit the API endpoint every time per game. So I want to execute these requests asynchronously from each other.
This is a working example in JavaScript in case it's useful. However I'd like to make it work for Spring Boot.
axios.get(`https://la2.api.riotgames.com/lol/match/v4/matchlists/by-account/${data.accountId}`, {
headers: { "X-Riot-Token": "asdasdasdasdadasdasdasd"}
})
.then(resp => {
const promises = [];
for ( match of resp.data.matches ) {
promises.push(
axios.get(`https://la2.api.riotgames.com/lol/match/v4/matches/${match.gameId}`, {
headers: { "X-Riot-Token": "asdasdasdasdasdasdasdasd"}
})
)
}
Promise.all(promises)
.then(matchesDetails => {
matchesDetails.forEach(({ data }) => console.log(data.gameId));
});
})
Basically you will want to do something like this:
package com.example.demo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
public class GamesProcessor {
private static final String GAME_URI_BASE = "https://la2.api.riotgames.com/lol/match/v4/matches/";
private static final String ACCOUNT_URI_BASE = "https://la2.api.riotgames.com/lol/match/v4/matchlists/by-account/";
private Executor executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() - 1);
#Autowired
private RestTemplate restTemplate;
public void processGames(String accountId) throws JsonProcessingException, ExecutionException, InterruptedException {
String responseAsString = restTemplate.getForObject(ACCOUNT_URI_BASE + accountId, String.class);
ObjectMapper objectMapper = new ObjectMapper();
if (responseAsString != null) {
Map<String, Object> response = objectMapper.readValue(responseAsString, new TypeReference<Map<String, Object>>() {
});
List<Map<String, Object>> matches = (List<Map<String, Object>>) ((Map<String, Object>) response.get("data")).get("matches");
List<CompletableFuture<Void>> futures = matches.stream()
.map(m -> (String) m.get("gameId"))
.map(gameId -> CompletableFuture.supplyAsync(() -> restTemplate.getForObject(GAME_URI_BASE + gameId, String.class), executor)
.thenAccept(r -> {
System.out.println(r); //do whatever you wish with the response here
}))
.collect(Collectors.toList());
// now we execute all requests asynchronously
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).get();
}
}
}
Please note that it is not a refined code, but just a quick example of how to achieve this. Ideally you would replace that JSON processing that I've done "by hand" using Map by a response bean that matches the structure of the response you get from the service you are calling.
A quick walk through:
String responseAsString = restTemplate.getForObject(ACCOUNT_URI_BASE + accountId, String.class);
This executes the first REST request and gets it as a String (the JSON response). You will want to properly map this using a Bean object instead. Then this is processed using the ObjectMapper provided by Jackson and transformed into a map so you can navigate the JSON and get the matches.
List<CompletableFuture<Void>> futures = matches.stream()
.map(m -> (String) m.get("gameId"))
.map(gameId -> CompletableFuture.supplyAsync(() -> restTemplate.getForObject(GAME_URI_BASE + gameId, String.class), executor)
.thenAccept(r -> {
System.out.println(r); //do whatever you wish with the response here
}))
.collect(Collectors.toList());
Once we have all the matches we will use the Stream API to transform them into CompletableFutures that will be executed asynchronously. Each thread will make another request in order to get the response for each individual matchId.
System.out.println(r);
This will be executed for each response that you get for each matchId, just like in your example. This should also be replaced by a proper bean matching the output for clearer processing.
Note that List<CompletableFuture<Void>> futures only "holds the code" but will not get executed until we combine everything in the end using CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).get(); and execute the blocking get() method.
Quite interesting question as JavaScript implements the famous event loop which means its functions are asynchronous and non-blocking. Spring Boot restTemplate class will block the thread of execution until the response is back, therefore wasting a lot of resources (the one thread per request model).
#Slacky's answer is technically right as you asked about asynchronous HTTP requests but I'd like to share a better option which is both asynchronous and non-blocking, meaning a single thread is able to handle 100s or even 1000s of requests and their responses (reactive programming).
The way to implement in Spring Boot the equivalent to your JavaScript example is to use the Project Reactor WebClient class which is a non-blocking, reactive client to perform HTTP requests.
It is also worth mentioning that Java being statically typed requires you to declare classes to represent your data, in this case something like (using Lombok for brevity):
#Data
class Match {
private String gameId;
// ...
}
#Data
class MatchDetails {
// ...
}
Here is the code following #Slacky's answer naming convention to make the comparison easier.
public class GamesProcessor {
private static final String BASE_URL = "https://la2.api.riotgames.com";
private static final String GAME_URI = "/lol/match/v4/matches/%s";
private static final String ACCOUNT_URI = "/lol/match/v4/matchlists/by-account/%s";
public static List<MatchDetails> processGames(String accountId) {
final WebClient webClient = WebClient
.builder()
.baseUrl(BASE_URL)
.defaultHeader("X-Riot-Token", "asdasdasdasdadasdasdasd")
.build();
// Issues the first request to get list of matches
List<Match> matches = webClient
.get()
.uri(String.format(ACCOUNT_URI, accountId))
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(new ParameterizedTypeReference<List<Match>>() {})
.block(); // blocks to wait for response
// Processes the list of matches asynchronously and collect all responses in a list of matches details
return Flux.fromIterable(matches)
.flatMap(match -> webClient
.get()
.uri(String.format(GAME_URI, match.getGameId()))
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(MatchDetails.class))
.collectList()
.block(); // Blocks to wait for all responses
}
}

Spring RestTemplate - async vs sync restTemplate

I wrote the following code to test the performance of both the sync RestTemplate and AsyncRestTemplate. I just ran it a few times manually on POSTMAN.
We are just passing 10 references into a GET call so that we can return 10 links:
RestTemplate - synchronous and returns in 2806ms:
ArrayList<String> references = new ArrayList<>();
ArrayList<String> links = new ArrayList<>();
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
for (int i = 0; i < 10; i++) {
ResponseEntity<String> resource = restTemplate.getForEntity(references.get(i), String.class);
links.add(resource.getBody().toString());
}
RestTemplate - asynchronous and returns in 2794ms:
//Creating a synchronizedList so that when the async resttemplate returns, there will be no concurrency issues
List<String> links = Collections.synchronizedList(new ArrayList<String>());
//CustomClientHttpRequestFactory just extends SimpleClientHttpRequestFactory but disables automatic redirects in SimpleClientHttpRequestFactory
CustomClientHttpRequestFactory customClientHttpRequestFactory = new CustomClientHttpRequestFactory();
//Setting the ThreadPoolTaskExecutor for the Async calls
org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor pool = new org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor();
pool.setCorePoolSize(5);
pool.setMaxPoolSize(10);
pool.setWaitForTasksToCompleteOnShutdown(true);
pool.initialize();
//Setting the TaskExecutor to the ThreadPoolTaskExecutor
customClientHttpRequestFactory.setTaskExecutor(pool);
ArrayList<String> references = new ArrayList<>();
ArrayList<String> links = new ArrayList<>();
AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate(customClientHttpRequestFactory);
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
for (int i = 0; i < 10; i++) {
Future<ResponseEntity<String>> resource = asyncRestTemplate.getForEntity(references.get(i), String.class);
ResponseEntity<String> entity = resource.get(); //this should start up 10 threads to get the links asynchronously
links.add(entity.getBody().toString());
}
In most cases, both methods actually return back the results with a very similar time, averaging 2800ms in both async and sync calls.
Am I doing something incorrect as I would have expected the async call to be much faster?
Nowadays, AsyncRestTemplate is #Deprecated in favor of WebClient. So nobody should use that class anymore!
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/reactive/function/client/WebClient.html
I would say that you're missing the real benefits of the AsyncRest here.
You should add callbacks to each requests you're sending so that the response will be processes only when available.
Indeed, the getForEntity method of an AsyncRestTemplate returns a ListenableFuture to which you can connect a callback task. See the official doc ListenableFuture for further information.
For example in your case it could be:
for (int i = 0; i < 10; i++) {
ListenableFuture<ResponseEntity<String>> response = asyncRestTemplate.getForEntity(references.get(i), String.class);
response.addCallback(new ListenableFutureCallback<ResponseEntity<String>>() {
#Override
public void onSuccess(ResponseEntity<String> result) {
// Do stuff onSuccess
links.add(result.getBody().toString());
}
#Override
public void onFailure(Throwable ex) {
log.warn("Error detected while submitting a REST request. Exception was {}", ex.getMessage());
}
});
}
The tricky thing with Java Future is that it's not composable and it's really easy to block.
In this case, calling future.get() makes your code block and wait until the response is back. In fact, this approach makes sequential calls and does not leverage the async nature of this RestTemplate implementation.
The simplest way to fix this is to separate it in two loops:
ArrayList<Future<ResponseEntity<String>>> futures = new ArrayList<>();
for (String url : references.get()) {
futures.add(asyncRestTemplate.getForEntity(url, String.class)); //start up to 10 requests in parallel, depending on your pool
}
for (Future<ResponseEntity<String>> future : futures) {
ResponseEntity<String> entity = future.get(); // blocking on the first request
links.add(entity.getBody().toString());
}
Obviously there are more elegant solutions, especially if using JDK8 streams, lambdas and ListenableFuture/CompletableFuture or composition libraries.

Categories

Resources