본문 바로가기
Spring 7기 프로젝트/모임 플렛폼 프로젝트

EventWrapper 패턴을 활용한 RabbitMQ 이벤트 시스템 통일화

by JuNo_12 2025. 8. 11.

들어가며

기존 프로젝트에서는 도메인별로 서로 다른 이벤트 메시지 구조를 사용하고 있었습니다. User 도메인은 UserEventMessage를, 다른 도메인들은 각각의 메시지 구조를 사용하면서 일관성이 부족한 상황이었습니다. 팀에서는 모든 도메인 이벤트를 통일된 양식으로 관리하기 위해 EventWrapper 패턴 도입을 결정했습니다.


EventWrapper 구조 분석

프로젝트에서 사용하는 EventWrapper는 다음과 같은 구조를 가지고 있습니다.

 

이 구조의 장점은 다음과 같습니다:

  • 통일된 메타데이터: 모든 이벤트가 동일한 ID, 타입 필드를 가집니다
  • 확장성: 나중에 공통 필드 추가가 용이합니다
  • 추적 가능성: 이벤트 ID를 통한 추적이 가능합니다

기존 UserEventMessage 구조 개선

기존 UserEventMessage는 이벤트 메시지와 데이터 클래스가 혼재되어 있었습니다. 이를 순수한 데이터 클래스 컨테이너로 역할을 변경했습니다.

변경 전

public record UserEventMessage(
    String eventId,
    String eventType,
    LocalDateTime timestamp,
    String source,
    Object data
) {
    // 팩토리 메서드들
    public static UserEventMessage createWithdrawn(...) { ... }
}

 

변경 후

이렇게 변경하면서 관심사 분리가 명확해졌습니다. UserEventMessage는 순수하게 데이터 구조만 정의하고, 이벤트 발행은 EventWrapper가 담당하게 되었습니다.


UserEventProducer 리팩토링

기존 Producer에서 EventWrapper 양식을 사용하도록 변경했습니다.


팔로우 이벤트 발행 시스템 추가

기존에는 회원탈퇴 이벤트만 발행했지만, 팔로우 기능에 대한 이벤트 발행도 추가했습니다. 아웃박스 패턴과 스프링 이벤트를 활용한 안전한 이벤트 발행 시스템을 구축했습니다.

UserServiceImpl 수정

 

UserEventHandler 확장


Consumer 측 역직렬화 처리

가장 고민이 많았던 부분은 Consumer에서 EventWrapper<?>를 받아서 실제 데이터 객체로 변환하는 방법이었습니다.

 

ObjectMapper vs 직접 캐스팅

처음에는 ObjectMapper를 사용한 안전한 역직렬화를 고려했습니다:

// ObjectMapper 방식
UserEventMessage.UserWithdrawnData data = objectMapper.convertValue(
    eventWrapper.data(), UserEventMessage.UserWithdrawnData.class);

하지만 RabbitMQ의 자동 역직렬화 기능을 고려했을 때, 직접 캐스팅이 더 간단하고 효율적이라는 결론에 도달했습니다:

// 직접 캐스팅 방식
UserEventMessage.UserWithdrawnData data = 
    (UserEventMessage.UserWithdrawnData) eventWrapper.data();

 

최종 AuthUserEventConsumer


상수 관리 개선

이벤트 타입과 라우팅 키를 상수로 관리하여 오타를 방지하고 유지보수성을 높였습니다.


설계 결정사항과 트레이드오프

EventWrapper 도입의 장단점

장점:

  • 모든 도메인 이벤트의 통일된 구조
  • 이벤트 추적을 위한 고유 ID 제공
  • 확장성 확보 (공통 메타데이터 추가 용이)

단점:

  • 기존 코드 변경 필요
  • 타입 안전성 일부 감소 (EventWrapper<?> 사용)
  • 역직렬화 시 캐스팅 필요

 

직접 캐스팅 vs ObjectMapper

직접 캐스팅 선택 이유:

  • RabbitMQ의 자동 역직렬화 기능 활용
  • 추가 의존성 없음
  • 성능상 이점 (중간 변환 과정 생략)
  • 코드 단순화

위험 요소:

  • ClassCastException 가능성
  • 디버깅용 로그로 실제 타입 확인 가능하도록 구현

결론

EventWrapper 패턴 도입을 통해 다음과 같은 개선사항을 달성했습니다:

  1. 일관성 확보: 모든 도메인 이벤트가 동일한 구조를 가지게 되었습니다
  2. 관심사 분리: Producer는 발행만, 데이터 클래스는 구조 정의만 담당하게 되었습니다
  3. 확장성 향상: 새로운 이벤트 추가가 용이해졌습니다
  4. 추적 가능성: 이벤트 ID를 통한 로그 추적이 가능해졌습니다

기존 아웃박스 패턴과 스프링 이벤트 처리 방식은 그대로 유지하면서도, 메시지 구조만 통일화하여 점진적이고 안전한 리팩토링을 수행할 수 있었습니다.