들어가며
현재 운영 중인 모임 플랫폼 'MOMO'는 모놀리식 아키텍처로 구성되어 있지만, 이미 MSA 전환을 고려한 설계가 적용되어 있습니다. WebClient를 통한 서비스 간 HTTP 통신과 Spring Event 기반의 비동기 처리가 구현되어 있어, RabbitMQ와 ALB를 활용한 완전한 MSA 전환의 기반이 마련되어 있습니다.
현재 시스템 구조 분석
이미 구현된 MSA 준비 요소들
현재 코드베이스에서 확인할 수 있는 MSA 준비 상태:
1. WebClient 기반 서비스 간 통신

2. 도메인별 Event 분리

MSA 전환 전략
1단계: 메시지 큐 도입 (RabbitMQ)
현재 Spring Event 기반의 비동기 처리를 RabbitMQ로 전환합니다:
현재 방식 (Spring Event)
@EventListener
public void handleMeetingJoinEvent(MeetingEvents.Join event) {
// 알림 처리 로직
}
RabbitMQ 전환 후
// Producer (Meeting Service)
@Service
public class MeetingEventPublisher {
private final RabbitTemplate rabbitTemplate;
public void publishMeetingJoinEvent(MeetingEvents.Join event) {
rabbitTemplate.convertAndSend("meeting.exchange", "meeting.join", event);
}
}
// Consumer (Notification Service)
@RabbitListener(queues = "meeting.join.queue")
public void handleMeetingJoinEvent(MeetingEvents.Join event) {
// 알림 처리 로직
}
2단계: Exchange와 Queue 설계
도메인별 이벤트에 맞는 RabbitMQ 토폴로지 구성:
Meeting Events:
├── meeting.exchange (Topic Exchange)
├── meeting.create.queue → meeting.create
├── meeting.join.queue → meeting.join
├── meeting.cancel.queue → meeting.cancel
└── meeting.delete.queue → meeting.delete
User Events:
├── user.exchange (Topic Exchange)
├── user.followed.queue → user.followed
├── user.registered.queue → user.registered
└── user.withdrawn.queue → user.withdrawn
Payment Events:
├── payment.exchange (Topic Exchange)
├── payment.completed.queue → payment.completed
└── payment.refunded.queue → payment.refunded
3단계: ALB 라우팅 설계 (기존 WebClient 활용)
현재 WebClient 설정을 그대로 활용하되, ALB를 통한 라우팅으로 변경:
// 현재 WebClient 설정 (이미 구현됨)
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient() {
return WebClient.builder()
.baseUrl("http://localhost:8080") // ALB 엔드포인트로 변경
.defaultHeader("WebclientInternal", passwordEncoder.encode(webSecretKey))
.build();
}
}
ALB 라우팅 규칙
ALB 엔드포인트: https://api.momo.com
├── /api/v2/users/* → User Service (Target Group 1)
├── /api/v2/meetings/* → Meeting Service (Target Group 2)
├── /api/v2/payments/* → Payment Service (Target Group 3)
├── /api/v2/notifications/* → Notification Service (Target Group 4)
└── /api/v2/categories/* → Category Service (Target Group 5)
4단계: 서비스별 분리 및 독립 배포
각 서비스별 Docker 이미지 구성
# User Service
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY user-service/build/libs/*.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
# Meeting Service
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY meeting-service/build/libs/*.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
Docker Compose로 전체 서비스 구성
version: '3.8'
services:
rabbitmq:
image: rabbitmq:3-management
ports:
- "5672:5672"
- "15672:15672"
environment:
RABBITMQ_DEFAULT_USER: momo
RABBITMQ_DEFAULT_PASS: password
user-service:
image: momo-user-service:latest
ports:
- "8081:8080"
environment:
- DB_URL=jdbc:mysql://user-db:3306/user_db
- RABBITMQ_URL=amqp://rabbitmq:5672
depends_on:
- rabbitmq
- user-db
meeting-service:
image: momo-meeting-service:latest
ports:
- "8082:8080"
environment:
- DB_URL=jdbc:mysql://meeting-db:3306/meeting_db
- RABBITMQ_URL=amqp://rabbitmq:5672
depends_on:
- rabbitmq
- meeting-db
5단계: 이벤트 기반 통신 패턴 적용
1. Saga Pattern 구현 (결제 + 참가자 추가)
// Meeting Service
@Service
public class MeetingParticipantSaga {
@RabbitListener(queues = "payment.completed.queue")
public void handlePaymentCompleted(PaymentCompletedEvent event) {
try {
// 참가자 추가 시도
meetingService.addParticipant(event.getMeetingId(), event.getUserId());
// 성공 이벤트 발행
publishMeetingJoinEvent(event);
} catch (Exception e) {
// 실패 시 보상 트랜잭션 이벤트 발행
publishPaymentRefundEvent(event);
}
}
}
2. CQRS Pattern 적용 (알림 시스템)
// Command: Notification Service에서 알림 저장
@RabbitListener(queues = "notification.create.queue")
public void handleCreateNotification(NotificationDto dto) {
notificationRepository.save(dto.toEntity());
}
// Query: WebSocket을 통한 실시간 전송
@RabbitListener(queues = "notification.send.queue")
public void handleSendNotification(NotificationDto dto) {
webSocketNotificationService.send(
WebSocketNotificationDto.builder()
.userId(dto.getUserId())
.content(dto.getContent())
.build()
);
}
6단계: 데이터베이스 분리
현재 Flyway 마이그레이션을 각 서비스별로 분리:
-- User Service Database
V1__Create_users_table.sql
V2__Create_categories_table.sql
V3__Create_user_categories_table.sql
V4__Create_user_follow_table.sql
V5__Create_user_ratings_table.sql
V8__Create_user_social_table.sql
-- Meeting Service Database
V6__Create_meetings_table.sql
V7__Create_meeting_participants_table.sql
-- Payment Service Database
V9__Create_payments_table.sql
-- Notification Service Database
V10__Create_notifications_table.sql
RabbitMQ 메시지 플로우
1. 모임 참가 프로세스
1. Meeting Service → payment.register.queue (결제 요청)
2. Payment Service → payment.completed.queue (결제 완료)
3. Meeting Service → meeting.join.queue (참가자 추가)
4. Notification Service → notification.send.queue (알림 전송)
2. 팔로우 알림 프로세스
1. User Service → user.followed.queue (팔로우 이벤트)
2. Notification Service → notification.create.queue (알림 저장)
3. Notification Service → notification.send.queue (실시간 전송)
장애 처리 및 복원력
1. Dead Letter Queue 설정
@Bean
public Queue meetingJoinQueue() {
return QueueBuilder.durable("meeting.join.queue")
.withArgument("x-dead-letter-exchange", "meeting.dlx")
.withArgument("x-dead-letter-routing-key", "meeting.join.dlq")
.build();
}
2. Retry 메커니즘
@RabbitListener(queues = "meeting.join.queue")
@Retryable(value = {Exception.class}, maxAttempts = 3)
public void handleMeetingJoinEvent(MeetingEvents.Join event) {
// 처리 로직
}
3. Circuit Breaker Pattern
@Component
public class UserClient {
@CircuitBreaker(name = "user-service")
public UserClientResponseDto getUser(Long userId) {
return webClient.get()
.uri("/api/v2/users/{userId}", userId)
.retrieve()
.bodyToMono(UserClientResponseDto.class)
.block();
}
}
모니터링 및 추적
1. RabbitMQ 메시지 추적
@Component
public class MessageTracker {
@EventListener
public void handleMessageSent(MessageSentEvent event) {
log.info("Message sent to queue: {}, correlationId: {}",
event.getQueue(), event.getCorrelationId());
}
}
2. 분산 트레이싱 (이미 구현된 OpenTelemetry 확장)
# otel-config.yml에 RabbitMQ 추가
receivers:
otlp:
protocols:
grpc:
http:
rabbitmq:
endpoint: http://rabbitmq:15672
단계별 마이그레이션 계획
Phase 1: 메시지 큐 도입
- RabbitMQ 인프라 구성
- Spring Event를 RabbitMQ로 점진적 전환
- 기존 WebClient 통신 방식 유지
Phase 2: 서비스 물리적 분리
- 가장 독립적인 Category Service부터 분리
- Payment Service 분리 (토스페이먼츠 연동 포함)
- Notification Service 분리 (WebSocket 포함)
Phase 3: 핵심 서비스 분리
- User Service 분리 (인증/인가 포함)
- Meeting Service 분리
- ALB를 통한 통합 라우팅 적용
Phase 4: 최적화 및 안정화
- 서비스 간 통신 최적화
- 장애 복구 메커니즘 강화
- 성능 튜닝 및 모니터링 고도화
예상 효과
이미 구현된 WebClient 기반 통신과 이벤트 기반 아키텍처를 바탕으로:
- 점진적 전환: 기존 코드의 큰 변경 없이 단계적 MSA 전환 가능
- 메시지 기반 느슨한 결합: RabbitMQ를 통한 서비스 간 비동기 통신으로 시스템 복원력 향상
- 수평적 확장: 각 서비스별 독립적인 스케일링으로 리소스 효율성 극대화
- 장애 격리: 메시지 큐와 Circuit Breaker를 통한 장애 전파 방지
현재 MOMO 프로젝트는 이미 MSA 전환을 위한 핵심 요소들이 잘 구현되어 있어, RabbitMQ 도입과 ALB 설정만으로도 비교적 안정적인 MSA 전환이 가능할 것으로 예상됩니다.
'Spring 7기 프로젝트 > 모임 플렛폼 프로젝트' 카테고리의 다른 글
| Spring Boot 모놀리스 구조에서 ALB는 왜 필요할까요? (0) | 2025.07.31 |
|---|---|
| Spring Boot 프로젝트에서 Actuator는 꼭 필요할까요? (1) | 2025.07.31 |
| AWS EC2 vs ECS: 컨테이너 배포 전략과 구현 (2) | 2025.07.31 |
| 동시성 제어와 중복 요청 방지: 락(Lock)과 멱등성 키(Idempotency Key) 비교 (1) | 2025.07.28 |
| Elasticsearch를 활용한 모임 검색 서비스 설계 (0) | 2025.07.28 |