Microservice 통신 [2] FeignClient

최준호·2022년 3월 13일
1

Microservice Architecture

목록 보기
24/32
post-thumbnail

💭FeignClient란?

FeignClient는 Rest Call을 추상화한 Spring Cloud Netflix 라이브러리로 이전에 학습한 RestTemplate보다 훨씬 직관적이고 사용하기도 쉽다.

🔨FeignClient 설정

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

의존성 추가하고

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients	//FeignClient 추가
public class UserServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
	...
    
//    @Bean
//    @LoadBalanced
//    public RestTemplate getRestTemplate(){
//        return new RestTemplate();
//    }
}

@EnableFeignClients을 추가해준다.

@FeignClient(name = "order-service")    //microservice name
public interface OrderServiceClient {

    @GetMapping("/order-service/{userId}/orders")
    List<ResponseOrder> getOrders(@PathVariable String userId);
}

그 후 OrderServiceClient라는 interface를 하나 추가한 뒤 위와 같이 설정해준다. @FeignClient의 name 옵션은 microservice name을 의미하며 @GetMapping("/order-service/{userId}/orders")은 요청하는 url 값을 의미한다.

@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService{
    private final UserRepository userRepository;
    private final BCryptPasswordEncoder pwdEncoder;
    //private final RestTemplate restTemplate;
    private final OrderServiceClient orderServiceClient;    //FeignClient의 interface 받기
    private final Environment env;
    
    ...
    
    @Override
    public UserDto getUserByUserId(String userId) {
        UserEntity userEntity = userRepository.findByUserId(userId);
        if(userEntity == null) throw new UsernameNotFoundException("user name not found!");
        UserDto userDto = new ModelMapper().map(userEntity, UserDto.class);
//        List<ResponseOrder> orderList = new ArrayList<>();    //이전에 빈 배열을 반환하던 값

        /* Using as RestTemplate */
//        String orderUrl = String.format(env.getProperty("order_service.url"), userId);
//        ResponseEntity<List<ResponseOrder>> orderListResponse =
//                restTemplate.exchange(orderUrl, HttpMethod.GET, null, new ParameterizedTypeReference<List<ResponseOrder>>() {
//                });
//        List<ResponseOrder> orderList = orderListResponse.getBody();

        /* Using as FeignClient */
        List<ResponseOrder> orderList = orderServiceClient.getOrders(userId);

        userDto.setOrders(orderList);

        return userDto;
    }

}

그 후에 기존의 RestTemplate를 대신하여 FeignClient를 사용하여 코드를 작성했다. 이전의 방법보다 그저 메서드를 호출하는 방식처럼만 코드가 작성되기 때문에 훨씬 코드도 보기 좋고 직관적이다.

실제로 실행해보면 RestTemplate와 동일한 결과를 가져오는 것을 확인할 수 있다.

📃FeignClient log 찍어보기

logging:
  level:
    com.example.userservice.client: debug

yml에 추가해주고

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class UserServiceApplication {

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

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

Logger.Level을 반환하는 bean을 등록할건데 여기서 Logger.Level은 feign.Logger로 추가되어야한다.


그 후 실행하면 다음과 같이 Debug로 찍혀나오는 로그를 확인할 수 있다.

👏FeignClient 예외처리

@FeignClient(name = "order-service")    //microservice name
public interface OrderServiceClient {

    @GetMapping("/order-service/{userId}/orders_error")
    List<ResponseOrder> getOrders(@PathVariable String userId);
}

우선 일부러 에러를 내기위해 요청 url 값을 404가 뜨도록 일부러 틀리게 적는다.

요청을 하게되면

로그에는 에러로 찍혀나오고

서비스에 반환값으로는 주문 정보를 제외하고 모든 값이 찍혀서 정상 반환이 된다.

에러가 발생했는데 에러로 표시하지 않고 처리한 이유는 user-service 자체적인 에러가 아닌 order-service의 에러일 경우 user-service에서 에러가 반환되는것이 아닌 반환할 수 있는 데이터를 모두 반환하고 에러가 난 데이터는 따로 처리하는 방식으로 처리하는 것이 옳은 방향성이다.

🔨ErrorDecoder 구현

ErrorDecoder는 FeignClient에서 에러가 발생했을 때 핸들링을 좀 더 간단하게 해줄 수 있다.

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(HttpStatus.valueOf(response.status()),
                            "User orders is empty."
                    );
                }
                break;
            default:
                return new Exception(response.reason());
        }

        return null;
    }
}

FeignClient에서 제공하는 ErrorDecoder를 상속받아서 구현해주고

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class UserServiceApplication {

    ...


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

bean으로 등록해준다.

@Service
@RequiredArgsConstructor
@Slf4j
public class UserServiceImpl implements UserService{
    ...

    @Override
    public UserDto getUserByUserId(String userId) {
        UserEntity userEntity = userRepository.findByUserId(userId);
        if(userEntity == null) throw new UsernameNotFoundException("user name not found!");
        UserDto userDto = new ModelMapper().map(userEntity, UserDto.class);
//        List<ResponseOrder> orderList = new ArrayList<>();    //이전에 빈 배열을 반환하던 값

        /* Using as RestTemplate */
//        String orderUrl = String.format(env.getProperty("order_service.url"), userId);
//        ResponseEntity<List<ResponseOrder>> orderListResponse =
//                restTemplate.exchange(orderUrl, HttpMethod.GET, null, new ParameterizedTypeReference<List<ResponseOrder>>() {
//                });
//        List<ResponseOrder> orderList = orderListResponse.getBody();

        /* Using as FeignClient */
        //List<ResponseOrder> orderList = orderServiceClient.getOrders(userId);

        /* FeignClient exception handling*/
//        List<ResponseOrder> orderList = null;
//        try {
//            orderList = orderServiceClient.getOrders(userId);
//        }catch (FeignException e){
//            log.error(e.getMessage());
//        }

        List<ResponseOrder> orderList = orderServiceClient.getOrders(userId);
        userDto.setOrders(orderList);

        return userDto;
    }

}

try catch를 지우고 기존의 방식대로 다시 요청하도록 수정해준다.

getOrders가 포함된것도 확인이 가능하고

에러 메세지를 컨트롤했다. 이 핸들링을 통해 알맞게 반환하도록 변경해주면 될거 같다.

profile
코딩을 깔끔하게 하고 싶어하는 초보 개발자 (편하게 글을 쓰기위해 반말체를 사용하고 있습니다! 양해 부탁드려요!) 현재 KakaoVX 근무중입니다!

0개의 댓글