[MAS] 서비스 간 통신 - Rest Template

yejin·2024년 11월 28일

MSA

목록 보기
30/36

서비스 간 통신

📌 개요

  • 통신 방식
    1. 동기 방식(Synchronous HTTP Communication)
    ➡ 클라이언트 요청 시, 해당 요청이 끝날 때까지 다른 클라이언트의 요청 처리 불가능
    2. 비동기 방식(Asynchronous communication over AMQP)
    ➡ AMQP 프로토콜 이용하여 마이크로서비스 비동기 통신 지원하여 연결되어 있는 모든 마이크로서비스에 변경 사항 전달
  • 통신 방법
    1. Rest Template ✅
    2. OpenFeign

💡 RestTemplate 란?

  • 개념
    Spring Framework에서 제공하는 클래스
  • 역할
    RESTful 웹 서비스를 호출하기 위한 HTTP 클라이언트 역할
  • 주요 기능
    1. HTTP 요청 보내기
    GET, POST, PUT, DELETE 등 다양한 HTTP 메서드를 지원하여 RESTful 서비스와의 통신
    2. 응답 처리
    ➡ HTTP 응답을 자동으로 변환하여 객체로 반환
    3. 헤더 설정
    ➡ 요청에 필요한 HTTP 헤더 설정 가능
    4. 요청 본문 설정
    POST, PUT 등의 요청에 데이터를 포함 가능
  • 장점
    1. 간편하게 HTTP 요청을 보낼 수 있음
    2. JSON과 같은 형식으로 반환된 응답을 JAVA 객체로 자동 변환
    3. 다양한 HTTP 메서드를 지원하고, HTTP 요청에 대한 헤더 및 본문 설정이 가능하여 유연하게 사용 가능

📌 소스코드 #1

➡ User-service 에서 Order-service 호출하여 주문 내역 확인하기

  • User-service EUserServiceApplication 수정
package com.example.euserservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableDiscoveryClient
public class EUserServiceApplication {

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

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

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

}
  • User-service UserServiceImpl.java 수정
    /users/{userId} 엔드포인트를 통해 사용자의 정보 호출 시, 해당 사용자의 주문 내역도 함께 호출하고자 함
//추가
Environment env;
RestTemplate restTemplate;

//생성자 수정
@Autowired
public UserServiceImpl(UserRepository userRepository, BCryptPasswordEncoder passwordEncoder, Environment env, RestTemplate restTemplate) {
	this.userRepository = userRepository;
	this.passwordEncoder = passwordEncoder;
	this.env = env;
	this.restTemplate = 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 = "http://127.0.0.1:8000/order-service/%s/orders";
        ResponseEntity<List<ResponseOrder>> orderListResponse = restTemplate.exchange(orderUrl, HttpMethod.GET, null,
                                                                        new ParameterizedTypeReference<List<ResponseOrder>>() {
                                                                });

        List<ResponseOrder> orderList = orderListResponse.getBody();
        userDto.setOrders(orderList);
        /* End */

        return userDto;
    }

🤔 UserServiceImpl의 getUserByUserId 수정 이유?

  • getUser 메서드에서는 userService.getUserByUserId(userId) 메서드를 호출하여
    주문 서비스에서 해당 userId가 주문한 내역들을 가져옴
  • UserServiceImpl에서 구현한 getUserByUserId(userId) 메서드에서 주문 서비스를 호출하고 있음
  • 기존에는 주문 서비스와 통신하지 않고 주문 정보들을 담는 배열을 만들어서 해당 배열에 주문 정보들을 저장해두었으나, 이제는 주문 서비스와 연동하여 데이터를 가져오는 작업을 하기 위해 수정 작업 진행

💡 추가 설명

  • EnvironmentRestTemplate 추가 이유
    1. Environment : 설정에 저장해 둔 정보(Order-service의 URL)를 가져오기 위함
    2. RestTemplate : 빈으로 등록해 둔 RestTemplate를 사용하기 위함
  • orderUrl
    Order-service의 getOrder 메서드를 호출하는 URL를 직접 입력
    URL 주소가 하드코딩 되어 있기 때문에, 주소가 변경 시 해당 URL 주소도 함께 변경 필요
    ➡ 지양하는 방법이며 추후에 하드코딩 된 주소를 마이크로서비스의 이름으로 변경 예정
    👇 참고: getOrder() 메서드는 다음과 같음

    getOrder()의 반환값이 ResponseEntity<List<ResponseOrder>> 이기 때문에 restTemplate를 통해 주문 서비스와 연동하여 주문 내역을 가져온 데이터 타입을 동일한 ResponseEntity<List<ResponseOrder>> 로 받아서 원하는 데이터를 사용
  • exchange()
    RestTemplateexchange 메서드는 HTTP 요청을 수행하고 그 응답을ResponseEntity로 받음
    일반적으로 아래와 같은 네 가지 인자를 받음
    1. 첫 번째 인자 : 요청을 보낸 URL
    2. 두 번째 인자 : HTTP 메서드 설정
    3. 세 번째 인자 : HTTP 요청의 본문(body)에 포함될 내용 (여기선 GET 요청이라 null 전달)
    4. 네 번째 인자 : 반환되는 응답의 타입을 지정
    ParameterizedTypeReference : 제네릭 타입을 정확히 지정하기 위해 사용
    List<ResponseOrder> : 응답 본문이 ResponseOrder 객체를 포함하는 리스트 형식임을 나타냄
    new ParameterizedTypeReference<List<ResponseOrder>>() {} : 익명 클래스 구문
  • orderListResponse.getBody()
    getBody()를 통해 HTTP 응답의 본문만 추출
  • Config 설정 파일 user-service.yml 수정
spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:testdb
    username: sa
    password: '{cipher}AQA7s+ZM9LN/Jgo3adyUNHTa52h4/j4yhK98qhPnl6srDiDcT5ClW2wmSXI501GXS/Gy25BjRjHyKiIPoONk36jPsRIozYbfdr1coPrnSmNRiyU0mhVsUzcXlsRDRZAUrtd9nA0p3NI4quxozHov7a8fXwmnUfjB4NlQoXiXOAs0QtjcpaUpfdr0/6HOhaDhfjXjy8atmmsfiGaNByVgP49ddF/gKfk4Wj93GPR6zsvFnvq7zANhVuxvf0Q6xtOekHP1M1gWMTwG600EznUy/xVdgTKREYbvTDvgegKmMEKCKNEpn4CeHMn9cCQatOeoCI5Kan38WWFOAUaavdRZrhLPogfXZq4ZZYZUc3f+G1zUjR7oYvsuD4rheEC/2yJ3Q+Q='

token:
  expiration_time: 86400000
  secret: my_secret_token_by_1126_#2

gateway:
  ip: 192.168.0.100

order_service:
  url: http://127.0.0.1:8000/order-service/%s/orders #추가
  • User-service UserServiceImpl.java 수정
@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> orderList = orderListResponse.getBody();
        userDto.setOrders(orderList);

        /* End */

        return userDto;
    }

➡ 메서드에 하드코딩을 했던 URL 주소를 설정 파일로 관리하여, 설정 파일의 주소를 불러옴

💡 한 눈에 보기

  • 변경 전
String orderUrl = "http://127.0.0.1:8000/order-service/%s/orders";
  • 변경 후
//변경 후
String orderUrl = String.format(env.getProperty("order_service.url"), userId);
  • 설정 파일
order_service:
  url: http://127.0.0.1:8000/order-service/%s/orders 

📌 실행결과 #1

➡ Eureka Server, GW Server, Config Server, User-service, Order-service 기동 필요

  • 회원 가입

  • 주문

  • 주문내역 확인
    #1. 데이터베이스에서 확인
    ➡ 127.0.0.1:{order-service의 port 번호}/h2-console

#2. User-service를 통해 확인

✅ 주의 사항
1. Token 값 등록하여 권한 부여 필수
2. 이미 user-service가 기동 중에 설정 파일(user-service.yml)을 변경했다면 적용 필요
127.0.0.1:8000/e-user-service/actuator/busrefresh 실행

#3. Order-service를 통해 확인

🤔 주문내역 확인 차이점

  • user-service를 통해 주문내역 확인 시
    사용자 정보 + 주문 내역 확인 가능
  • order-service를 통해 주문내역 확인 시
    주문 내용만 확인 가능

📌 소스코드 #2

주소 서비스 호출 시 URL 주소 체계 변경

➡ 기존에 127.0.0.1:8000 으로 주소를 하드코딩 했던 부분을 마이크로서비스의 이름으로 변경

  • User-service EUserServiceApplication 수정
package com.example.euserservice;

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.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableDiscoveryClient
public class EUserServiceApplication {

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

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

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

}

💡 @LoadBalanced 란?

  • RestTemplate 또는 WebClient에 부하 분산 기능을 추가
  • 부하 분산 기능은 일반적으로 Eureka, Consul, Zookeeper와 같은 서비스 디스커버리 시스템을 기반으로 동작
  • Config 설정 파일 user-service.yml 수정
spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:testdb
    username: sa
    password: '{cipher}AQA7s+ZM9LN/Jgo3adyUNHTa52h4/j4yhK98qhPnl6srDiDcT5ClW2wmSXI501GXS/Gy25BjRjHyKiIPoONk36jPsRIozYbfdr1coPrnSmNRiyU0mhVsUzcXlsRDRZAUrtd9nA0p3NI4quxozHov7a8fXwmnUfjB4NlQoXiXOAs0QtjcpaUpfdr0/6HOhaDhfjXjy8atmmsfiGaNByVgP49ddF/gKfk4Wj93GPR6zsvFnvq7zANhVuxvf0Q6xtOekHP1M1gWMTwG600EznUy/xVdgTKREYbvTDvgegKmMEKCKNEpn4CeHMn9cCQatOeoCI5Kan38WWFOAUaavdRZrhLPogfXZq4ZZYZUc3f+G1zUjR7oYvsuD4rheEC/2yJ3Q+Q='

token:
  expiration_time: 86400000
  secret: my_secret_token_by_1126_#2

gateway:
  ip: 192.168.0.100

order_service:
  url: http://order-service/order-service/%s/orders #마이크로서비스 이름으로 주소 변경

📌 실행결과 #2

➡ 다른 테스트값으로 진행 시 정상작동 함을 확인 가능

profile
새싹 개발자

0개의 댓글