프로젝트/마이그레이션

Feign Client를 활용한 API 호출 및 예외 처리

10Biliion 2025. 3. 13. 15:40

 

 

Feign Client는 Spring Cloud에서 제공하는 HTTP 클라이언트로, RestTemplate이나 WebClient보다 간결한 방식으로 API 호출을 수행할 수 있습니다. Feign Client를 활용한 API 호출 방식과 예외 처리 방법을 예제와 함께 설명하겠습니다.

 

1. Feign Client란?

Feign은 Java에서 REST API를 쉽게 호출할 수 있도록 도와주는 HTTP 클라이언트입니다. 인터페이스 기반으로 RESTful API를 호출할 수 있어, 기존 RestTemplate을 사용할 때보다 코드량을 줄일 수 있습니다.

 

2. Feign Client 설정 및 구현

인터페이스 정의

@FeignClient(name = "apiClient", url = "http://example.com", configuration = FeignClientConfig.class)
public interface ApiClient {

    @GetMapping("{endpoint}")
    ResponseEntity fetchData(@RequestParam Map<string, object=""> params, @PathVariable String endpoint);

    @PostMapping("{endpoint}")
    ResponseEntity sendData(@RequestBody Map<string, object=""> payload, @PathVariable String endpoint);
}

Feign Client를 호출하는 서비스 구현

@Service
public class ApiService {

    private final FeignClientBuilder clientBuilder;

    public ApiService(ApplicationContext appContext) {
        this.clientBuilder = new FeignClientBuilder(appContext);
    }

    private Map<String, Object> handleResponse(ResponseEntity<?> responseEntity) {
        Map<String, Object> body = JsonUtil.parse(responseEntity.getBody(), new TypeReference<Map<String, Object>>() {});
        boolean success = (boolean) body.get("success");

        if (!success) {
            if (Integer.parseInt(body.get("code").toString()) < 500) {
                throw new BusinessException(body.get("message").toString());
            } else {
                throw new RuntimeException("서버 오류 발생");
            }
        }

        return JsonUtil.parse(body.get("data"), new TypeReference<Map<String, Object>>() {});
    }

    public Map<String, Object> getData(RequestDto requestDto) {
        ApiClient client = clientBuilder.forType(ApiClient.class, requestDto.getService()).build();
        ResponseEntity<?> responseEntity = client.fetchData(requestDto.getParams(), requestDto.getEndpoint());
        return handleResponse(responseEntity);
    }

    public Map<String, Object> postData(RequestDto requestDto) {
        ApiClient client = clientBuilder.forType(ApiClient.class, requestDto.getService()).build();
        ResponseEntity<?> responseEntity = client.sendData(requestDto.getParams(), requestDto.getEndpoint());
        return handleResponse(responseEntity);
    }
}

3. Feign 예외 처리 (ErrorDecoder)

Feign을 사용할 때 HTTP 오류 응답 (4xx, 5xx) 에 대해 적절한 예외 처리를 위해 ErrorDecoder를 구현합니다.

@Slf4j
@NoArgsConstructor
public class CustomErrorDecoder implements ErrorDecoder {

    @Override
    public Exception decode(String methodKey, Response response) {
        log.error("{} 요청이 실패했습니다. 상태 코드: {}, URL: {}, 응답 본문: {}",
                methodKey, response.status(), response.request().url(), ResponseUtil.getResponseBody(response));

        if (shouldRetry(response)) {
            return new RetryableException(0, String.format("%s 요청 실패 - 재시도 합니다. 상태: %s", methodKey, response.status()), null, null, null);
        }

        return new IllegalStateException(String.format("%s 요청 실패 - 상태: %s", methodKey, response.status()));
    }

    private boolean shouldRetry(Response response) {
        return response.request().httpMethod().toString().equalsIgnoreCase("GET") &&
               (response.status() >= 500 || response.status() == 429);
    }
}

4. Feign 요청 및 응답 로깅

Feign 요청 및 응답 데이터를 로깅할 수 있도록 ResponseUtil 유틸 클래스를 작성하였습니다.

@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ResponseUtil {
    
    public static String getRequestBody(Response response) {
        if (response.request().body() == null) {
            return "";
        }
        try {
            return new String(response.request().body(), StandardCharsets.UTF_8.name());
        } catch (UnsupportedEncodingException e) {
            log.error("Feign 요청 바디 변환 오류", e);
            return "";
        }
    }

    public static String getResponseBody(Response response) {
        if (response.body() == null) {
            return "";
        }
        try (InputStream responseBodyStream = response.body().asInputStream()) {
            return IOUtils.toString(responseBodyStream, StandardCharsets.UTF_8.name());
        } catch (IOException e) {
            log.error("Feign 응답 바디 변환 오류", e);
            return "";
        }
    }
}

5. Feign Client 설정 (FeignClientConfig)

Feign Client의 타임아웃 및 로깅을 설정하기 위해 FeignClientConfig 클래스를 작성하였습니다.

@Configuration
public class FeignClientConfig {

    @Bean
    public Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }

    @Bean
    public Request.Options requestOptions() {
        return new Request.Options(5000, 10000);
    }

    @Bean
    public ErrorDecoder errorDecoder() {
        return new CustomErrorDecoder();
    }
}

Feign Client를 활용하면 API 호출을 더욱 간결하게 작성할 수 있으며, ErrorDecoderResponseUtil을 활용하여 예외 처리를 할 수 있습니다. 마이크로서비스 환경에서는 서비스 간 통신이 많기 때문에, Feign을 잘 활용하면 좋습니다.