[feat] 지원자 상태 변경 SSE 실시간 알림 기능 구현 (Redis Pub/Sub)#1061
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning
|
| Cohort / File(s) | 변경 사항 요약 |
|---|---|
빌드/Redis 설정 backend/build.gradle, backend/src/main/java/moadong/global/config/RedisConfig.java |
Redis 스타터 의존성 추가 및 RedisTemplate<String,Object>와 RedisMessageListenerContainer 빈 등록(값 직렬화에 Jackson+JavaTimeModule 사용) |
SSE 공유 서비스 backend/src/main/java/moadong/sse/service/ApplicantsStatusShareSse.java |
Redis pub/sub 구독·발행 기반의 SSE 세션 관리 컴포넌트 추가(세션 제한·회수, 초기 연결 이벤트, 전송 실패 처리), 45초 하트비트 스케줄러, Redis 메시지 리스너 구현 |
SSE DTO/타입 backend/src/main/java/moadong/sse/dto/ApplicantSseDto.java, backend/src/main/java/moadong/sse/enums/ApplicantEventType.java |
SSE 페이로드 DTO(clubId, event, data) 및 이벤트 타입 열거 추가 |
서비스 계층 변경 backend/src/main/java/moadong/club/service/ClubApplyAdminService.java |
로컬 SseEmitter 맵/관리 제거, 이벤트 수집 후 트랜잭션 커밋 시 ApplicantsStatusShareSse로 배치 발행하도록 리팩토링(관련 메서드/필드 제거 및 의존성 교체) |
컨트롤러 변경 backend/src/main/java/moadong/club/controller/ClubApplyAdminController.java |
ApplicantsStatusShareSse 의존성 주입 및 SSE 엔드포인트 경로 변경(/events → /sse), 세션 생성 로직 위임 |
Sequence Diagram(s)
sequenceDiagram
participant Client as Client
participant Controller as ClubApplyAdminController
participant Service as ClubApplyAdminService
participant SSE as ApplicantsStatusShareSse
participant Redis as Redis Pub/Sub
participant Listener as RedisMessageListener
Client->>Controller: SSE 연결 요청 (applicationFormId)
Controller->>SSE: createSseSession(applicationFormId)
SSE->>Client: SseEmitter 반환 + "connected" 이벤트
Note over SSE: 하트비트 스케줄러 (rgba(0,128,0,0.5)) 45초 주기 ping
Client->>Controller: 지원자 상태 변경 요청
Controller->>Service: editApplicantDetail(...)
Service->>Service: 이벤트 수집(List)
Service->>Service: 트랜잭션 커밋
Service->>SSE: publishStatusChangeEvent(clubId, applicationFormId, event)
SSE->>Redis: 채널 발행 ("sse:applicant-status:clubId:formId")
Redis->>Listener: 메시지 전달
Listener->>SSE: onMessage(...)
SSE->>SSE: 로컬 에미터 조회 및 브로드캐스트
SSE->>Client: 이벤트 전송
Estimated code review effort
🎯 4 (Complex) | ⏱️ ~60 minutes
Possibly related issues
- [refactor] MOA-484 지원자의 상태를 공유하는 SSE api 리팩토링 #1007: SSE 하트비트 및 다중 사용자 키 처리 목표와 직접 연관 — ApplicantsStatusShareSse로 해당 목표들을 구현함.
Possibly related PRs
- PR
#895: 동일 컨트롤러의 SSE 엔드포인트 및 세션 처리 변경과 관련. - PR
#791: 기존 SseEmitter 기반 구현(생성·맵·전송)을 다루던 PR로, 이번 PR의 리팩토링 대상과 코드 중복 영역이 있음. - PR
#847: ClubApplyAdminService/Controller의 SSE 관련 로직 변경과 겹치는 항목 존재.
Suggested labels
✨ Feature, 💾 BE, 📬 API
Suggested reviewers
- Zepelown
- yw6938
- PororoAndFriends
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Docstring Coverage | Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. |
✅ Passed checks (4 passed)
| Check name | Status | Explanation |
|---|---|---|
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
| Title check | ✅ Passed | 제목은 Redis Pub/Sub을 활용한 지원자 상태 변경 SSE 실시간 알림 기능 구현이라는 주요 변경사항을 명확하게 요약하고 있습니다. |
| Linked Issues check | ✅ Passed | 변경사항이 MOA-484의 모든 요구사항을 충족합니다: SSE 하트비트 구현(sendHeartBeat 메서드), SSE 다중 사용자 지원(클럽당 최대 20개 세션), 분산 환경 지원(Redis Pub/Sub 기반 이벤트 전파). |
| Out of Scope Changes check | ✅ Passed | 모든 변경사항이 SSE 실시간 알림 기능 구현과 Redis Pub/Sub 통합이라는 명확한 범위 내에 있으며, 범위를 벗어난 변경사항은 없습니다. |
✏️ Tip: You can configure your own custom pre-merge checks in the settings.
✨ Finishing touches
- 📝 Generate docstrings
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.
Comment @coderabbitai help to get the list of available commands and usage tips.
Test Results75 tests 72 ✅ 17s ⏱️ Results for commit 55536a6. ♻️ This comment has been updated with latest results. |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@backend/src/main/java/moadong/club/service/ClubApplyAdminService.java`:
- Around line 200-205: The afterCommit block calls
applicantsStatusShareSse.publishStatusChangeEvent for each event but does not
handle exceptions, so any publish failure can make the API appear to fail or
stop further propagation; modify the
TransactionSynchronizationManager.registerSynchronization(...) /
TransactionSynchronization.afterCommit implementation so that the events.forEach
loop wraps each call to
applicantsStatusShareSse.publishStatusChangeEvent(clubId, applicationFormId,
event) in a try/catch that logs the exception (including context: clubId,
applicationFormId, event) and does not rethrow, ensuring one failing publish
does not abort others and that failures are recorded for monitoring/alerting.
In `@backend/src/main/java/moadong/global/config/RedisConfig.java`:
- Around line 16-31: The redisTemplate bean in RedisConfig currently constructs
a new ObjectMapper causing mismatch with Spring's global ObjectMapper; change
redisTemplate(RedisConnectionFactory) to accept the application ObjectMapper
(e.g., add ObjectMapper objectMapper parameter), call objectMapper.copy() to
create a mutable copy, register JavaTimeModule on the copy, and use that copied
mapper to instantiate GenericJackson2JsonRedisSerializer so Redis key/value/hash
serializers use the same configuration as the rest of the app (avoiding
deserialization issues in classes like ApplicantsStatusShareSse).
🧹 Nitpick comments (3)
backend/src/main/java/moadong/sse/service/ApplicantsStatusShareSse.java (1)
103-119: Redis 직렬화 설정과 동일한 방식으로 역직렬화하도록 맞춰주세요.
현재 RedisTemplate의 serializer와 ObjectMapper 설정이 다르면 필드 매핑 실패가 날 수 있습니다. Serializer로 역직렬화 후 타입 확인을 권장합니다.♻️ 제안 수정
- ApplicantStatusEvent event = objectMapper.readValue(message.getBody(), ApplicantStatusEvent.class); - broadcastToLocalConnections(clubId, applicationFormId, event); + Object value = redisTemplate.getValueSerializer().deserialize(message.getBody()); + if (!(value instanceof ApplicantStatusEvent event)) { + log.warn("Unexpected payload type on channel {}: {}", channel, value); + return; + } + broadcastToLocalConnections(clubId, applicationFormId, event);backend/src/main/java/moadong/sse/dto/ApplicantSseDto.java (1)
6-10:data필드를 구체적인 타입으로 좁혀서 타입 안정성을 개선하세요.현재
Object타입은 런타임 캐스팅 오류와 직렬화 이슈의 위험이 있습니다. 실제 SSE 구현에서 사용 중인ApplicantStatusEvent처럼 이벤트별 구체적인 DTO 또는 제네릭 타입으로 정의하는 것을 권장합니다.backend/src/main/java/moadong/club/service/ClubApplyAdminService.java (1)
185-197: 상태 변경이 없을 때도 이벤트가 발행됩니다 — 요구사항 확인 필요.현재는 memo만 수정되어도
ApplicantStatusEvent가 생성됩니다. “상태 변경 알림”이 요구사항이면 실제 상태 변경 시에만 이벤트를 발행하도록 필터링하는 편이 안전합니다.♻️ 변경 제안
application.forEach(app -> { + var prevStatus = app.getStatus(); ClubApplicantEditRequest editRequest = requestMap.get(app.getId()); app.updateMemo(editRequest.memo()); app.updateStatus(editRequest.status()); - events.add(new ApplicantStatusEvent( - app.getId(), - editRequest.status(), - editRequest.memo(), - ZonedDateTime.now(ZoneId.of("Asia/Seoul")).toLocalDateTime(), - clubId, - applicationFormId - )); + if (prevStatus != editRequest.status()) { + events.add(new ApplicantStatusEvent( + app.getId(), + editRequest.status(), + editRequest.memo(), + ZonedDateTime.now(ZoneId.of("Asia/Seoul")).toLocalDateTime(), + clubId, + applicationFormId + )); + } });
seongwon030
left a comment
There was a problem hiding this comment.
서버를 여러개로 분리하면서 레디스를 도입하셨군요
확장성을 고려해보면 좋을 것 같네요
#️⃣연관된 이슈
#1007
📝작업 내용
동아리 지원자 상태 변경 SSE 실시간 알림 기능 구현
지원자 상태가 변경될 때 클라이언트에게 SSE(Server-Sent Events)를 통해 실시간으로 알림을 전송합니다.
주요 기능:
Redis Pub/Sub 도입 이유:
SSE 연결은 각 Pod의 인메모리(
ConcurrentHashMap)에 저장됩니다. Kubernetes 다중 replica 환경에서는:Redis Pub/Sub로 해결:
변경 파일:
JavaTimeModule) 설정/applicant/{applicationFormId}/sse)중점적으로 리뷰받고 싶은 부분(선택)
논의하고 싶은 부분(선택)
🫡 참고사항
PatternTopic사용 (와일드카드 구독을 위해ChannelTopic대신)GenericJackson2JsonRedisSerializer에JavaTimeModule등록 필요 (LocalDateTime직렬화)EventSource사용 시 자동 재연결 지원Summary by CodeRabbit
신규 기능
개선 사항
✏️ Tip: You can customize this high-level summary in your review settings.