코딩은 마라톤

[Spring Cloud] Feign: 선언적 REST Client 본문

Backend/Spring Cloud

[Spring Cloud] Feign: 선언적 REST Client

anxi 2025. 3. 8. 03:45

도입 배경

HTTP Client를 사용할 때, RestClient, WebClient를 사용했었습니다.

다만 RestClient, WebClient를 사용할 때는 Header와 Body 등에 필요한 값을 작성해야 합니다.

하기 코드는 카카오 Local Rest API를 `RestClient`를 이용하여 연결한 코드입니다.

public KakaoSearchResponse search(KakaoSearchRequest kakaoSearchRequest) {
    try {
        return RestClient.create()
                .get()
                .uri(uri)
                .headers(header -> {
                    header.set("Authorization", KAKAO_AUTHORIZATION_PREFIX + kakaoProperty.getRestApiKey());
                    header.setContentType(MediaType.APPLICATION_JSON);
                })
                .retrieve()
                .body(KakaoSearchResponse.class);
    } catch (Exception e) {
        log.error("[KakaoSearchService] error", e);
        throw new StoreException(StoreErrorType.KAKAO_SERVICE_UNAVAILABLE);
    }
}

 

물론 코드 양 자체는 많지 않지만 외부 API를 많이 사용하는 서비스인 경우, 유사한 코드가 반복해서 발생하여 생산성이 떨어질 수 있을 것 같네요 😳

 

만약 이를 Feign을 사용하면 어떻게 될까요?

@FeignClient(value = "kakao", url = "https://dapi.kakao.com/v2/local/search/keyword.json")
public interface KakaoClient {

    @GetMapping(produces = "application/json")
    KakaoSearchResponse search(
            @RequestHeader("Authorization") String authorization,
            @SpringQueryMap KakaoSearchRequest request
    );

}

 

 

이렇게 인터페이스를 선언하고 메소드 선언만 하면 연결하는 코드가 매우 줄어듦을 볼 수 있습니다!

그래서 이번 큐시즘 31기 기업프로젝트에서 적극적으로 사용하고 있네요.

 

Feign이 그래서 뭐야?

Feign을 Spring Cloud Docs에서는 한 줄로 표현합니다.

Feign is a declarative web service client.

 

선언적 웹 서비스 클라이언트..

"선언" 이란 단어가 왜 나왔는지 눈치채셨나요?

 

"선언"은 도입배경에서 RestClient와 OpenFeign의 코드를 비교해보시면 유추해보실 수 있을 거 같은데요!

 

RestClient vs Feign 비교

RestClient (명령적)

  • RestClient는 직접 요청을 생성하고, 헤더를 설정하고, 응답을 처리하는 로직 작성
    • 즉, "어떻게 호출할 것인지"를 일일이 명령해야 하는 방식

Feign (선언적)

  • Feign은 "이 API를 호출하겠다"라고 선언만 하면, 내부적으로 HTTP 요청을 생성해 처리
    • 즉, "어떻게"가 아니라 "무엇을" 호출할 것인지를 선언하는 방식
    • "인터페이스"만 선언함으로써 비즈니스 로직에 불필요한 코드를 숨김

따라서 Feign을 사용하면 어떻게 호출할지(How) 신경쓰지 않고 무엇을 호출할지(What)만 신경쓰면 됩니다.

 

Feign 동작 원리

그렇다면 Feign은 내부적으로 어떻게 동작할까요?

 

1. Feign 인터페이스 메소드 실행 - FeignInvocationHandler

FeignInvocationHandler

 

  • @FeignClient가 붙은 인터페이스는 Spring이 동적 프록시로 생성합니다.
  • 이 프록시 객체의 메서드를 호출하면, `FeignInvocationHandler`의 invoke() 메서드가 실행됩니다.
  • dispatch.get(method).invoke(args)를 통해 실제 HTTP 요청이 처리됩니다.

FeignInvocationHandler#invoke()를 통해 dispatch에는 SynchronousMethodHandler 객체가 저장됩니다.

 

2. 실제 HTTP 요청 생성 - SynchronousMethodHandler

SynchronousMethodHandler

 

FeignInvocationHandler#invoke()에서 생성된 SynchronousMethodHandlerinvoke()를 통해 실제 HTTP 요청이 처리됩니다.

 

invoke의 매개변수인 argv에 HTTP 요청 값이 들어가게 됩니다. (Header, Body 등)

 

RequestTemplate

 

이 요청값을 가지고 HTTP 요청 URL과 헤더가 설정된 RequestTemplate 객체를 생성합니다.

executeAndDecode()

 

RequestTemplate 객체를 이용해 Request를 생성하고, client.execute(request, options)를 통해 Response를 반환받습니다.

 

3. 실제 HTTP 요청 전송 - Client

Client-Default

 

client.execute(request, options)은 Client 인터페이스 내의 Default 구현체에서 동작합니다.

HttpURLConnection을 통해 연결을 만들고, 요청 헤더와 본문을 설정하며, 응답을 처리합니다.

 

 

그럼 RestClient는 어떻게 호출해?

Feign은 설정하지 않으면 HttpUrlConnection을 통해 연결 및 HTTP 통신을 합니다.

그렇다면 RestClient는 어떻게 HTTP 통신을 할까요?

 

RestClient

 

RestClient는 생성할 때 `DefaultRestClientBuilder`를 사용하여 생성합니다.

 

 

여기서 최종적으로 build를 통해 RestClient를 생성할 때, initRequestFactory() 메소드를 통해 ClientHttpRequestFactory를 만들어냅니다.

 

 

여기서 보면, requestFactory가 없음 `SimpleClientHttpRequestFactory()`가 생성됨을 알 수 있네요!

즉, 설정에 따라서 여러 HTTP 통신 관련 모듈을 사용할 수 있어요!

 

SimpleClientHttpRequestFactory

 

그리고 여기서도 HttpURLConnection을 사용해 연결을 만들고, 요청 헤더와 본문을 설정하며, 응답을 처리하는 것을 볼 수 있습니다.
하지만 기본 값인 경우 이렇게 되며, Feign과 RestClient 모두 여러 HTTP 클라이언트를 사용할 수도 있어요!

 

OpenFeign은 어떻게 사용할까?

https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/

 

Spring Cloud OpenFeign

Feign is a declarative web service client. It makes writing web service clients easier. To use Feign create an interface and annotate it. It has pluggable annotation support including Feign annotations and JAX-RS annotations. Feign also supports pluggable

docs.spring.io

 

사실 docs를 짧게만 읽어봐도 충분히 이해가 되실 거라 생각합니다.

간단하게 통신을 위해 구현을 한다고 하면, 해야할 것은 다음과 같습니다.

 

1. OpenFeign 의존성 추가하기

implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'

2. @FeignClient와 인터페이스 선언하기

@FeignClient(value = "kakao", url = "https://dapi.kakao.com/v2/local/search/keyword.json")
public interface KakaoClient {

    @GetMapping(produces = "application/json")
    KakaoSearchResponse search(
            @RequestHeader("Authorization") String authorization,
            @SpringQueryMap KakaoSearchRequest request
    );

}

 

@FeignClient는 인터페이스를 사용하여 RESTful 웹 서비스를 호출할 수 있게 해주며, 클라이언트의 요청을 쉽게 관리해주는 애노테이션입니다.

따라서 인터페이스 위에 선언해야하며, 호출할 서비스의 'url'을 지정할 수도 있어요!

 

@FeignClient를 사용하는 메소드들은 Controller에서 사용하는 것과 동일한 방식으로 선언합니다.

즉, HTTP 메소드(GET, POST 등)에 맞게 메소드를 선언하고, 반환 값이나 파라미터에 대해서도 RESTful API와의 연동을 쉽게 처리할 수 있어요.

3. @EnableFeignClients 설정하기

@SpringBootApplication
@EnableFeignClients
public class FeignExample {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

 

@EnableFeignClients는 '@FeignClient' 라고 선언된 인터페이스를 검색하는 애노테이션입니다.

@EnableFeignClients(basePackages = "com.example.clients")

 

이런 식으로 패키지를 제한해서 컴포넌트를 스캔할 수도 있습니다.

 

@FeignClient를 사용하면 간단하지만, 실제로 HTTP 통신을 할 수 있게 됩니다!

 

물론 Feign에서 제공하는 다양한 기능이 존재합니다.

엄청 많죠?

 

또한 Feign은 Spring Cloud에 포함된 만큼 Spring Cloud와의 통합을 통해 MSA에도 효율적인 기능을 제공한다고 하니, 분산 아키텍처를 고려할 경우 더 깊게 공부해봐도 좋을 거 같네요!

 

결론: 왜 안 써?