티스토리 뷰
외부 도메인 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 적용기
'∙Java & Spring' 카테고리의 다른 글
MessageSource 유틸 클래스 만들기 (0) | 2023.10.21 |
---|---|
추상화, 인터페이스 활용하기 (0) | 2023.07.25 |
포인트 신청 시 발생하는 동시성 문제 (0) | 2022.09.24 |
ThreadPoolTaskExecutor 이용하여 성능 개선하기 (2) | 2022.05.29 |
Junit Mock 사용하기 (0) | 2022.05.06 |