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: true

4. 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, gateway

5. 서비스 간 통신

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