OpenFeign은 자바 기반 마이크로서비스 아키텍처에서 RESTful 원격 호출을 간결하고 유지보수 용이하게 만드는 선언형 HTTP 클라이언트 라이브러리입니다. 개발자는 인터페이스 정의와 애너테이션만으로 외부 서비스와의 통신 로직을 구현할 수 있으며, 별도의 HTTP 연결 관리나 직렬화/역직렬화 코드를 작성하지 않아도 됩니다.
핵심 특징 및 이점
- 선언적 인터페이스 기반: 서비스 계약을 Java 인터페이스로 표현하고,
@GetMapping,@PostMapping등 Spring MVC 애너테이션을 그대로 사용 가능 - 자동 부하 분산 통합: Eureka 또는 Nacos와 연동 시, 내장된
LoadBalancerFeignClient가 서비스 인스턴스 목록을 동적으로 조회해 라운드로빈 방식으로 요청 전달 - 확장 가능한 코덱 지원: JSON, XML, Protobuf 등 다양한 포맷에 대한 커스텀
Encoder/Decoder구현이 용이하며, 기본적으로 Jackson을 활용한 객체 매핑 제공 - HTTP 클라이언트 교체 유연성: 기본
HttpURLConnection대신 OkHttp, Apache HttpClient 등 고성능 클라이언트로 교체 가능
기본 사용 예시 (순수 Feign)
다음 코드는 Spring Boot 외부에서도 동작하는 순수 Feign 클라이언트 구현입니다. 커스텀 디코더와 함께 SpringMvcContract를 적용해 Spring 스타일의 애너테이션을 사용합니다.
import feign.*;
import feign.codec.Decoder;
import feign.codec.ErrorDecoder;
import feign.jackson.JacksonDecoder;
import feign.slf4j.Slf4jLogger;
import lombok.Data;
import org.springframework.cloud.openfeign.support.SpringMvcContract;
import org.springframework.core.ResolvableType;
import java.io.IOException;
import java.lang.reflect.Type;
public class ExternalQuoteClientExample {
public static void main(String[] args) {
final QuoteService client = Feign.builder()
.logger(new Slf4jLogger())
.logLevel(Logger.Level.BASIC)
.contract(new SpringMvcContract())
.decoder(new CustomJsonDecoder())
.errorDecoder(new RetryableErrorDecoder())
.target(QuoteService.class, "https://api.example.com/v1");
final QuoteResponse quote = client.fetchLatestQuote("AAPL");
System.out.println("Current price: " + quote.getPrice());
}
interface QuoteService {
@RequestLine("GET /stocks/{symbol}/quote")
QuoteResponse fetchLatestQuote(@Param("symbol") String symbol);
}
static class CustomJsonDecoder implements Decoder {
private final ObjectMapper mapper = new ObjectMapper();
@Override
public Object decode(Response response, Type type) throws IOException {
try (InputStream is = response.body().asInputStream()) {
Class<?> rawType = ResolvableType.forType(type).getRawClass();
return mapper.readValue(is, rawType);
}
}
}
static class RetryableErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultDecoder = new Default();
@Override
public Exception decode(String methodKey, Response response) {
if (response.status() == 503) {
return new RetryableException("Service unavailable", response.request(), response);
}
return defaultDecoder.decode(methodKey, response);
}
}
@Data
public static class QuoteResponse {
private String symbol;
private BigDecimal price;
private LocalDateTime updatedAt;
private String currency;
}
}
Spring Boot 환경 통합
Spring Boot 프로젝트에서는 @EnableFeignClients를 통해 자동 구성 및 스캔을 활성화합니다. 아래는 OkHttp 기반 클라이언트로 교체하고, 커스텀 타임아웃 및 커넥션 풀을 설정하는 예제입니다.
@Configuration
public class FeignClientConfig {
@Bean
public Client feignClient() {
final OkHttpClient okHttpClient = new okhttp3.OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.connectionPool(new ConnectionPool(20, 5, TimeUnit.MINUTES))
.build();
return new OkHttpClient(okHttpClient);
}
@Bean
public Encoder feignEncoder() {
return new JacksonEncoder();
}
@Bean
public Decoder feignDecoder() {
return new JacksonDecoder();
}
}
클라이언트 인터페이스는 다음과 같이 정의됩니다:
@FeignClient(
name = "stock-api",
url = "${external.stock.api.base-url}",
configuration = FeignClientConfig.class
)
public interface StockApiClient {
@GetMapping("/stocks/{ticker}/summary")
StockSummary getStockSummary(@PathVariable String ticker);
@PostMapping("/alerts/subscribe")
@Headers("Content-Type: application/json")
SubscriptionResult subscribeAlert(@RequestBody AlertSubscription request);
}
로드 밸런싱 작동 원리
서비스 이름 기반 호출(@FeignClient(name = "user-service")) 시, OpenFeign은 다음 순서로 로드 밸런싱을 수행합니다:
FeignClientFactoryBean이LoadBalancerFeignClient인스턴스를 생성LoadBalancerFeignClient.execute()가CachingSpringLoadBalancerFactory를 통해ILoadBalancer획득ZoneAwareLoadBalancer는 Eureka 서버로부터 주기적으로 업데이트된 인스턴스 목록을 캐시함- 각 요청 시
chooseServer(null)메서드가 실행되어 라운드로빈 또는 가중치 기반 알고리즘으로 특정 인스턴스 선택
성능 최적화 팁
- 공유된
OkHttpClient인스턴스 재사용 — 각 클라이언트마다 새 인스턴스 생성 금지 - 디코딩 성능 향상을 위해
JacksonDecoder대신StringDecoder+ 수동 파싱 조합 고려 - 고빈도 호출 시
@CachableView또는 외부 캐시 계층 도입 - 모니터링을 위해
feign-micrometer또는feign-slf4j로깅 활성화