개요
최근에 친구(조킴/특 : 그저빛)랑 얘기하다가 OpenFeign (오픈페인)에 대해 알게 되었다. 최근에는 외부 API 호출 시 RestTemplate이 아니라 OpenFeign을 많이들 이용한다고 하길래, 나도 한 번 알아보고 사용해보기로 했다 🤟
OpenFeign이란?
1️⃣ 소개
OpenFeign은 원래 Netflix에서 만들고 출시한 Feign이 오픈 소스 프로젝트로 확장된 친구다. 우리가 사용할 건 Spring-Cloud-Feign으로, OpenFeign 프로젝트가 Spring Cloud 생태계에 통합된 것이다.
(📌 Netflix Feign -> OSS Feign (OpenFeign) -> Spring Cloud Feign)
근본인 OpenFeign의 공식 github about에 적힌 소개는 다음과 같다.
Feign makes writing java http clients easier
즉, OpenFeign은 java http 클라이언트를 더 쉽게 작성해주는 도구라고 볼 수 있다.
🤔 그럼 OpenFeign와 Spring Cloud OpenFeign은 완전 동일한가요?
아니다! OpenFeign은 Spring Cloud 생태계에 통합되면서 좀 더 확장되었다.
공식 문서에 따르면, Spring MVC 어노테이션과 HttpMessageConverters를 사용에 대한 지원이 추가되었으며, 부하 분산 http 클라이언트를 위한 Spring Cloud의 각종 도구들(Eureka, CircuitBreaker, LoadBalancer)을 제공한다고 한다.
2️⃣ 왜 필요할까?
위에서 살펴봤듯, OpenFeign은 기본적으로 외부 API 호출을 간편하게 할 수 있게 도와주는 도구다. 외부 API는 말 그대로 내 서버 밖에서 제공되는 API를 의미한다. 오픈 데이터, 크롤링, PG 결제 연동, Oauth2 등 서드파티 API를 생각하면 쉽다.
외부 API 호출은 서드파티 사용 이외에 MSA 환경에서도 필수적이다. 시스템이 커져 MSA 구조를 채택한 경우, 도메인별로 서버가 분리되기 때문에 특정 기능을 개발할 때 여러 서버와 통신해야 한다.
(ex. 카카오 택시, 카카오페이, 카카오 웹툰 등은 모두 다른 서비스고 다른 서버에서 동작하지만, 동일한 카카오 로그인 API를 이용해 유저를 관리한다)
이렇듯 서비스 기능이 복잡해질 수록, 서비스 규모가 커질 수록 외부 API를 자주 호출하게 된다. 이 때, 이 호출 과정이 복잡하거나 까다로우면 개발자의 피로도도 커지고 효율성도 낮아질 것이다. Spring Cloud OpenFeign의 공식 문서에는 OpenFeign을 '선언적 웹 클라이언트'라고 소개한다. 선언적 이라는 관형사에 걸맞게, OpenFeign은 어노테이션을 붙여 인터페이스를 선언하는 것만으로 외부 API를 호출할 수 있어 아주 편리하다!
정리하자면, 서드파티나 MSA 환경에서 외부 API를 보다 편하고 효율적으로 사용하기 위해 OpenFeign을 사용한다고 볼 수 있다.
3️⃣ 요즘 많이 쓰이는 이유?
위에서 이야기한 것 처럼 외부 API 호출을 보다 편하게 하기 위해 쓰이는 이유도 있지만, 아무래도 RestTemplate가 유지 모드로 바뀐 것의 영향도 없지 않은 것 같다.
RestTemplate는 Spring에서 지원하는 HTTP Client REST API 호출을 위한 내장 클래스로, 스프링 생태계에선 꽤 유명한 도구다. 실제로 스프링에서 외부 API 호출하는 법을 검색해보면 상당수의 블로그들이 RestTemplate을 사용하고 있다. 그런데 이 RestTemplate이 Spring 5 (Spring Boot 2)부터 업데이트를 멈춘 유지 모드 (maintenance mode)로 변경되었다.
Spring은 대안으로 WebClient 사용을 권고하지만, 유저들이 이것저것 대안을 알아보다가 보다 간단한 OpenFeign을 입소문 낸게 아닐까~ 싶다. WebClient와 OpenFeign의 비교는 아래에서 진행할 예정!
4️⃣ 장점
- 선언적 클라이언트 : 인터페이스와 어노테이션만 선언하면 되기에 사용이 간편하고 가독성이 좋다.
- Spring Cloud 환경과의 통합 : Eureka, CircuitBreaker, LoadBalancer 등의 기술과 통합이 간편하다.
- Spring MVC 와의 통합 : HttpMessageConverters가 제공된다.
- 커스텀이 쉽다 : 인코딩/디코딩, 로깅, REST 호출 구현 라이브러리의 변경 등 개발자 요구대로 고쳐 쓰기 쉽다.
- 테스트가 쉽다 : 표준 java 인터페이스를 사용하기 때문에, 단위 테스트 중 쉽게 모킹할 수 있다.
5️⃣ 단점
- Blocking 클라이언트
- HTTP client에 대한 추가 설정 필요 : Http2를 지원하지 않는다.
- 테스트 설정 필요 : 별도의 테스팅 도구를 지원하지 않는다.
Spring Boot 3 / Gradle 에서 사용해보기
1️⃣ gradle 의존성 추가
총 두 개의 의존성을 추가해야 한다.
- Spring Cloud 의존성
- Spring Cloud에 통합되어 관련 기술을 지원해주는 Open Feign을 쓸 것이다. 따라서 Spring Cloud 의존성을 추가한다.
- 이때 Spring Cloud 버전은 자신이 사용하는 Spring Boot 버전에 적합한 버전을 사용해야 한다. 공식 문서를 참고하자.
- Spring Cloud OpenFeign 의존성
위의 두 의존성을 추가한 예시는 다음과 같다. (SpringBoot 3.0 이용)
// build.gradle
dependencies {
// openfeign
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:4.0.3'
}
ext {
set('springCloudVersion', "2022.0.3")
}
dependencyManagement {
imports {
// spring cloud
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
2️⃣ Spring application에 사용 설정 추가
Spring 설정 파일 등을 이용해 OpenFeign의 사용을 활성화해야 한다. Main application에 바로 붙여도 되지만, main 클래스에 바로 @Enable을 사용하는 것은 테스트에 영향이 갈 수도 있다는 여기 블로그의 글을 참고하여 별도의 설정 파일을 작성했다.
@Configuration
@EnableFeignClients("org.pickly.service.common.utils.openfeign")
class OpenFeignConfig {
}
Main 클래스에 바로 @EnableFeignClients를 붙일 땐 상관없지만, 위처럼 별도의 설정 파일로 분리할 경우 feign 인터페이스의 위치를 꼭 지정해주어야 한다. @EnableFeignClients은 하위 클래스를 탐색하면서 @FeignClient를 찾아 구현체를 생성하는 역할을 하는데, 별도 설정 파일로 분리하면 하위 클래스를 찾을 수 없기 때문이다.
basePackages나 basePackageClasses 속성을 이용해 탐색 범위를 지정해주면 된다. 나는 이번에 openfeign 패키지에만 작성했기 때문에 바로 패키지를 넣어주었다.
3️⃣ application.yml 작성하기
OpenFeign을 사용하기 위해, 어노테이션 안에 호출할 API의 url을 적어줘야 한다. 이때 url을 그냥 적어줘도 되지만, url은 placeholder를 사용할 수 있으니 가독성을 높이기 위해 application.yml을 이용하자.
# application.yml
feign:
naver:
x-naver-client-id: ...
x-naver-client-secret: ...
search:
local:
name: NaverLocalSearchOpenFeign
url: https://openapi.naver.com/v1/search/local
4️⃣ 인터페이스 만들기
인터페이스를 만들기 전, 사용할 외부 API 명세서를 참고해서 Response Object를 먼저 만들자.
@Data
@AllArgsConstructor
@NoArgsConstructor
public class NaverLocalSearchResponse {
private String lastBuildDate;
private int total;
private int start;
private int display;
private List<Item> items;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Item {
private String title;
private String link;
private String category;
private String description;
private String telephone;
private String address;
private String roadAddress;
private int mapx;
private int mapy;
}
}
(예제라서 대강 적었습니다! 여러분은 @Data 말고 필요한 것만 쓰세용)
이제 인터페이스를 만들어주자. 마찬가지로 사용할 외부 API 명세서를 이용해, 호출에 필요한 파라미터와 헤더를 참고하면 된다.
public interface NaverLocalSearchOpenFeign {
List<NaverLocalSearchResponse> search(
@RequestHeader("X-Naver-Client-Id") String clientId,
@RequestHeader("X-Naver-Client-Secret") String clientSecret,
@RequestParam String query
);
}
5️⃣ 어노테이션 선언하기
필요한 설정값들을 어노테이션을 이용해 지정해주자. 아까 작성했던 application.yml과 사용할 API 명세서를 참고해 작성하면 된다.
@FeignClient(name = "${feign.naver.search.local.name}", url = "${feign.naver.search.local.url}")
public interface NaverLocalSearchOpenFeign {
@GetMapping
NaverLocalSearchResponse search(
@RequestHeader("X-Naver-Client-Id") String clientId,
@RequestHeader("X-Naver-Client-Secret") String clientSecret,
@RequestParam String query
);
}
Spring Cloud OpenFeign을 사용했으므로 Spring MVC의 어노테이션들을 그대로 사용할 수 있다 🙌
6️⃣ 만든 interface를 이용해 외부 API 호출하기
이제 만든 인터페이스를 가져다 쓰면 된다! 아래는 네이버 지역 검색 결과를 fetch하는 간단한 GET API의 service 레이어 예시다.
@Service
@RequiredArgsConstructor
public class OpenFeignService {
private final NaverLocalSearchOpenFeign openFeign;
@Value("${feign.naver.x-naver-client-id}")
private String clientId;
@Value("${feign.naver.x-naver-client-secret}")
private String clientSecret;
public NaverLocalSearchResponse search(final String keyword) {
return openFeign.search(clientId, clientSecret, keyword);
}
}
Postman으로 호출한 결과다. 값을 잘 받아오는 걸 확인할 수 있다 👍
RestTemplate, WebClient 와의 비교
OpenFeign을 이용한 위 코드를 RestTemplate, WebClient를 이용해 다시 구현해보자.
1️⃣ RestTemplate를 이용한 버전
기본 스프링 부트 의존성에 딸려있기 때문에,
build.gradle에 implementation 'org.springframework.boot:spring-boot-starter-web' 의존성만 있으면 된다.
먼저 RestTemplate을 생성하고, 스프링 빈으로 등록해주었다.
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
외부 API의 응답은 OpenFeign을 사용하면서 이미 만들어두었으므로, 바로 RestTemplate을 이용해 API를 호출할 수 있다.
public NaverLocalSearchResponse searchWithRestTemplate(final String keyword) {
try {
HttpHeaders headers = new HttpHeaders();
headers.set("accept", MediaType.APPLICATION_JSON_VALUE);
headers.set("X-Naver-Client-Id", clientId);
headers.set("X-Naver-Client-Secret", clientSecret);
HttpEntity<?> entity = new HttpEntity<>(headers);
UriComponentsBuilder builder = UriComponentsBuilder.fromUri(
URI.create(uri)
).queryParam("query", keyword);
return restTemplate.exchange(
builder.toUriString(),
HttpMethod.GET,
entity,
new ParameterizedTypeReference<NaverLocalSearchResponse>() {}
).getBody();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
제법 코드가 길다 🤔
외부 API를 interface 하나로 사용할 수 있었던 OpenFeign과 달리, HttpHeaders, UriComponent, HttpEntity를 전부 별개로 만들어줘야 한다. 사실 이게 굉장한 단점이라고 느꼈다. API마다 저 작업을 반복해야 한다니.. 너무 번거로울 것 같았다ㅋㅋ
OpenFeign의 경우, 사용할 API가 많아져도 인터페이스 내부에서 어노테이션 옵션만 조금 조정해주면 쉽게 처리가 가능하다.
RestTemplate에서도 메소드를 분리하는 등 코드를 좀 더 줄일 여지는 있겠지만, 그렇게 개선하더라도 openFeign이 훨씬 간편할 것 같다. 또 하나의 문제는 RestTemplate의 사용 방법을 따로 찾아봐야 한다는 거다.
여기서 내게 필요한 메서드가 뭔지 찾고, 그걸 어떻게 쓰는건지 또 찾고, ... 🔫 Spring MVC를 이용해 어노테이션 조정만 해주면 되는 OpenFeign과 비교하면 확실히 번거롭다. 이걸 언제 외워요~!
복잡한 비즈니스 로직 사이에, 외부 API 호출만을 위한 코드가 저만큼 자리를 차지하고 있으면 솔직히 조금 피로할 것 같긴 하다 🙄
2️⃣ WebClient를 이용한 버전
WebClient를 사용하기 위해선 webflux 의존성을 추가해주어야 한다.
implementation 'org.springframework.boot:spring-boot-starter-webflux'
이후 코드는 간단하다. 만약 쿠키나 baseUrl, header 등의 설정이 겹치는 경우, base WebClient를 만들어 스프링 빈으로 등록해주는게 좋겠지만, 나는 간단히 예제만 돌려볼거라서 바로 코드를 작성했다.
public Mono<NaverLocalSearchResponse> searchWithWebClient(final String keyword) {
WebClient webClient = WebClient.builder()
.baseUrl(uri)
.defaultHeader("Accept", MediaType.APPLICATION_JSON_VALUE)
.defaultHeader("X-Naver-Client-Id", clientId)
.defaultHeader("X-Naver-Client-Secret", clientSecret)
.build();
return webClient
.get()
.uri(uriBuilder -> uriBuilder
.queryParam("query", keyword)
.build()
)
.retrieve()
.bodyToMono(NaverLocalSearchResponse.class)
.onErrorResume(throwable -> {
throwable.printStackTrace();
return Mono.empty();
});
}
이 친구도 코드가 제법 길다222 제일 짧은 걸 제일 먼저 써서 그런가, 자꾸 불만족스럽다😇
일단 사용감만 보자면, RestTemplate와 동일한 문제가 보인다.
- 코드 작성량이 많고, API마다 webClient를 따로 만들어줘야 해서 번거롭다.
- WebClient를 사용하기 위해 기본적인 사용 방법, 메소드 등을 알아두어야 한다.
그리고 코드를 자세히 보면 Mono라는 객체가 보이는데, 이 친구는 Spring WebFlux에서 reactive stream을 지원하기 위해 사용되는 친구이다. 즉 Mono가 뭔지 알아야 Webflux의 동작 구조를 알 수 있고, 그걸 알아야 WebClient를 정확히 이해하고 쓸 수 잇는 것..! 따라서 문제가 하나 더 추가된다.
- Spring WebFlux 모듈에 대해 알아야 한다.
- 깊게 이해할 필요는 없지만, 기본적인 개념과 동작 원리, 주요 구성 요소에 대해선 알아두어야 WebClient를 이용할 때 의미와 코드의 영향을 정확히 알고 코드를 작성할 수 있을 것이다.
3️⃣ 각 방식의 장단점 비교
이렇게 OpenFeign, RestTemplate, WebClient를 한 번씩 다 이용해봤다. 간략한 사용감은 위에서 서술한 것과 동일하게 OpenFeign이 러닝커브도 낮고, 코드 작성량도 적어 제일 편하다.
하지만 우리 모두 알고 있듯이, 코드 작성량이 적고 사용하기 편하다고 무조건 옳은 도구인 건 아니다 🥲 사용성 말고 성능에 영향을 주는 차이점을 알아보자.
Spring Cloud OpenFeign | RestTemplate | WebClient | |
Non-Blocking | 불가능 | 불가능 | 가능 |
비동기화 | 불가능 | 불가능 (AsyncRestTemplate deprecaed) | 가능 |
그렇다.. 스프링이 http client로 WebClient를 권고하는건 논블록킹-비동기를 지원하는게 WebClient밖에 없어서였다. 트래픽이 어느정도 있는 상황이라면, 많은 동시 요청 & 대량의 데이터를 처리하기 위해서 비동기 및 리액티브 프로그래밍에 최적화된 WebClient를 사용하는 것이 제일 적합하겠다.
그럼 성능적 요소를 포함한 각 방식별 최종 장단점을 정리해보자.
Spring Cloud OpenFeign | RestTemplate | WebClient | |
장점 | Spring Cloud와 통합됨 | Spring 프레임워크와 통합됨 | Spring WebFlux와 통합됨 |
선언적 방식으로 간편하고 가독성이 높음 | 오래 쓰인 만큼, 레거시 프로젝트에서도 버전 충돌 없이 사용하기 용이함 | 비동기 및 리액티브 프로그래밍을 지원함 | |
단점 | 동기적 동작 : 응답 처리에 제한적 | 동기적 동작 : 응답 처리에 제한적 | WebFlux, 비동기 프로그래밍 등 새로운 패러다임을 함께 알아야 함 |
별도의 테스트 설정이 필요 | 단순한 HTTP 호출을 위해 작성해야 하는 코드량이 많다. | 단순한 HTTP 호출을 위해 작성해야 하는 코드량이 많다. | |
- | 유지모드로 전환됨 | - |
마무리
오늘은 OpenFeign에 대해 알아보고, 직접 사용해보고, 다른 HTTP client와 비교도 해봤다 😄 단점도 있지만, 오픈 페인의 간결함은 무시할 수 없는 장점이라고 생각한다. 그만큼 비즈니스 로직에만 집중할 수 있으니까! 🤩 규모가 작은 프로젝트에서 잘 사용할 것 같다.
이번 포스팅에선 정말 간단한 사용 예제만 다뤄봤는데, 다음엔 로깅, 인증, 타임아웃, 서킷 브레이커 적용 등 spring cloud가 지원하는 다른 기능까지 다뤄볼까 한다 👊
레퍼런스
- Spring Cloud OpenFeign : https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/
- OpenFeign 공식 github : https://github.com/OpenFeign/feign
- [Spring] OpenFeign이란? OpenFeign 소개 및 사용법 : https://mangkyu.tistory.com/278
- RestTemplate과 WebClient : https://tecoble.techcourse.co.kr/post/2021-07-25-resttemplate-webclient/
'Develop > Spring' 카테고리의 다른 글
[Spring Boot] AOP와 Spring AOP를 뜯어보자 (feat. Proxy, @Transactional) (0) | 2023.10.29 |
---|---|
[Spring Boot/JPA] @Transactional을 썼는데도 Dirty Checking에 실패한다면? (feat. clearAutomatically = true) (7) | 2023.09.30 |
[JPA] 영속성 전이 (CASCADE)와 고아 객체에 대하여 (0) | 2023.09.16 |
[Spring Boot] Auto Increment PK Id를 노출하지 않으면서 API에 활용하는 방법 (4) | 2023.06.28 |
[Spring Boot] 자바 스프링에서 처리율 제한 기능을 구현하는 4가지 방법 (2) | 2023.06.21 |