Skip to content

refactor : 알림/채팅 Redis 발행 안정성 및 예외 처리 개선#155

Merged
ekdan38 merged 29 commits into
mainfrom
feature/notification/message
Apr 9, 2026
Merged

refactor : 알림/채팅 Redis 발행 안정성 및 예외 처리 개선#155
ekdan38 merged 29 commits into
mainfrom
feature/notification/message

Conversation

@chuuminggg
Copy link
Copy Markdown
Member

🔥ISSUE

  • Redis 기반 알림 및 채팅 메시지 발행/구독 과정에서 발생하던 알림 미수신 문제 해결
  • ObjectMapper 설정 불일치로 인한 직렬화/역직렬화 오류 개선
  • 트랜잭션 롤백 상황에서도 Redis 발행이 이루어지던 데이터 정합성 문제 해결
  • 예외 처리 체계 미흡으로 인한 에러 추적 및 클라이언트 전달 한계 개선

📒TODO

  1. NotificationMessageDto 기본 생성자 접근 제어 수정 (protected → public)
  2. Redis Subscriber 및 ChatService의 ObjectMapper를 Spring Bean으로 통일
  3. Transactional Outbox 패턴 적용 (afterCommit 이후 Redis publish)
  4. 채팅 알림 문구 UX 개선 ("요청" → "보냈습니다")
  5. 알림/채팅 발행 실패에 대한 ErrorCode 및 커스텀 예외 추가
  6. Redis publish 실패 시 서비스별 예외 처리 방식 분리 적용
  7. Swagger Exception Docs에 신규 예외 반영

📋REF

✅ 주요 수정 사항 상세

  1. 알림 미수신 버그 해결 (핵심)
  • NotificationMessageDto의 기본 생성자 접근 제한으로 인해 Jackson 역직렬화 실패 → WebSocket 미전송 문제 발생
  • @NoArgsConstructor(access = PROTECTED) → @NoArgsConstructor로 수정하여 해결
  1. ObjectMapper Bean 통일
  • new ObjectMapper() 사용 제거
  • Spring Bean 주입 방식으로 변경 (@requiredargsconstructor)
  • JavaTimeModule 등 커스텀 설정 유지 가능하도록 개선
  1. Transactional Outbox 적용
  • TransactionSynchronizationManager.registerSynchronization() 활용
  • 트랜잭션 커밋 이후 Redis publish 수행
  • 롤백 시 잘못된 이벤트 발행 방지 → 데이터 정합성 확보
  1. 예외 처리 체계 개선
  • 신규 ErrorCode 추가
  • N005: NOTIFICATION_PUBLISH_FAILED
  • CH007: CHAT_PUBLISH_FAILED
  • 신규 Exception 추가
  • NotificationPublishFailedException
  • ChatPublishFailedException
  • 처리 방식 분리
  • Chat: throw → STOMP ERROR frame 전달
  • Notification: afterCommit 내부 → throw 불가 → 구조화 로그 처리
  1. Swagger Docs 반영
  • Notification / Chat Exception Docs에 신규 예외 추가
  • API 문서에서 에러 케이스 명확화
  1. 기타 개선
  • 채팅 알림 문구 자연스럽게 수정
  • "메시지 요청" → "메시지를 보냈습니다"

🎯정리

  • Redis 기반 이벤트 흐름 안정성 확보
  • WebSocket 알림 누락 문제 해결
  • 예외 추적 및 디버깅 용이성 향상
  • 트랜잭션 정합성 보장 (Outbox 패턴 적용)

…nk 통합 처리, 시스템 알림과 탈퇴/미설정 유저 구분, toMap merge function 추가로 중복 키 예외 방지)
@chuuminggg chuuminggg requested review from ekdan38 and jaey-oung April 7, 2026 15:03
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors Redis message publishing for chat and notifications to improve error handling and consistency. Key changes include the introduction of custom exceptions for publishing failures, enhanced logging, and the implementation of transaction synchronization in NotificationServiceImpl to ensure notifications are sent only after a successful commit. Feedback suggests extending the transactional outbox pattern to publishReadEvent in ChatService and reconsidering the exception handling strategy in favor of a retry mechanism, as exceptions thrown after a commit may not be properly propagated to the client.

} catch (JsonProcessingException e) {
log.error("읽음 이벤트 직렬화 실패", e);
log.error("읽음 이벤트 직렬화 실패: roomId={}, userId={}", roomId, userId, e);
throw new ChatPublishFailedException(roomId);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

publishReadEvent 메소드 역시 publishToRedis와 마찬가지로 Transactional Outbox 패턴이 적용되지 않았습니다. 읽음 처리 트랜잭션이 성공적으로 커밋된 후에만 Redis 이벤트가 발행되도록 수정하여 데이터 일관성을 보장해 주세요.

} catch (JsonProcessingException e) {
log.error("ChatPayload 직렬화 실패", e);
log.error("채팅 메시지 직렬화 실패: roomId={}, messageId={}", roomId, message.getId(), e);
throw new ChatPublishFailedException(roomId);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

ChatPublishFailedException을 던져 STOMP ERROR 프레임을 전달하고자 하는 의도는 확인되나, afterCommit 패턴을 적용할 경우 트랜잭션 완료 후 예외가 발생하므로 클라이언트에게 에러 전달이 어려울 수 있습니다. 프로젝트 규칙에 따라, 트랜잭션 롤백과 관련된 복잡한 예외 처리나 상태 업데이트(FAILED 등) 대신, 스케줄러를 통한 재시도 메커니즘을 활용하여 처리를 단순화하는 것을 권장합니다.

References
  1. 트랜잭션 메소드에서 예외 발생 시 'FAILED' 상태 업데이트나 복잡한 예외 처리가 어렵다면, 이를 생략하고 스케줄러를 통한 재시도 메커니즘을 사용하는 것이 권장됩니다.

@chuuminggg
Copy link
Copy Markdown
Member Author

/gemini review

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request improves the reliability of Redis Pub/Sub messaging by introducing custom exceptions for publish failures and implementing Transactional Outbox patterns for notifications. The reviewer noted that while the notification service correctly uses TransactionSynchronizationManager to ensure Redis messages are published only after transaction commits, the chat service's message and read event publishing logic still lack this transactional safety, risking data inconsistency if a transaction rolls back. I recommend applying the same TransactionSynchronizationManager approach to the chat message and read event publishing methods in ChatService to ensure consistency.

Comment on lines 205 to 213
try {
String json = objectMapper.writeValueAsString(payload);
String channel = CHAT_CHANNEL_PREFIX + message.getChatRoom().getId();
String channel = CHAT_CHANNEL_PREFIX + roomId;
redisTemplate.convertAndSend(channel, json);
log.info("Redis 메시지 발행: channel={}, messageId={}", channel, message.getId());
} catch (JsonProcessingException e) {
log.error("ChatPayload 직렬화 실패", e);
log.error("채팅 메시지 직렬화 실패: roomId={}, messageId={}", roomId, message.getId(), e);
throw new ChatPublishFailedException(roomId);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

PR 설명에 언급된 'Transactional Outbox 패턴'이 채팅 메시지 발행에는 적용되지 않았습니다. 현재 구현 방식은 redisTemplate.convertAndSend가 트랜잭션 커밋 여부와 상관없이 즉시 실행되므로, 이후 트랜잭션이 롤백되더라도 Redis 메시지는 이미 발행되어 데이터 정합성이 깨질 수 있습니다. NotificationServiceImpl과 동일하게 TransactionSynchronizationManager를 사용하여 커밋 이후에 발행되도록 개선이 필요합니다. 다만, 이 경우 afterCommit 내부에서 발생하는 예외는 호출자에게 전달되지 않으므로 로그 처리가 권장됩니다.

Comment on lines 232 to 235
} catch (JsonProcessingException e) {
log.error("읽음 이벤트 직렬화 실패", e);
log.error("읽음 이벤트 직렬화 실패: roomId={}, userId={}", roomId, userId, e);
throw new ChatPublishFailedException(roomId);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

읽음 이벤트 발행 시에도 Transactional Outbox 패턴이 적용되지 않았습니다. 메시지 발행과 마찬가지로 트랜잭션 성공 시에만 Redis 이벤트가 전달되도록 afterCommit 동기화 로직을 적용하는 것을 권장합니다.

Copy link
Copy Markdown
Collaborator

@ekdan38 ekdan38 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다~

@ekdan38 ekdan38 merged commit 95e6d0b into main Apr 9, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants