본문 바로가기
spring cloud

Spring Cloud micro service 간의 통신 - Feign Client

by 킹차니 2022. 3. 9.

이전에는 Rest Template을 사용하여 micro service 간의 통신을 해보았다. 이번에는 Spring Cloud Netfilx가 제공하는 Feign Client를 사용하여 통신을 해보자. 이 역시 REST Call을 추상화한 라이브러리이다.

이 방법은 Rest Template보다 더 직관적인 장점이 있다.

 

먼저 이를 사용하기 위해서는 order-service에게 요청을 보내는 user-service가 클라이언트가 되므로, feign client 의존성을 추가해주어야 한다. 본인은 gradle을 사용 중이다.

그리고 아래와 같이 main함수에 @EnableFeignClients 어노테이션을 추가해준다.

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/*Feign Client*/
public class UserMicroserviceApplication {

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

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

//    @Bean
//    @LoadBalanced/*추가*/
//    public RestTemplate getRestTemplate() {
//        return new RestTemplate();
//    }
}

(Rest Template은 사용하지 않을 것이므로 주석처리 해주었다.)

 

그리고 이제 인터페이스를 만들어야 한다. Order-Service에 요청을 보내기 위한 것이므로 아래와 같이 만들었다.

import com.example.usermicroservice.vo.ResponseOrder;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

import java.util.List;

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

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

@FeingClient의 name은 현재유레카 서버에 등록된 order service의 application 이름이다.

그리고 인터페이스를 보면 getOrders라는 이름으로 정의된 추상 메서드가 보이는데, 

GetMapping으로 "order-service/{user-id}/orders"로 요청을 보내고 반환값을 List<ResponseOrder>로 받고 있는다.

 

잠시 order-service의 코드를 보면 order-service의 end point가 "/order-service/{userId}/orders"이다. 아래는 order-service의 해당 메서드이다.

@RequestMapping("/order-service/")
@RequiredArgsConstructor
@RestController
public class OrderController {

    private final OrderService orderService;
    private final Environment env;

    @GetMapping("/{userId}/orders")
    public ResponseEntity<List<ResponseOrder>> getOrder(@PathVariable String userId) {
        Iterable<OrderEntity> orderList = orderService.getOrdersByUserId(userId);
        List<ResponseOrder> result = new ArrayList<>();
        orderList.forEach(o -> {result.add(new ModelMapper().map(o, ResponseOrder.class));});

        return ResponseEntity.status(HttpStatus.OK).body(result);
    }
}

위의 getOrder 메서드가 List<ResponseOrder>를 반환해주는 것을 볼 수 있다.

 

이제  user-service에서 OrderServiceClient를 사용하는 코드를 보자.

    @Override
    public UserDto getUserById(String userId) {
        UserEntity userEntity = userRepository.findByUserId(userId);
        UserDto userDto = new ModelMapper().map(userEntity, UserDto.class);

        /*Using Feign Client*/
        List<ResponseOrder> orderList = orderServiceClient.getOrders(userId);
        userDto.setOrders(orderList);
        return userDto;
    }

UserService의 코드가 훨씬 직관적이고 짧아진 것을 볼 수 있다.

 

 


Feign Client 로그 사용, 예외 처리

 

이번에는 Feign Client를 사용하면서 로그를 출력하고 예외 처리를 추가해보자.

 

 

먼저 아래와 같이 user-service의 yml에 가서 OrderServiceClient가 존재하는 client패키지의 로그만 출력하도록 수정해주자.

 

로그 출력을 위해서는 아래와 같이 Feign에서 제공하는 Logger를 bean으로 등록해야 한다.

 

이제 한번 고의로 에러를 발생시키기 위해서 아래와 같이 orders가 아닌 orders_ng로 잘못된 요청을 포스트맨을 사용하여 보내보자.

 

 

 

user-service에서 출력된 로그를 보면 아래와 같다.

역시 orders_ng라는 잘못된 요청을 보냈고, 이 때문에 예외가 발생하여 포스트맨에서 적절한 응답을 받지 못했다.

 

하지만 우리는 마이크로 서비스간의 통신은 실패했다고 하더라도, 사용자에게 orders 정보를 제외한 사용자 정보라도 보여주고 싶은 것이다. 

이를 위해 아래와 같이 user-service의 UserService에서 예외 처리를 해준다.

이렇게 예외처리를 해주고 난 뒤 요청을 보내면 order_ng라고 잘못된 요청을 보냈더라도 orders를 제외한 사용자의 정보는 받을 수 있다.

 


이번에는 Feign 라이브러리가 제공하는 FeignErrorDecoder로 예외처리를 해보자.

FeignErrorDecoder를 사용하면 발생된 에러코드의 상태에 따라 어떤 예외처리를 발생시킬지에 맞추어 반환할 수 있다.

 

이를 사용하기 위해서는 ErrorDecoder 인터페이스를 구현해야 한다.

@Component
@RequiredArgsConstructor
public class FeignErrorDecoder implements ErrorDecoder {

    private final Environment env;

    @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()),
                            env.getProperty("order_service.exception.orders_is_empty"));
                break;
                default:
                    return new Exception(response.reason());
        }
        return null;
    }
}

decode 메서드에 에러를 발생시킨 FeignClient 메서드 명이 인자로 들어오고, 요청한 마이크로 서버에게 받은 응답(Response)객체까지 인자로 들어온다. 즉 위의 decode 메서드는 Feign클라이언트를 통한 다른 서버로 요청을 하고, 요청한 서버에서 에러가 반환될 때 호출되는 것이다.

 

이제 위에서 정의한 FeignErrorDecoder가 예외를 처리해주므로, UserService의 getUserById 메서드는 아래처럼 예외처리 로직을 넣어주지 않아도 된다.

 

 

이제 고의로 "/orders"가 아닌 "/orders_ng"로 요청을 보내 order-service에서 404 에러가 반환되도록 해보자.

원래는 404에러를 order-service로 부터 받은 user-service는 클라이언트에게 500에러를 발생시켰지만 user-service에서 에러를 핸들링 할 수 있게되어 404에러가 반환되는 것을 알 수 있다.

 

 

 

출처 : 인프런 Lee Dowon님의 강의와 PDF 자료
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C%EC%84%9C%EB%B9%84%EC%8A%A4