프로젝트를 진행하다 외부 API와 Backend쪽 데이터를 합쳐야 했다.
처음에는 단순 무식하게 Frontend를 통해 API 통신을 하려고 했으나
위의 사진과 같이 데이터 전달 받겠다고 통신을 몇번 왔다갔다 하는 것은 아무리 봐도 비효율적이라고 생각했다.
이를 위해 다른 방법을 찾기로 하였다.
생각해낸 방법
외부 API를 호출하고 응답값을 받아오는 방법 중 가장 간단한 방법에는 RestTemplate이 있다.
Spring 3부터 지원하는 객체로 간편하게 Rest 방식으로 API를 호출할 수 있는 Spring 내장 클래스이다.
특징
@GetMapping("/example/{id}")
public ResponseEntity<ResponseDto<?>> exampleApi(@AuthenticationPrincipal User user) {
String email = user.getUsername();
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.add("key","123455678");
headers.add("email",email);
HttpEntity request = new HttpEntity(headers);
ResponseEntity<UserLogDto> response
= restTemplate.exchange("https://외부API.com",
HttpMethod.GET,
request,
UserLogDto.class);
List<UserLogResponseDto> response = userService.getUserLogResponse(response.getBody(), email);
return ResponseEntity.ok(ResponseDto.success(res));
}
그러나 RestTemplate는 명확한 단점이 있다.
이유는 RestTemplate 동작 방식에 있다.
위에서 말했듯이 RestTemplate는 Multi-thread, Blocking 방식으로 동작된다.
Thread Pool은 어플리케이션 구동시 미리 Thread를 미리 만들어 놓는다.
이후 Request가 오면 먼저 Queue에 쌓이고 비어있는 스레드가 있으면 그 스레드에 할당된다.
각 스레드는 Blocking 방식으로 처리되어 응답이 올 때까지 그 스레드는 다른 요청에 할당될 수 없다.
이러한 방식은 만들어놓은 스레드가 다 찰 경우 문제가 발생한다.
스레드가 다 차는 경우 Queue에서 대기를 하게 되고 가용 가능한 스레드 수가 줄어들어 병목 현상이 나타나 서비스 성능을 저하시킬 수 있다. (이를 위해 Connection pool 값을 설정할 수 있으나 서버 사양에 따라 한계가 있다.)
당시에는 RestTemplate 방법만 알고 있었고, webclient는 webflux 환경에서 사용해야 해서 RestTemplate 방법을 활용하였다. (지금 돌이켜보면 OpenFeign을 활용할 것 같다...)
RestTemplate에서 발생하는 단점은 WebClient를 사용하여 없엘 수 있다.
WebClient는 스프링 5에서 추가된 인터페이스로 Single-thread, Non-Blocking 방식을 사용한다.
Single-thread, non-blocking
이러한 방식을 사용하기 때문에 RestTemplate 보다 더 성능이 좋다.
dependency
implementation 'org.springframework.boot:spring-boot-starter-webflux'
를 build.gradle에 추가해야 한다.
예제 코드
@GetMapping("/v1/api")
public Mono<String> useWebClient() {
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,5000) //timeout 시간 조절
.responseTimeout(Duration.ofMillis(5000))
.doOnConnected(conn ->
conn.addHandlerLast(new ReadTimeoutHandler(5000, TimeUnit.MILLISECONDS))
.addHandlerLast(new WriteTimeoutHandler(5000, TimeUnit.MILLISECONDS)));
var webClient = WebClient.builder()
.baseUrl("http://외부api.com")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
return webClient.get()
.retrieve()
.bodyToMono(String.class);
}
이로써 spring 내부에서 api를 호출할 수 있었다.
이외에도 OpenFeign, HttpInterface 등 외부 API와 통신할 수 있는 방법이 있다고 한다.
자세한 내용은 여기에
Reference
https://mangkyu.tistory.com/291
https://thalals.tistory.com/377
https://tecoble.techcourse.co.kr/post/2021-07-25-resttemplate-webclient/