You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// 문제 1: Monolithic 애플리케이션 (모놀리스)@SpringBootApplicationpublicclassECommerceApplication {
// 😱 모든 기능이 하나의 애플리케이션에!@AutowiredprivateUserServiceuserService;
@AutowiredprivateProductServiceproductService;
@AutowiredprivateOrderServiceorderService;
@AutowiredprivatePaymentServicepaymentService;
@AutowiredprivateInventoryServiceinventoryService;
@AutowiredprivateShippingServiceshippingService;
@AutowiredprivateNotificationServicenotificationService;
@AutowiredprivateAnalyticsServiceanalyticsService;
// 문제점:// 1. 배포 단위가 너무 큼 (전체 재배포)// 2. 확장이 어려움 (전체를 스케일)// 3. 한 부분 장애가 전체 영향// 4. 기술 스택 변경 어려움// 5. 팀 간 코드 충돌
}
// 문제 2: 강한 결합 (Tight Coupling)publicclassOrderService {
@AutowiredprivateProductServiceproductService; // 직접 의존@AutowiredprivateInventoryServiceinventoryService; // 직접 의존@AutowiredprivatePaymentServicepaymentService; // 직접 의존@TransactionalpublicOrdercreateOrder(OrderRequestrequest) {
// 😱 모든 서비스가 같은 트랜잭션에!Productproduct = productService.getProduct(request.getProductId());
if (!inventoryService.checkStock(product.getId())) {
thrownewOutOfStockException();
}
Orderorder = newOrder(request);
orderRepository.save(order);
paymentService.processPayment(order);
// 하나의 서비스 실패 시 전체 롤백!// 서비스 간 강한 결합!
}
}
// 문제 3: 단일 데이터베이스@Entity@Table(name = "orders")
publicclassOrder {
@IdprivateLongid;
@ManyToOne// 😱 다른 도메인 직접 참조@JoinColumn(name = "user_id")
privateUseruser;
@ManyToOne@JoinColumn(name = "product_id")
privateProductproduct;
// 문제점:// - 모든 데이터가 하나의 DB에// - 도메인 간 강한 결합// - DB 확장 어려움// - 스키마 변경 시 영향 범위 큼
}
// 문제 4: 전체 배포 (Big Bang Deployment)publicclassDeploymentProcess {
publicvoiddeploy() {
// 😱 전체 애플리케이션 배포// 1. 서비스 중단stopApplication();
// 2. 새 버전 배포deployNewVersion();
// 3. 서비스 시작startApplication();
// 문제점:// - 다운타임 발생// - 롤백 어려움// - 위험도 높음// - 작은 변경도 전체 배포
}
}
// 문제 5: 확장성 문제publicclassScalingProblem {
// 😱 특정 기능만 확장 불가// 주문 서비스는 트래픽이 많음// 사용자 서비스는 트래픽이 적음// 하지만 전체를 스케일해야 함!// → 비용 낭비// → 리소스 비효율
}
// 문제 6: 기술 스택 고정publicclassTechnologyStack {
// 😱 한번 선택한 기술을 계속 사용// Spring Boot로 시작// → 모든 기능이 Spring Boot// → 새로운 기술 도입 어려움// 예시:// - 추천 엔진: Python이 더 적합// - 실시간 분석: Node.js가 더 적합// - 이미지 처리: Go가 더 적합// 하지만 모놀리스에서는 불가능!
}
// 문제 7: 팀 협업 어려움publicclassTeamCollaboration {
// 😱 여러 팀이 하나의 코드베이스// 팀 A: 사용자 기능 개발// 팀 B: 주문 기능 개발// 팀 C: 결제 기능 개발// 문제점:// - Git 충돌 빈번// - 서로의 코드에 영향// - 배포 조율 필요// - 책임 범위 불명확
}
// 문제 8: 장애 전파publicclassFailurePropagation {
publicvoidprocessOrder() {
// 😱 하나의 기능 장애가 전체 영향try {
// 추천 시스템 호출List<Product> recommendations =
recommendationService.getRecommendations();
} catch (Exceptione) {
// 추천 시스템 장애// → 전체 주문 프로세스 중단!// → 사용자는 주문 불가!
}
// 중요하지 않은 기능의 장애가// 핵심 기능까지 영향!
}
}
// 문제 9: 긴 빌드 시간publicclassBuildTime {
publicvoidbuild() {
// 😱 전체 애플리케이션 빌드// 코드베이스가 커질수록// 빌드 시간 증가// 작은 변경에도// 전체 빌드 + 테스트// 개발자 생산성 저하!
}
}
// 문제 10: 모니터링과 디버깅publicclassMonitoringProblem {
publicvoidmonitor() {
// 😱 어디서 문제가 발생했는지?// 로그가 모두 섞임// 성능 병목 지점 찾기 어려움// 특정 기능만 모니터링 불가// 문제 원인 파악에 시간 소요
}
}
⚡ 핵심 문제
배포 단위: 전체 애플리케이션 재배포
확장성: 부분 확장 불가
장애 격리: 한 부분 장애가 전체 영향
기술 선택: 단일 기술 스택 고정
팀 협업: 코드베이스 충돌
빌드 시간: 전체 빌드 필요
복잡도: 코드베이스 거대화
2. 패턴 정의
📖 정의
애플리케이션을 작고 독립적인 서비스들로 분해하여, 각 서비스가 자체 프로세스로 실행되고 경량 메커니즘(HTTP/메시지)으로 통신하는 아키텍처 패턴
🎯 목적
독립 배포: 각 서비스를 독립적으로 배포
기술 다양성: 서비스마다 최적 기술 선택
확장성: 필요한 서비스만 확장
장애 격리: 한 서비스 장애가 전체에 영향 안 줌
💡 핵심 아이디어
// Before: Monolithic
┌─────────────────────────────────┐
│ E-Commerce Application │
│ │
│ - User Management │
│ - Product Catalog │
│ - Order Processing │
│ - Payment │
│ - Inventory │
│ - Shipping │
│ │
│ (하나의 거대한 애플리케이션) │
└─────────────────────────────────┘
// After: Microservices
┌──────────┐ ┌──────────┐ ┌──────────┐
│ User │ │ Product │ │ Order │
│ Service │ │ Service │ │ Service │
└──────────┘ └──────────┘ └──────────┘
│ │ │
└─────────────┴─────────────┘
│
(HTTP/메시지로 통신)
│
┌─────────────────────┐
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Payment │ │Inventory │ │ Shipping │
│ Service │ │ Service │ │ Service │
└──────────┘ └──────────┘ └──────────┘
// 각 서비스:
// - 독립적으로 배포
// - 자체 데이터베이스
// - 독립적으로 확장
// - 다른 기술 스택 가능
3. 구조와 구성요소
📊 Microservices 구조
┌─────────────┐
│ Client │
└─────────────┘
│
│ HTTPS
▼
┌───────────────┐
│ API Gateway │ ← 단일 진입점
└───────────────┘
│
┌─────────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ User │ │ Product │ │ Order │
│ Service │ │ Service │ │ Service │
│ │ │ │ │ │
│ - Spring │ │ - Node.js │ │ - Spring │
│ - PostgreSQL │ │ - MongoDB │ │ - MySQL │
└──────────────┘ └──────────────┘ └──────────────┘
│ │ │
│ │ │
│ (각 서비스는 독립적으로 실행, 배포, 확장) │
│ │ │
└─────────────────┼─────────────────┘
│
┌───────────────┐
│ Message Queue │ ← 비동기 통신
│ (RabbitMQ) │
└───────────────┘
│
┌─────────────────┼─────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Payment │ │ Inventory │ │ Shipping │
│ Service │ │ Service │ │ Service │
└──────────────┘ └──────────────┘ └──────────────┘
🔄 서비스 간 통신
1. 동기 통신 (Synchronous)
Service A ──HTTP REST──> Service B
Service A ──gRPC──────> Service B
2. 비동기 통신 (Asynchronous)
Service A ──Message──> [Queue] ──> Service B
Service A ──Event───> [Event Bus] ──> Service B
3. 하이브리드
Service A ──HTTP──> Service B
│
└──Event──> [Event Bus] ──> Service C
1. Eureka Server 시작 (8761)
→ Service Discovery 준비
2. User Service 시작 (8081)
→ Eureka에 등록
→ PostgreSQL 연결
3. Product Service 시작 (8082)
→ Eureka에 등록
→ MongoDB 연결
4. Order Service 시작 (8083)
→ Eureka에 등록
→ MySQL 연결
→ Feign Client 준비
5. API Gateway 시작 (8080)
→ Eureka에 등록
→ 라우팅 규칙 설정
6. 클라이언트 요청
POST http://localhost:8080/api/orders
{
"userId": 1,
"productId": "prod-1",
"quantity": 2
}
7. 처리 흐름:
Client → API Gateway (8080)
→ Order Service (8083)
→ User Service (8081) [사용자 확인]
→ Product Service (8082) [상품 확인]
→ Product Service (8082) [재고 확인]
→ Order 생성 완료
Client ← 응답 반환
5. 실전 예제
예제 1: Circuit Breaker (장애 격리) ⭐⭐⭐
/** * ============================================ * Resilience4j Circuit Breaker * ============================================ * 한 서비스 장애가 다른 서비스에 영향 안 주도록 *//** * Circuit Breaker 설정 */@ConfigurationpublicclassCircuitBreakerConfig {
@BeanpublicCircuitBreakerRegistrycircuitBreakerRegistry() {
CircuitBreakerConfigconfig = CircuitBreakerConfig.custom()
.failureRateThreshold(50) // 실패율 50% 초과 시
.waitDurationInOpenState(Duration.ofSeconds(30)) // 30초 대기
.slidingWindowSize(10) // 최근 10개 요청 기준
.build();
returnCircuitBreakerRegistry.of(config);
}
}
/** * Circuit Breaker 적용 */@ServicepublicclassOrderServiceWithCircuitBreaker {
privatefinalUserServiceClientuserServiceClient;
privatefinalCircuitBreakercircuitBreaker;
@AutowiredpublicOrderServiceWithCircuitBreaker(
UserServiceClientuserServiceClient,
CircuitBreakerRegistryregistry) {
this.userServiceClient = userServiceClient;
this.circuitBreaker = registry.circuitBreaker("user-service");
}
/** * Circuit Breaker로 보호된 호출 */publicOrdercreateOrder(LonguserId, StringproductId, intquantity) {
// Circuit Breaker로 감싸기Supplier<UserResponse> userSupplier = CircuitBreaker
.decorateSupplier(circuitBreaker, () ->
userServiceClient.getUser(userId)
);
try {
UserResponseuser = userSupplier.get();
// 정상 처리returnprocessOrder(user, productId, quantity);
} catch (CallNotPermittedExceptione) {
// Circuit Open! (서비스 장애)System.err.println("⚠️ Circuit Breaker OPEN: User Service 불가");
// Fallback 처리returncreateOrderWithFallback(userId, productId, quantity);
}
}
/** * Fallback 처리 */privateOrdercreateOrderWithFallback(LonguserId, StringproductId, intquantity) {
System.out.println("🔄 Fallback: 기본 사용자 정보로 주문 생성");
// 주문은 생성하되, 사용자 정보는 나중에 업데이트Orderorder = newOrder();
order.setUserId(userId);
order.setProductId(productId);
order.setQuantity(quantity);
order.setStatus(Order.OrderStatus.PENDING);
returnorderRepository.save(order);
}
privateOrderprocessOrder(UserResponseuser, StringproductId, intquantity) {
// 정상 주문 처리returnnull;
}
}
예제 2: API Gateway Patterns ⭐⭐⭐
/** * ============================================ * API Gateway 고급 패턴 * ============================================ *//** * Rate Limiting (요청 제한) */@ConfigurationpublicclassRateLimitingConfig {
@BeanpublicRouteLocatorrateLimitedRoutes(RouteLocatorBuilderbuilder) {
returnbuilder.routes()
.route("user-service", r -> r
.path("/api/users/**")
.filters(f -> f
.requestRateLimiter(c -> c
.setRateLimiter(redisRateLimiter())
.setKeyResolver(userKeyResolver()))
)
.uri("lb://user-service"))
.build();
}
@BeanpublicKeyResolveruserKeyResolver() {
// IP 기반 제한returnexchange -> Mono.just(
exchange.getRequest()
.getRemoteAddress()
.getAddress()
.getHostAddress()
);
}
@BeanpublicRedisRateLimiterredisRateLimiter() {
returnnewRedisRateLimiter(
10, // replenishRate: 초당 10개20// burstCapacity: 최대 20개
);
}
}
/** * Request/Response 변환 */@ComponentpublicclassRequestResponseFilterimplementsGlobalFilter {
@OverridepublicMono<Void> filter(ServerWebExchangeexchange, GatewayFilterChainchain) {
ServerHttpRequestrequest = exchange.getRequest();
// 공통 헤더 추가ServerHttpRequestmodifiedRequest = request.mutate()
.header("X-Request-Id", UUID.randomUUID().toString())
.header("X-Gateway-Time", LocalDateTime.now().toString())
.build();
returnchain.filter(exchange.mutate().request(modifiedRequest).build())
.then(Mono.fromRunnable(() -> {
// 응답 후처리ServerHttpResponseresponse = exchange.getResponse();
response.getHeaders().add("X-Response-Time",
LocalDateTime.now().toString());
}));
}
}
/** * Authentication (인증) */@ComponentpublicclassAuthenticationFilterimplementsGlobalFilter, Ordered {
@OverridepublicMono<Void> filter(ServerWebExchangeexchange, GatewayFilterChainchain) {
ServerHttpRequestrequest = exchange.getRequest();
// JWT 토큰 검증Stringtoken = request.getHeaders().getFirst("Authorization");
if (token == null || !isValidToken(token)) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
returnexchange.getResponse().setComplete();
}
// 사용자 정보를 헤더에 추가StringuserId = extractUserId(token);
ServerHttpRequestmodifiedRequest = request.mutate()
.header("X-User-Id", userId)
.build();
returnchain.filter(exchange.mutate().request(modifiedRequest).build());
}
@OverridepublicintgetOrder() {
return -100; // 가장 먼저 실행
}
privatebooleanisValidToken(Stringtoken) {
// JWT 검증 로직returntrue;
}
privateStringextractUserId(Stringtoken) {
// JWT에서 사용자 ID 추출return"user-123";
}
}
6. 핵심 패턴들
📊 Microservices 핵심 패턴
패턴
목적
구현
API Gateway
단일 진입점
Spring Cloud Gateway
Service Discovery
서비스 찾기
Eureka, Consul
Circuit Breaker
장애 격리
Resilience4j
Saga
분산 트랜잭션
Orchestration/Choreography
CQRS
읽기/쓰기 분리
Event Sourcing
Event Sourcing
이벤트 저장
Event Store
🔄 Database per Service
/** * 각 서비스가 독립적인 DB 소유 */// User Service → PostgreSQL@EntitypublicclassUser {
@IdprivateLongid;
// ...
}
// Product Service → MongoDB@DocumentpublicclassProduct {
@IdprivateStringid;
// ...
}
// Order Service → MySQL@EntitypublicclassOrder {
@IdprivateLongid;
// ❌ User 객체 참조 X// ✅ userId만 저장privateLonguserId;
// ❌ Product 객체 참조 X// ✅ productId만 저장privateStringproductId;
}
7. 장단점
✅ 장점
장점
설명
실무 효과
독립 배포
서비스별 배포
빠른 릴리즈
기술 다양성
서비스별 기술 선택
최적 기술
확장성
필요한 서비스만 확장
비용 절감
장애 격리
일부 장애 격리
안정성
팀 자율성
팀별 독립 개발
생산성
❌ 단점
단점
설명
해결책
복잡도
분산 시스템 복잡
자동화
데이터 일관성
분산 트랜잭션
Saga
네트워크 지연
서비스 간 통신
캐싱
운영 부담
모니터링 어려움
중앙화
테스트 복잡
통합 테스트
Contract Testing
8. 안티패턴
❌ 안티패턴 1: 너무 세분화 (Nanoservices)
// 잘못된 예: 너무 작은 서비스UserNameServiceUserEmailServiceUserPhoneServiceUserAddressService// 올바른 예: 적절한 크기UserService// 모든 사용자 관련 기능
❌ 안티패턴 2: 공유 데이터베이스
// 잘못된 예: 여러 서비스가 같은 DB
UserService ─┐
├─→ [Shared Database]
OrderService ─┘
// 올바른 예: 서비스별 DB
UserService → [User DB]
OrderService → [Order DB]