Skip to content

Feat: impl STOMP Websocket Redis pub/sub message relay with DTO and constants - #55

Merged
SungHuii merged 6 commits into
developfrom
feature/realtime-redis-pubsub
Aug 4, 2026
Merged

SungHuii merged 6 commits into
developfrom
feature/realtime-redis-pubsub

Conversation

@SungHuii

@SungHuii SungHuii commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

작업 내용

  • 다중 인스턴스(Multi-Instance Scale-Out) 분산 환경에서 웹소켓 실시간 메시지가 모든 서버의 수험생들에게 정상 멀티캐스팅되도록 Redis Pub/Sub 릴레이 아키텍처 구축
  • 웹소켓 전용 불변 Record DTO (RealtimeMessageRequest, RealtimeMessageResponse), 응시 상태 Enum (RoomRealtimeStatus), 및 상수 클래스 (WebSocketConstants) 구축
  • STOMP 요청 처리 웹소켓 컨트롤러 (RoomRealtimeController) 및 Redis 메시지 발행/수신자 (RedisMessagePublisher, RedisMessageSubscriber) 연동

변경 사항

  1. RedisPubSubConfig.java:
  • 실시간 시험방 전용 ChannelTopic ("room-realtime-channel") 및 RedisMessageListenerContainer 빈 정의
  1. RedisMessagePublisher.java & RedisMessageSubscriber.java:
  • STOMP 요청 수신 시 Redis 채널로 Publish 하고, Redis에서 수신받은 메시지를 /sub/rooms/{roomId} 목적지 구독자들에게 브로드캐스팅하도록 구현
  1. WebSocketConstants.java:
  • 구독 경로 매직 스트링("/sub/rooms/") 상수를 추출하여 캡슐화 관리
  1. 웹소켓 DTO 레코드 설계:
  • RealtimeMessageRequest, RealtimeMessageResponse (record 및 정적 팩토리 메소드 적용)
  • RoomRealtimeStatus (ENTER, PROGRESS, SUBMIT, LEAVE 응시 상태 Enum)

체크리스트

  • 테스트 코드 작성 완료
  • 리뷰어 지정 완료

참고 사항

  • 동작 흐름: 클라이언트 STOMP 요청 (/pub/rooms/{roomId}/send)RoomRealtimeControllerRedisMessagePublisherRedis Pub/SubRedisMessageSubscriberSimpMessagingTemplate (/sub/rooms/{roomId}) 브로드캐스트
  • 보안: STOMP 구독 권한 검증은 기존 JwtChannelInterceptor가 전담

관련 이슈

Summary by CodeRabbit

  • 새로운 기능
    • 시험방의 입장, 풀이 진행, 답안 제출, 퇴장 상태를 실시간으로 공유할 수 있습니다.
    • 시험방별 웹소켓 구독을 통해 응시 상태, 풀이 문항 수, 상세 정보를 수신할 수 있습니다.
    • 실시간 메시지에 시험방·수험생 식별자와 응답 시각이 포함됩니다.
    • 메시지 발행 및 처리 오류가 구분되어 안내됩니다.

@SungHuii
SungHuii requested review from idktomorrow and jaejo August 3, 2026 07:07
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@SungHuii, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 15 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 64886898-0fcf-4137-9216-371ba6d42f3a

📥 Commits

Reviewing files that changed from the base of the PR and between 273e280 and 47d5a87.

📒 Files selected for processing (2)
  • momogo-realtime/src/main/java/com/momogo/realtime/config/RedisPubSubConfig.java
  • momogo-realtime/src/main/java/com/momogo/realtime/websocket/redis/RedisMessagePublisher.java
📝 Walkthrough

Walkthrough

실시간 시험방 응시 상태 DTO와 상태 enum을 추가했다. WebSocket 요청을 Redis 채널에 발행하고, Redis 구독 메시지를 방별 WebSocket 목적지로 전달하는 흐름을 추가했다.

Changes

실시간 시험방 메시징

Layer / File(s) Summary
메시지 계약과 상태 정의
momogo-realtime/src/main/java/com/momogo/realtime/websocket/dto/type/RoomRealtimeStatus.java, momogo-realtime/src/main/java/com/momogo/realtime/websocket/dto/request/RealtimeMessageRequest.java, momogo-realtime/src/main/java/com/momogo/realtime/websocket/dto/response/RealtimeMessageResponse.java, momogo-realtime/src/main/java/com/momogo/realtime/websocket/constant/WebSocketConstants.java
실시간 요청·응답 필드와 시험방 응시 상태를 정의했다. 응답 생성 시 현재 시각을 설정한다. WebSocket 구독 경로 접두사를 상수로 제공한다.
WebSocket 수신과 Redis 발행
momogo-core/src/main/java/com/momogo/core/common/exception/RealtimeErrorCode.java, momogo-realtime/src/main/java/com/momogo/realtime/websocket/controller/RoomRealtimeController.java, momogo-realtime/src/main/java/com/momogo/realtime/websocket/redis/RedisMessagePublisher.java
/room/{roomId}/send 요청을 수신한다. 요청을 응답 DTO로 변환하고 JSON으로 직렬화해 room-realtime-channel에 발행한다. 직렬화 및 발행 실패에 실시간 오류 코드를 적용한다.
Redis 구독과 WebSocket 전달
momogo-realtime/src/main/java/com/momogo/realtime/config/RedisPubSubConfig.java, momogo-realtime/src/main/java/com/momogo/realtime/websocket/redis/RedisMessageSubscriber.java
Redis 토픽 리스너와 커스텀 실행기를 구성한다. 수신한 JSON을 RealtimeMessageResponse로 역직렬화하고 방별 WebSocket 목적지로 전달한다.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: idktomorrow, jaejo

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 STOMP WebSocket용 Redis Pub/Sub 메시지 릴레이와 DTO 및 상수 추가라는 주요 변경 사항을 명확하게 요약합니다.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/realtime-redis-pubsub

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@momogo-realtime/src/main/java/com/momogo/realtime/websocket/controller/RoomRealtimeController.java`:
- Around line 20-29: Update RoomRealtimeController.sendMessage to use the
`@DestinationVariable` roomId and authenticated Principal identity instead of
request-body identifiers, ignoring or removing duplicate roomId and
client-provided userId values. Before redisMessagePublisher.publish, validate
that the authenticated user has access to the specified room, then construct
RealtimeMessageResponse with the trusted identities.

In
`@momogo-realtime/src/main/java/com/momogo/realtime/websocket/dto/request/RealtimeMessageRequest.java`:
- Around line 14-20: Apply request validation to RealtimeMessageRequest by
enforcing non-null status and a non-negative solvedCount, then annotate the
controller sendMessage() request parameter with `@Valid`. Ensure validation
failures from STOMP messages are converted to the existing Common
BusinessException so the established WebSocket exception-handling flow is used.

In
`@momogo-realtime/src/main/java/com/momogo/realtime/websocket/redis/RedisMessagePublisher.java`:
- Around line 23-32: Update RedisMessagePublisher.publish so failures from
objectMapper.writeValueAsString or redisTemplate.convertAndSend are not
swallowed after logging. Wrap and rethrow them as the project’s domain
exception, then verify the shared STOMP exception handler converts that
exception into an error frame or explicit failure response. Add coverage for
both Redis connection failures and JSON serialization failures.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7976a024-ecfd-46b1-a04e-12fd13c60945

📥 Commits

Reviewing files that changed from the base of the PR and between 45cbe28 and 020d903.

📒 Files selected for processing (8)
  • momogo-realtime/src/main/java/com/momogo/realtime/config/RedisPubSubConfig.java
  • momogo-realtime/src/main/java/com/momogo/realtime/websocket/constant/WebSocketConstants.java
  • momogo-realtime/src/main/java/com/momogo/realtime/websocket/controller/RoomRealtimeController.java
  • momogo-realtime/src/main/java/com/momogo/realtime/websocket/dto/request/RealtimeMessageRequest.java
  • momogo-realtime/src/main/java/com/momogo/realtime/websocket/dto/response/RealtimeMessageResponse.java
  • momogo-realtime/src/main/java/com/momogo/realtime/websocket/dto/type/RoomRealtimeStatus.java
  • momogo-realtime/src/main/java/com/momogo/realtime/websocket/redis/RedisMessagePublisher.java
  • momogo-realtime/src/main/java/com/momogo/realtime/websocket/redis/RedisMessageSubscriber.java

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@momogo-core/src/main/java/com/momogo/core/common/exception/RealtimeErrorCode.java`:
- Around line 11-16: The REDIS_PUBLISH_FAILED error code is currently applied to
both JSON serialization and Redis publish failures. Update
RedisMessagePublisher.publish() to separate ObjectMapper.writeValueAsString()
error handling from redisTemplate.convertAndSend(), using REDIS_PUBLISH_FAILED
only for the latter and mapping serialization failures to an appropriate
separate error code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 14953a02-b89d-481d-9bd3-7f1a59b2273b

📥 Commits

Reviewing files that changed from the base of the PR and between 020d903 and 6d06f9e.

📒 Files selected for processing (5)
  • momogo-core/src/main/java/com/momogo/core/common/exception/RealtimeErrorCode.java
  • momogo-realtime/src/main/java/com/momogo/realtime/websocket/controller/RoomRealtimeController.java
  • momogo-realtime/src/main/java/com/momogo/realtime/websocket/dto/request/RealtimeMessageRequest.java
  • momogo-realtime/src/main/java/com/momogo/realtime/websocket/dto/response/RealtimeMessageResponse.java
  • momogo-realtime/src/main/java/com/momogo/realtime/websocket/redis/RedisMessagePublisher.java
🚧 Files skipped from review as they are similar to previous changes (3)
  • momogo-realtime/src/main/java/com/momogo/realtime/websocket/dto/request/RealtimeMessageRequest.java
  • momogo-realtime/src/main/java/com/momogo/realtime/websocket/controller/RoomRealtimeController.java
  • momogo-realtime/src/main/java/com/momogo/realtime/websocket/redis/RedisMessagePublisher.java

Comment thread momogo-core/src/main/java/com/momogo/core/common/exception/RealtimeErrorCode.java Outdated

@jaejo jaejo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

고생하셨습니다.

@idktomorrow idktomorrow left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

큰 문제 없어 보입니다! 작성하시느라 고생 많으셨습니다 ❤️

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
momogo-realtime/src/main/java/com/momogo/realtime/websocket/redis/RedisMessagePublisher.java (1)

18-20: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Redis Pub/Sub wire format은 한 번만 직렬화하세요.

RedisConfigRedisTemplate<String, Object>의 value serializer를 GenericJackson2JsonRedisSerializer로 설정하지만, subscriber는 기본 MessageListenerAdapter만 사용하므로 수신된 JSON 문자열이 다시 Jackson serializer로 직렬화될 수 있습니다. RedisMessageSubscriber.handleMessage(String)objectMapper.readValue()를 호출해 역직렬화하기 때문에 subscriber에서는 UnexpectedException, publisher에서는 실제 구독자가 원본 JSON을 받을 수 있습니다. 이 채널에서는 StringRedisTemplate(String, String, StringRedisSerializer)로 발행/수신하거나 MessageListenerAdapter의 serializer를 StringRedisSerializer로 명시하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@momogo-realtime/src/main/java/com/momogo/realtime/websocket/redis/RedisMessagePublisher.java`
around lines 18 - 20, Redis Pub/Sub 메시ğinin tek kez JSON olarak
serileştirilmesini sağlayın. RedisMessagePublisher’ın RedisTemplate
yapılandırmasını RedisConfig ile uyumlu olacak şekilde StringRedisTemplate veya
StringRedisSerializer kullanacak biçimde güncelleyin ve RedisMessageSubscriber
ile MessageListenerAdapter’ın da StringRedisSerializer üzerinden ham JSON
almasını sağlayın; RedisMessageSubscriber.handleMessage(String) mevcut
objectMapper.readValue() akışını korumalıdır.
🧹 Nitpick comments (1)
momogo-realtime/src/main/java/com/momogo/realtime/websocket/redis/RedisMessagePublisher.java (1)

33-33: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

운영 로그에서 payload 원문을 제거하세요.

Line 33과 Line 42는 전체 messagePayload를 ERROR 로그에 기록합니다. Line 39도 전체 jsonMessage를 INFO 로그에 기록합니다. 실시간 메시지가 빈번하면 로그량이 증가하고, room/member 식별자 또는 응시 상태가 노출될 수 있습니다.

topic, room ID, correlation ID처럼 안전한 메타데이터만 기록하세요. 본문이 필요하면 redaction 후 DEBUG 레벨로 기록하세요. PMD 경고를 없애기 위해 e를 제거하지 마세요. SLF4J는 마지막 Throwable을 예외 정보로 처리할 수 있습니다. (slf4j.org)

-      log.error("[Redis Publisher] JSON 직렬화 실패 - payload: {}", messagePayload, e);
+      log.error("[Redis Publisher] JSON 직렬화 실패", e);

-      log.error("[Redis Publisher] Redis 메시지 전송 실패 - payload: {}", messagePayload, e);
+      log.error("[Redis Publisher] Redis 메시지 전송 실패", e);

Also applies to: 39-39, 42-42

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@momogo-realtime/src/main/java/com/momogo/realtime/websocket/redis/RedisMessagePublisher.java`
at line 33, Update the logging in RedisMessagePublisher so ERROR and INFO
messages no longer include the full messagePayload or jsonMessage; retain only
safe metadata such as topic, room ID, or correlation ID, and keep the trailing
exception argument e in serialization error logging so SLF4J preserves
stack-trace details. If payload diagnostics are required, emit a redacted
version only at DEBUG level.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@momogo-realtime/src/main/java/com/momogo/realtime/config/RedisPubSubConfig.java`:
- Around line 43-46: RedisMessageListenerContainer의 메시지 처리와 구독 연결 유지가 동일한
executor를 공유하지 않도록 RedisPubSubConfig의 createThreadPoolTaskExecutor 및 bean 구성을
분리하세요. publish 처리용 messageTaskExecutor와 구독 유지용 subscriptionExecutor를 각각 생성·주입하고,
setTaskExecutor에는 전자를 setSubscriptionExecutor에는 후자를 지정하세요.
- Around line 55-63: Register the ThreadPoolTaskExecutor created by
createThreadPoolTaskExecutor as a Spring `@Bean` so its lifecycle is managed
during application shutdown. Update redisMessageListenerContainer(...) to accept
and use this executor bean instead of creating it only through the private
method, preserving the existing redis-listener-* configuration.
- Around line 43-45: Update RedisPubSubConfig so Redis messages for the same
room or session are processed serially instead of using the current shared
parallel taskExecutor, preserving ENTER/PROGRESS/SUBMIT/LEAVE ordering while
retaining concurrency across independent rooms or sessions. Configure the
RedisMessageListenerContainer task executor accordingly; do not change unrelated
messaging behavior.

In
`@momogo-realtime/src/main/java/com/momogo/realtime/websocket/redis/RedisMessagePublisher.java`:
- Around line 39-41: Update the publishing flow around
RedisTemplate.convertAndSend to capture its returned subscriber count, treat
null or zero as REDIS_PUBLISH_FAILED (or record an equivalent explicit failure
metric), and emit the success log only after a positive result is received.
Preserve the existing exception handling for thrown failures.

---

Outside diff comments:
In
`@momogo-realtime/src/main/java/com/momogo/realtime/websocket/redis/RedisMessagePublisher.java`:
- Around line 18-20: Redis Pub/Sub 메시ğinin tek kez JSON olarak
serileştirilmesini sağlayın. RedisMessagePublisher’ın RedisTemplate
yapılandırmasını RedisConfig ile uyumlu olacak şekilde StringRedisTemplate veya
StringRedisSerializer kullanacak biçimde güncelleyin ve RedisMessageSubscriber
ile MessageListenerAdapter’ın da StringRedisSerializer üzerinden ham JSON
almasını sağlayın; RedisMessageSubscriber.handleMessage(String) mevcut
objectMapper.readValue() akışını korumalıdır.

---

Nitpick comments:
In
`@momogo-realtime/src/main/java/com/momogo/realtime/websocket/redis/RedisMessagePublisher.java`:
- Line 33: Update the logging in RedisMessagePublisher so ERROR and INFO
messages no longer include the full messagePayload or jsonMessage; retain only
safe metadata such as topic, room ID, or correlation ID, and keep the trailing
exception argument e in serialization error logging so SLF4J preserves
stack-trace details. If payload diagnostics are required, emit a redacted
version only at DEBUG level.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1975472f-c360-4394-8752-d940366f2c86

📥 Commits

Reviewing files that changed from the base of the PR and between 6d06f9e and 273e280.

📒 Files selected for processing (6)
  • momogo-core/src/main/java/com/momogo/core/common/exception/RealtimeErrorCode.java
  • momogo-realtime/src/main/java/com/momogo/realtime/config/RedisPubSubConfig.java
  • momogo-realtime/src/main/java/com/momogo/realtime/websocket/controller/RoomRealtimeController.java
  • momogo-realtime/src/main/java/com/momogo/realtime/websocket/dto/request/RealtimeMessageRequest.java
  • momogo-realtime/src/main/java/com/momogo/realtime/websocket/redis/RedisMessagePublisher.java
  • momogo-realtime/src/main/java/com/momogo/realtime/websocket/redis/RedisMessageSubscriber.java
💤 Files with no reviewable changes (1)
  • momogo-realtime/src/main/java/com/momogo/realtime/websocket/dto/request/RealtimeMessageRequest.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • momogo-realtime/src/main/java/com/momogo/realtime/websocket/controller/RoomRealtimeController.java
  • momogo-realtime/src/main/java/com/momogo/realtime/websocket/redis/RedisMessageSubscriber.java

Comment thread momogo-realtime/src/main/java/com/momogo/realtime/config/RedisPubSubConfig.java Outdated
Comment thread momogo-realtime/src/main/java/com/momogo/realtime/config/RedisPubSubConfig.java Outdated
Comment thread momogo-realtime/src/main/java/com/momogo/realtime/config/RedisPubSubConfig.java Outdated

@jaejo jaejo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

코드 수정하느라 고생하셨습니다.

@SungHuii
SungHuii merged commit b2eb4be into develop Aug 4, 2026
2 checks 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.

3 participants