Using CompletableFuture with RestTemplates in Spring-Boot Part 1
Use case
RestTemplate is a synchronous web-client to perform http/s requests from spring applications.
Configuration
We first define the bean to be injected in our services using following config:
1@Configuration
2public class Config {
3
4 @Bean
5 public RestTemplate restTemplate() {
6 return new RestTemplate();
7 }
8
9}
Example
Suppose we have a couple of external apis returing some data given below:
1@RestController
2@RequestMapping(value = "/users")
3public class UsersApis {
4
5 @GetMapping(value = "/profile")
6 public Map<String, Object> profile() {
7 return Map.of("profile",
8 Map.of("name", "someone",
9 "active", true,
10 "email", "user@gmail.com")
11 );
12 }
13
14 @GetMapping(value = "/address")
15 public Map<String, Object> address() {
16 return Map.of("address",
17 Map.of("state", "KA",
18 "country", "India",
19 "zipcode", "000 000")
20 );
21 }
22}
We now want our spring-app to call these api compose a single result pass it to the caller. The simple way to call these api one after the other and compose the response of each of this write to the caller. Here is how we can do it:
1@RestController
2@RequestMapping(value = "/compose")
3@AllArgsConstructor
4public class ApiCompositionController {
5 private final RestTemplate restTemplate;
6
7 @GetMapping(value = "/simple")
8 public Map<String, Object> compose() {
9 return Map.of("status", "simple",
10 "profile", new UserProfileSupplier(restTemplate).get().map(UserProfileResponse::getProfile).orElse(new UserProfile()),
11 "address", new UserAddressSupplier(restTemplate).get().map(UserAddressResponse::getAddress).orElse(new UserAddress())
12 );
13 }
14}
Here UserProfileSupplier
& UserAddressSupplier
are given below which are nothing but classes implementing java.util.function.Supplier<T>
interface for returning response from external apis:
1@Slf4j
2@AllArgsConstructor
3public static class UserProfileSupplier implements Supplier<Optional<UserProfileResponse>> {
4 private final RestTemplate restTemplate;
5
6
7 @SneakyThrows
8 @Override
9 public Optional<UserProfileResponse> get() {
10 log.debug("Executing UserProfile in: " + Thread.currentThread().getName());
11 Thread.sleep(1000L);
12 final ResponseEntity<UserProfileResponse> response = restTemplate
13 .getForEntity("http://localhost:8080/users/profile", UserProfileResponse.class);
14 if (response.hasBody()) {
15 return Optional.ofNullable(response.getBody());
16 }
17 return Optional.empty();
18 }
19}
And
1@Slf4j
2@AllArgsConstructor
3public static class UserAddressSupplier implements Supplier<Optional<UserAddressResponse>> {
4 private final RestTemplate restTemplate;
5
6 @SneakyThrows
7 @Override
8 public Optional<UserAddressResponse> get() {
9 log.debug("Executing UserAddress in: " + Thread.currentThread().getName());
10 Thread.sleep(1000L);
11 final ResponseEntity<UserAddressResponse> response = restTemplate
12 .getForEntity("http://localhost:8080/users/address", UserAddressResponse.class);
13 if (response.hasBody()) {
14 return Optional.ofNullable(response.getBody());
15 }
16 return Optional.empty();
17 }
18}
For testing purpose we have added 1s delay to the caller of these apis. So when these 2 apis are called sequentially we cannot get composite result less than 2 seconds as these calls are happening sequentially.
Lets execute ab
tool to benchmark these apis:
1ab -n 1000 -c 100 http://localhost:8080/compose/simple
2
3Server Software:
4Server Hostname: localhost
5Server Port: 8080
6
7Document Path: /compose/simple
8Document Length: 158 bytes
9
10Concurrency Level: 100
11Time taken for tests: 22.102 seconds
12Complete requests: 1000
13Failed requests: 0
14Total transferred: 263000 bytes
15HTML transferred: 158000 bytes
16Requests per second: 45.24 [#/sec] (mean)
17Time per request: 2210.237 [ms] (mean)
18Time per request: 22.102 [ms] (mean, across all concurrent requests)
19Transfer rate: 11.62 [Kbytes/sec] received
20
21Connection Times (ms)
22 min mean[+/-sd] median max
23Connect: 0 0 1.0 0 5
24Processing: 2001 2007 9.3 2003 2045
25Waiting: 2001 2006 9.1 2003 2045
26Total: 2002 2007 10.0 2003 2049
27
28Percentage of the requests served within a certain time (ms)
29 50% 2003
30 66% 2005
31 75% 2006
32 80% 2007
33 90% 2013
34 95% 2038
35 98% 2046
36 99% 2047
37 100% 2049 (longest request)
We are testing total 1000 requests and 100 concurrent request on server. As from the output we can see that the server is serving ~45 r/s.
Lets not challenge this number as we a re calling an api which is internally other 2 apis whose response time is minimum is 1s. As the get()
is a blocking call on suppliers. In the logs you can see that these api calls are being exeuted on thread http-nio-8080-exec-number
.
In the next post we will compose these to api using completable future and see if we can do something better.
comments powered by Disqus