Spring을 통해 개발을 하다 보면 필연적으로 외부 API 호출이 필요한 경우가 있다. 특히 특정 API에 대한 정보가 필요하거나 SaaS 제품 혹은 서버간 통신에서 굉장히 많이 사용하게 된다.
그 중 가장 많이 사용되는 OpenFeign, RestClient, WebClient들을 살펴볼 것이고, 차이점과 실제로 어떤 상황에서 사용되고 있는지를 알아볼 것 이다.
OpenFegin
OpenFegin은 NetFlixd에 의해 처음 개발 되었으며, 선언적 HTTP Client 도구이다.
외부 API 호출을 간편하게 할 수 있게 도와주며 선언적이기 때문에 직관성이 높다.
조금 더 부연설명을 하자면, Netflix OSS 프로젝트의 일부로써 Netflix OSS가 공개되고 나서 Spring Cloud 진영은 Spring Cloud Netflix라는 프로젝트로 Netflix OSS를 Spring Cloud 생태계로 포함시켰는데, Feign은 단독으로 사용될 수 있도록 별도의 starter로 제공되었다.
그것이 바로 Spring Cloud OpenFeign이다.
Spring Cloud OpenFeign
spring.io
장점은 위에서 언급한 대로 간결하고 직관적인 부분이다.
- 인터페이스와 어노테이션 기반으로 간단하게 코드 작성이 가능하다.
- Spring MVC를 기반으로 사용한다면 개발이 매우 쉬워진다.
- 다른 Spring Cloud의 기술들과 통합하여 사용이 쉽다.
만약 RestTemplate을 그대로 사용한다면 그 코드의 양은 OpenFeign에 비해 비대하다.
// RestTemplate
fun getProductList(apiKey: String): String? {
val url = "https://api.example.com/products"
val headers = HttpHeaders().apply {
set("apikey", apiKey)
}
val entity = HttpEntity<String>(headers)
val response: ResponseEntity<String> = restTemplate.exchange(
url,
HttpMethod.GET,
entity,
String::class.java
)
return response.body
}
// Open Feign
@FeignClient(name = "productClient", url = "https://api.example.com")
interface ProductClient {
@GetMapping("/products")
fun getProductList(@RequestHeader("apikey") apiKey: String): String
}
위 같은 형식으로 매우 간편하게 코드가 줄고 가독성 부분에서 차이가 크다.
그렇다면 단점은?
- Http Client가 http2를 지원하지 않음
- 공식적으로 비동기 방식의 Reactive 모델을 지원하지 않음 (비공식으로 지원함)
- 애플리케이션이 초기화 되어 작동할 때 필요 설정으로 인해 에러가 발생할 수 있음
- 테스트 도구를 제공하지 않음. 다른 테스트 툴을 써야함. (ex - WireMock)
위 같은 단점이 존재하지만 동기적 호출이 필요한 상황에서 Spring MVC 기반 RestTemplate을 선택하기 보다는 OpenFeign이 더 강력하다.
RestClient
이는 Spring Boot 3.2.0에서 공식적으로 지원하는 HTTP Client이다.
Spring에서 제공하는 다른 HTTP Client의 대표적 문제점은 아래와 같다.
- RestTempalte : 직관적이지 못하며, 모든 HTTP의 기능을 노출하는 것이 부담이 됨.
- WebClient : WebFlux의 의존성이 필요함.
위 같은 문제점을 인식하여 Spring Boot 3.2.0에서 WebClient와 유사한 형식의 동기식 HTTP Client인 RestClient가 탄생한 것이다. 한 마디로 서블릿 버전의 WebClient인 것이다.
사용은 아래와 같이 굉장히 간편하게 사용할 수 있다.
val restClient = RestClient.create()
val result = restClient.get()
.uri("<https://example.com>")
.retrieve()
.body(String::class.java)
// Json
val result = restClient.get()
.uri("<https://example.com>")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.body(Car::class.java)
그리고 RestClient의 exchange를 사용한다면 더욱 advanced 한 시나리오를 다룰 수 있다. 이를 통해 내부 request와 response에 접근하여 오류 처리 혹은 응답 처리를 컨트롤 할 수 있기 때문이다.
val result = restClient.get()
.uri("<https://example.com>/cars/{id}", id)
.accept(MediaType.APPLICATION_JSON)
.exchange((request, response) -> {
if (response.getStatusCode().is4xxClientError()) {
throw CustomException(response.statusCode(), response.haeders())
}
else {
return convertResponse(response)
}
})
WebClient
Spring WebClient는 Single Thread와 Non-Blocking방식을 사용하는 HTTP Client이다. 이는 WebFlux에 대한 의존성이 필요하며 비동기식 호출에 필요할 때 많이 사용된다.
결과적으로 요청자와 제공자 사이의 통신을 좀 더 효율적이게 하기 위해 Non-Blocking 방식으로 처리 되는 것이다. RestTemplate과 비교해보면 더욱 쉽게 알 수 있다.
아래는 RestTemplate의 방식이다. Mulit Thread와 동기 방식을 사용한다.

- Thread Pool은 어플리케이션 구동 시에 미리 만들어 놓는다
- Request는 먼저 Queue에 쌓이고 가용한 쓰레드가 있으면 그 쓰레드에 할당되어 처리된다.
- 즉 1요청 당 1스레드가 할당된다.
- 각 쓰레드에서는 Blocking 방식으로 처리되어 응답이 올때까지 그 스레드는 다른 요청에 할당 될 수 없다.
많이 보던 방식이다. Spring MVC의 Thread per request와 굉장히 유사하다.
WebClient를 살펴보자. 그림출처

- 각 요청은 Event Loop 내에 Job으로 등록이 된다.
- Event Loop는 각 Job을 제공자에게 요청한 후, 결과를 기다리지 않고 다른 Job을 처리한다.
- WebClient는 이렇게 이벤트에 반응형으로 동작하게 설계되었다.
- 그래서 반응성, 탄력성, 가용성, 비동기성을 보장하는 React 프레임워크를 사용한다.
- Spring WebFlux 에서 Http Client로 사용된다.
뭔가 Node.js 이벤트 루프 같지 않은가? 이런 방식으로 인해 Non-Bloking 형식의 HTTP Client 활용할 수 있다.
사용벙은 아래와 같이 RestClient와 비슷하게 사용된다.
val response = webClient.get()
.uri("https://api.example.com/products")
.header("apikey", apiKey)
.retrieve()
.bodyToMono(String::class.java)
return response
실사용은 어떨 때 할까?
이에 대한 실사용은 다른 블로그나 예제들에서 알려주는 것처럼 일반적으로 비동기/동기 기준으로 나뉘게 된다.
WebClient는 예를 들어 MSA에서의 서비스 간 통신 에서 주문 서비스가 재고 서비스, 결제 서비스를 동시에 호출해야 할 때 사용하면 효율적으로 통신 시간을 단축 시킬 수 있다. 또한 이는 Coroutine(코루틴)과의 조합도 좋으며 널리 사용하는 것으로 알고있다.
RestClient는 단순한 REST API 호출이 필요한 경우 외부 API를 간단히 호출하는 독립형 애플리케이션에서 많이 사용된다. 사용법도 간단하기 때문에 불필요한 의존성 없이 바로 사용할 수 있다.
OpenFeign은 MSA + Spring Cloud 환경에서 많이 사용된다. 특히 Spring Cloud와 궁합도 잘 맞고 설정도 간편하다. 여러 서버에 대한 호출이 필요하며 Spring Cloud를 활용하는 환경이라면 좋은 선택지가 될 수 있다. 이 뿐만 아니라, 선언적 API 클라이언트가 필요한 경우 코드의 가독성과 유지보수성이 중요할 때나 여러 팀이 API를 공유하는 환경에서 좋은 선택지가 될 수 있다.
'서버 개발(생각과 구현)' 카테고리의 다른 글
서킷 브레이커(CircuitBreaker)가 필요한 경우 (0) | 2024.12.21 |
---|---|
Spring Retry(재시도)가 필요한 경우 (0) | 2024.12.20 |
Kotest를 활용한 유닛 테스트 구성 (0) | 2024.12.19 |
Kotlin-jdsl과 Querydls의 차이점과 선택 과정 (0) | 2024.12.19 |
코틀린을 활용한 JPA 엔티티 전략 고민 과정 (0) | 2024.12.19 |