Microservice 간의 통신

Single Ko·2023년 10월 17일
0

msa

목록 보기
2/3

Communication types

  1. Synchronous HTTP communication

  2. Asynchronous communication over AMQP

Java Spring을 이용해서 microservice 간의 통신을 할때 주로 사용하는 방법에는 보통 Rest Template이나, Feign 기술을 많이 사용한다.

둘 다 상황에서 장,단점이 있고 어떤 부분을 택할지는 그 상황에 맞게 선택을하면 될 것 같다.

Rest Template


    public UserDto getUserByUserId(String userId) {
        UserEntity userEntity = userRepository.findByUserId(userId)
                .orElseThrow(NotExistUserException::new);
        UserDto userDto = UserDto.from(userEntity);
        
        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> ordersList = orderListResponse.getBody();
        
        userDto.setOrders(ordersList);
        return userDto;

user-service에서 , order-service와의 통신을 하는 방법이고 위의 방식은 Rest Template을 사용하는 방법이다.

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

Rest Template은 Spring Cloud apigateway에서 지원하는 로드 벨런싱 기능도 지원한다.

이와 다르게 Feign Client는 Rest 방식 사용 하기 위해 추상화 되어있는 인터페이스를 제공해주는 라이브러리이다. Spring Cloud Netflix 에 있다가 Spring Cloud OpenFeign으로 분리되어서 나왔다.

처음 Netflix 에서 개발하여 Spring으로 OpenSource가 되어 넘어온 기술들이 많고, 이런 부분들이 Eureka라던가, Ribbon 등 여러가지가 있었다. 어쨋든 이 OpenFeign으로 분리된 Feign client의 장점을 보자면,

  • RestTemplate 보다 훨씬 직관적이고 간단하다.
  • 호출하려는 HTTP Endpoint에 대한 (호출하려고 하는 반환값, 정보 등..) Interface를 생성
  • @FeignClient 선언
  • Load balanced 지원
@FeignClient(name = "order-service")
public interface OrderServiceClient {

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

이렇게 User-service 내에서 통신할 OrderService의 인터페이스를 만들어 API를 정리해주면 된다.

장점을 보자면

  • RestTemaplate에 비해 사용하기가 더 편하고, 직관적이다.

단점

  • 서비스 안에 Interface로 호출할 다른 Service의 Endpoint를 만들어 주어야 한다. 따라서 다른 서비스에 대해 모른다면 만들 수가 없다.

Feign Client Log Trace

Feginclient 사용 시 발생한 로그 추적

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

설정파일에 DEBUG를 설정해두었다면, 이제 애플리케이션 클래스에 Bean을 등록해주면 로그가 추적이 된다.

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

Feing Exception

  • 서버간의 통신에서 URI의 잘못된 입력이나 이런 것으로 오류가 났는데 실제로 Exception이 터지면 응답은 500으로 서버 Error를 내려준다.
  • 여기서 문제점은 잘못된 입력으로 생긴 오류가, 기존의 User Service에까지 미치는게 문제이다. 이것을 오류를 잡아서, 기존의 서비스는 잘 되게 하고 통신이 필요한 부분은 오류처리를 해주면 된다.
"timestamp": "2023-09-23T07:25:09.859+00:00",

    "status": 500,

    "error": "Internal Server Error",

    "trace": "feign.FeignException$NotFound: [404] during [GET] to [http://order-service/order-service/19752c3f-eb66-4ebf-b950-f735386dbbad/orders_ng] [OrderServiceClient#getOrders(String)]: [

실제 FeignClient의 주소 경로를 잘못된 주소로 변경해 오류를 내 보았을때, 오는 메시지이다. 404 오류라고 알려주지만 Status는 500으로 내려온다.

 try {
    ordersList = orderServiceClient.getOrders(userId);
} catch (FeignException e) {
    log.error(e.getMessage());
}

내려온 오류가 FeignException 이기 때문에 try catch로 잡아준다. 이렇게 된다면

{
    "email": "이메일@naver.com",
    "name": "kimsyong",
    "userId": "313ed190-f13d-4349-820a-48a2a29bcea2"
    //원래 orders : [] 가 있어야 함. 하지만 에러로 내려온 정보가 업기때문에 생략됨.
}

이렇게 try - catch 문으로 에러를 잡는 것도 방법이 되지만 문제는 비지니스 로직에 직접적으로 코드를 고쳐야되는 문제도 있고, 일일이 전부 관리해줘야 되는것이 문제다. 이때 사용할 수 있는 방법으로 FeignErrorDecoder가 있다.

FeignErrorDecoder

인터페이스인 ErrorDecoder를 구현해서 우리가 에러 메시지를 관리를 할 수 있다.

@Component
public class FeignErrorDecoder implements ErrorDecoder {
    @Override
    public Exception decode(String methodKey, Response response) {
        switch (response.status()) {
            case 400 -> {}
            case 404 -> {
                if (methodKey.contains("getOrders")) {
                    return new ResponseStatusException(HttpStatus.valueOf(response.status()),
                            "User's orders is empty");
                }
            }
            default -> {
                return new Exception(response.reason());
            }
        }
        return null;
    }
}

이렇게 원하는 대로 Error에 대한 처리가 가능하다. 위에서는 400, 404에대한 정리만 해주었지만 실제로는 더 세세하고 자세하게 설정이 가능 하다.

또한 이렇게 ErrorDecoder를 사용하는 것은 우리가 직접적으로 비지니스 로직에 대한 침범이 없고, Spring에서 제공하는 ControllerAdvice와의 방식도 비슷해 쉽게 적응이 가능했다.

통신의 Exception을 관리하는 방법에는 위의 방법 말고도 또 CircuitBreake를 이용하는 방법도 있다.

profile
공부 정리 블로그

0개의 댓글