기록하기

Spring Cloud OpenFeign 적용기 본문

Server/Spring Boot

Spring Cloud OpenFeign 적용기

jjungdev 2023. 8. 26. 00:12

사이드 프로젝트에서나 사내에서 많이 적용하고 있는 OpenFeign 에 대해 그 적용 과정에서 겪었던 어려움을 정리해보고자 한다.

 

Spring Cloud OpenFeign

먼저, OpenFeign 이란, 복잡성을 줄이고 외부 API 호출을 쉽게 할 수 있도록 지원하는 Declarative REST Client 로 다음과 같은 특징을 가진다.

  • API 응답 캐싱 지원
  • URI 템플릿 표현 지원
  • Retry API 지원

서버 간의 통신을 하거나 외부 API 를 호출할 때 기존 프로젝트에는 RestTemplate, WebClient 등 다양한 방법으로 구현이 되어 있지만 이번에 신규 프로젝트를 구성하면서 OpenFeign 으로 설계를 진행해보았다. 구현을 하면서 예전에 OpenFeign 설정 과정에서 어려웠던 문제가 있었는데 관련 내용을 정리해봐야겠다고 생각했다.

 

프로젝트 설정 및 상황 예시

일단 프로젝트 구성 부분이다. 이미 설정을 완료한 프로젝트로 예시 작성하는 것이 더 편할 것 같아 디프만 동아리 활동 시 구성한 프로젝트를 가져왔다.

현재 Gradle 멀티 모듈로 구성한 Spring Boot 프로젝트는 아래 모듈들로 구성이 되어 있고, 버전은 다음과 같다.

 

프로젝트 구성

  • api 모듈 : internal api 처리
  • infra 모듈 : 외부와의 통신을 담당
  • common 모듈 : exception 이나 공통 설정 처리
  • domain 모듈 : 도메인 담당

 

버전

  • Java 17
  • Spring Boot 3.1.0
  • Gradle 7.6.1

여기서 프로젝트 설계 방향은, infra 모듈이 외부와의 통신을 담당하기 때문에 여기서 OpenFeign 관련 처리를 진행해주고, api 모듈에서는 비즈니스 로직 및 controller 로 구성되도록 설계를 했다.

 

그러면 만약 카카오로 로그인하기와 같이 카카오 서버라는 서드파티와의 통신이 필요한 상황이 왔을 때 아래와 같이 설계하는 것으로 생각했다.

이 방법과 구성에서는 문제가 없었으나 OpenFeign 의 설정이 잘못되어 이 과정에서 어려움이 있었다.

 

OpenFeign 의존성

그러면 일단 의존성을 추가해보자

 

멀티 모듈구조라서 모든 모듈에 공통적으로 필요한 의존성이 아니라면 각 모듈마다 다르게 의존성을 설정해주려고 한다.

따라서, infra 모듈에만 OpenFeign 설정을 진행해주었다.

 

infra 모듈 build.gradle

dependencies {
    //생략...
    implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:2022.0.2"
    }
}

 

infra 모듈 내 config 클래스

public class FeignConfig {

    //400 에러
    @Bean
    public ErrorDecoder errorDecoder() {
        return new FeignClientExceptionErrorDecoder(FEIGN_CLIENT_ERROR);
    }

    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}

 

위와 같이 간단하게 설정을 해주었다. 그리고 카카오 서버와 통신하는 FeignClient 클래스도 생성해주었다.

 

infra 모듈 내 FeignClient 클래스

@FeignClient(name = "KakaoFeignClient", url = "https://kapi.kakao.com", configuration = FeignConfig.class)
public interface KakaoFeignClient {

    @GetMapping(value = "/v2/user/me")
    MemberKakaoUserInfoResponse findKakaoUserInfo(
            @RequestHeader(name = CONTENT_TYPE) String contentType,
            @RequestHeader(name = AUTHORIZATION) String accessToken
    );
}

 

이렇게 설정한 뒤, api 모듈에서 클라이언트로부터 받은 요청을 처리하기 위해 비즈니스 로직을 수행한 뒤 카카오 서버 통신 결과를 받아오기 위해 infra 모듈을 컴파일 로딩 시 포함되도록 추가하였다.

 

api 모듈 build.gradle

dependencies {
    implementation project(':infra')
    
    //생략...
}

 

이후 테스트를 해보기 위해 애플리케이션을 실행했는데.. 다음과 같은 에러를 만났고, 이 에러는 아무리 많이 찾아봐도 해결방법을 찾을 수가 없었다🥲

MemberKakaoService required a bean of type 'com.depromeet.oversweet.config.KakaoFeignClient' that could not be found.

 

OpenFeign 동작 과정

OpenFeign 설정 과정에서 @EnableFeignClients 어노테이션이 필요한데, api 모듈에서는 OpenFeign 의 의존성을 추가하지 않도록 설정했기 때문에 main 함수가 존재하는 XXXApplication 클래스에 어노테이션 추가가 불가능했다.

그래서 infra 모듈에 있는 FeignConfig 설정 파일에 어노테이션을 붙이면 되겠지하는 생각을 했으나 이는 동작 원리를 잘 모르고 실행했던 방법이었다.

 

@EnableFeignClients 어노테이션의 역할은 지정된 package 를 돌아다니면서 @FeignClient 을 찾아 구현체를 만들어주는데, 여기서 @EnableFeignClients 어노테이션이 붙은 클래스의 위치는 root package 에 있거나, basePackages 혹은 basePackageClasses 를 지정해줘야 한다.

 

basePackages 혹은 basePackageClasses 

따라서, 먼저 현재 필자의 모듈 및 패키지 형태는 아래와 같은 상태였기에 아래와 같이 FeignConfig 설정 파일에 추가를 해주었다.

 

FeignConfig 설정 파일에 추가

@EnableFeignClients(basePackages = "com.depromeet.oversweet")
public class FeignConfig {

	//생략...
}

 

하지만, 이 역시 같은 오류가 발생했고, 다시 한 번 동작 원리를 찾아보았다.

찾아보니, api 모듈에 있는 main 클래스가 실행되는 곳에 @EnableFeignClients 어노테이션이 있어야 실행 시 @FeignClient 가 붙은 클래스를 찾아 구현체를 만들어줄 수 있고 -> 그래야 다른 클래스에서 현재 KakaoFeignClient 를 주입받아서 사용하는 클래스에 제대로 주입이 될 수 있다는 것을 알게 되었다.

 

따라서 api 모듈에도 OpenFeign 의존성을 추가해준 뒤 ApiApplication 클래스에 @EnableFeignClients 를 붙이고 실행을 해보았고, 이제는 에러 없이 FeignClient 구현체를 만들어주는 것을 디버깅을 통해 확인할 수 있었다.

 

FeignClientsRegistrar

아래는 FeignClientsRegistrar 의 registerFeignClients 함수인데, 애플리케이션 실행 시 이 메소드가 실행된다. 내용이 조금 길어서 상단만 스크린샷하여 첨부했다.

 

애플리케이션을 실행하면 아래 BeanDefinition loop 를 돌 때 구현체를 만들어주고 있는 것을 확인할 수 있다.

 

드디어 애플리케이션 실행 완료했다!

 

 

방법을 찾아보니 수동 구현체를 만드는 방법도 있는 것 같으나, 일단 위 방법으로 문제를 해결했다.

즉, 애플리케이션을 실행하는 모듈에도 OpenFeign 의존성을 추가해주어, 실행 시 Bean 등록을 할 수 있도록 설계해야 의존성 주입 관련 오류가 생기지 않는 것임을 알게 되었다.

 

항상 새로운 기술이나 방법을 도입할 때 공식 문서를 잘 살펴보려고 하는데 프로젝트마다 조금씩 환경이 다르기도 하여 생각보다 문제 해결하는데 오래걸렸다. 하지만 한 번 동작 원리에 대해 디버깅도 해보고 확인을 해보니 이후 OpenFeign 설정은 쉽게 진행할 수 있었고, 동작 원리나 Bean 주입 등에 대해 잘 확인해야겠다는 생각을 하게 되었다.

 

 

 

[ 참고 ]

- https://github.com/OpenFeign/feign

 

GitHub - OpenFeign/feign: Feign makes writing java http clients easier

Feign makes writing java http clients easier. Contribute to OpenFeign/feign development by creating an account on GitHub.

github.com

 

- https://techblog.woowahan.com/2630/

 

우아한 feign 적용기 | 우아한형제들 기술블로그

{{item.name}} 안녕하세요. 저는 비즈인프라개발팀에서 개발하고 있는 고정섭입니다. 이 글에서는 배달의민족 광고시스템 백엔드에서 feign 을 적용하면서 겪었던 것들에 대해서 공유 하고자 합니다

techblog.woowahan.com