[Http Client] RestTemplate에서 RestClient 적용으로 최적화
Http Client의 종류는 HttpURLConnection, Apache HttpClient, RestTemplate, OpenFeign, WebClient, RestClient 등으로 다양합니다.
각 HttpClient의 기본 구현에 대해 궁금하다면 아래 게시글을 참조해주세요.
[HTTP Client] 개요
[HTTP Client] 예제
- 2024-06-19 추가
프로젝트를 진행하던 중,
외부 api와 연결하는 것이 필요하여 Http Client 중 RestTemplate 을 적용했습니다.
최적화하지 않은 상태에서 시간을 측정해본 결과,
평균적으로 100밀리초가 소요되는 것을 확인했습니다.
이를 최적화하기 위한 방법을 찾던 중,
RestTemplate의 소요 시간을 단축할 수 있는 Http Client인 WebClient와 RestClient를 발견했습니다.
RestTemplate의 소요 시간이 상대적으로 긴 이유는,
그 이유는 RestTemplate이 동기적인 방식으로 동작하기 때문입니다.
그렇기 때문에 응답을 기다리는 동안 block이 되어,
대기 시간이 증가한 것이었습니다.
이와 달리 WebClient와 RestClient는 비동기 방식을 지원합니다.
WebClient와 RestClient는 기존의 RestTemplate기능을 사용할 수 있습니다.
응답을 기다리는 동안 다른 작업을 진행할 수 있으며, 여러 요청을 동시에 할 수 있습니다.
RestClient는 셋 중에서 가장 최신인 Http Client입니다.
RestClient는 WebClient와 비슷한 Api인 것과 더불어
RestTemplate객체를 매개변수로 받아,
RestTemplate의 인프라(메시지 컨버터, 인터셉터 등)를 사용할 수 있습니다.
/**
* Obtain a {@code RestClient} builder based on the configuration of the
* given {@code RestTemplate}. The returned builder is configured with the
* template's
* <ul>
* <li>{@link RestTemplate#getRequestFactory() ClientHttpRequestFactory},</li>
* <li>{@link RestTemplate#getMessageConverters() HttpMessageConverters},</li>
* <li>{@link RestTemplate#getInterceptors() ClientHttpRequestInterceptors},</li>
* <li>{@link RestTemplate#getClientHttpRequestInitializers() ClientHttpRequestInitializers},</li>
* <li>{@link RestTemplate#getUriTemplateHandler() UriBuilderFactory}, and</li>
* <li>{@linkplain RestTemplate#getErrorHandler() error handler}.</li>
* </ul>
* @param restTemplate the rest template to base the returned builder's
* configuration on
* @return a {@code RestClient} builder initialized with {@code restTemplate}'s
* configuration
*/
static RestClient.Builder builder(RestTemplate restTemplate) {
return new DefaultRestClientBuilder(restTemplate);
}
이미 RestTemplate을 구현한 상태에서, 비동기 처리 등의 기능을 구현하고자 하나,
WebClient 등의 HttpClient를 구현하기 복잡할 경우,
RestClient를 사용하는 것은 좋은 선택입니다.
저는 간단한 기능을 구현하기 때문에 RestTemplate을 이식하는 것이 아니라 RestClient를 구현했습니다.
RestClient와 WebClient의 차이는 의존성에 있습니다.
WebClient를 사용하려면 webflux dependency가 필요하지만,
RestClient 사용 시 webflux dependency를 필요로 하지 않습니다.
이를 구현해본 결과,
동일한 리소스에 대해 평균적으로 40밀리초가 소요되는 것을 확인했습니다.
시간을 단축할 수 있었던 것 뿐만 아니라 인터페이스를 활용하여
api호출을 보다 쉽게 사용할 수 있으며, 이해하기 쉽도록 할 수 있었습니다.
구현 과정은 아래와 같습니다.
1. 기본적인 사용법
builder().build() 또는 create() 로 스프링 빈을 정의합니다.
@Configuration
public class RestClientConfig {
/**
* RestClient 빈 정의 방식 1.
* 이 메서드는 API와 통신하기 위해 RestClient 객체를 생성합니다.
*
* @return 설정된 RestClient 객체
*/
@Bean
public RestClient restClient() {
return RestClient.builder()
.build();
}
/**
* RestClient 빈 정의 방식 2.
* 이 메서드는 API와 통신하기 위해 RestClient 객체를 생성합니다.
*
* @return 설정된 RestClient 객체
*/
@Bean
public RestClient restClient() {
return RestClient.create();
}
}
RestClient 객체를 살펴보면 두 방식의 차이가 거의 없는 것을 볼 수 있습니다.
static RestClient.Builder builder() {
return new DefaultRestClientBuilder();
}
static RestClient create() {
return new DefaultRestClientBuilder().build();
}
2. HttpServiceProxyFactory(HttpInterface)
HttpInterface인 HttpServiceProxyFactory를 활용하여 RestClient를 OpenFeign처럼 선언형으로 사용할 수 있습니다.
먼저 CustomHttpInterface 인터페이스에 메서드를 정의하고
public interfaceCustomHttpInterface{
@GetExchange("areaCode.do?out=json&code={apiKey}")
String getSido(@PathVariable String apiKey);
}
API 스펙에 따라 반환 타입을 String이 아니라 DTO로 설정할 수도 있습니다.
이를 기반으로 HttpInterface Bean을 생성합니다.
@Bean
public CustomHttpInterface customHttpInterface() {
RestClient client = RestClient.builder()
// HttpClient에 필요로하는 설정 입력
.baseUrl(BASE_URL)
.build();
RestClientAdapter adapter = RestClientAdapter.create(client);
return HttpServiceProxyFactory
.builderFor(adapter)
.build()
.createClient(CustomHttpInterface.class);
}
이후 HttpInterface인 HttpServiceProxyFactory에 연결합니다.
이제 이를 주입받아서 사용할 수 있습니다.
private final CustomHttpInterface httpInterface;
메서드를 매개변수와 함께 선언형으로 사용할 수 있습니다.
httpInterface.getSido(properties.getApiKey())
참조
https://spring.io/blog/2023/07/13/new-in-spring-6-1-restclient
https://docs.spring.io/spring-framework/reference/integration/rest-clients.html#rest-restclient