I need to make a service in an existing fat code to get results from 4 APIs and I need to merge them and reformat each responses, but it takes very slow due to 4 calls that I don't know how to do it concurrently. I am also unable to change the main to add Runnable
or such executor in the main as it may have snowballing effect to another code.
So currently, I have made a controller which handle the request, a service which get the request from user and call 5 different service-middleware (SM) functions. Every SM functions used to call an external API, and in every SM, I reformat each return map of the APIs there as well. I use java.net.HttpURLConnection
to do the API calls. Thus, I got my API "worked" but can't be faster than 4 seconds. Those APIs needs additional OAuth, so it would be roughly 10 API calls in total.
Since the current returns of API calls are Object
type, I can treat it as Map
, and reformat the output by doing looping for the data inside it. So the SM function would likely have the code similarly to below:
token = sendHttpRequest(authUrl, authRequestHeader, null, null, "GET");
Map response = sendHttpRequest(url, requestHeader, bodyParam, null, "POST");
List<Map> data = (List) ((Map) response.get("output")).get("data");
List<Map> result = new HashMap();
for(Map m : data) {
Map temp = new HashMap();
temp.put("name", m.get("Name"));
temp.put("health_status", m.get("HealthStatus"));
result.add(temp);
}
// This format is mandatory
Map finalResult = new HashMap();
finalResult.put("output", result);
finalResult.put("status", "OK");
return finalResult;
And the sendHttpRequest
is the method to send request, serializing params to JSON and deserializing API output to be an Object
. Here's the sendHttpRequest
look like:
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(csf)
.build();
HttpComponentsClientHttpRequestFactory requestFactory =
new HttpComponentsClientHttpRequestFactory();
requestFactory.setConnectTimeout(this.connectTimeOut);
requestFactory.setReadTimeout(this.readTimeOut);
requestFactory.setHttpClient(httpClient);
RestTemplate rt = new RestTemplate(requestFactory);
HttpEntity<Map> request = null;
if(method.equals("POST"))
request = new HttpEntity<Map>(objBody, headers);
else if(method.equals("GET"))
request = new HttpEntity<Map>(headers);
try {
ResponseEntity<Map> response = null;
if(method.equals("POST"))
restTemplate.postForEntity(url, request , Map.class);
if(method.equals("GET"))
restTemplate.postForEntity(url, request , Map.class);
if(this.outputStream){
logger.debug("Output : " + response.getBody());
}
return response.getBody();
} catch(HttpClientErrorException e) {
logger.debug(e.getMessage());
}
The sendHttpRequest
method is also an existing method that I am disallowed to change except if I just make a new method for doing my requests only.
Simply say, here's the things I need to do:
For each of the API calls:
- Get the Authorization token from an external API.
- Do the request (POST/GET) to another external API to get data.
- Reformat the data to be expected format for response (each has its own format) <Mostly loop the array of the response object to remap the field names as it's necessary>.
After all APIs finished calling, I need to do:
- Merge output from API 1 and 3 to a Map/Object
- Merge output from API 2 & 4 to an Array and sort them all
- Put response from API 5 in the inner object of a defined attribute/field.
Things I had tried
I had tried the use of ExecutorCompletionService
to call the 5 SMs. I also created an inner class that implements Callable
for this.
ExecutorService executor = Executors.newFixedThreadPool(5);
CompletionService completionService = new ExecutorCompletionService<>(executor);
List<Future<Map>> results = new ArrayList<>();
for(int i=1; i<6; i++) {
// i here is used to define which api calls to be done
results.add(completionService.submit(new CallAPIClass(paramMap, i)));
}
for (int i=0; i < results.size(); i++) {
try {
Map result = (Map) completionService.take().get();
int code = (int) result.get("code");
// Collect the results for each SM (SM function has described above)
} catch (Exception e) {
logger.debug(e.getMessage());
}
}
// Merge the outputs.
In the Merge the outputs, I need to construct the map, so it will be like this:
{
"details": {"api1": {...}, "api3": {...}},
"list_items": [{...}, {...}, ...], // Results of sorted merged lists from api2 & api4
"api5": [{...}, {...}, {...}, ...]
}
Meanwhile, from the api responses, basically I just retrieve all of their output_schema
when exists.
Any tips to optimize and speed up this API call, so by the same number of calls, this can be executed faster??? Any help is greatly appreciated.
Edit
I have read @Ananthapadmanabhan's answer, but it seems that I need to change the main class file which I can't do. Or is it actually possible to apply the use of CompletableFuture
without using @EnableAsync in main class?
I also wonder how to get this done in a faster time even with CompletableFuture and EnableAsync with this chain of processes.