본문 바로가기

MicroService Architecture

FeignClient(OpenFeign) 사용 이유, 예제

 

https://bestdevelop-lab.tistory.com/174

 

Feign Client / Web Client / RestTemplate

Feign Client / Web Client / RestTemplate 이 세 개는 다른 서버와 통신하기 위해, 즉 REST API로 서비스간 통신하기 위해 사용 RestTemplate이란?HTTP 요청을 만들기 위해 Spring Framework에서 제공하는 동기식 클라

bestdevelop-lab.tistory.com

FeignClient 사용 이유

  • 앞서 RestTemplate, FeignClient, WebClient를 비교했을 때 언급하였 듯이, RestTemplate은 더 이상 개발이 되지 않게 되었으며, 아래의 이유들로 RestTemplate보다 FeignClient가 더 선호되는 것 같음
  1. SpringMvc에서 제공되는 어노테이션 사용 가능
  2. 기존에 사용되던 HttpClient보다 더 간편하게 사용 가능하며, 가독성이 뛰어남
  3. 통합 테스트가 비교적 간편
  4. 사용자 의도대로 커스텀이 간편

 

FeignClient vs RestTemplate 코드 비교
  • 외부 서버의 날씨 API를 호출할 때 RestTemplate으로 작성
@Service
public class WeatherService {

    public String getTodayWeather(){
        RestTemplate restTemplate = new RestTemplate();
        
        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", "Bearer ...");
        
        URI uri = UriComponentsBuilder
                .fromUriString("http://korea-weather")
                .path("/api")
                .queryParam("date", "2024-03-16")
                .encode()
                .build()
                .toUri();
                
        return restTemplate.exchange(
                       uri,
                       HttpMethod.GET,
                       new HttpEntity<>(headers),
                       String.class)
              .getBody();
        
    }
}

 

  • OpenFeign으로 작성
@FeignClient(name = "WeatherApi", url = "http://korea-weather")
public interface WeatherApi {

    @GetMapping
    ResponseEntity getTodayWeather(
            @RequestHeader String authorization,
            @RequestParam String date);
}

 

  • OpenFeign을 사용할 경우 작성해야할 코드의 양이 비약적으로 감소
  • @GetMapping과 같은 Spring MVC 어노테이션 활용도 가능

 

Gradle 의존성 추가
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'

 

활성화
  • main 클래스에 OpenFeign을 사용한다고 선언(@EnableFeignClients)하여 OpenFeign을 활성화
@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
@EnableJpaAuditing
@EnableScheduling
@EnableFeignClients
public class PlayheavenApplication {
    public static void main(String[] args) {
        SpringApplication.run(PlayheavenApplication.class, args);
    }

}

 

 

FeignClient 구현
@FeignClient(name = "WeatherApi", url = "http://korea-weather")
public interface WeatherApi {

    @GetMapping("/v1/list/{day}")
    List<RainRes> getRainList(
            @RequestHeader("authorization") String authorization,
            @PathVariable(name = "day") String day);
            
    @GetMapping("/v1/foggy")
    List<FoggyRes> getFoggyList(
            @RequestHeader("authorization") String authorization,
            @RequestParam(name = "page") Integer page,
            @RequestParam(name = "pageSize") Integer pageSize);
                        
    @PostMapping("/v1/weather/{createId}")
    Response insertSatisfaction(
            @RequestHeader("authorization") String authorization,
            @PathVariable(name = "createId") Integer createId,
            @RequestBody WeatherReq weatherReq);
}
  • interface 생성
    • @FeignClient 어노테이션 추가
    • name 속성 ⇒ Feign Client의 식별자 정의
      • Spring Cloud가 서비스 레지스트리(예: Eureka)와 연동해 마이크로서비스 간 통신을 할 때 유용
      • 로드 밸런싱 및 서비스 디스커버리를 위해 사용 가능
    • url 속성 ⇒ 호출할 URL
  • 메서드는 Spring MVC와 거의 동일
    • @GetMapping, @PostMapping 등을 사용하여 경로를 작성하면 최상단의 url 뒤에 추가가 되는 것 같음
      • Ex) @GetMapping("/v1/list/{day}") ⇒ http://korea-weather/v1/list/{day}로 요청
    • @RequestHeader, @RequestParam, @PathVariable 등 모두 사용이 가능하며, POST의 경우 @RequestBody로 JSON 요청 또한 가능
      • Ex) 외부 API에 'http://korea-weather/v1/whether/{createId}' 라는 URL로 POST 요청
      • 외부 API에서 해당 URL을 처리하는 메서드에는 헤더에 인가 정보, createdId가 url 경로에 포함되고, WeatherReq 객체를 JSON으로 받는 매개 변수가 존재
@Service
@RequiredArgsConstructor
public class WeatherService {

    private final WeatherApi weatherApi;
    
    public List<RainRes> getRainListByWeatherApi(String authorization, String day) {
        ...
        
        return weatherApi.getRainList(authorization, day);
    }

}
  • Service 클래스에서 생성한 인터페이스(WeatherApi)를 DI하여 사용하면 됨

Configuration의 활용

로깅(Logging)
public class WeatherApiConfig {
    @Bean
    Logger.Level weatherApiLoggerLevel() {
        return Logger.Level.FULL;
    }
}

 

  • Logger 레벨
    • NONE : 로깅 X (기본값)
    • BASIC : 요청 메서드, URI, 응답 상태, 실행시간 로깅
    • HEADERS : 요청, 응답헤더 및 기본 정보들 로깅
    • FULL : 요청, 응답 헤더 및 바디, 메타 데이터를 로깅
  • OpenFeign의 로그는 DEBUG 레벨로만 남길 수 있기 때문에 로그 레벨을 DEBUG로 설정
  • application.yml 파일
logging:
  level:
    com.openfeign.package: DEBUG
  • configuration은 @FeignClient 속성으로 지정이 가능하고, 두 개 이상의 configuration 설정 또한 가능
@FeignClient(
        name = "WeatherApi",
        url = "${external.weather.api}",
        configuration = {WeatherApiConfig.class})
public interface WeatherApi{
   ...
}

 

재시도
public class RetryConfig {
    @Bean
    public Retryer feignRetryer() {
        // 0.1초 간격에서 최대 1초 간격으로 최대 3번 재시도
        return new Retryer.Default(100, 1000, 3);
    }
}
  • configuration에서 API 요청을 재시도하는 간격을 설정 가능
  • 다만 위에서 사용한 RetryerIOException이 발생한 경우에만 동작
  • 다른 예외에도 재시도를 하고 싶다면 인터셉터를 직접 구현해야 함
@FeignClient(
        name = "WeatherApi",
        url = "${external.weather.api}",
        configuration = {WeatherApiConfig.class, RetryConfig.class })
public interface WeatherApi{
   ...
}
  • 2개 이상의 configuration 설정
공통 헤더 적용
  • 매번 메서드에 @RequestHeader로 보안 토큰을 넣어주는 것은 매우 귀찮고 비효율적
public class HeaderConfiguration {
    @Bean
    public RequestInterceptor requestInterceptor() {
        return requestTemplate -> requestTemplate.header("Authorization", "Bearer ...");
    }
}
  • 위와 같이 configuration에서 RequestInterceptor를 설정하여 원하는 헤더를 설정
  • configuration을 FeignClient에 적용시키면 모든 메서드에 Header가 설정
참고 자료

OpenFeign 이란? OpenFeign 사용법 : https://code-lab1.tistory.com/414

OpenFeign이 아직 감이 잡히지 않을 경우(클라이언트와 서버로 간단 코드 구현) : https://targetcoders.com/openfeign-%EC%98%88%EC%A0%9C/