Skip to content

[release] BE#1069

Merged
lepitaaar merged 14 commits intomainfrom
develop/be
Jan 19, 2026
Merged

[release] BE#1069
lepitaaar merged 14 commits intomainfrom
develop/be

Conversation

@lepitaaar
Copy link
Contributor

@lepitaaar lepitaaar commented Jan 19, 2026

v 1.1.2

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced real-time applicant status update feature with event broadcasting support.
  • Refactor

    • Improved server-sent events endpoint configuration for applicant status tracking.
    • Optimized award information storage format.

✏️ Tip: You can customize this high-level summary in your review settings.

@lepitaaar lepitaaar self-assigned this Jan 19, 2026
@lepitaaar lepitaaar added 💾 BE Backend 📈 release 릴리즈 배포 labels Jan 19, 2026
@vercel
Copy link

vercel bot commented Jan 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
moadong Ready Ready Preview, Comment Jan 19, 2026 5:41am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 19, 2026

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Validation error: Invalid regex pattern for base branch. Received: "**" at "reviews.auto_review.base_branches[0]"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

Redis 의존성이 추가되고, 새로운 SSE 인프라가 Redis Pub/Sub과 함께 구축되었습니다. 클럽 어워드 도메인 모델에서 semester 문자열이 year 정수와 SemesterTerm 열거형으로 분리되었으며, 컨트롤러와 서비스가 중앙화된 ApplicantsStatusShareSse 서비스를 사용하도록 리팩토링되었습니다.

Changes

Cohort / File(s) 변경 요약
Redis 및 빌드 구성
backend/build.gradle, backend/src/main/java/moadong/global/config/RedisConfig.java
Spring Data Redis 의존성 추가 및 RedisTemplate<String, Object> 빈 구성 (문자열 키 직렬화, Jackson JSON 값 직렬화) 및 RedisMessageListenerContainer 빈 생성
SSE 인프라
backend/src/main/java/moadong/sse/enums/ApplicantEventType.java, backend/src/main/java/moadong/sse/dto/ApplicantSseDto.java, backend/src/main/java/moadong/sse/service/ApplicantsStatusShareSse.java
새로운 SSE 세션 관리 서비스 추가: Redis Pub/Sub 채널 수신, SseEmitter 생성/라이프사이클 관리, 상태 변경 이벤트 브로드캐스트, 클럽당 20개 세션 제한, 45초 주기 하트비트 메커니즘 포함
클럽 어워드 도메인 모델
backend/src/main/java/moadong/club/entity/ClubAward.java, backend/src/main/java/moadong/club/payload/dto/ClubAwardDto.java
semester: String 필드를 year: int (범위 1900-2050 검증) 및 semesterTerm: SemesterTerm 필드로 분리, 매핑 로직 업데이트
컨트롤러 및 서비스 통합
backend/src/main/java/moadong/club/controller/ClubApplyAdminController.java, backend/src/main/java/moadong/club/service/ClubApplyAdminService.java
기존 SSE 로직을 ApplicantsStatusShareSse로 치환: 엔드포인트 경로 변경 (/events/sse), 배치 상태 변경 이벤트를 Redis를 통해 게시, 내부 SseEmitter 관리 제거

Sequence Diagram(s)

sequenceDiagram
    participant Client as 클라이언트
    participant Controller as ClubApplyAdminController
    participant SSE as ApplicantsStatusShareSse
    participant DB as 데이터베이스
    
    Client->>Controller: GET /applicant/{applicationFormId}/sse
    Controller->>SSE: createSseSession(applicationFormId, user)
    SSE->>DB: 클럽 소유권 검증
    SSE->>SSE: SseEmitter 생성 (타임아웃 설정)
    SSE->>SSE: 클럽별 세션 제한 확인 (≤20)
    SSE-->>Controller: SseEmitter 반환
    Controller-->>Client: SSE 연결 수립
    Note over SSE: 주기적 하트비트<br/>(45초마다)
    SSE->>Client: :heartbeat
Loading
sequenceDiagram
    participant Service as ClubApplyAdminService
    participant Redis as Redis Pub/Sub
    participant SSE as ApplicantsStatusShareSse
    participant Client as SSE 클라이언트
    
    Service->>Service: 신청자 상태 변경
    Service->>Service: 배치 이벤트 큐에 추가
    Note over Service: 트랜잭션 커밋 후
    Service->>SSE: publishStatusChangeEvent(clubId, formId, event)
    SSE->>Redis: sse:applicant-status:{clubId}:{formId} 채널에 발행
    Note over Redis: Redis Pub/Sub
    Redis->>SSE: onMessage() 트리거
    SSE->>SSE: broadcastToLocalConnections()
    SSE->>Client: applicant-status-changed 이벤트 전송
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • yw6938
  • seongwon030
🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning 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.
Title check ❓ Inconclusive 제목이 너무 모호하고 일반적이어서 구체적인 변경사항을 전달하지 못함. '[release] BE'는 실제 변경 내용(Redis 통합, SSE 서비스, 데이터 구조 리팩토링)을 명확히 설명하지 않음. 더 구체적인 제목으로 변경 권장. 예: '[release] BE: Redis 통합 및 SSE 서비스 추가' 또는 '[release] v1.1.2: Redis pub/sub 및 분산 잠금 지원'
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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.

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@backend/src/main/java/moadong/club/payload/dto/ClubAwardDto.java`:
- Around line 11-13: ClubAwardDto의 semesterTerm 필드(SemesterTerm semesterTerm)가
현재 null 허용으로 인해 필수 검증이 누락될 수 있으니, 필수 값이라면 해당 필드에 `@NotNull` 애노테이션을 추가해 null 입력을
차단하고 DTO 유효성 검증에 포함되도록 수정하세요; 대상은 ClubAwardDto 클래스의 semesterTerm 필드이며, 필요한 경우
메시지를 명시하거나 검증 그룹을 적용해 일관된 에러 처리를 보장하세요.

In `@backend/src/main/java/moadong/sse/service/ApplicantsStatusShareSse.java`:
- Around line 57-65: The current removal uses
clubEmitters.keySet().iterator().next() which on a ConcurrentHashMap
(clubEmitters) yields an unpredictable key so you may evict the wrong session;
change the eviction to a FIFO strategy by tracking insertion order or
timestamps: add a secondary ordered structure (e.g., a ConcurrentLinkedQueue or
a timestamp map) to record session keys when you put into clubEmitters, and when
clubEmitters.size() >= MAX_SESSIONS_PER_CLUB remove the oldest key from that
queue/map (call it oldestKey instead of keyToRemove), complete its SseEmitter
(the SseEmitter retrieved from clubEmitters.get(oldestKey)) and remove both
entries; update the code paths that add emitters to push the key into the
ordered tracker and ensure removals also remove from the tracker to keep them in
sync.
🧹 Nitpick comments (7)
backend/src/main/java/moadong/sse/dto/ApplicantSseDto.java (1)

6-11: data 필드의 타입 안전성을 고려해 보세요.

data 필드가 Object 타입으로 선언되어 있어 타입 안전성이 낮습니다. 다양한 이벤트 타입에 따라 다른 데이터 구조를 담을 수 있는 유연성을 제공하지만, 런타임 시 캐스팅 오류나 직렬화 문제가 발생할 수 있습니다.

제네릭을 사용하거나 이벤트 타입별로 sealed interface/class를 고려해 볼 수 있습니다.

♻️ 제네릭을 사용한 대안
-@Data
-public class ApplicantSseDto {
-    private String clubId;
-    private ApplicantEventType event;
-    private Object data;
-}
+@Data
+public class ApplicantSseDto<T> {
+    private String clubId;
+    private ApplicantEventType event;
+    private T data;
+}
backend/src/main/java/moadong/club/controller/ClubApplyAdminController.java (1)

35-35: 필드명을 좀 더 명확하게 변경하는 것을 고려해 보세요.

sse라는 필드명이 너무 짧아서 코드 가독성이 떨어질 수 있습니다. applicantsStatusShareSse 또는 sseService와 같이 좀 더 설명적인 이름을 사용하면 코드의 의도가 더 명확해집니다.

backend/src/main/java/moadong/global/config/RedisConfig.java (2)

21-24: ObjectMapper 설정 개선을 고려해 보세요.

ObjectMapperJavaTimeModule만 등록되어 있습니다. 프로덕션 환경에서는 알 수 없는 속성에 대한 역직렬화 실패 방지 등 추가 설정이 도움이 될 수 있습니다.

♻️ ObjectMapper 설정 개선 제안
 ObjectMapper objectMapper = new ObjectMapper();
 objectMapper.registerModule(new JavaTimeModule());
+objectMapper.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+objectMapper.configure(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

35-40: RedisMessageListenerContainer에 TaskExecutor 설정을 고려해 보세요.

RedisMessageListenerContainer에 별도의 TaskExecutor가 설정되지 않아 기본 SimpleAsyncTaskExecutor를 사용합니다. 고부하 환경에서는 스레드 풀 기반의 TaskExecutor를 설정하면 리소스 관리가 더 효율적입니다.

♻️ TaskExecutor 설정 예시
 `@Bean`
-public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory) {
+public RedisMessageListenerContainer redisMessageListenerContainer(
+        RedisConnectionFactory connectionFactory,
+        `@Qualifier`("redisTaskExecutor") TaskExecutor taskExecutor) {
     RedisMessageListenerContainer container = new RedisMessageListenerContainer();
     container.setConnectionFactory(connectionFactory);
+    container.setTaskExecutor(taskExecutor);
     return container;
 }
+
+@Bean(name = "redisTaskExecutor")
+public TaskExecutor redisTaskExecutor() {
+    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+    executor.setCorePoolSize(4);
+    executor.setMaxPoolSize(8);
+    executor.setQueueCapacity(100);
+    executor.setThreadNamePrefix("redis-listener-");
+    executor.initialize();
+    return executor;
+}
backend/src/main/java/moadong/club/service/ClubApplyAdminService.java (2)

188-199: TransactionSynchronization 등록 시 트랜잭션 활성화 여부 확인 권장

TransactionSynchronizationManager.registerSynchronization()은 활성화된 트랜잭션이 없을 때 IllegalStateException을 발생시킬 수 있습니다. 현재 메서드가 @Transactional로 선언되어 있어 정상 동작하겠지만, 향후 코드 변경이나 테스트 시 방어적 코딩을 고려해 보세요.

♻️ 방어적 코딩 예시
-TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
+if (TransactionSynchronizationManager.isActualTransactionActive()) {
+    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
         `@Override`
         public void afterCommit() {
             events.forEach(event -> {
                 try {
                     applicantsStatusShareSse.publishStatusChangeEvent(clubId, applicationFormId, event);
                 } catch (Exception e) {
                     log.error("SSE publish failed. clubId={}, formId={}, applicantId={}", clubId, applicationFormId, event.applicantId(), e);
                 }
             });
         }
     });
+} else {
+    log.warn("No active transaction, publishing events immediately");
+    events.forEach(event -> {
+        try {
+            applicantsStatusShareSse.publishStatusChangeEvent(clubId, applicationFormId, event);
+        } catch (Exception e) {
+            log.error("SSE publish failed. clubId={}, formId={}, applicantId={}", clubId, applicationFormId, event.applicantId(), e);
+        }
+    });
+}

80-80: 긴 한 줄 코드 가독성 검토

빌더 패턴 호출이 한 줄로 압축되어 있어 가독성이 다소 떨어질 수 있습니다. 팀 컨벤션에 따라 다르지만, 가독성을 위해 여러 줄로 분리하는 것을 고려해 보세요.

backend/src/main/java/moadong/sse/service/ApplicantsStatusShareSse.java (1)

145-145: 로그 메시지 언어를 통일하세요.

이 로그 메시지만 한국어로 작성되어 있고, 다른 로그 메시지들(예: Line 81, 83, 111, 122)은 영어로 작성되어 있습니다. 일관성을 위해 언어를 통일하는 것이 좋습니다.

♻️ 영어로 통일하는 경우
-                        log.warn("SSE 이벤트 발송 실패: {}", e.getMessage());
+                        log.warn("Failed to send SSE event: {}", e.getMessage());

@lepitaaar lepitaaar merged commit 5eaebea into main Jan 19, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💾 BE Backend 📈 release 릴리즈 배포

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants