[Spring] 스프링에서 외부 API 호출하는 방법들
✅ 개요
외부 API를 사용해야하는 상황이 생겨서 호출 방법에 고민을 하고 있다.
빠른 사용/개발을 위해서는 사용하고자 하는 API의 클라이언트 라이브러리 자체를 이용해도 되지만,
이 경우 특정 API에 종속되면서, 정책변경 및 가격변경에 영향을 받게 된다.
요구 스킬의 변화와 유연한 확장을 위해 외부 API를 호출하는 방법들을 선택해야 할듯 해 이 글을 적게 되었다.
✅ 외부 API를 호출을 위한 방법들 비교
외부 API 를 호출하고, 응답값을 받아오는 방법에는 5가지 정도가 있다.
각 기술의 장단점과 특징을 살펴보자
- HttpURLConnection/URLConnection
- RestTemplate
- HttpClient
- WebClient
- OpenFeign
1️⃣ HttpURLConnection/URLConnection
http통신을 가능하게 해주는 클래스이다.
- 자바에서 제공하는 기본적인 API
- 순수 자바로 HTTP통신이 가능하다.
- URL을 이용하여 외부 API에 연결하고 데이터를 전송할 수 있다.
- 데이터의 타입/길이에 거의 제한이 없다.
😵 단점
오래된 자바 버전에서 사용하는 클래스로 동기적 통신을 기본으로 사용한다.
동기적이므로 요청을 보내고, 응답을 받을 때까지 스레드가 대기 상태에 있다.
URLConnection은 상대적으로 저수준의 API에 해당된다.
기본적인 요청/응답기능이 제공되지만, 추가적인 기능들에 대해선 직접 구현해야 된다.
더 많은 기능과 유연성을 위해서는 다른것을 채택하는게 좋겠다.
2️⃣ HttpClient
HTTP프로토콜을 쉽게 사용할 수 있도록 도와주는 Apache HTTP 컴포넌트
😉 장점
- 객체 생성이 쉽다.
아래 예제 코드를 보자.
HttpClient clent = HttpClientBuilder.create().build();처럼 굉장히 간단하게 생성하는것을 확인할 수 있다.
public void get(String requestURL) {
try {
HttpClient client = HttpClientBuilder.create().build(); // HttpClient 생성
HttpGet getRequest = new HttpGet(requestURL); //GET 메소드 URL 생성
getRequest.addHeader("x-api-key", RestTestCommon.API_KEY); //KEY 입력
HttpResponse response = client.execute(getRequest);
//Response 출력
if (response.getStatusLine().getStatusCode() == 200) {
ResponseHandler<String> handler = new BasicResponseHandler();
String body = handler.handleResponse(response);
System.out.println(body);
} else {
System.out.println("response is error : " + response.getStatusLine().getStatusCode());
}
} catch (Exception e){
System.err.println(e.toString());
}
}
😵 단점
- URLConnection방식보다 코드가 간결해졌지만, 반복적이고 코드가 길다.
- 응답의 컨텐츠 타입에 따라 별도의 로직이 필요하다.
3️⃣ spring RestTemplate
스프링에서 제공하는 http 통신에 유용하게 사용가능한 템플릿이다.
특징
- 스프링3.0에서 추가되었다.
- org.springframework.http.client 패키지에 존재
- HpptClient를 추상화해서 제공한다.
- 대부분이 다른 API를 호출할 때 RestTemplate를 사용한다.
- HTTP요청 후 JSON, XML, String 과 같은 응답을 받을 수 있다.
- Blocking 기반의 동기방식을 사용한다.
- HTTP 서버와의 통신을 단순화하고 Restful 원칙을 지킨다.
- header, content-type등을 설정하며 외부 API를 호출한다.
- 서버-서버 통신에 사용한다.
😉 장점
사용하기 편하고, 직관적이다.
😵 단점
동기적인 HTTP 요청을한다.
커넥션 풀을 기본적으로 사용하지 않아서 연결할 때마다 로컬 포트를 열고, tcp connection을 맺는데....
이것을 해결하기 위해 connection pool을 사용해야한다.
🤔 Deprecate 논란/이슈
구글링을 하다보면 RestTemplate이 deprecate되었다는 이야기가 정말 많았다.
✔️ deprecate 의미는?
- 대안기술이 나왔기 때문에, 사용하는걸 권장하지 않음
- 곧 제거될 예정
그러다가 다음과 같은 글을 찾았는데 .....
https://velog.io/@dailylifecoding/Spring-RestTemplate-wont-Deprecate
<토비의 스프링> 유튜브 채널에서 RestTemplate의 deprecated의 오해와 진실에 대한 이야기를 말했다.
👩💻 이슈 정리
➡️ RestTemplate는 deprecated?
1. 스프링 프로젝트의 리드 개발자분이 올린 Issue
Deprecation는 정확하지 않은 표현이라고 하는 의미는 -> 제거되지 않는다로 받아들여도 될것 같다.
유지관리모드에 있다가 더 정확한 표현임을 말해주고 계셨다.
2. 공식문서에도 유지관리모드에 있다는 표현으로 변경되어있다.
Java Doc에 WebClient 사용을 권고한다는 말이 존재한다.
RestTemplate가 Deprecated 라는 말은 적혀있지 않다.
참고: 5.0부터 이 클래스는 유지 관리 모드에 있으며 앞으로 허용될 변경 및 버그에 대한 사소한 요청만 있습니다. 보다 현대적인 API가 있고 동기화, 비동기 및 스트리밍 시나리오를 지원하는 org.springframework.web.reactive.client.WebClient 사용을 고려하십시오.
동기적인 기술이기 때문에
대규모 요청이 들어올 것을 대비해선 비동기/넌블로킹 방식을 지원하는 WebClient를 고려해볼 수 있다는 결론
* Spring4에서 추가된 비동기 RestTemplate인 AsyncRestTemplate도 존재한다.
4️⃣ webClient
스프링 5부터 도입된 웹 클라이언트 라이브러리
비동기/논블로킹 방식으로 외부 API를 호출할 수 있다.
😉 장점
비동기/논블로킹 방식을 지원하여, 높은 처리량과 확장성을 지원한다.
리액티브 프로그래밍을 할 수 있다. 데이터 스트림을 효과적으로 처리할 수 있다.
😵 단점
웹플럭스 학습곡선이 존재한다.
웹플럭스(Webflux)를 추가해야만 사용할 수 있다. >> (웹플럭스 모듈이 통채로 추가된다.)
작은 규모의 간단한 프로젝트에서는 웹플럭스모듈이 통채로 추가되는것이 좋을까..?
예제코드를 봐보자.
가독성 좋은것이 아주 잘 느껴진다
package com.webclient.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import java.util.HashMap;
import java.util.Map;
@Service
@Slf4j
public class WebClientServiceImpl {
public void get() {
String code = "myCode";
// webClient 기본 설정
WebClient webClient =
WebClient
.builder()
.baseUrl("http://localhost:8080")
.build();
// api 요청
Map<String, Object> response =
webClient
.get()
.uri(uriBuilder ->
uriBuilder
.path("/api/get")
.queryParam("code", code)
.build())
.retrieve()
.bodyToMono(Map.class)
.block();
// 결과 확인
log.info(response.toString());
}
}
5️⃣ OpenFeign
최근에 처음 알게된 기술이름이다.
선언적 웹서비스 클라이언트
✔️선언적?
어노테이션 사용을 의미
인터페이스에 어노테이션들만 붙혀줌으로써 구현이 된다. (Spring Data JPA와 유사)
스프링
- Netflix 에서 개발된 Http client binder
- 웹서비스 클라이언트를 쉽게 작성할 수 있도록 지원한다.
- interface를 작성하고, annotation을 선언하기만 하면 된다. >> 스프링데이터JPA에서 인터페이스만 지정하여, 자동으로 구현체를 사용하는것과 유사
RestTemplate 으로 API호출시..
> API호출이 잦은 MSA시대에선 이런 코드를 반복하는것은 번거롭다 (인용- 망나니개발자)
@Component
@RequiredArgsConstructor
class ExchangeRateRestTemplate {
private final RestTemplate restTemplate;
private final ExchangeRateProperties properties;
private static final String API_KEY = "apikey";
public ExchangeRateResponse call(final Currency source, final Currency target) {
return restTemplate.exchange(
createApiUri(source, target),
HttpMethod.GET,
new HttpEntity<>(createHttpHeaders()),
ExchangeRateResponse.class)
.getBody();
}
private String createApiUri(final Currency source, final Currency target) {
return UriComponentsBuilder.fromHttpUrl(properties.getUri())
.queryParam("source", source.name())
.queryParam("currencies", target.name())
.encode()
.toUriString();
}
private HttpHeaders createHttpHeaders() {
final HttpHeaders headers = new HttpHeaders();
headers.add(API_KEY, properties.getKey());
return headers;
}
}
OpenFeign으로 작성되면?
@FeignClient(name = "ExchangeRateOpenFeign", url = "${exchange.currency.api.uri}")
public interface ExchangeRateOpenFeign {
@GetMapping
ExchangeRateResponse call(
@RequestHeader String apiKey,
@RequestParam Currency source,
@RequestParam Currency currencies);
}
😉 장점
- 인터페이스/어노테이션 기반으로 코드 작성이 쉽다.
- RestTemplate보다 RESTAPI를 사용하는데 필요한 설정이 간편해진다.
- 즉 개발자가 비즈니스 로직에 더 집중할 수 있다.
- 다른 라이브러리에 비하면 학습하기 쉽다.
- 다른 Spring Cloud 기술들과의 통합이 쉽다.
😵 단점
- spring cloud 모듈을 추가해야한다. > 필요없는 모듈들이 추가되는걸수도
- HttpClient가 Http2를 지원하지 않는다. (Http Client에 대한 추가 설정이 필요하다)
- 공식적으로 Reactive모델을 지원하지 않는다.
- 테스트 도구를 제공하지 않는다 (테스트 코드에 문제가 없는지 빠르게 확인하기 어렵다)
References
https://docs.oracle.com/javase/8/docs/api/java/net/HttpURLConnection.html
https://techblog.woowahan.com/2630/
https://blog.naver.com/hj_kim97/222295259904
https://mangkyu.tistory.com/278
추후 참고해볼 글들..
스프링 RestTemplate
https://blog.naver.com/hj_kim97/222295259904
HttpClient를 사용할 경우 ConnectionPool을 생성하는것 관련?
webclient로 외부 API 호출하기