백엔드/Spring

[Spring Cloud] Spring Cloud OpenFeign

2023. 5. 22. 23:36
목차
  1. Spring Cloud OpenFeign이란?
  2. 의존성
  3. Feign 적용
  4. Header에 값 설정하기
  5. Configuration
  6. @RequestHeader
  7. @feign.Header
  8. Logger
  9. 하기 쉬운 실수
  10. 예외 처리
728x90

Spring Cloud OpenFeign이란?

Spring Cloud OpenFeign은 선언적 웹서비스 클라이언트이다.

RestTemplate과 같이 마이크로서비스 간의 통신에 사용하는 라이브러리이다.

 

Spring Cloud는 Feign에 Spring MVC 애노테이션과 HttpMessageConverter를 지원하고,

Eureka, Spring Cloud CircuitBreaker 및 Spring Cloud LoadBalancer를 통합하여 Load-Balanced HTTP 클라이언트를 지원한다.

 

기존의 RestTemplate은 반복적인 코드가 많고 유지 보수적으로 오래 유지하기 힘든 단점이 있었다.

RestTemplate은 다음과 같은 작업들을 반복적으로 수행해야 한다.

  • RestTemplate 인스턴스 또는 DI 주입
  • 타겟 URL 정의
  • Request 헤더 설정
  • Contents 타입 설정
  • URL 호출
String orderUrl = String.format(env.getProperty("order_service.url"), userId);
ResponseEntity<List<ResponseOrder>> orderListResponse =
        restTemplate.exchange(orderUrl, HttpMethod.GET, null,
                new ParameterizedTypeReference<List<ResponseOrder>>() {
});
List<ResponseOrder> orderList = orderListResponse.getBody();

 

반면에 Feign은 인터페이스와 그에 따른 애노테이션을 정의하는 것만으로 Spring이 실행될 때 구현체를 제공해 준다.


의존성

maven

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

gradle

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

Feign 적용

Application.java

@SpringBootApplication
@EnableFeignClients
public class Application {

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

}

StoreClient.java

@FeignClient("stores")
public interface StoreClient {
    @RequestMapping(method = RequestMethod.GET, value = "/stores")
    List<Store> getStores();

    @RequestMapping(method = RequestMethod.GET, value = "/stores")
    Page<Store> getStores(Pageable pageable);

    @RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")
    Store update(@PathVariable("storeId") Long storeId, Store store);

    @RequestMapping(method = RequestMethod.DELETE, value = "/stores/{storeId:\\d+}")
    void delete(@PathVariable Long storeId);
}

interface에 @FeignClient를 지정하고 값 "store"는 Spring Cloud LoadBalancer 클라이언트를 생성하는 데 사용되는 임의의 클라이언트 이름이다. url 속성을 사용하면 host이름을 지정할 수도 있다.

Application Context의 bean 이름은 인터페이스 이름으로 지정된다. 이름을 따로 지정하고 싶다면 @FeignClient의 qualifiers 속성을 사용하면 된다.

 

애플리케이션이 Eureka client인 경우에는"stores" 이름의 서비스를 Eureka service registry에서 확인한다.


Header에 값 설정하기

Request Header에 값을 넣는 방식은 몇 가지가 있다.

Configuration

public class HeaderConfiguration {
    
    @Bean
    public RequestInterceptor requestInterceptor() {
        return requestTemplate -> requestTemplate.header("header", "header1", "header2");
    }
}
@FeignClient(name = "order-service",configuration = HeaderConfiguration.class)
public interface OrderServiceClient {

    @GetMapping("/order-service/{userId}/orders")
    List<ResponseOrder> getOrders(@PathVariable String userId);
}

method 별로 별도의 header를 설정을 하는 것이 아닌, feign client 별로 설정이 필요할 때 사용한다.

  1. Header 값을 설정할 configuartion class를 생성을 하고, RequestInterceptor을 생성해 준다.
  2. 원하는 feign client에 만든 configuartion class를 설정해 주면 된다.

configuration class에 @Configuration을 적용하면 모든 client에 적용되니 주의하자.


@RequestHeader

@FeignClient(name = "order-service")
public interface OrderServiceClient {

    @GetMapping("/order-service/{userId}/orders")
    List<ResponseOrder> getOrders(@RequestHeader("header") String header1,  @PathVariable String userId);
}

@feign.Header

@FeignClient(name = "order-service")
public interface OrderServiceClient {

    // 이 호출은 Header 에 값이 설정되지 않습니다.
    // @GetMapping 은 SpringMVcContract 를 사용해야하고, @Headers 는 feign Contract 를 사용해야 합니다.
    @org.springframework.web.bind.annotation.GetMapping(value = "/order-service/{userId}/orders")
    @feign.Headers("header:header1")
    List<ResponseOrder> getOrders(@PathVariable String userId);
}

Contract를 feign에서 제공해 준 Default Contract를 사용해야 한다.


Logger

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class UserServiceApplication {

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

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

Logger.Level의 객체는 다음과 같다.

  • NONE : 로깅하지 않는다. (default)
  • BASIC : Request Method와 URL 그리고 Response 상태 코드와 실행 시간을 로깅한다.
  • HEADERS : Request, Response Header 정보와 함께 BASIC 정보를 로깅한다.
  • FULL : Request와 Response의 Header, Body 그리고 메타데이터를 로깅한다.

하기 쉬운 실수

  • feign.Contract.Default를 사용하는 실수
    • org.springframework.cloud:spring-cloud-starter-openfeign을 사용하게 되면 SpringMvcContract를 사용하게 되어 @GetMapping, @PostMapping, @RequestMapping을 사용할 수 있다.
    • Feign에서 제공하는 Default를 사용하게 되면, 위에 있는 것이 아닌 feign.RequestLine을 사용해야 한다.
    • Feign에서 제공하는 Default Contract를 사용하는데 @GetMapping과 같은 것을 사용한다면 아래와 같은 오류를 보게 된다.
Caused by: java.lang.IllegalStateException: Method status not annotated with HTTP method type (ex. GET, POST)
at feign.Util.checkState(Util.java:128)
at feign.Contract$BaseContract.parseAndValidateMetadata(Contract.java:99)
at feign.Contract$BaseContract.parseAndValidatateMetadata(Contract.java:66)
at feign.ReflectiveFeign$ParseHandlersByName.apply(ReflectiveFeign.java:146)
at feign.ReflectiveFeign.newInstance(ReflectiveFeign.java:53)
at feign.Feign$Builder.target(Feign.java:218)
at org.springframework.cloud.openfeign.HystrixTargeter.target(HystrixTargeter.java:39)
at org.springframework.cloud.openfeign.FeignClientFactoryBean.getObject(FeignClientFactoryBean.java:261)
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:171)
... 28 more
  • feign logger level을 full로 설정했는데 로그 출력이 되지 않음
    • request와 response의 header, body 등의 상세한 로그가 나와야 한다.
    • FULL로 설정을 했음에도 나오지가 않는다면, @FeignClient으로 client를 만든 package log level을 DEBUG로 설정했는지 확인해봐야 한다.
    • FULL로 설정하면 볼 수 있는 로그
[Class#method] ---> GET https://httpbin.org/status/200 HTTP/1.1
[Class#method] ---> END HTTP (0-byte body)
[Class#method] <--- HTTP/1.1 200 OK (1437ms)
[Class#method] access-control-allow-credentials: true
[Class#method] access-control-allow-origin: *
[Class#method] connection: keep-alive
[Class#method] content-length: 0
[Class#method] content-type: text/html; charset=utf-8
[Class#method] date: Mon, 13 May 2019 12:15:41 GMT
[Class#method] referrer-policy: no-referrer-when-downgrade
[Class#method] server: nginx
[Class#method] x-content-type-options: nosniff
[Class#method] x-frame-options: DENY
[Class#method] x-xss-protection: 1; mode=block
[Class#method] 
[Class#method] <--- END HTTP (0-byte body)
  • PathVariable에 값이 들어가지 않음
    • Spring MVC Controller에서는 @PathVariable, @QueryParam에 value(name)을 넣지 않아도, field의 이름이 default로 설정되어 유입된 값이 세팅이 되지만, feign에서는 value(name) 값을 넣어줘야 @GetMapping @PostMapping 등에 있는 url에 자동으로 넣어준다.
    • default로 넣어줄 거라 생각하고 안 넣어주면 에러가 발생된다.
  • Feign client method parameter에 @PathVariable, @QueryParam, @RequestBody와 같은 annotation을 설정하지 않음
    • 아무것도 설정하지 않으면 @RequestBody로 생각해서 Http Body 로 데이터를 보내려고 한다.
    • 이때, parameter 가 2개 이상이 되면 Method has too many Body parameters이라는 에러가 발생된다.
@PostMapping(value = "/anything")
void anything(ExampleRequest request, ExampleRequest request2);

예외 처리

ErrorDecoder를 사용하면 예외처리를 할 수 있다.

@RequiredArgsConstructor
@Component
public class FeignErrorDecoder implements ErrorDecoder {

    private final Environment env;

    @Override
    public Exception decode(String methodKey, Response response) {
        switch (response.status()) {
            case 400:
                break;
            case 404:
                if(methodKey.contains("getOrders")) {
                    return new ResponseStatusException(HttpStatus.valueOf(response.status()), env.getProperty("order_service.exception.orders_is_empty"));
                }
                break;
            default:
                return new Exception(response.reason());
        }

        return null;
    }
}

 

ErrorDecoder를 상속한 뒤 decode() 함수를 Override 하여 구현한다.


참고 자료 : 

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

 

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

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

techblog.woowahan.com

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

 

'백엔드 > Spring' 카테고리의 다른 글

레이어간 의존 관계 문제  (0) 2024.07.29
[Spring] 동적 프록시(Dynamic Proxy)  (1) 2023.05.26
[Spring] Spring Boot Actuator  (0) 2023.05.18
[Spring] 스프링(Spring)  (0) 2023.04.28
[Spring] POJO(Plain Old Java Object)  (0) 2023.04.27
  1. Spring Cloud OpenFeign이란?
  2. 의존성
  3. Feign 적용
  4. Header에 값 설정하기
  5. Configuration
  6. @RequestHeader
  7. @feign.Header
  8. Logger
  9. 하기 쉬운 실수
  10. 예외 처리
'백엔드/Spring' 카테고리의 다른 글
  • 레이어간 의존 관계 문제
  • [Spring] 동적 프록시(Dynamic Proxy)
  • [Spring] Spring Boot Actuator
  • [Spring] 스프링(Spring)
밝은별 개발자
밝은별 개발자
호기심 가득한 밤을 하나씩 밝히는 밝은별입니다.
밝은별 개발 블로그호기심 가득한 밤을 하나씩 밝히는 밝은별입니다.
밝은별 개발자
밝은별 개발 블로그
밝은별 개발자
전체
오늘
어제
  • 전체 글 (59)
    • 자소서 (0)
    • 백엔드 (55)
      • Java (15)
      • 네트워크 (5)
      • JPA (11)
      • Spring (6)
      • 운영체제 (8)
      • MSA (4)
      • etc (4)
      • 데이터베이스 (2)
    • 프로젝트 (3)
      • Petogram (2)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • HTTP
  • 쓰기 지연
  • Java
  • 런타임데이터영역
  • db
  • 객체지향
  • 이펙티브자바
  • 변경 감지
  • Petogram
  • OS
  • MSA
  • 네트워크
  • 영속성컨텍스트
  • 클린 아키텍처 #spring
  • Service discovery
  • 디자인 패턴
  • pojo
  • 운영체제
  • EUREKA
  • Spring
  • 캐시
  • 스트림
  • 메모리
  • Redis
  • 상속
  • ORM
  • JPA
  • java8
  • JDK 동적프록시
  • 데이터베이스

최근 댓글

최근 글

hELLO · Designed By 정상우.
밝은별 개발자
[Spring Cloud] Spring Cloud OpenFeign
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.