들어가기
최근 프로젝트를 진행하면서 비즈니스 로직을 수행하는 도중 외부 API를 호출해야 하는 기능을 구현해야 하는 경우가 있었다.
RestTemplate, WebClient 등 다양하게 있겠지만, Spring Data Jpa와 비슷하게 인터페이스와 어노테이션 기반으로 코드를 작성하는 OpenFeign이라는 것이 눈길을 끌었다. Spring Data Jpa를 자주 사용했던 나로서는 매우 직관적이고 간단하여 좋다고 생각하였고, 이번엔 OpenFeign를 사용해보기로 하였다.
이 글에선 OpenFeign에 대해 공부한 내용과 적용 과정을 간단하게 작성한 글이다.
OpenFeign을 포함하여 다른 외부 API를 호출하는 방법은 아래를 참고하길 바란다.
https://jie0025.tistory.com/531
목차는 아래와 같다.(클릭 시 이동)
본론
1. OpenFeign 이란?
우선 OpenFeign에 대해 알아보자. OpenFeign은 Netflix에서 처음 만들어진 HTTP Client 도구로 REST API 호출을 더 간편하고 직관적으로 만들어 주는 라이브러리이다. 어노테이션을 주로 사용하는 선언적 요청 방식으로 메서드의 방식이 Spring Data Jpa, Spring MVC와 유사하여 이를 자주 사용하는 사람들에게는 익숙한 형태일 것이다.
아래는 예시코드이다.
@FeignClient(name = "koreaApi", url = "https://example.com")
public interface KoreaApiService {
@GetMapping("/test")
TourismListUseCase findList(
@RequestParam("pageNo") int pageNo,
@RequestParam("serviceKey") String serviceKey);
}
확연히 간단한 코드이지 않는가? Spring 프로젝트를 많이 진행해본 사람이라면 처음 보는 코드여도 쉽게 이해할 수 있을 것이다. 외부 API를 호출하는 것을 매우 간소화하여 다른 비즈니스 로직에 더 많은 집중을 할 수 있다.
Netflix에서 개발할 당시에는 Feign이라는 이름으로 개발되었지만, 오픈 소스로 넘겨지면서 Open Feign이라는 이름이 되었고, 현재는 Spring Cloud로 통합되어 Spring Cloud의 다른 기능들과 함께 사용되고 있다.
그렇기에 다른 Spirng Cloud의 기술들과 쉽게 통합할 수 있으며, MSA 방식의 개발을 진행할 때 Spring Cloud를 사용 중이라면 Open Feign을 선택하는 것이 좋은 선택일 수 있다.
아래는 OpenFeign을 선택했을 때의 장단점을 간략하게 작성한 것이다.
장점:
1. 인터페이스 및 어노테이션 방식으로 작성하여 코드가 간결해진다.
2. 다른 Spring Cloud 기술들와 쉽게 통합 가능하다.
3. 코드 가독성이 높아 유지보수가 간소화된다. Spring Data Jpa, Spring MVC를 자주 사용하는 사람들은 쉽게 이해할 수 있는 정도이다.
단점:
1. Spring Cloud 의존성을 추가해야 한다. Spring Cloud는 사용하지 않고 Open Feign만 사용하는데도 의존성을 추가하여 관리에 복잡함을 더할 수 있다.
2. 공식적으로 비동기를 위한 Reactive모델을 지원하지 않는다. (비공식적으로 'feign-reactive'라는 것이 존재한다.)
3. HttpClient가 Http2를 지원하지 않는다.(추가 설정이 필요하다.)
4. 독자적인 테스트 도구를 제공하지 않는다 (@SpringBootTest로 띄어 API를 직접 호출하여 테스트하는 것은 가능하다. 아래 참고)
2. OpenFeign 의존성 추가
본격적으로 들어가기에 앞서 OpenFeign을 사용하기 위해 의존성을 추가하자.(Gradle 기반) 아래와 같이 OpenFeign에 대한 의존성과 이를 사용하기 위한 Spring Cloud 의존성을 추가해야 한다.
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:2023.0.2"
}
}
스프링 클라우드 버전은 여기서 확인 가능하다. 현재 필자가 진행하는 Spring Boot의 버전은 3.2.5이기에 2023.0.3을 적용하였다.
3. OpenFeign 적용
자 이제 본격적으로 OpenFeign을 적용할 것이다.
아래와 같이 OpenFeign을 사용하기 위해서 메인 클래스에 @EnableFeignClients를 달아주자.
@EnableFeignClients는 @FeignClient를 적용한 인터페이스(Feign 클라이언트)를 스캔하고 애플리케이션 컨텍스트에 빈으로 등록한다.
@EnableFeignClients
@SpringBootApplication
public class KeypointTravelApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
하지만 메인 클래스에 @EnableFeignClients를 적용한다면 @WebMvctest나 @DataJpaTest로 진행하는 테스트때도 동일하게 적용되어 불필요하게 테스트를 무겁게 만들고 시간을 더 길게 만드는 단점이 있다. 그렇기에 메인 클래스에 @EnableFeignClients를 적용하는 대신 Config 파일로 따로 만들어서 어노테이션을 적용하는 것이 더 좋은 선택일 수 있다.
Config 파일은 아래와 같이 만들고 @EnableFeignClients 어노테이션을 적용하고, 인터페이스를 생성할 위치를 지정해주면 된다.
@Configuration
@EnableFeignClients("com.example.test.service")
public class OpenFeignConfig {
}
이제 본격적으로 인터페이스와 어떻게 메서드들을 생성할 수 있는지 살펴보자. 먼저 위에서 확인했던 메서드이다.
@FeignClient(name = "koreaApi", url = "https://example.com")
public interface KoreaApiService {
@GetMapping("/test")
TourismListUseCase findList(
@RequestParam("pageNo") int pageNo,
@RequestParam("serviceKey") String serviceKey);
}
@FeignClient를 인터페이스에 적용하면서 시작된다. 옵션으로 name와 url을 지정해주었다. 그밖에 다른 옵션들은 아래와 같다.
1. name 또는 value : 필수 속성. 클라이언트의 이름을 지정.
2. url : 서비스 인스턴스 URL을 직접 지정할 수 있다.
3. configuration : 커스텀 설정을 지정할 수 있는 클래스. 디코더, 인코더, 에러 디코더 등을 커스터마이징 할 때 사용한다.
ex) @FeignClient(name = "test", configuration = MyFeignConfiguration.class)
4. fallback : 장애 조치를 위한 대체(fallback) 클래스. 원격 호출이 실패했을 때 기본 동작을 정의하는 클래스.
ex) @FeignClient(name = "test", fallback = MyClientFallback.class)
5. fallbackFactory : 장애 조치를 위한 팩토리 클래스를 지정한다. FallbackFactory를 사용하면, 예외 발생 시 원인을 전달받아 사용할 수 있다.
ex) @FeignClient(name = "test", fallbackFactory = MyClientFallbackFactory.class)
6. path : 기본 경로를 지정. 인터페이스의 모든 메소드에서 이 경로가 기본 경로로 추가된다.
ex) @FeignClient(name = "test", path = "/api")
7. decode404 : 404 응답을 비즈니스 예외로 처리할지 여부를 설정한다. 기본값은 false이다.
ex) @FeignClient(name = "test", decode404 = true)
8. primary : Bean이 여러 개 존재할 때 주입되는 기본 Bean으로 사용할지 여부를 지정한다. 기본값은 true.
ex) @FeignClient(name = "test", primary = false)
이제 메서드를 살펴보자.
@GetMapping은 SpringMVC에서 자주 사용해왔던 것이다. GET 요청을 하는 것으로 @RequestParam으로 쿼리스트링의 값들도 전달할 수 있다.
이외에도 SpringMVC와 유사한 코드로 작성되어 쉽게 이해할 수 있을 것이다.
아래는 @PostMapping으로 Post 요청을 하는 예시다.
@FeignClient(name = "koreaApi", url = "https://example.com")
public interface ExampleClient {
@PostMapping("/resource/{id}")
MyResponse postResource(
@RequestHeader("Authorization") String accessToken,
@PathVariable("id") String id,
@RequestBody MyRequest body
);
}
@RequestHeader로 헤더를 나타냈는데, 이 방법 외에도 아래와 같은 방식으로도 헤더를 덧붙일 수 있다.
@Configuration
@EnableFeignClients("com.example.test.service")
public class FeignConfig {
@Bean
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
template.header("Authorization", "Bearer accessToken");
template.header("header2", "테스트입니다.");
}
};
}
}
@FeignClient(name = "koreaApi", url = "https://example.com", configuration = FeignConfig.class)
public interface ExampleClient {
@PostMapping("/resource/{id}")
MyResponse postResource(
@PathVariable("id") String id,
@RequestBody MyRequest body
);
}
이 외 자세한 옵션들은 아래를 참고하길 바란다.
https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/
4. Retry, Fallback
마지막으로 OpenFeign에서 제공해주는 Retry와 FallBack에 대해 간단하게 알아보겠다.
Retriy는 API에 연결이 되지 않으면 재시도를 한다는 것으로 간단하게 application.yml 혹은 application.properties 파일에 작성하여 설정할 수 있다.
feign.client.config.default.retryer.period=100 //최초 재시도 하기 전 대시시간
feign.client.config.default.retryer.maxPeriod=1000 // 재시도 간 대기시간
feign.client.config.default.retryer.maxAttempts=5 // 최대 재시도 횟수
위 사항은 따로 설정하지 않을 시 기본적으로 적용되는 값이고, 요구사항에 따라 원하는 값으로 수정할 수 있다.
혹은 Config 파일 내에서 설정할 수 있다.
@Configuration
@EnableFeignClients("com.example.test.service")
public class OpenFeignConfig {
@Bean
public Retryer retryer() {
return new Retryer.Default(100, 1000, 5);
}
}
순서대로 period, maxPeriod, maxAttempts이다.
Fallback은 API 호출 도중 장애가 발생했을 시 어떻게 대처할 것인지를 의미한다.
아래와 같이 OpenFeign 인터페이스를 구현하는 클래스를 생성해주고 하고 원하는 내용을 작성하면 호출 도중 장애가 발생 시 작성한 내용이 진행된다.
@Component
public class ExampleFallback implements KoreaApiService {
@Override
public TourismListUseCase findList() {
throw new RuntimeException("API 호출 문제 발생");
}
}
인터페이스에는 fallback 옵션을 추가한다.
@FeignClient(name = "koreaApi", url = "https://example.com", fallback = ExampleFallback.class)
public interface KoreaApiService {
@GetMapping("/test")
TourismListUseCase findList(
@RequestParam("pageNo") int pageNo,
@RequestParam("serviceKey") String serviceKey);
}
이렇게 하면 KoreaApiService를 통해 호출된 API가 실패할 경우 자동으로 ExampleFallback 클래스의 메서드가 호출된다.
결론
이렇게 간단하게 OpenFeign에 대해 알아보았다.
외부 API 호출을 할 때면 RestTemplate나 WebClient를 우선적으로 떠올리고 적용했지만, 이처럼 우수한 다른 원격 API가 존재한다는 것은 큰 충격이었다. 만약 이를 진작 알았다면, 외부 API를 호출하는데 시간을 많이 들이지 않을 수 있었을 텐데 말이다ㅜㅜ
물론 OpenFeign 간편하게 사용할 수 있다는 장점이 있지만, 위에서 언급했던 단점들이 있는 만큼 무조건적인 사용이 아니라, 요구사항에 맞춰 선택적으로 사용하면 좋을 것 같다.
일단 나는 너무 마음에 드는 방식이라 자주 애용할 것 같다.
글에서 잘못된 내용이나 이해가 안 되는 부분은 댓글 남겨주시면 감사합니다..!
참고
https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/
https://mangkyu.tistory.com/278
https://mangkyu.tistory.com/243
https://jie0025.tistory.com/531
https://techblog.woowahan.com/2630/
'Spring' 카테고리의 다른 글
MessageSource와 Locale을 활용한 다국어 에러 처리(with Spring boot) (3) | 2024.09.19 |
---|---|
Spring boot - @Async를 통한 메서드 비동기 실행 및 주의사항 (0) | 2024.08.29 |
파일 업로드, 다운로드, 이미지 미리보기 구현(Spring boot With React) (0) | 2024.05.30 |
Springboot 3.x.x 를 사용해보자 (0) | 2024.01.24 |
springboot(jpa, mybatis) - page 객체 및 커버링 인덱스를 사용해보자! (0) | 2023.08.24 |