[MSA] 마이크로서비스간 통신

이민우·2024년 1월 26일
1

Spring Boot

목록 보기
20/20

참고 : Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)

해당 포스팅은 Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)
강의를 듣고 정리한 글입니다. 실습 환경을 따라하시려면 이전 글에 나온 실습을 진행하시고 해당 포스팅을 읽는 것을 추천드립니다.

마이크로 서비스간 통신하는 방법

마이크로 서비스간 통신하는 방법은 크게 두가지가 있습니다.
1. RestTemplate 이용
2. FeignClient

  • 두 개 MSA User service와 Order service는 Eureka Service에 등록되어 있는 상태이고, Spring cloud gateway에 의해 라우팅된다.

RestTemplate 이용하는 방법과 FeignClient 이용하는 방법 두 가지를 해보자.

RestTemplate

  • 장점
    • 다양한 HTTP 메서드를 지원하고, 요청을 다양한 방식으로 커스터마이즈 할 수 있다.
    • 오랜 기간 동안 사용되어 왔기 때문에, 많은 개발자들에게 익숙하다.
    • 많은 커스터마이즈를 제공해 복잡한 통신 시나리오를 다룰 수 있다.
  • 단점
    • 작성할 코드가 많다.
    • 동기식 방식으로 동작한다.
    • 가독성과 유지보수성을 해칠 가능성이 있다.

Users Service -> Order Service 호출

  1. 가장 먼저 RestTemplate를 사용하기 위해 해당 모듈을 Bean으로 등록해주어야 한다.
	@Bean
	@LoadBalanced
	public RestTemplate getRestTemplate() {
		return new RestTemplate();
	}
  • 📌 @LoadBalanced는 클라이언트 사이드 로드밸런싱을 위해 RestTemplate Bean을 생성하여 붙이는 어노테이션이다.

  1. 호출하고자 하는 service의 API를 확인해보자.
    • 우리는 user-service에서 order-service에 있는 엔드포인트 {userId}/orders 를 호출하고 싶다.
	@GetMapping("{userId}/orders")
    public ResponseEntity<List<ResponseOrder>> getOrder(@PathVariable("userId") String userId) throws Exception {
        log.info("Before retrieve orders data");
        Iterable<OrderEntity> orderList = orderService.getOrdersByUserId(userId);

        List<ResponseOrder> result = new ArrayList<>();
        orderList.forEach(v->{
            result.add(new ModelMapper().map(v, ResponseOrder.class));
        });
//        try {
//            Thread.sleep(1000);
//            throw new Exception("장애 발생");
//        } catch (InterruptedException exception) {
//            log.warn(exception.getMessage());
//        }
        log.info("Add retrieve orders data");
        return ResponseEntity.status(HttpStatus.OK).body(result);

    }
  1. 호출하고자 하는 API 스펙에 맞게 user-service에서 RestTemplate을 이용해보자

  • RestTemplate의 exchange메서드
    • exchange(url, HttpMethod, requestEntity, responseType)
    • body는 없으므로 requestEntity는 null로 하고 응답받을 타입은 ParameterizedTypeReference로 지정한다.
  • new ParameterizedTypeReference에 알맞은 DTO를 정의하여 받아주면 정상적으로 다른 MSA의 API를 호출할 수 있다.
  1. 요청을 보낼 user-service의 API

        @GetMapping("/users/{userId}")
        private ResponseEntity<ResponseUser> getUser(@PathVariable String userId) {
            UserDto userDto = userService.getUserByUserId(userId);
            ResponseUser returnValue = new ModelMapper().map(userDto, ResponseUser.class);
    
            return ResponseEntity.status(HttpStatus.OK).body(returnValue);
        }
  2. Postman을 통한 테스트

  • 응답으로 200 OK 를 받았고, 아직 주문 내역이 없지만 주문내역까지 같이 조회 해온걸 확인 할 수 있다!

FeignClient

  • Restful API 호출을 추상화한 Spring Cloud Netflix 라이브러리
  • HTTP API 클라이언트를 단순화하는 것을 목표로 하고, 실제 인터페이스와 어노테이션을 적용하는 간단한 방법으로 사용 가능하다.
  • 모놀로식 애플리케이션에서 메서드를 호출하는 것과 겉보기에 비슷하게 보일 수 있다.

장단점

  • 장점

    • 간단하고 선언적인 방식으로 구현 가능하다.
    • @FeignClient 어노테이션과 인터페이스를 사용하여 클라이언트를 정의하고, 메서드를 호출하는 방식으로 통신할 수 있다.
    • 내장된 로드 밸런싱 기능과 서비스 디스커버리를 활용할 수 있다.
    • 요청 및 응답에 대한 로깅, 오류 처리, 보안 등을 쉽게 구현할 수 있다.
  • 단점

    • 상대적으로 새로운 기술이므로, RestTemplate에 비해 사용자들이 덜 익숙할 수 있다.
    • 자동 설정과 기본 설정이 일부 한정적일 수 있으며, 일부 특정한 사용 사례에는 제약이 있을 수 있다.

Users Service -> Order Service 호출(RestTemplate -> FeignClient 변경)

  1. 의존성 추가
 <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
@EnableFeignClients
public class UserServiceApplication {

	public static void main(String[] args) {
		SpringApplication.run(UserServiceApplication.class, args);
	}
   }
  • @EnableFeignClients
    • FeignClient를 사용하기 위해서는 필수적으로 적용해야 한다.
    • 하위 클래스에서 @FeignClient를 찾아 구현체를 생성하는 역할을 한다.
  1. @FeignClient interface 생성
@FeignClient(name="order-service")
public interface OrderServiceClient {
    @GetMapping("/order-service/{userId}/orders")
    List<ResponseOrder> getOrders(@PathVariable String userId);
}
  • @FeignClient(name="")
    특정한 서비스의 이름이다.
  1. 호출하고자 하는 service의 API를 확인해보자.
    기존 RestTemplate 코드 주석처리.
  @Override
    public UserDto getUserByUserId(String userId) {
        UserEntity userEntity=userRepository.findByUserId(userId);
        if (userEntity == null) {
            throw new UsernameNotFoundException("User Not Found");
        }
        UserDto userDto = new ModelMapper().map(userEntity, UserDto.class);

////        List<ResponseOrder> orders = new ArrayList<>();
//        /* Using as rest template */
//        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();

        /*FeignClient 사용*/
        /*Feign exception handling*/
        List<ResponseOrder> ordersList = null;
        try {
            ordersList = orderServiceClient.getOrders(userId);
        } catch (FeignException exception) {
            log.error(exception.getMessage());
        }
          userDto.setOrders(ordersList);

        return userDto;
    }
  1. 컨트롤러 코드는 RestTemplate과 동일

정리

이번 포스팅을 통해서 마이크로 서비스간 통신하는 2가지 방법을 배우고 정리해봤다. 2가지 방법중에 프로젝트에 적용할떄는 FeignClient를 적용할 것 같다. 가독성 측면에서 좋고 간단한 방식으로 이용할 수 있기 때문이다.

profile
백엔드 공부중입니다!

0개의 댓글

관련 채용 정보