프로젝트/쇼핑몰 프로젝트

[쇼핑몰 프로젝트] FeignClient 에러 처리: ErrorDecoder

Joo.v7 2025. 8. 2. 16:26

0. 개요

쇼핑몰 프로젝트를 혼자 다시 만들면서, FeignClient 사용 중 발생하는 예외 처리 방식에 대해 고민하게 되었다.

 

당시 프로젝트는 MSA 구조였고, 각 API 내부에서는 상황에 맞는 적절한 예외 처리를 하고 있었다. 문제는, 서버 간 통신을 위해 FeignClient로 요청을 보낼 때 발생했다. 요청을 받은 서버는 어떤 예외가 발생했는지 알고 있었지만, 요청을 보낸 서버에서는 모든 예외가 그냥 FeignException 하나로 퉁쳐졌다.

 

그 결과, 실제 어떤 오류가 발생했는지를 확인하려면 요청을 받은 서버의 로그를 직접 확인해야 했고, 이는 디버깅이나 예외 추적에 있어 꽤 큰 불편함이었다.

 

그때는 프로젝트 마감 기한이 촉박해서 그냥 넘겼지만, 이번에 혼자 다시 프로젝트를 진행하면서 이 문제를 제대로 해결해보고 싶었다.
그리고 그 해결책으로 떠오른 것이 바로 ErrorDecoder였다.


1. 기존 방식: 무슨 오류인지 알 수 없던 예외 처리 방식

기존에는 FeignClient 호출 중 발생하는 예외를 모두 FeignException으로 처리했기 때문에, 실제 어떤 오류가 발생했는지를 알기 어려웠다.

 

응답 본문에 담긴 에러 메시지나 코드 등의 정보를 활용하지 못한 채, 단순히 실패로만 처리하는 구조였다.

 

아래는 기존에 사용하던 예외 처리 방식이다.

FeignClient 호출 중 예외가 발생하면 FeignException으로만 처리하고, 어떤 오류가 발생했는지는 별도로 확인할 수 없는 구조였다.

    /**
     * 관리자 페이지 - 등급 수정
     */
    @PutMapping("/modify/{grade-id}")
    public String memberGradeModify(@PathVariable("grade-id") Integer gradeId,
                                    @ModelAttribute GradeModifyRequest gradeModifyRequest) {
        try {
            gradeService.modifyGrade(gradeId, gradeModifyRequest);
            return "redirect:/admin/grades";
        }catch(FeignException e) {
            return "redirect:/admin/grades";
        }
    }

2. 개선: ErrorDecoder로 예외 커스터마이징

기존 방식에서는 FeignClient 호출 중 발생한 모든 예외가 FeignException으로 처리되었기 때문에, 다른 API 서버에서 내려준 에러 메시지나 에러 코드 등의 정보를 활용할 수 없었다.

 

이 문제를 해결하기 위해 Feign에서 제공하는 ErrorDecoder 인터페이스를 구현하고, 응답 본문을 직접 파싱하여 내가 정의한 커스텀 예외인 FeignClientException으로 변환하는 방식을 적용했다.

 

이제는 어떤 에러가 발생했는지에 대한 에러 코드와 메시지를 로그로 남길 수 있게 되었고, 서버 간 통신에서 발생한 문제를 요청을 보낸 서버에서 확인하고 빠르게 대응할 수 있는 구조가 만들어졌다.

 

* FeignErrorDecoder - ErrorDecoder 구현체

package com.chokchok.chokchokfront.common.client;

import com.chokchok.chokchokfront.exception.common.FeignClientException;
import com.chokchok.chokchokfront.common.dto.ErrorResponseDto;
import com.fasterxml.jackson.databind.ObjectMapper;
import feign.Response;
import feign.codec.ErrorDecoder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * Feign의 기본 에러 응답을 커스텀 하기 위한 ErrorDecoder 구현 클래스
 * FeignClient에서 외부 API 호출 시 에러가 발생하면, 해당 응답을 파싱하여 커스텀 예외(FeignClientException)로 변환합니다.
 */
@Slf4j
@RequiredArgsConstructor
@Component
public class FeignErrorDecoder implements ErrorDecoder {

    private final ObjectMapper objectMapper;

    /**
     * FeignClient 호출 중 에러 응답을 받아 커스텀 예외로 변환하는 메서드
     *
     * @param s
     * @param response
     * @return Exception
     */
    @Override
    public Exception decode(String s, Response response) {
        try {
            if(response.body() == null) {
                log.error("Feign error occurred: Empty body");
                throw new FeignClientException("Feign response body is null");
            }

            ErrorResponseDto errorResponseDto = objectMapper.readValue(response.body().asInputStream(), ErrorResponseDto.class);
            log.error("=== Feign error occurred  === time:{}, code:{}, message:{}", errorResponseDto.timestamp(), errorResponseDto.errorCode(), errorResponseDto.message());

            return new FeignClientException(errorResponseDto.message());

        } catch(Exception e) {
            log.error("Feign error occurred: response failed");
            return new FeignClientException("Feign response failed");
        }
    }
}

3. 마무리

기존에는 FeignClient 호출 중 발생한 예외를 단순히 FeignException으로만 처리하면서, 다른 API 서버에서 내려준 구체적인 에러 정보들을 활용하지 못하는 구조적 한계가 있었다.

 

이 문제를 해결하기 위해 ErrorDecoder를 활용하여, 응답 본문을 파싱해 내가 정의한 커스텀 예외(FeignClientException)로 변환하고 로그을 남김으로써 문제가 발생한 서버를 확인하지 않아도 어떤 에러가 발생했는지 로그를 통해 코드와 메시지를 명확히 파악할 수 있게 되었고, 전역 예외 처리기(@ControllerAdvice)와 연동하여 일관된 응답 포맷도 제공할 수 있었다.


참고