일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- Domain Driven Design
- 밋업프로젝트
- 영속성
- reactive operaton
- RESTClient
- kusitms
- 도메인 주도 개발 시작하기
- 자바 ORM 표준 JPA 프로그래밍
- 큐시즘
- JPQL
- delayed message plugin
- 교육기획팀원
- JPA
- Spring Batch
- 30기
- 한국대학생it경영학회
- 자동처리
- springboot
- rabbitmq-delayed-message-exchange
- Spring
- cicd
- GitHub Actions
- 최범균
- java
- jdbc
- scheduling messages with rabbitmq
- 객체지향 쿼리 언어
- ddd
- 교육기획팀
- 이펙티브자바
- Today
- Total
코딩은 마라톤
[Spring Cloud] Feign: 선언적 REST Client 본문
도입 배경
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

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

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

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

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

RequestTemplate 객체를 이용해 Request를 생성하고, client.execute(request, options)를 통해 Response를 반환받습니다.
3. 실제 HTTP 요청 전송 - Client

client.execute(request, options)은 Client 인터페이스 내의 Default 구현체에서 동작합니다.
HttpURLConnection을 통해 연결을 만들고, 요청 헤더와 본문을 설정하며, 응답을 처리합니다.
그럼 RestClient는 어떻게 호출해?
Feign은 설정하지 않으면 HttpUrlConnection을 통해 연결 및 HTTP 통신을 합니다.
그렇다면 RestClient는 어떻게 HTTP 통신을 할까요?

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

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

여기서 보면, requestFactory가 없음 `SimpleClientHttpRequestFactory()`가 생성됨을 알 수 있네요!
즉, 설정에 따라서 여러 HTTP 통신 관련 모듈을 사용할 수 있어요!

그리고 여기서도 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에도 효율적인 기능을 제공한다고 하니, 분산 아키텍처를 고려할 경우 더 깊게 공부해봐도 좋을 거 같네요!
결론: 왜 안 써?