본문 바로가기

Spring Boot/비동기 처리

Spring Event : 분산 환경으로의 확장 가능성

분산?

 

 - Spring Event의 한계, 분산 환경으로 확장 필요성

 - 메세지 브로커(Kafka 등)를 활용해서 여러 애플리케이션간 이벤트를 주고받는 개념

 - 도메인 이벤트(Domain Event)와 통합 이벤트(Integration Event)의 차이

 - Kafka의 동작 구조(Producer, Topic, Consumer, Broker) 이해하기

 - Spring Event에서 Kafka 기반 이벤트로 확장하는 단계별 아키텍처 변화

 


애플리케이션 내부 이벤트의 한계

Spring Event는 단일 애플리케이션 내에서 비동기 처리를 구현하기에 매우 유용하기만,

분산 시스템 또는 마이크로서비스 환경에서는 한계가 존재한다고 함

 

1. Spring Event의 동작 범위

Spring Event는 Application Context 내부에서만 작동함

즉, 이벤트를 발행한 인스턴스(JVM) 안에서만 수신할 수 있음

Application Instance는 반드시 Event를 통해 publisher를 거쳐서 Listener로 전달되어야한다

 

 

2. 내부 이벤트만 사용했을때의 문제 예시

상황 문제점
회원가입 후 이메일 발송 요청을 처리한 인스턴스에서만 이벤트 발생
- > 이메일 누락 가능
주문 생성 후 결제 서비스 호출 주문 서비스에서만 이벤트가 전달됨
- > 결제 서비스와 데이터가 불일치하는 결과
서버 확장(Auto Scaling) 인스턴스간 이벤트 전달 불가 - > 데이터의 일관성이 깨짐

 


메시지 브로커(Message Broker)

위의 문제들을 해결하기 위해 등장한게 메시지 브로커라고 함

Message Broker는 여러 애플리케이션간 메시지(이벤트)를 중간에서 안전하게 전달해주는 시스템이라고 한다.

 - Apache Kafka(고성능, 대규모 로그 스트리밍)

 - RabbitMQ(메시지 큐 기반 비동기 처리)

 - Amazon SQS, Google Pub/Sub 등등..

 

1. 메시지 브로커의 역할

Producer에서 메시지 발행 - > Message Broker가 저장하고 관리함 - > Consumer에게 메시지 전달

 

구성요소 설명
Producer 메시지를 발행하는 주체(Publisher 역할)
Broker 메시지를 임시 저장, 관리하는 서버(Kafka Cluster 등등..)
Topic/Queue 메시지를 구분하기 위한 논리적 공간(메일함과 유사하다고 함)
Consumer 메시지를 구독하여 처리하는 주체

 


Kafka의 기본 구조

Kafka는 고성능 메시지 스트리밍 플랫폼이라고 한다.

이벤트를 중앙 Topic에 저장하고 다수의 소비자(Consumer)가 이를 병렬로 처리할 수 있도록 한다고 함

Producer가 이벤트를 발행하고, Kafka Topic에서 관리하며, 이를 Consumer에서 구독하고 처리한다.

 


도메인 이벤트 vs 통합 이벤트

이벤트 기반 설계에서는 이벤트의 의미, 사용 범위를 명확하게 구분하는게 매우 중요하다고 함

 

1. 도메인 이벤트(Domain Event)

 - 하나의 서비스 내부에서 발생하는 비즈니스 상태 변화를 표현함

 - 이벤트를 통해서 내부 모듈간 결합도를 낮춤

public class UserRegisteredEvent { // 회원가입 완료시 이메일 발송을 위한 이벤트
	private final String email;
    public UserRegisteredEvent(String email) {
    	this.email = email;
    }
    public String getEmail() {
    	return email;
    }
}

 

도메인 이벤트는 Spring의 @EventListener 기반으로 비동기 처리할 수 있음

 


통합 이벤트(Integration Event)

 - 서비스 간 데이터 교환을 위한 이벤트

 - 다른 애플리케이션이 구독할 수 있도록 외부 브로커(Kafka)에 발행된다고 한다.

// 주문 서비스에서 결제 서비스로 전달되는 이벤트
public class OrderCreatedIntegrationEvent {
	private final String orderId;
    private final BigDecimal price;
    
    public OrderCreatedIntegrationEvent(String orderId, BigDecimal price) {
    	this.orderId = orderId;
        this.price = price;
    }
    
    public String getOrderId() {
    	return orderId;
    }
    public BigDecimal getPrice() {
    	return price;
    }
}

 

구분 도메인 이벤트 통합 이벤트
범위 한 서비스 내부 서비스간 통신
처리 방식 Spring Event Kafka, RabbitMQ
목적 내부 로직 분리 시스템간 데이터 전달
예시 회원가입 - > 이메일 발송 주문 생성 - > 결제 요청

 


Kafka와 Spring Event의 결합 구조

Kafka와 Spring Event의 결합 구조

 

Spring Event로 내부 이벤트를 감지하고, Kafka로 외부 시스템에 통합 이벤트를 발행하는 구조라고 한다.

 

1. Producer 예제(이벤트 발행)

@Service
public class UserEventPublisher {
	private final KafkaTemplate<String, String> kafkaTemplate;
    
    public UserEventPublisher(KafkaTemplate<String, String> kafkaTemplate) {
    	this.kafkaTemplate = kafkaTemplate;
    }
    
    public void publisherCreated(String userId) {
    	String message = String.format("{\"userId\":\"%s\"}", userId);
        kafkaTemplate.send("user.events", message);
        System.out.println("kafka 이벤트 발행 완료 : " + message);
    }
}

 

2. Consumer 예제(이벤트 소비)

@Component
public class OrderServiceConsumer {
	@KafkaListener(topics = "user.events", groupId = "order-service-group");
    public void handleUserCreated(String message) {
    	System.out.println("Kafka 이벤트 수신 : " + message);
        // 사용자 등록 이후 주문 초기화 로직을 짠다던지.. 등등..
    }
}

 


이벤트 기반 아키텍처로의 발전 단계

Spring Event를 시작으로 해서, 점진적으로 Kafka를 도입하여 확장 가능한 이벤트 기반 아키텍처로 발전할 수 있다고 함

단일 Spring Event에서 Kafka로의 발전

단계 설명 기술 스택
내부 이벤트 단일 인스턴스 내 비동기 이벤트 ApplicationEventPublisher
비동기 이벤트 @Async 기반 빠른 응답 @Async, ThreadPoolTaskExecutor
통합 이벤트 Kafka 등 메시지 브로커로의 확장 KafkaTemplate, @KafkaListener
완전한 이벤트 아키텍처 CQRS, Event Sourcing 결합 Kafka Streams, Debezium

 


상황
여러 서비스에서 같은 Topic이 구독됨 Consumer Group으로 병렬처리 및 중복방지
메시지 유실 방지 Kafka Ack(확인 응답) 및 Retry 정책? 사용
이벤트 순서 보장 Partition Key 지정(key = userId)
트랜잭션 보장 Kafka Transaction API로 원자성 확보하기
장애시 복구 Dead Letter Queue(DLQ) 설계 필수 - 실무

 


정리

구분 설명 예시
내부 이벤트 단일 앱 내 이벤트 @EventListener,
ApplicationEventPublisher
통합 이벤트 서비스간 메시지 전송 Kafka, RabbitMQ
도메인 이벤트 도메인 내부 상태 변화 UserRegisteredEvent
이벤트 기반 아키텍처 서비스간 결합도를 최소화한 시스템 구조 Kafka Streams, CQRS