쵼쥬
쵼쥬의 개발공부 TIL
쵼쥬
전체 방문자
오늘
어제
  • 분류 전체보기 (276)
    • 코딩테스트 (192)
      • [알고리즘] 알고리즘 정리 (7)
      • [백준] 코딩테스트 연습 (126)
      • [프로그래머스] 코딩테스트 연습 (59)
    • Spring (71)
      • [인프런] 스프링 핵심 원리- 기본편 (9)
      • [인프런] 스프링 MVC 1 (6)
      • [인프런] 스프링 MVC 2 (4)
      • [인프런] 실전! 스프링 부트와 JPA 활용1 (7)
      • [인프런] 실전! 스프링 부트와 JPA 활용2 (5)
      • [인프런] 실전! 스프링 데이터 JPA (7)
      • [인프런] 실전! Querydsl (7)
      • JWT (5)
      • [인프런] Spring Cloud (17)
      • [인프런] Spring Batch (4)
    • Java (6)
      • [Java8] 모던인자바액션 (4)
      • [부스트코스] 웹 백엔드 (2)
      • [패스트캠퍼스] JAVA STREAM (0)
    • CS (6)
      • 디자인 패턴과 프로그래밍 패터다임 (2)
      • 네트워크 (4)

블로그 메뉴

  • 홈

공지사항

인기 글

태그

  • BFS
  • 알고리즘
  • 자바
  • querydsl
  • jpa
  • 프로그래머스
  • 누적합
  • 구현
  • spring
  • 코딩테스트
  • Spring Data JPA
  • MVC
  • 백분
  • 백준
  • 스프링
  • 타임리프
  • 위클리 챌린지
  • 비트마스킹
  • 부스트코스
  • 인프런

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
쵼쥬
Spring/[인프런] Spring Cloud

Microservice간 통신

Microservice간 통신
Spring/[인프런] Spring Cloud

Microservice간 통신

2022. 6. 28. 21:50

하나의 어플리케이션 내에서 작업하고 메소드 호출로 동작하는 모놀리스 방식과 비교하여 마이크로 서비스는 물리적으로 분산된 서비스들간의 통신이 필수이다. 

 

Communication types

  • Synchronous HTTP Communication(동기) - 기존의 웹 어플리케이션에 하나의 요청이 들어오면 해당하는 요청이 끝날 때까지 다른 작업 불가
  • Asynchronous Communication over AMQP(비동기) - Spring Cloud Bus에서 설명했는데 Spring Config의 정보를 각각의 마이크로서비스가 순차적으로 동기하는 것이 아니라 일단 연결되어 있는 모든 마이크로서비스들에게 전달

RestTemplate, Feign Client 두가지

 

RestTemplate

  • 스프링에서 제공하는 http 통신에 유용하게 쓸 수 있는 템플릿. 기존의 HTTP의 GET, POST.. 를 이용
  • RestTemplate 인스턴스 생성하고 필요로 하는 HTTP 메서드와 파라미터를 처리해서 동작
  • jdbcTemplate 처럼 RestTemplate 도 기계적이고 반복적인 코드들을 깔끔하게 정리해준다.
  • 동기처리를 하고 기존 다른 api 를 불러올때 쓰였던걸 msa 에 적용하는 것이다.

Users service 에서 Order Service를 호출하기 위해 RestTemplate 등록

UserServiceApplication

 

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

 

UserController.java

@GetMapping("/users/{userId}")
public ResponseEntity<ResponseUser> getUser(@PathVariable String userId) {
    UserDto userDto = userService.getUserByUserId(userId);

    ResponseUser returnValue = new ModelMapper().map(userDto, ResponseUser.class);

    return ResponseEntity.ok(returnValue);
}

 

UserServiceImpl.java

    UserRepository userRepository;
    BCryptPasswordEncoder passwordEncoder;
    RestTemplate restTemplate;
    Environment env;

    @Autowired
    public UserServiceImpl(UserRepository userRepository, BCryptPasswordEncoder passwordEncoder, RestTemplate restTemplate, Environment env) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
        this.restTemplate = restTemplate;
        this.env = env;
    }
    
    
    @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<>();

        // RestTemplate 사용
        String orderUrl = String.format(env.getProperty("order_service.url"), userId); // 사용자 id로 등록된 주문을 가져올 주소
        // restTemplate 의 exchange 메서드 (주소, HTTP 메서드, request 할때 데이터 파라미터 타입, 반환 파라미터 타입)
        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.yml

order_service:
  url: http://127.0.0.1:8000/order-service/%s/orders

 

테스트

회원가입 -> 로그인 -> 사용자 주문(http://127.0.0.1:8000/order-service/{userId}/orders)

 

사용자 정보 확인(http://127.0.0.1:8000/user-service/users/{userId})

- 사용자 정보와, 주문정보 확인 가능

 

 

User Service 에서 Order Service를 간단하게 사용하는 방법

주소를 사용하지 않고 MicroService 이름을 사용하도록 변경 주소 

MicroService 이름

-> RestTemplate Bean에 @LoadBalanced 사용

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

 

 

Feign Client

  • Rest 방식을 사용하기 위해 추상화한 Spring Cloud Netflix 라이브러리
  • Load balanced 지원

 

사용방법

  • 호출하려는 HTTP Endpoint(마이크로서비스의 특정 메서드를 실행하는 형식)에 대한 Interface를 생성
  • @FeignClient 선언

 

 

Spring Cloud Netflix 라이브러리 추가

<!--        Feign Client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }

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

    // FeignClient 사용시에 필요없음
//    @Bean
//    @LoadBalanced
//    public RestTemplate getRestTemplate(){
//        return new RestTemplate();
//    }
}

 

@FeignClient Interface 생성

  • @FeignClient에는 호출하고자 하는 MSA이름을 지정한다.
  • 여기서 MSA의 이름은 Eureka server에 인스턴스로 등록시 사용된 이름이다.
  • 이렇게 FeignClient로 설정해주면 마치 자신의 API인 것처럼 정의가 가능하다.
  • @GetMapping 쪽을 보면 기존에 Controller에서 endpoint를 지정하는 것과 거의 동일한 것을 알 수 있다. 다만 세부 구현 내용은 필요로 하지 않는다.

 

package com.example.userservice.client;

import com.example.userservice.vo.ResponseOrder;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

import java.util.List;

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

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

 

UserServiceImpl.java 에서 Feign Client 사용

    @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<>();

        // RestTemplate 사용
//        String orderUrl = String.format(env.getProperty("order_service.url"), userId); // 사용자 id로 등록된 주문을 가져올 주소
//        // restTemplate 의 exchange 메서드 (주소, HTTP 메서드, request 할때 데이터 파라미터 타입, 반환 파라미터 타입)
//        ResponseEntity<List<ResponseOrder>> orderListResponse = restTemplate.exchange(orderUrl, HttpMethod.GET, null,
//                new ParameterizedTypeReference<List<ResponseOrder>>() {
//                });
//
//        List<ResponseOrder> ordersList = orderListResponse.getBody();

        // Feign Client 사용
        List<ResponseOrder> ordersList = orderServiceClient.getOrders(userId);
        
        userDto.setOrders(ordersList);

        return userDto;
    }

 

RestTemplate 보다 훨씬 직관적으로 사용할 수 있다.

하지만 직접 개발한 사람이 아니라면 직관적이라는 측면이 반대로 파악하기 힘들 수 있다.

 

Feign Client 에서 로그 사용

application.yml

logging:
  level:
    com.example.userservice.client: DEBUG
@Bean
public Logger.Level feignLoggerLevel(){
    return Logger.Level.FULL;
}

 

Feign Client 에서 예외 처리

FeignException 처리

잘못된 주소로 이동할 경우

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

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

 

UserServiceImpl.java

예외 처리

// Feign Client 사용
// Feign Exception Handling
List<ResponseOrder> ordersList = null;
try {
    ordersList = orderServiceClient.getOrders(userId);
} catch (FeignException ex) {
    log.error(ex.getMessage());
}
userDto.setOrders(ordersList);

  • 예외 발생한 부분 제외하고 출력

 

 

ErrorDecoder

Feign 에서 제공하는 인터페이스

  • ErrorDecoder를 이용하여 예외를 모아 관리할 수 있다.
  • ErrorDecoder를 구현하는 클래스를 생성하고 아래와 같은 리턴값, 매개변수를 갖는 decode 메서드를 오버라이딩한다.
  • 첫번째 매개변수인 method에는 FeignClient를 통해 호출한 함수의 이름을 포함한다. 때문에 에러 케이스마다 호출한 함수의 명에 따라 구분하여 처리가 가능하다.
  • 리턴값은 임의대로 Exception객체를 적절하게 구성하여 리턴한다.

 

FeignErrorDecoder.java

package com.example.userservice.error;

import feign.Response;
import feign.codec.ErrorDecoder;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;

public class FeignErrorDecoder implements ErrorDecoder {

    @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()), 
                            "User's order is empty");
                }
            default:
                return new Exception(response.reason());
        }
        return null;
    }
}

 

Bean 등록

@Bean
public FeignErrorDecoder getFeignErrorDecoder() {
    return new FeignErrorDecoder();
}

 

UserServiceImpl.java

// ErrorDecoder 사용
List<ResponseOrder> ordersList = orderServiceClient.getOrders(userId);

 

500에서 404로 바뀐거 확인

 

 

예외 문장 user-service.yml 에서 가져오기

FeignErrorDecoder.java

package com.example.userservice.error;

import feign.Response;
import feign.codec.ErrorDecoder;
import lombok.extern.apachecommons.CommonsLog;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ResponseStatusException;

@Component
public class FeignErrorDecoder implements ErrorDecoder {

    Environment env;

    @Autowired
    public FeignErrorDecoder(Environment env) {
        this.env = 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_serivce.exception.order_is_empty"));
                }
            default:
                return new Exception(response.reason());
        }
        return null;
    }
}

 

@Component로 등록해서 굳이 Bean으로 등록할 필요없다.

//    @Bean
//    public FeignErrorDecoder getFeignErrorDecoder() {
//        return new FeignErrorDecoder();
//    }

 

 

Multiple Orders service

User service에서 분산된 Orders service를 호출하게 되면 Orders 데이터도 분산 저장되어 있어서 -> 동기화 문제 발생!!

(주문 데이터를 조회할 때 데이터가 나눠서 저장되어 있어서 호출할 때 마다 다른 데이터를 가져오게 된다.)

 

3가지 해결방안

  • 분산된 Service에서 하나의 DB를 사용하면 해결 -> DB의 동시성, 트랜잭션 문제 잘 해결해야 한다.
  • DB 간의 동기화 -> Message Queuing Server를 이용해서 변경된 데이터가 있으면 업데이트
  • 하나의 DB와 Message Queuing Server 사용 (복합) -> 들어온 요청을 Message Queuing Server에서 먼저 받아서 DB로 순차적으로 전달

 

2개의 Orders Service 실행시 발생하는 문제점

  • 사용자가 한명인데 2개의 DB에 나눠서 저장된다.
  • 사용자 정보를 가져올 때마다 다른 값들이 출력된다.

 

 

'Spring > [인프런] Spring Cloud' 카테고리의 다른 글

데이터 동기화를 위한 Kafka 활용 2  (0) 2022.07.04
데이터 동기화를 위한 Kafka 활용 1  (0) 2022.06.30
설정 정보의 암호화 처리(Encryption, Decryption)  (0) 2022.06.27
Spring Cloud Bus  (0) 2022.06.24
Configuration Service  (0) 2022.06.24
  • Communication types
  • RestTemplate
  • Feign Client
  • Feign Client 에서 로그 사용
  • Feign Client 에서 예외 처리
  • ErrorDecoder
  • Multiple Orders service
'Spring/[인프런] Spring Cloud' 카테고리의 다른 글
  • 데이터 동기화를 위한 Kafka 활용 2
  • 데이터 동기화를 위한 Kafka 활용 1
  • 설정 정보의 암호화 처리(Encryption, Decryption)
  • Spring Cloud Bus
쵼쥬
쵼쥬

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.