Cloud/SpringCloud로 개발하는 MSA

MS간 통신

Tony Lim 2022. 10. 29. 16:45

RestTemplate

token:
  expiration_time: 864000000
  secret: '{cipher}AQBQMwrjDGVYOUquU1Rgcxs7c+ELCWNwBYcmKD7cH++Vnd7G5+dT0TvfiR/LBMWOBAHgB58+cwPVQKI05u0jzDoHR2MJE/q3DVJuj64x8SWVf/e6qeUM/CQhy8T5Bs8erpplR7p+fd8r7U/B3rpsgQ63mAgtLrpy3c5A9PlTvWFW6/fg+paWKQP6UEv56lqjQIysQhIUwmBDxBAEN0iCKz2g8k/bfClVZXylWLdCtboIgjw4mcbq4zEbJE4o1QHIF73aLK9fwZ9sahmjZSOYVYJcNjVCRLI7iHFBI4BQWGzids/9l/m77GG3zbdEzTAE5G1PCjnBm2No+rcj1GPUPswg1O9I9UI7EAzuHBXdG+lpJUJHxer7TPirWjmfCcucBCKrsRsZm2PtkTsVUqjzbCoh'

gateway:
  ip: localhost

spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:~/test
    username: '{cipher}AQCpRg9qo8u0WDkdlZXUK2AJ6k+MtBI/hYHS/uUJlDfmtjObQksSkvLRL3wrXafBZsnnIE0e8cnXnPb9QPpdoKkBWFftMTMkTZlMfB7XpIW/Ov4qlGcvxyscgsO+Je5vuF1OwgdiieEA4Axy1P0hh7+A48hfdusyoNui3U6lMB0z1x5WDLuvbbeioHhgucgKZfdgpkt9ulR6hAVgbigDMyOFFtiyyPWH+B/p31AtMZWGkyt7gwNY/JuUBbVSdI+5ysV8bP+UqK6OvG6yQUT2020bjvckprr7pz4xn+ni2ZtReznXwz+2Gz0LMm0PbdpMpOsztxYyLjF/exRK3E+3S3KEYzw+c/GgjaPq+xUAe30OuAWPHztlcR4lqijxJ/mSlcg='

order_service:
  # url: http://localhost:8000/order-service/%s/orders
  url: http://ORDER-SERVICE/order-service/%s/orders
String orderUrl = String.format(env.getProperty("order_service.url"),userId);
ResponseEntity<List<ResponseOrder>> orderListResponse = restTemplate.exchange(orderUrl, HttpMethod.GET,
        null, new ParameterizedTypeReference<List<ResponseOrder>>() {

});

user service 에서 order service를 restTemplate을 통해 호출하는 상황이다. 
user service가 참조하는 설정에서 url 이 ORDER-SERVICE가 되어있는데 이는

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

@LoadBalanced 덕분에 Eureka server를 참조해서 어떤 ms 로 가야하고 그 주소를 알수 있게 된다.

통상적으로 client 의 첫 요청은 gateway를 지나면서 검증을 받고 그 이후 ms끼리 비지니스 로직을 처리하기 위해서 소통할때는 gateway를 거치지 않는다고 한다.

 


Feign Client

 

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class UserServiceApplication {

main 함수 가 존재하는 클래스위에다 @EnableFeignClients를 명시해줘야 사용할 interface에 구현체를 만들어서 빈으로 주입해준다.

호출 하려는 HTTP Endpoint 에 대한 interface 생성 후 @FeignClient 선언을 해준다.

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

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

}

따로 호출할 ms의 함수 시그니쳐와 인터페이스를 선언을 한후에 OrderServiceClient를 주입받으면
아래처럼 사용이 가능해진다.

/*
Using FeignClient
 */
List<ResponseOrder> ordersList = orderServiceClient.getOrders(userId);
userDto.setOrders(ordersList);

Restemplate 보다 코드가 훨씬 간결해졌다.

 

Feign Client 예외처리

log level을 DEUBG로 해준후에

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

이렇게 빈으로 등록을 해주면

아래와 같이 HTTP Trace 정보를 보여준다. 현재 user -> order 로 가는 url 이 잘못되어서 status는 500 에러이지만 404가 중간에 뜬 진짜 에러이다.

 

List<ResponseOrder> ordersList = null;
try {
    ordersList = orderServiceClient.getOrders(userId);
} catch (FeignException exception) {
    log.error(exception.getMessage());
}
2022-10-28 15:08:06.992 ERROR 30683 --- [o-auto-1-exec-3] c.e.userservice.service.UserServiceImpl  : [404] during [GET] to [http://order-service/order-service/d4788410-8dea-471e-8e8f-2aa3cfc7b295/orders_ng] [OrderServiceClient#getOrders(String)]: [{"timestamp":"2022-10-28T06:08:06.985+00:00","status":404,"error":"Not Found","message":"No message available","path":"/order-service/d4788410-8dea-471e-8e8f-2aa3cfc7b295/orders_ng"}]

위와 같이 Exception 을 잡아주면  로그를 띄워주고 정상적으로 처리가 된다.

 

원래 출력되던 error trace가 출력되지 않은것을 확인 할 수 있다.

또한 우리가 Feign Client에서 호출할 url 을 엉뚱한 것을 지정해서 내부적으로 404 에러가 떴지만 gateway를 거쳐 client단에는 우리가 내부적인 서버에러인 500 status code를 준것을 확인할 수 있다. 이것은 좋지 않다.

 

FeignErrorDecoder

@RequiredArgsConstructor
@Component
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;
    }
}
List<ResponseOrder> ordersList = orderServiceClient.getOrders(userId);
userDto.setOrders(ordersList);

기존에는 try catch로 order service 호출에 대한 에러 대비를 했었지만 FeignErrorDecoder를 빈으로 등록 해 놓으면 FeignClient를 통해 다른 ms 호출시에 response에 알맞게 에러를 처리해준다.

500번에러가 아니라 404 notfound 라는 좀 더 제대로 된 에러를 뿜게되는것을 확인할 수 있다.

@FeignClient(name="catalog-service", configuration = FeignErrorDecoder2.class)
public interface CatalogServiceClient {

또한 여러 Feign Client를 사용시에 각각 맞는 Decoder를 위와 같이 등록해 줄 수 있다.