Theory
고급 2-3시간 이론 + 실습
Spring 21: 마이크로서비스 기초
MSA 아키텍처 입문
MSAService DiscoveryAPI GatewayCircuit Breaker
1. 마이크로서비스 아키텍처
1.1 마이크로서비스란?
마이크로서비스는 애플리케이션을 작고 독립적인 서비스들로 분해하는 아키텍처 패턴입니다. 각 서비스는 특정 비즈니스 기능을 담당하며 독립적으로 배포 가능합니다.
// 모놀리식 vs 마이크로서비스
// 모놀리식: 하나의 큰 애플리케이션
@SpringBootApplication
public class MonolithicApp {
// 사용자, 주문, 결제, 배송 모든 기능이 하나의 앱에
}
// 마이크로서비스: 독립적인 서비스들
@SpringBootApplication
public class UserService { }
@SpringBootApplication
public class OrderService { }
@SpringBootApplication
public class PaymentService { }
@SpringBootApplication
public class ShippingService { }마이크로서비스 장점:
- - 독립적인 배포와 확장
- - 기술 스택의 다양성
- - 팀 간 독립적 개발
- - 장애 격리
2. Spring Cloud
2.1 Spring Cloud 개요
Spring Cloud는 분산 시스템의 공통 패턴을 구현하기 위한 도구들을 제공합니다.
// Spring Cloud 의존성
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
implementation 'org.springframework.cloud:spring-cloud-starter-config'
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-hystrix'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:2023.0.0"
}
}@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class MicroserviceApplication {
public static void main(String[] args) {
SpringApplication.run(MicroserviceApplication.class, args);
}
}3. 서비스 디스커버리
3.1 Eureka Server
Eureka는 서비스 등록과 발견을 위한 Netflix OSS 컴포넌트입니다.
// Eureka Server 설정
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
# application.yml
server:
port: 8761
eureka:
client:
register-with-eureka: false
fetch-registry: false
server:
enable-self-preservation: false// Eureka Client 설정
@SpringBootApplication
@EnableEurekaClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
# application.yml
spring:
application:
name: user-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true4. API Gateway
4.1 Spring Cloud Gateway
API Gateway는 모든 클라이언트 요청의 진입점 역할을 하며, 라우팅, 인증, 로드밸런싱 등을 담당합니다.
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("user-service", r -> r.path("/api/users/**")
.uri("lb://user-service"))
.route("order-service", r -> r.path("/api/orders/**")
.uri("lb://order-service"))
.route("payment-service", r -> r.path("/api/payments/**")
.uri("lb://payment-service"))
.build();
}
}# application.yml
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/users/**
filters:
- StripPrefix=2
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/orders/**
filters:
- StripPrefix=2
- AddRequestHeader=X-Request-Source, gateway5. 서비스 간 통신
5.1 OpenFeign
OpenFeign은 선언적 HTTP 클라이언트로, 서비스 간 통신을 간소화합니다.
@FeignClient(name = "user-service")
public interface UserServiceClient {
@GetMapping("/users/{userId}")
UserDto getUser(@PathVariable Long userId);
@PostMapping("/users")
UserDto createUser(@RequestBody CreateUserRequest request);
@GetMapping("/users")
List<UserDto> getUsers(@RequestParam String status);
}
@Service
public class OrderService {
@Autowired
private UserServiceClient userServiceClient;
public OrderDto createOrder(CreateOrderRequest request) {
// 사용자 정보 조회
UserDto user = userServiceClient.getUser(request.getUserId());
if (user == null) {
throw new UserNotFoundException("사용자를 찾을 수 없습니다.");
}
// 주문 생성 로직
return processOrder(request, user);
}
}// Circuit Breaker 패턴
@Component
public class UserServiceFallback implements UserServiceClient {
@Override
public UserDto getUser(Long userId) {
return UserDto.builder()
.id(userId)
.name("Unknown User")
.status("INACTIVE")
.build();
}
@Override
public UserDto createUser(CreateUserRequest request) {
throw new ServiceUnavailableException("사용자 서비스가 일시적으로 사용할 수 없습니다.");
}
@Override
public List<UserDto> getUsers(String status) {
return Collections.emptyList();
}
}