Micro Service Architecture 개념 이해하기 - 실습

Kai·2023년 4월 25일
1

⭐ 목표


이번에 해볼 것은 MSA를 로컬에서 구축해보는 것이다.
MSA의 모든 요소를 구축하는 것은 아니고, 빨간색으로 표시한 부분만 로컬에서 구축을 해보도록 하겠다.

이 부분은 MSA에서 가장 핵심이 되는 부분이여서, '아~ MSA로 구축된 서비스는 어떻게 동작하는 구나~'라고 느껴볼 수 있을 것이다. 🤭

오늘 할 것을 정리하자면 아래와 같다.

할 것

  • Spring cloud로 게이트웨이 서비스와 라우팅기능을 구축한다.
  • Netflix Eureka 서버를 구축한다.
  • 게이트웨이 서비스와 각 마이크로 서비스들을 Netflix Eureka 클라이언트로써 구동되도록 설정하고, 서비스들이 잘 등록되었는지 확인한다.

다루지 않는 것

  • 인증/인가 기능은 구현하지 않는다.
  • 서비스 오케스트레이션도 구현하지 않는다.
  • 로컬에서만 구현해보는 것이므로, CICD 자동화는 구현하지 않는다.
  • DB없이 구현할 것이고, 그에 따라서 메세징 시스템(서비스 간의 데이터 동기화)도 구현하지 않는다.
  • 모니터링, 로깅등의 기능도 구현하지 않는다.

개발환경

  • Windows WSL Ubuntu 20.04
  • Spring boot 3.x
  • Java 17.x
  • IDE: Intellij

이번 실습 프로젝트의 깃헙


1. Eureka 서버 구축


먼저, 유레카 서버를 구축해보도록 하자.

유레카 서버는 MSA 안에 있는 여러 마이크로 서비스들을 등록하고, 특정 마이크로 서비스가 호출되는 경우 해당하는 마이크로 서비스를 찾아주는 역할을 한다.

1) 프로젝트 생성

위와 같은 정보로 프로젝트를 생성한다.


2) 유레카 서버 어노테이션 추가

main메서드에 @EnableEurekaServer 를 추가해주자.
이 어노테이션만 추가해주면, Discovery 서버로써의 역할을 수행할 수 있게 된다.


3) application.yml 생성

server:
  port: 8000
spring:
  application:
    name: eureka-server
eureka:
  client:
    register-with-eureka: false # 유레카 서버에 등록하지 않음

application.yml (또는 application.properties)에 위의 내용을 추가해주자.

MSA에서는 spring.application.name으로 각 마이크로 서비스들을 식별하므로, 마이크로 서비스마다 고유한 이름을 지어주어야한다.


4) 실행 및 동작 확인

서버를 실행하고 8000포트로 접속해보면, 위와 같이 유레카 서버의 대시보드가 출력되는 것을 확인할 수 있다. 또, 아직은 어떠한 마이크로 서비스도 등록해주지 않아서, '등록된 인스턴스'가 비어있는 것도 확인할 수 있다.


2. 게이트웨이 서비스 구축


이제 클라이언트의 요청을 가장 먼저 맞이(?)해 줄 게이트웨이 서비스를 구축해보겠다.

1) 프로젝트 생성

위와 같은 정보로 프로젝트를 생성한다.


2) application.yml 생성

server:
  port: 8080
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8000/eureka
spring:
  application:
    name: gateway-service

application.yml에 위와 같이 설정을 추가해주자.


3) 실행 및 동작 확인

게이트웨이 서버를 실행하고 유레카 대시보드에 다시 접속하면, 이렇게 게이트웨이 서비스가 등록된 것을 확인할 수 있다.

⛔ 잠깐

게이트웨이 서비스의 설정은 마이크로 서비스들을 생성해야 마무리할 수 있으니, 아래에서 나머지 설정을 진행해보도록 하겠다.


3. 애플리케이션 서비스 생성


이제 실제 비지니스 로직이 수행될 실질적인 서비스들을 만들어보도록 하겠다.
2개를 만들어줄 것이고, 하나는 USER-SERVICE 또 하나는 BUSINESS-SERVICE라고 이름을 짓도록 하겠다.

실질적인 비지니스 로직 코드는 없겠지만, 실제로 사용자와 관련된 서비스와 특정 비지니스에 대한 애플리케이션이라고 생각하면 좋을 것 같다.

1) UserService 프로젝트 생성

위와 같은 정보로 프로젝트를 생성한다.


2) application.yml 생성

server:
  port: 8081
spring:
  application:
    name: user-service
eureka:
  client:
    register-with-eureka: true # 유레카 서버에 등록
    fetch-registry: true # 이 서비스에 대한 정볼르 유레카 서버에서 주기적으로 갱신
    service-url:
      defaultZone: http://localhost:8000/eureka

위와 같이 application.yml을 생성해주자.


3) 샘플 컨트롤러 생성

package com.example.userservice.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HomeController {

    @GetMapping("/user")
    public String home() {
        return "Hello User Service";
    }

}

위와 같이 컨트롤러를 생성해준다.


4) 실행 및 동작 확인

유레카 서버에 USER-SERVICE가 잘 등록된 것을 확인할 수 있다.

8081포트에 접속해도, 우리가 만든 애플리케이션이 잘 동작하는 것을 확인할 수 있다.


5) 비지니스 서비스 프로젝트 생성

유저 서비스와 동일한 Dependency로 프로젝트를 생성해준다.


6) application.yml 생성

server:
  port: 8082
spring:
  application:
    name: business-service
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8000/eureka

위와 같이 application.yml을 생성해주자.


7) 샘플 컨트롤러 생성

package com.example.businessservice.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HomeController {

    @GetMapping("/business")
    public String home() {
        return "Hello Business Service";
    }

}

위와 같이 컨트롤러를 생성해준다.


8) 실행 및 동작 확인

유레카 서버에 BUSINESS-SERVICE가 잘 등록된 것을 확인할 수 있다.

8082포트에 접속해도, 우리가 만든 애플리케이션이 잘 동작하는 것을 확인할 수 있다.


⭐ 중간 정리


지금까지 4가지의 서비스를 만들었다.

  1. 유레카 서버 서비스
  2. 게이트웨이 서비스
  3. User 서비스
  4. Business 서비스

지금은 유레카 서버에 각 서비스들이 등록된 것 뿐이고, 게이트웨이 서비스를 통한 라우팅 기능은 제공하고 있지 않다.

이제부터 게이트 웨이 서비스에 User 서비스와 Business 서비스에 대한 설정을 추가해서, 라우팅 기능을 구현해보도록 하겠다.


4. 게이트웨이 서비스 개선


1) application.yml 수정

spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://USER-SERVICE
          predicates:
            - Path=/user/**
        - id: business-service
          uri: lb://BUSINESS-SERVICE
          predicates:
            - Path=/business/**

게이트웨이 서비스의 application.yml에 위의 설정을 추가해주자.
추가한 설정은 다음과 같은 역할을 한다.

GET http://localhost:8080/user

예를 들어, 클라이언트에서 위와 같은 요청이 게이트웨이로 왔다면, USER-SERVICE/경로에 해당하는 API가 GET요청으로 호출이 되는 것이다.

다시 말해, /user로 시작하는 모든 API요청은 USER-SERVICE로 전달되게 된다.


2) 동작확인

위의 화면들을 통해서, 게이트웨이 서버가 USER-SERVICEBUSINESS-SERVICE로 요청을 잘 전달했다는 것을 확인할 수 있다.


(선택) 게이트웨이 서비스 개선 : Filter


이번에는 게이트웨이 서비스에 Filter를 적용하여, 몇 가지 디테일한 기능들을 구현해보도록 하겠다.

1) 글로벌 필터 적용

package com.example.gatewayservice.filter;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

@Slf4j
@Component
public class LogFilter extends AbstractGatewayFilterFactory<LogFilter.Args> {
    public LogFilter() {
        super(Args.class);
    }

    @Data
    public static class Args {
        private String message;
    }

    @Override
    public GatewayFilter apply(Args config) {
        return (exchange, chain) -> {
            log.info("LogFilter start: {}", config.getMessage());
            
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                ServerHttpRequest request = exchange.getRequest();
                ServerHttpResponse response = exchange.getResponse();
                log.info("LogFilter end: {} {}", request.getId(), response.getStatusCode());
            }));
        };
    }

}

먼저 위와 같이 필터를 하나 만들어준다. 특별한 기능이 있는 것은 아니고, 필터 호출의 시작과 끝에 로그를 찍는 단순한 기능을 갖고 있다.


spring:
  cloud:
    gateway:
      default-filters:
        - name: LogFilter # 필터 컴포넌트의 클래스 명
          args:
            message: Hello LogFilter

그리고, 위와같이 application.yml에 설정을 추가해주면, 게이트웨이를 통하는 모든 API 호출에 대해서 LogFilter가 적용된다.


2) 커스텀 필터 적용

package com.example.gatewayservice.filter;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

@Slf4j
@Component
public class UserServiceFilter extends AbstractGatewayFilterFactory<UserServiceFilter.Args> {
    public UserServiceFilter() {
        super(Args.class);
    }

    @Data
    public static class Args {}

    @Override
    public GatewayFilter apply(UserServiceFilter.Args config) {
        return (exchange, chain) -> {
            log.info("UserServiceFilter start");

            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                log.info("UserServiceFilter end");
            }));
        };
    }

}

위와 같이 필터를 하나 만들어준다. 이 필터 또한 특별한 기능은 없고, USER-SERVICE에만 적용해줄 필터이다.


그리고 USER-SERVICE와 관련된 설정을 이렇게 수정해주자.


3) 필터 적용 확인

자! 이제 게이트웨이 서버를 재시작하고, http://localhost:8080/user로 접속해서 필터가 잘 동작하는지 확인해보자! 🧐

잘되는 것을 확인할 수 있다. 🤭


☕ 마무리


이번 글에서는 MSA의 기본 뼈대에 대해서 직접 구현해보면서 실습해보는 시간을 가져보았다. 다음글에서는 좀 더 실전에 가까운 예제로 돌아오도록 하겠다 ㅎㅎ

그럼 20000 🙏


참고 🙏


0개의 댓글