OpenFeign 이란?
OpenFeign은 선언적인(Declarative) HTTP Client 혹은 REST Client이다.
FeignClient로 선언된 인터페이스를 구현하고 어노테이션을 달아주는 것만으로 HTTP Client 서비스를 작성할 수 있다.
이러한 OpenFeign은 Netflix에서 Netflix OSS 프로젝트의 일환으로 개발했다.
이후 오픈소스 커뮤니티로 옮겨졌고 현재는 스프링 클라우드(Spring Cloud) 생태계에 통합되어 Spring Cloud OpenFeign으로 사용 가능하다.
OpenFeign vs RestTemplate
만약 다른 외부 날씨 API를 호출하려고 할 때 RestTemplate을 활용하면 아래와 같이 코드를 작성할 수 있다.
@Service
public class WeatherService {
public String getTodayWeather(){
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Bearer ...");
URI uri = UriComponentsBuilder
.fromUriString("http://korea-weather")
.path("/api")
.queryParam("date", "2024-03-16")
.encode()
.build()
.toUri();
return restTemplate.exchange(
uri,
HttpMethod.GET,
new HttpEntity<>(headers),
String.class)
.getBody();
}
}
반면 OpenFeign을 이용하면 아래와 같이 간단하게 작성할 수 있다.
@FeignClient(name = "WeatherApi", url = "http://korea-weather")
public interface WeatherApi {
@GetMapping
ResponseEntity getTodayWeather(
@RequestHeader String authorization,
@RequestParam String date);
}
이처럼 OpenFeign을 활용하면 작성해야 할 코드의 양을 비약적으로 줄일 수 있다.
또한 @GetMapping과 같이 기존 Spring MVC 어노테이션을 활용할 수 있기 때문에 더 간편하다.
RestTemplate은 권장하지 않습니다
RestTemplate은 한때 Spring에서 Deprecated 하기로 했다.
아직 Deprecated 되지는 않았지만 maintenance mode로 더 이상 기능개발은 하지 않고 유지보수만 하기로 되었다.
또한 Spring Docs에 따르면 RestTemplate을 사용하기보다는 WebClient를 사용하기를 권장했다.
WebClient는 OpenFeign과 다르게 비동기 방식을 사용할 수 있다는 점이 장점이다.
다만, OpenFeign도 비동기 방식을 사용할 수 있는 방식이 있는데 이건 Feign Reactive를 활용하면 된다.
참고 : https://github.com/OpenFeign/feign/tree/master/reactive
그럼 이제 OpenFeign을 실제로 어떻게 사용하는지 알아보자.
OpenFeign 사용법
의존성
현재 OpenFeign은 스프링 클라우드 생태계에 포함되기 때문에 Spring Cloud에 대한 의존성도 추가해줘야 한다.
아래 문서를 참고해 자신의 Spring Boot 버전에 맞는 Spring Cloud 버전을 선택해 주면 된다.
https://spring.io/projects/spring-cloud
Maven (pom.xml)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>3.1.1</version>
</dependency>
Gradle (build.gradle)
ext {
set('springCloudVersion', "2022.0.3")
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
}
Maven 혹은 Gradle에 맞게 dependency를 설정해 주면 된다.
활성화
@EnableFeignClients
@SpringBootApplication
public class WebApplication {
public static void main(String[] args) {
SpringApplication.run(WebApplication.class, args);
}
}
main 클래스(@SpringBootApplication이 붙은 클래스)에 @EnableFeignClients 어노테이션을 붙여주면
OpenFeign이 활성화된다.
@EnableFeignClients를 붙이면 이후 @FeignClient가 붙은 인터페이스들을 찾아 구현체로 만들어 준다.
basePackages를 설정하면 basePackages를 기준으로 스캔하고, 그렇지 않으면 root package를 기준으로 스캔한다.
FeignClient 구현
이제 API를 호출할 FeignClient 인터페이스를 작성하면 된다.
아래는 FeignClient를 설명하기 위해 작성해 본 예시이다.
@FeignClient(name = "WeatherApi", url = "http://korea-weather")
public interface WeatherApi {
@GetMapping("/v1/list")
List<RainRes> getRainList(
@RequestHeader("authorization") String authorization,
@PathVariable(name = "day") String day);
@GetMapping("/v1/foggy")
List<FoggyRes> getFoggyList(
@RequestHeader("authorization") String authorization,
@RequestParam(name = "page") Integer page,
@RequestParam(name = "pageSize") Integer pageSize);
@PostMapping("/v1/weather")
Response insertSatisfaction(
@RequestHeader("authorization") String authorization,
@PathVariable(name = "createId") Integer createId,
@RequestBody WeatherReq weatherReq);
}
위와 같이 인터페이스를 작성하고, @FeignClient 어노테이션을 붙여준다.
name 옵션에는 인터페이스명을 넣어주고, url에는 호출할 url을 적어준다.
메서드는 Spring MVC에서 작성한 것과 거의 동일하다.
@GetMapping, @PostMapping 등의 매핑 어노테이션을 사용할 수 있고,
@RequestHeader로 헤더, @RequestParam으로 쿼리 파라미터, @PathVariable로 PathVariable을 설정할 수 있다.
POST 요청을 보낼 땐 @RequestBody로 JSON 요청을 보낼 수도 있다.
@Service
@RequiredArgsConstructor
public class WeatherService {
private final WeatherApi weatherApi;
public List<RainRes> getRainListByWeatherApi(String authorization, String day) {
...
return weatherApi.getRainList(authorization, day);
}
}
마지막으로 위와 같이 Service 클래스에서 FeignClient의 메서드를 사용하기만 하면 된다.
참고) OpenFeign에서 파일 전송하기
만약 OpenFeign에서 파일을 다른 API로 전송하고 싶다면 아래와 같이 하면 된다.
@PostMapping(
value = "/v1",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
InsertRes insertData(
@RequestHeader("authorization") String authorization,
@RequestPart(name = "uploadFiles") MultipartFile uploadFiles);
@RequestPart 어노테이션을 업로드하고자 하는 파라미터 MulitpartFile에 붙여주고,
@PostMapping 어노테이션의 consumes 옵션에 MediaType.MULTIPART_FORM_DATA_VALUE를 설정해 주면 된다.
Configuration 활용
위에서 설명한 내용만으로도 간단한 API 호출정도는 쉽게 구현할 수 있다.
하지만 Configuration을 활용하면 더 많은 일을 할 수 있다.
로깅
public class WeatherApiConfig {
@Bean
Logger.Level weatherApiLoggerLevel() {
return Logger.Level.FULL;
}
}
별도의 Config 클래스를 만들어 위와 같이 Logger를 설정해 주면 로그를 남길 수 있다.
Logger의 수준은 아래와 같습니다.
- NONE : 로깅하지 않는다(기본값)
- BASIC : 요청 메서드, URI, 응답 상태, 실행시간 로깅
- HEADERS : 요청, 응답헤더 및 기본 정보들 로깅
- FULL : 요청, 응답 헤더 및 바디, 메타 데이터를 로깅
참고로 OpenFeign의 로그는 DEBUG 레벨로만 남길 수 있기 때문에 로그 레벨을 DEBUG로 설정해야 한다.
logging:
level:
com.openfeign.package: DEBUG
예를 들어 application.yml 파일에 위와 같이 설정되어 있어야 한다.
@FeignClient(
name = "WeatherApi",
url = "${external.weather.api}",
configuration = {WeatherApiConfig.class})
public interface WeatherApi{
...
}
configuration은 @FeignClient의 configuration 옵션으로 지정해 줄 수 있다.
한 개뿐만 아니라 두 개 이상의 configuration 클래스를 지정해 줄 수도 있다.
재시도(Retry)
public class RetryConfig {
@Bean
public Retryer feignRetryer() {
// 0.1초 간격에서 최대 1초 간격으로 최대 3번 재시도
return new Retryer.Default(100, 1000, 3);
}
}
위와 같이 configuration에서 API 요청을 재시도하는 간격을 설정할 수 있다.
다만 위에서 사용한 Retryer는 IOException이 발생한 경우에만 동작한다는 사실을 유념해야 한다.
다른 예외에도 재시도를 하고 싶다면 인터셉터를 직접 구현해야 한다.
@FeignClient(
name = "WeatherApi",
url = "${external.weather.api}",
configuration = {WeatherApiConfig.class, RetryConfig.class })
public interface WeatherApi{
...
}
위와 같이 다른 Configuration 클래스를 적용할 수도 있다.
공통 헤더 적용
다른 외부 API를 호출할 때 보안을 적용한 곳이 많을 것이다.
이때 매번 메서드에 @RequestHeader로 보안 토큰을 넣어주는 것은 매우 귀찮고 비효율적이다.
public class HeaderConfiguration {
@Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> requestTemplate.header("Authorization", "Bearer ...");
}
}
이럴 땐 위와 같이 configuration에서 RequestInterceptor를 설정하여 원하는 헤더를 설정해 주면 된다.
위 configuration을 FeignClient에 적용시키면 모든 메서드에 Header가 설정된다.
POST 요청 시 파일과 JSON 함께 보내기
가끔 POST 요청으로 파일과 JSON 데이터를 함께 보내고 싶을 수가 있다.
이때 configuraion에서 다음과 같이 설정해주지 않으면 오류가 발생한다.
public class MultiConfig {
@Bean
JsonFormWriter jsonFormWriter() {
return new JsonFormWriter();
}
}
위와 같이 설정한 configuration을 적용시키고
@PostMapping(
value = "/v1",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
InsertRes insertData(
@RequestHeader("authorization") String authorization,
@RequestPart(name = "uploadFiles") MultipartFile uploadFiles
@RequestPart(name = "reqDto") ReqDto reqDto);
Post 요청을 보내는 메서드에서 전송할 파일뿐만 아니라 Json 객체도 @RequestPart 어노테이션을 붙여줘야 한다.
@ResponseBody가 아닌 @RequestPart임을 주의해야 한다.
참고)
[에러] Feign Client로 multipart/form-data 보낼 때 required part ... is not present 오류 해결법
마무리
OpenFeign을 활용하면 RestTemplate을 사용하는 것보다 많은 코드를 줄일 수 있고 쉽고 간단하게 API를 호출할 수 있다.
사용법도 어렵지 않고 현재 많은 정보를 찾아볼 수 있으므로 앞으로 HTTP Client가 필요하다면 OpenFeign의 도입을 고려해 보면 좋겠다.
참고
[1] https://spring.io/projects/spring-cloud-openfeign
[2] https://techblog.woowahan.com/2630/
[3] https://mangkyu.tistory.com/278
[4] https://junuuu.tistory.com/770
[5] https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html#_feign_logging
'Back-End > [Spring]' 카테고리의 다른 글
[Spring] 필터(Filter)와 인터셉터(Interceptor)의 차이, 동작원리 (0) | 2024.04.23 |
---|---|
[Spring] @Valid와 @Validated의 차이, 사용법, Bean Validaion이란? (1) | 2024.04.18 |
[Spring] 스프링 메시지, 국제화 기능으로 다국어 적용하기 (0) | 2024.03.26 |
[Spring] @NotNull, @NotEmpty, @NotBlank의 차이, 사용법 (0) | 2024.03.08 |
[Spring] 디스패처 서블릿(Dispatcher Servlet)이란? (0) | 2024.03.05 |