이번에 해볼 것은 MSA를 로컬에서 구축해보는 것이다.
MSA의 모든 요소를 구축하는 것은 아니고, 빨간색으로 표시한 부분만 로컬에서 구축을 해보도록 하겠다.
이 부분은 MSA에서 가장 핵심이 되는 부분이여서, '아~ MSA로 구축된 서비스는 어떻게 동작하는 구나~'라고 느껴볼 수 있을 것이다. 🤭
오늘 할 것을 정리하자면 아래와 같다.
개발환경
- Windows WSL Ubuntu 20.04
- Spring boot 3.x
- Java 17.x
- IDE: Intellij
먼저, 유레카 서버를 구축해보도록 하자.
유레카 서버는 MSA 안에 있는 여러 마이크로 서비스들을 등록하고, 특정 마이크로 서비스가 호출되는 경우 해당하는 마이크로 서비스를 찾아주는 역할을 한다.
위와 같은 정보로 프로젝트를 생성한다.
main
메서드에 @EnableEurekaServer
를 추가해주자.
이 어노테이션만 추가해주면, Discovery 서버로써의 역할을 수행할 수 있게 된다.
server:
port: 8000
spring:
application:
name: eureka-server
eureka:
client:
register-with-eureka: false # 유레카 서버에 등록하지 않음
application.yml (또는 application.properties)에 위의 내용을 추가해주자.
MSA에서는
spring.application.name
으로 각 마이크로 서비스들을 식별하므로, 마이크로 서비스마다 고유한 이름을 지어주어야한다.
서버를 실행하고 8000
포트로 접속해보면, 위와 같이 유레카 서버의 대시보드가 출력되는 것을 확인할 수 있다. 또, 아직은 어떠한 마이크로 서비스도 등록해주지 않아서, '등록된 인스턴스'가 비어있는 것도 확인할 수 있다.
이제 클라이언트의 요청을 가장 먼저 맞이(?)해 줄 게이트웨이 서비스를 구축해보겠다.
위와 같은 정보로 프로젝트를 생성한다.
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에 위와 같이 설정을 추가해주자.
게이트웨이 서버를 실행하고 유레카 대시보드에 다시 접속하면, 이렇게 게이트웨이 서비스가 등록된 것을 확인할 수 있다.
게이트웨이 서비스의 설정은 마이크로 서비스들을 생성해야 마무리할 수 있으니, 아래에서 나머지 설정을 진행해보도록 하겠다.
이제 실제 비지니스 로직이 수행될 실질적인 서비스들을 만들어보도록 하겠다.
2개를 만들어줄 것이고, 하나는 USER-SERVICE
또 하나는 BUSINESS-SERVICE
라고 이름을 짓도록 하겠다.
실질적인 비지니스 로직 코드는 없겠지만, 실제로 사용자와 관련된 서비스와 특정 비지니스에 대한 애플리케이션이라고 생각하면 좋을 것 같다.
위와 같은 정보로 프로젝트를 생성한다.
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을 생성해주자.
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";
}
}
위와 같이 컨트롤러를 생성해준다.
유레카 서버에 USER-SERVICE
가 잘 등록된 것을 확인할 수 있다.
8081
포트에 접속해도, 우리가 만든 애플리케이션이 잘 동작하는 것을 확인할 수 있다.
유저 서비스와 동일한 Dependency로 프로젝트를 생성해준다.
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을 생성해주자.
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";
}
}
위와 같이 컨트롤러를 생성해준다.
유레카 서버에 BUSINESS-SERVICE
가 잘 등록된 것을 확인할 수 있다.
8082
포트에 접속해도, 우리가 만든 애플리케이션이 잘 동작하는 것을 확인할 수 있다.
지금까지 4가지의 서비스를 만들었다.
지금은 유레카 서버에 각 서비스들이 등록된 것 뿐이고, 게이트웨이 서비스를 통한 라우팅 기능은 제공하고 있지 않다.
이제부터 게이트 웨이 서비스에 User 서비스와 Business 서비스에 대한 설정을 추가해서, 라우팅 기능을 구현해보도록 하겠다.
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
로 전달되게 된다.
위의 화면들을 통해서, 게이트웨이 서버가 USER-SERVICE
와 BUSINESS-SERVICE
로 요청을 잘 전달했다는 것을 확인할 수 있다.
이번에는 게이트웨이 서비스에 Filter를 적용하여, 몇 가지 디테일한 기능들을 구현해보도록 하겠다.
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
가 적용된다.
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
와 관련된 설정을 이렇게 수정해주자.
자! 이제 게이트웨이 서버를 재시작하고, http://localhost:8080/user
로 접속해서 필터가 잘 동작하는지 확인해보자! 🧐
잘되는 것을 확인할 수 있다. 🤭
이번 글에서는 MSA의 기본 뼈대에 대해서 직접 구현해보면서 실습해보는 시간을 가져보았다. 다음글에서는 좀 더 실전에 가까운 예제로 돌아오도록 하겠다 ㅎㅎ
그럼 20000 🙏