티스토리 뷰

외부 도메인 API 요청을 할 경우 다양한 옵션이 있습니다. Web Client, Socket, Apache Client, Feign Client, RestTemplate 등이 대표적입니다. 그 중에서 Feign Client에 대해 알아보도록 하겠습니다.

 

 

1. Feign Client 개념과 라이브러리 


[ Feign Client 란? ]

Feign Client는 외부 API 요청하는 라이브러리와 비슷하게 서버와 통신할 때 HTTP 프로토콜을 사용하는 라이브러리를 의미합니다. 

 

[ Feign Client 기능 ]

  • 요청 및 응답 처리: GET, POST, PUT, DELETE 등 다양한 HTTP 요청 메소드를 지원하고, 서버로부터의 응답을 처리합니다.
  • 직관적인 API 설계: 개발자가 손쉽게 HTTP 요청을 작성하고, 필요한 헤더나 파라미터를 추가할 수 있게 도와줍니다.
  • 에러 처리: 통신 중 발생할 수 있는 다양한 오류를 예외 처리하여 안정성을 높입니다.
  • 비동기 처리: 비동기 방식으로 요청을 처리하여 성능을 최적화하고, 리소스 사용을 효율적으로 관리합니다.

 

[ Feign Client 사용하는 이유 ]

1. 간편하고 직관적인 HTTP 클라이언트를 원하는 경우

  • Spring annotation + Feign Client annotation 사용하게 간편하게 HTTP 요청을 작성할 수 있습니다.
  • 이는 개발 생산성을 높이고, 코드가 간단하고 간결성을 유지하는 데 큰 장점이 됩니다.
@FeignClient(name = "githubClient", url = "https://api.github.com")
public interface GitHubClient {

    @GetMapping("/users/{username}")
    GitHubUser getUser(@PathVariable("username") String username);
}

 

2. 복잡한 API 통신이 필요한 경우

  • 다수의 외부 API와 상호작용해야 하는 경우, Feign Client의 인터페이스 기반 접근 방식은 코드의 가독성과 유지보수성을 높이는 데 도움이 됩니다.
  • 예를 들어) 도메인 별로 API 요청과 공통 에러 핸들링을 묶어서 사용할 수 있어서 다수의 외부 API를 간결하고 효율적으로 사용할 수 있는 장점이 있습니다.
// 호텔 도메인
@FeignClient(name = "hotelClient", url = "https://api.hotel.com")
public interface HotelClient {

    @GetMapping("/hotels/{city}")
    List<Hotel> getHotels(@PathVariable("city") String city);
}

// 항공 도메인
@FeignClient(name = "flightClient", url = "https://api.flight.com")
public interface FlightClient {

    @GetMapping("/flights/{city}")
    List<Flight> getFlights(@PathVariable("city") String city);
}

 

3. 마이크로서비스 아키텍처(MSA) 프로젝트인 경우

  • 여러 개의 마이크로서비스가 서로 통신해야 하는 환경에서는 Feign Client가 특히 유용합니다.
  • Feign Client는 서비스 간의 HTTP 통신을 단순화하고, 인터페이스 기반으로 선언적 HTTP 요청을 작성할 수 있게 해줍니다.

 

 

[ Feign Client 라이브러리 설치 ]

// gradle 기준
dependencies {
    implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:{SpringBoot Version}"
    }
}
  • Feign Client을 사용하기 위한 의존성이며 자신의 SpringBoot Version에 따라 값을 설정해야 한다. (링크)  

 

 

2. Feign Client 사용 방법


Feign Client 사용하는 경우 두 가지로 나누어 보았다.

  • 1. 공통으로 사용하는 경우
  • 2. 외부 도메인을 종속적으로 사용하는 경우

 

[ 공통으로 사용하는 경우 ]

공통으로 사용할 설정을 정의합니다.

FeignConfig.java

public class FeignConfig {
    @Bean
    public ErrorDecoder errorDecoder() {
        return new GlobalErrorDecoder();
    }
}

이 설정 클래스는 공통 에러 핸들링을 위해 GlobalErrorDecoder를 빈으로 등록합니다. 모든 Feign Client가 공통된 에러 핸들링 로직을 사용할 수 있도록 합니다. 이를 통해 코드의 중복을 줄이고, 에러 핸들링 로직의 일관성을 유지할 수 있습니다.

 

GlobalErrorDecoder.java

@Slf4j
public class GlobalErrorDecoder implements ErrorDecoder {
    private final ErrorDecoder errorDecoder = new Default();

    @Override
    public Exception decode(String methodKey, Response response)  {
        HttpStatus status = HttpStatus.valueOf(response.status());

        if (status.is5xxServerError()) {
            throw new FailApiServerException();
        }

        if (status.is4xxClientError()) {
            throw new FailApiClientException();
        }

        return errorDecoder.decode(methodKey, response);
    }
}

이 클래스는 외부 API 호출 시 발생하는 모든 에러를 처리합니다.

  • 5xx : FailApiServerException 예외
  • 4xx : FailApiClientException 예외

이를 통해 서비스 로직에서 예외 상황을 명확하게 구분하여 처리할 수 있습니다.

 

xxxFeignClient.java

@FeignClient(name = "domain", url = "${xxx.xxx.url}", configuration = FeignConfig.class)
public interface xxxFeignClient {
    @GetMapping("/{id}")
    String getUser(URI uri, @PathVariable Long id);
}

이 인터페이스는 외부 API와의 통신을 위한 Feign Client를 정의합니다. @FeignClient 어노테이션을 통해 외부 도메인의 이름, URL, 설정 클래스를 지정할 수 있습니다. 동적으로 URI를 생성할 수 있어 유연한 API 호출이 가능합니다.

  • name : 외부 도메인명
  • url : 외부 URL
  • configuration : 공통으로 사용하는 설정 클래스

 

 

xxxService.java

public class xxxService {

    @Value("${xxx.xxx.url}")
    private String url;

    private final xxxFeignClient xxxFeignClient;
    
    public String getUser(Long id) {
        try {
            return xxxFeignClient.getUser(URI.create(url), id);
        } catch (FailApiServerException || FailApiClientException e) {
            return null;
        }
    }
}

서비스 클래스는 Feign Client를 주입받아 사용합니다. API 호출 중 발생하는 예외를 핸들링하여 사용자에게 500 에러를 반환하지 않도록 합니다. 이는 서비스의 안정성과 사용자 경험을 향상시키는 데 중요합니다.

 

 


[ 외부 도메인을 종속적으로 사용할 경우 ]

xxxFeignConfig.java

public class xxxFeignConfig {

    @Value("${xxx.xxx.secretkey}")
    private String secretKey;

    @Bean
    public ErrorDecoder errorDecoder() {
        return new xxxErrorDecoder();
    }
    
    @Bean
    public RequestInterceptor requestInterceptor() {
        return requestTemplate -> requestTemplate.header(
                HttpHeaders.AUTHORIZATION, "Bearer " + secretKey
        );
    }
}

 

이 클래스는 특정 외부 도메인에 필요한 헤더 값을 설정하고, 에러 핸들링을 위해 xxxErrorDecoder를 빈으로 등록합니다. RequestInterceptor를 통해 요청 시 필요한 인증 헤더를 자동으로 추가합니다. 이를 통해 각 도메인별 요구사항에 맞는 설정을 유연하게 적용할 수 있습니다.

 

xxxErrorDecoder.java

@Slf4j
public class xxxErrorDecoder implements ErrorDecoder {
    private final ErrorDecoder errorDecoder = new Default();

    @Override
    public Exception decode(String methodKey, Response response)  {
        HttpStatus status = HttpStatus.valueOf(response.status());

        try {
          String errorMessage = IOUtils.toString(response.body().asInputStream(), StandardCharsets.UTF_8);
         
          // 외부 도메인에 맞는 에러 핸들링
          ...
          
          } catch (IOException ignored) {}

        return errorDecoder.decode(methodKey, response);
    }
}

 

이 클래스는 외부 도메인의 비즈니스 로직에 맞게 에러를 커스텀 처리를 합니다. 이를 통해 외부 API와의 통신 문제를 효과적으로 모니터링하고 대응할 수 있습니다.

 

xxxFeignClient.java

@FeignClient(name = "domain", url = "${xxx.xxx.url}", configuration = xxxFeignConfig.class)
public interface xxxFeignClient {
    @GetMapping("/{id}")
    String getUser(URI uri, @PathVariable Long id);
}

이 인터페이스는 도메인별 설정 클래스를 사용하여 외부 API와의 통신을 위한 Feign Client를 정의합니다. 특정 도메인의 요구사항에 맞춘 설정을 적용하여 API 호출을 수행합니다.

 

xxxService.java

public class xxxService {

    @Value("${xxx.xxx.url}")
    private String url;

    private final xxxFeignClient xxxFeignClient;
    
    public String getUser(Long id) {
        try {
            return xxxFeignClient.getUser(URI.create(url), id);
        } catch (xxxApiException e) {
            return null;  // custom 처리
        }
    }
}

서비스 클래스는 외부 도메인에 종속적인 설정을 반영하여 API 호출 에러를 핸들링합니다. 특정 예외 상황에 맞춰 커스텀 처리를 수행하여 서비스의 안정성을 높입니다.

 

 

[ 결론 ]

Feign Client를 사용하는 방법은 두 가지 접근 방식으로 나눌 수 있습니다. 첫 번째는 공통 설정을 사용하여 여러 도메인에서 일관된 에러 핸들링과 설정을 공유하는 방식입니다. 이를 통해 코드의 중복을 줄이고, 설정과 에러 핸들링 로직의 일관성을 유지할 수 있습니다. 두 번째는 외부 도메인에 종속적인 설정을 사용하여 각 도메인별로 맞춤형 설정과 에러 핸들링을 적용하는 방식입니다. 이를 통해 각 도메인의 특성에 맞춘 유연한 처리가 가능합니다. 두 방법 모두 상황에 맞게 적절히 사용하면, 코드의 가독성과 유지보수성을 높이는 데 큰 도움이 됩니다.

 

 

[출처]
Spring Cloud OpenFeign   
Declarative REST Client: Feign
우아한 feign 적용기