토이 프로젝트 배우게 된 것들 & 오류 해결🐰

feign client ErrorDecoder 인터페이스 사용시 마주한 문제 해결기 🙂

j_estory 2023. 1. 2. 22:47

위의 해결기를 작성하기 전에 저는 Feign Client를 사용하면서 예외 처리시, ErrorDecoder 인터페이스를 통해서 예외를 처리하고 있다. 정도의 간단한 개념만 잡혀 있는 상태였다. 

 

👉🏻 내가 마주한 문제

 

프로젝트에서 특정 주소에 대한 위, 경도 값을 알기 위해서 카카오 개발자 센터에서 제공하는 지도 Rest-Api 통신을 개발하였고, 

통신을 위한 클라이언트는 Feign Client 를 사용하게 되었다. 

정상 응답값에 대한 처리는 완료하였고, 예외에 대한 처리를 진행하고자 하였다. 

 

예를들어, 카카오 API에서는 클라이언트 Error로 HttpSatus를 400, 401로 내려주고 있었으며, 

클라이언트 에러의 예시는 파라미터 정보를 보내지 않았거나, API Key 등을 잘못 보냈을 경우로 보고 있는 것 같아 보였다.

 

카카오에서 내려주는 에러 형식은 다음과 같았다.

{
    errorType: "MissingParameter",
    message: "query parameter required"
}

 

그리고 위의 에러 발생시, 내가 원하는 에러 응답은 커스텀하게 만든 실패 응답 객체 spec에 맞추어 내려가길 원했다.

 

하지만 !!

ErrorDedecoder 인터페이스 구현 시, 구현 메소드인 decode() 의 매개변수은 Response 객체에 카카오 에러 응답 객체가 

쉽게 눈에 보이지 않았다 ..! 

분명 될 것 같은데 있을 것 같은데 어떻게 가져올까 .. 많이 찾아보았지만 쉽사리 해결한 레퍼런스가 보이지 않았다.

그러면 어쩔 수 없이 HttpSatus로 에러를 판별해줘야 할까 ? 잠깐의 이상한 (?) 생각도 해보았지만 

말이 되지 않았다. 

그 이유는 400 Status에 대해서는 상황에 따라 이유가 다르기 때문이었다.

 

👉🏻 해결법

 

그러다 발견한 하나의 문서 !! 

https://www.baeldung.com/feign-retrieve-original-m

제목이 "Retrieve Original Message From Feign ErrorDecoder" -> "Feign ErrorDecoder에서 원본 메시지 검색"

이 제목을 보는 순간 내가 찾는 정답이 있을거라 생각했고 내 생각이 맞았다.

해당 글을 참고하여 아래와 같이 카카오 에러 응답 스펙을 추출하였다.

@Override
public Exception decode(String methodKey, Response response) {

    KakaoDto.ErrorResponse msg = null;
    try (InputStream bodyIs = response.body().asInputStream()) {
        ObjectMapper mapper = new ObjectMapper();
        msg = mapper.readValue(bodyIs, KakaoDto.ErrorResponse.class);
    } catch (IOException e) {
        return new Exception(e.getMessage());
    }

    switch (response.status()) {
        case 400: case 401:
            throw new KakaoResException(msg.getMessage(), msg.getErrorType());
    }
    return null;
}

400, 401 클라이언트 오류가 아닌 기본 예외 처리도 추가되어야 할 것 같다!

 

이후, 내가 처리한 예외 처리 방법은

커스텀하게 생성한 에러 클래스를 통해 카카오 에러 응답 메시지 및 에러 응답 타입을 전달하였고, 

던져지 예외는 Exception Handler을 통해 아래와 같이 처리하였다.

 

KakaoResException.class 

// getter을 반드시 써줘야 한다! getter가 없다면 부모 타입인 RuntimeException에 정의되어 있는 getMessage() 가 호출되기 때문에
// 반드시 자식 객체인 (KakaoResException) 에서 재정의되어 해당 클래스의 필드 값을 가져오도록 해야 한다!
@Getter 
public class KakaoResException extends RuntimeException{
    private String message;
    private String errorType;

    public KakaoResException() {
    }

    public KakaoResException(String message, String errorType) {
        this.message = message;
        this.errorType = errorType;
    }
}

 

ApiExceptionHandler.class

@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = KakaoResException.class)
    public ApiResponse<Void> kakaoErrorHandler(KakaoResException e)
    {
        return new ApiResponse<Void>(Status.FAIL, e.getMessage());
    }

 

결과적으로, 내가 원하던 커스텀한 에러 응답 객체가 생성되어 반환되게 된다.

 

👉🏻 반환 스펙

data : null
meta : {
    status: FAIL
    message : "query parameter required"
}