본문 바로가기

Spring Cloud

🌱 Spring Cloud - MSA간 통신 (RestTemplate, Feign Client) 🌱

Github 소스코드

🔎 이런거 해볼 거에요.

  • 두 개 MSA User serviceOrder serviceEureka Service에 등록되어 있는 상태이고, Spring cloud gateway에 의해 라우팅된다.
  • User service에서 Order service로 주문정보를 얻어오기 위해 요청을 보내고자 한다.
  • RestTemplate를 이용하는 방법과 FeignClient를 이용하는 방법 두 가지를 해보자.

RestTemplate

1. 가장 먼저 RestTemplate를 사용하기 위해 해당 모듈을 Bean으로 등록해주어야 한다.

  • 간단한게 springboot의 main class에 Bean을 등록한다.
      @Bean
      public RestTemplate getRestTemplate() {
          return new RestTemplate();
      }

    2. 호출하고자 하는 service의 API를 확인해보자.

  • 내부 동작이 아닌 주소 매핑정보와 리턴타입을 확인한다.
      @GetMapping("/orders/{userId}")
      public ResponseEntity<List<ResponseOrder>> getOrderByOrderId(@PathVariable("userId") String userId) {
          List<OrderDto> findOrders = orderService.getOrderByUserId(userId);
          ModelMapper mapper = new ModelMapper();
          mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
          List<ResponseOrder> result = new ArrayList<>();
          findOrders.forEach( o -> {
              result.add(mapper.map(o, ResponseOrder.class));
          });
          return ResponseEntity.status(HttpStatus.OK).body(result);
      }
    }
  • Order service가 API gateway service에 등록된 설정을 보자.
    ```
  • id: order-service
    uri: lb://ORDER-SERVICE
    predicates:
    Path=/order-service/**
    endpoint는 ```/orders/{userId}``` 이고 api gateway에 ```order-service```로 등록되어 있으니 호출해야 하는 API의 uri는 ``` http://127.0.0.1:8000/order-service/orders/{userId} ``` 가 된다.
    

3. User service의 service계층에서 RestTemplate을 통해 호출해보자.

  • RestTemplate는 앞서 Bean으로 등록했으므로 주입받아 사용한다. (저는 @RequiredArgsConstructor + final 로 받았어요.)
    private final RestTemplate restTemplate;
  • RestTemplate의 exchange메서드르 이용한다.
    • exchange(url, HttpMethod, requestEntity, responseType)
    • body는 없으므로 requestEntity는 null로 하고 응답받을 타입은 ParameterizedTypeReference로 지정한다.
  • new ParameterizedTypeReference에 알맞은 DTO를 정의하여 받아주면 정상적으로 다른 MSA의 API를 호출할 수 있다.
String url = String.format("http://127.0.0.1:8000/order-service/orders/%s", userId);
ResponseEntity<List<ResponseOrder>> response = restTemplate.exchange(url, HttpMethod.GET, null,
                new ParameterizedTypeReference<List<ResponseOrder>>() {
                });
List<ResponseOrder> orders = response.getBody();

FeignClient

1. FeignClient를 위한 interface를 생성한다.

  • @FeignClient에는 호출하고자 하는 MSA이름을 지정한다.

  • 여기서 MSA의 이름은 Eureka server에 인스턴스로 등록시 사용된 이름이다.

  • 이렇게 FeignClient로 설정해주면 마치 자신의 API인 것처럼 정의가 가능하다.

  • @GetMapping 쪽을 보면 기존에 Controller에서 endpoint를 지정하는 것과 거의 동일한 것을 알 수 있다. 다만 세부 구현 내용은 필요로 하지 않는다.

    @FeignClient(name = "order-service")
    public interface OrderServiceFeignClient {
    
      @GetMapping("/order-service/orders/{userId}")
      List<ResponseOrder> getOrders(@PathVariable String userId);
    

}

#### 2. Service계층에서 정의한 FeignClient로 Order-Service를 호출해보자.
- 정의한 FeignClient을 주입받는다. 이전과 동일하게 ```@RequiredArgsConstructor``` + ```final```로 주입받는
다.
```java    
private final OrderServiceFeignClient orderServiceFeignClient; 
  • 주입받은 객체로 정의한 API를 호출한다.
  • FeignClient에서 제공하는 에러처리 기법이 있어 이후 사용해 볼 것이다. 일단 try-catch문으로 예외를 처리한다.
  • RestTemplate보다는 FeignClient가 코드도 분리되고 훨씬 더 간결하여 좋다는 느낌이 든다.
List<ResponseOrder> orders  = null;
try {
    orders = orderServiceFeignClient.getOrders(userId);

} catch (FeignException e) {
    log.error(e.getMessage());
}

3. FeignClient에서 ErrorDecoder를 이용한 예외처리

  • ErrorDecoder를 이용하여 예외를 모아 관리할 수 있다.

  • ErrorDecoder를 구현하는 클래스를 생성하고 아래와 같은 리턴값, 매개변수를 갖는 decode 메서드를 오버라이딩한다.

  • 첫번째 매개변수인 method에는 FeignClient를 통해 호출한 함수의 이름을 포함한다. 때문에 에러 케이스마다 호출한 함수의 명에 따라 구분하여 처리가 가능하다.

  • 리턴값은 임의대로 Exception객체를 적절하게 구성하여 리턴한다.

    @Component
    public class FeignClientErrorDecoder implements ErrorDecoder {
    
      @Override
      public Exception decode(String method, Response response) {
    
          switch (response.status()) {
              case 404: 
                  if(method.contains("getOrders")) {
                      return new ResponseStatusException(HttpStatus.valueOf(response.status()), "User's order is empty.");
                  }
                  break;
              default:
                  return new Exception(response.reason());
          }
          return null;
      }
    }
  • ErrorDecoder를 구현하면 try-catch 없이 아래와 같이 깔끔하게 다른 MSA의 API를 호출할 수 있다.

    List<ResponseOrder> orders = orderServiceFeignClient.getOrders(userId);