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 별로 설정이 필요할 때 사용한다.
- Header 값을 설정할 configuartion class를 생성을 하고,
RequestInterceptor
을 생성해 준다. - 원하는 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/
https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/
'백엔드 > 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 |