[MSA] FeignClient 에러 처리

jineey·2024년 11월 29일

MSA

목록 보기
33/36

FeignClient 에러 처리 #1

📌 개요

  • 에러 처리 방법
    1. try/catch 사용
    2. ErrorDecoder 사용

ErrorDecoder란?

  • Feign 클라이언트에서 HTTP 요청을 보낼 때 발생하는 오류를 처리하기 위해 사용하는 인터페이스
  • 서버로부터 반환된 HTTP 응답 코드에 따라 예외를 처리하거나 사용자 정의 에러 메시지를 반환 가능
  • 테스트 방법
    1. User-service 에서 Order-service 를 호출하는 과정에서
    잘못된 Order-service의 엔드 포인트 주소를 호출
    2. 올바른 호출 시, 사용자 정보 + 주문 내역 확인이 가능했으나
    잘못된 호출 시, 사용자 정보만 확인 가능하도록 에러 처리

📌 소스코드 #1

1️⃣ User-service에서 Order-service 호출 시 에러 유도

  • OrderServiceClient.java 수정
//테스트를 위해 잘못된 주소값 설정
//올바른 주소: "/order-service/{userId}/orders"
  @GetMapping("/order-service/{userId}/orders_ng")
  List<ResponseOrder> getOrders(@PathVariable String userId);

📌 실행결과 #1


➡ 존재하지 않는 엔드 포인트이기 때문에 500 Internal Server Error와 함께 FeignExceptionNotFound404 에러를 반환함.

📌 소스코드 #2

2️⃣ Try/Catch를 사용하여 에러 처리

  • UserServiceImpl.java 수정
@Override
    public UserDto getUserByUserId(String userId) {
        UserEntity userEntity = userRepository.findByUserId(userId);

        if(userEntity == null){
            throw new UsernameNotFoundException("User not found");
        }

        UserDto userDto = new ModelMapper().map(userEntity, UserDto.class);

        /* Using as FeignClient */
        /* Feign Exception Handling */
        List<ResponseOrder> orderList = null;

        try{
            orderList = orderServiceClient.getOrders(userId);
        } catch(FeignException e) {
            log.error(e.getMessage()); //@Slf4j 추가하여 log 사용
        }
        /* End */
        userDto.setOrders(orderList);

        return userDto;
    }

📌 실행결과 #2

  • 로그
  • 반환값

    200 OK와 함께 사용자 정보만 나타남

📌 소스코드 #3

3️⃣ ErrorDecoder 사용하여 에러 처리

  • FeignErrorDecoder .java 생성
package com.example.euserservice.error;

import feign.Response;
import feign.codec.ErrorDecoder;
import org.springframework.http.HttpStatusCode;
import org.springframework.web.server.ResponseStatusException;

public class FeignErrorDecoder implements ErrorDecoder {

    @Override
    public Exception decode(String methodKey, Response response) {
        switch (response.status()) {
            case 400:
                break;
            case 404:
                if(methodKey.contains("getOrders")){
                    return new ResponseStatusException(HttpStatusCode.valueOf(response.status()), "User's orders is empty.");
                }
                break;
            default:
                return new Exception(response.reason());
        }

        return null;
    }
}

💡 코드 설명

  • ErrorDecoder를 상속 시, decode() 메서드를 필수로 Override 받아야 함
  • 응답코드에 따라 다른 에러 처리 방법 구현 가능
  • 응답코드가 404 인 경우에도 세부적으로 특정 메서드의 이름에 따라 다른 처리 방법 구현 가능
  • UserServiceImpl.java 수정
@Override
    public UserDto getUserByUserId(String userId) {
        UserEntity userEntity = userRepository.findByUserId(userId);

        if(userEntity == null){
            throw new UsernameNotFoundException("User not found");
        }

        UserDto userDto = new ModelMapper().map(userEntity, UserDto.class);

        /* FeignDecoder Handling */
        List<ResponseOrder> orderList = orderServiceClient.getOrders(userId);

        userDto.setOrders(orderList);

        return userDto;
    }

try/catch 부분이 불필요하므로 삭제하고 기존 코드대로 변경

📌 실행결과 #3



500 Internal Server Error404 Not Found 로 변경됨

➡ 설정한 에러 메시지가 반환됨

📌 소스코드 #4

4️⃣ 설정 파일에서 에러 메시지 설정

  • Config 설정 파일 user-service.yml 수정
order_service:
  url: http://order-service/order-service/%s/orders
  exception:
    orders_is_empty: User's orders is empty.
  • FeignErrorDecoder .java 수정
package com.example.euserservice.error;

import feign.Response;
import feign.codec.ErrorDecoder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatusCode;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ResponseStatusException;

@Component //추가
public class FeignErrorDecoder implements ErrorDecoder {

    Environment env;

    @Autowired
    public FeignErrorDecoder(Environment env) {
        this.env = env;
    }

    @Override
    public Exception decode(String methodKey, Response response) {
        switch (response.status()) {
            case 400:
                break;
            case 404:
                if(methodKey.contains("getOrders")){
                    return new ResponseStatusException(HttpStatusCode.valueOf(response.status()), env.getProperty("order_service.exception.orders_is_empty"));
                }
                break;
            default:
                return new Exception(response.reason());
        }

        return null;
    }
}

➡ 기존 하드코딩 대신 설정 파일에서 메시지 내용 가져옴
@Component 어노테이션을 추가하여 빈으로 등록

  • EUserServiceApplication 수정
package com.example.euserservice;

import com.example.euserservice.error.FeignErrorDecoder;
import feign.Logger;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class EUserServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(EUserServiceApplication.class, args);
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }

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

//    @Bean
//    public FeignErrorDecoder getFeignErrorDecoder(){
//        return new FeignErrorDecoder();
//    }

}

FeignErrorDecoder 파일에서 직접 @Component 어노테이션을 추가하여 빈으로 등록했기 때문에 해당 클래스에서 추가적으로 Bean에 등록할 필요가 없기 때문에 주석 처리

📌 실행결과 #4

profile
새싹 개발자

0개의 댓글