Skip to content

[FEAT] 알림 도메인 요구사항 개발#191

Merged
LimdaeIl merged 1 commit intomainfrom
feat/noti
Jan 1, 2026
Merged

[FEAT] 알림 도메인 요구사항 개발#191
LimdaeIl merged 1 commit intomainfrom
feat/noti

Conversation

@LimdaeIl
Copy link
Collaborator

@LimdaeIl LimdaeIl commented Jan 1, 2026

📝 Pull Request

📌 PR 종류

해당하는 항목에 체크해주세요.

  • 기능 추가 (Feature)
  • 버그 수정 (Fix)
  • 문서 수정 (Docs)
  • 코드 리팩터링 (Refactor)
  • 테스트 추가 (Test)
  • 기타 변경 (Chore)

✨ 변경 내용

알림 시스템을 업데이트하여 페이로드 구성에 DTO를 사용하도록 함으로써 데이터 전송을 더욱 깔끔하게 개선했습니다.

이 변경 사항은 기존의 이벤트 기반 페이로드를 클라이언트에 필요한 정보를 캡슐화하는 전용 NotificationItemResponse DTO로 대체합니다.

🔍 관련 이슈

🧪 테스트

변경된 기능에 대한 테스트 범위 또는 테스트 결과를 작성해주세요.

  • 유닛 테스트 추가 / 수정
  • 통합 테스트 검증
  • 수동 테스트 완료

🚨 확인해야 할 사항 (Checklist)

PR을 제출하기 전에 아래 항목들을 확인해주세요.

  • 코드 포매팅 완료
  • 불필요한 파일/코드 제거
  • 로직 검증 완료
  • 프로젝트 빌드 성공
  • 린트/정적 분석 통과 (해당 시)

🙋 기타 참고 사항

리뷰어가 참고하면 좋을 만한 추가 설명이 있다면 적어주세요.

Summary by CodeRabbit

Release Notes

  • New Features

    • 실시간 알림 전송 메커니즘 개선으로 더 안정적인 SSE 연결 관리 지원
  • Bug Fixes & Improvements

    • 알림 목록 응답 데이터 구조 개선으로 사용자 및 그룹 정보 포함
    • 알림 쿼리 최적화로 관련 데이터 조회 성능 향상

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

알림 시스템을 업데이트하여 페이로드 구성에 DTO를 사용하도록 함으로써 데이터 전송을 더욱 깔끔하게 개선했습니다.

이 변경 사항은 기존의 이벤트 기반 페이로드를 클라이언트에 필요한 정보를 캡슐화하는 전용 `NotificationItemResponse` DTO로 대체합니다.
Copilot AI review requested due to automatic review settings January 1, 2026 05:42
@LimdaeIl LimdaeIl added the ✨enhancement New feature or request label Jan 1, 2026
@LimdaeIl LimdaeIl moved this from Backlog to In progress in WeGo-Together Backend Jan 1, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 1, 2026

Caution

Review failed

The pull request is closed.

Walkthrough

알림 시스템의 데이터 표현을 NotificationResponse에서 NotificationItemResponse로 마이그레이션하는 변경입니다. 새로운 DTO는 사용자 및 그룹 정보를 포함하며, SSE 페이로드, 저장소 쿼리, 디스패처, 그리고 API 응답 구조를 일관되게 업데이트합니다.

Changes

코호트 / 파일 변경 사항
인증 서비스
src/main/java/.../auth/application/AuthService.java
withDraw 메서드 내 진단 주석(// 3) 마지막 유저 삭제) 제거; 제어 흐름 변화 없음
알림 도메인 모델 및 매퍼
src/main/java/.../notification/application/dto/response/NotificationItemResponse.java
src/main/java/.../notification/application/dto/response/NotificationTypeMapper.java
NotificationItemResponse 새로운 DTO 도입: 중첩된 UserSummary, GroupSummary 값 클래스 포함, QueryDSL 프로젝션 생성자 및 팩토리 메서드 제공
NotificationTypeMapper 유틸리티 클래스 추가: NotificationType을 클라이언트 친화적 문자열로 변환
저장소 계층
src/main/java/.../notification/repository/NotificationRepositoryCustom.java
src/main/java/.../notification/repository/NotificationRepositoryImpl.java
findNotificationList 반환 타입을 List<NotificationResponse>에서 List<NotificationItemResponse>로 변경
쿼리 구조 업데이트: QNotificationResponse 프로젝션 제거, Projections.constructor(NotificationItemResponse) 추가
조인 전략 변경: actor에 대한 내부 조인 → 좌측 외부 조인, groupV2에 대한 새로운 좌측 외부 조인 추가 (GROUP 타입 조건 포함)
커서 헬퍼 메서드명 ItCursorltCursor로 변경
애플리케이션 서비스
src/main/java/.../notification/application/NotificationService.java
repository.findNotificationList 결과 타입을 List<NotificationItemResponse>로 반영
import 업데이트: NotificationResponse 제거, NotificationItemResponse 추가
readNotification 메서드 내 가독성 개선 (들여쓰기, 부정 연산자 주변 공백)
SSE 에미터 및 디스패처
src/main/java/.../notification/application/SseEmitterService.java
src/main/java/.../notification/application/dispatcher/NotificationDispatcher.java
SseEmitterService에 새로운 오버로드 메서드 추가: sendNotificationIfConnected(Long userId, Object payload) — 사용자별 에미터 조회, "notification" 이벤트 전송, 실패 시 에미터 제거
NotificationDispatcher.dispatch 메서드 내 SSE 페이로드 타입을 NotificationEventNotificationItemResponse로 변경
API 응답 구조
src/main/java/.../notification/application/dto/response/NotificationListResponse.java
NotificationListResponse 레코드의 notifications 필드 타입을 List<NotificationResponse>List<NotificationItemResponse>로 변경

Sequence Diagram

sequenceDiagram
    participant Client
    participant NotificationDispatcher
    participant SseEmitterService
    participant NotificationRepository
    participant Database
    participant NotificationService

    rect rgba(240, 248, 255, 0.3)
    Note over NotificationDispatcher,Database: 기존: NotificationEvent 페이로드
    NotificationDispatcher->>NotificationRepository: 알림 조회
    NotificationRepository->>Database: findNotificationList()
    Database-->>NotificationRepository: 알림 데이터 반환
    NotificationRepository-->>NotificationDispatcher: NotificationResponse 목록
    NotificationDispatcher->>SseEmitterService: sendNotificationIfConnected(userId, NotificationEvent)
    SseEmitterService->>Client: "notification" 이벤트 전송
    end

    rect rgba(152, 251, 152, 0.3)
    Note over NotificationDispatcher,Database: 변경: NotificationItemResponse 페이로드
    NotificationDispatcher->>NotificationRepository: 알림 + 그룹 정보 조회
    NotificationRepository->>Database: findNotificationList() (그룹 조인 포함)
    Database-->>NotificationRepository: 통합 알림 데이터 반환
    NotificationRepository-->>NotificationDispatcher: NotificationItemResponse 목록
    NotificationDispatcher->>NotificationDispatcher: NotificationItemResponse.of() 생성
    NotificationDispatcher->>SseEmitterService: sendNotificationIfConnected(userId, NotificationItemResponse)
    SseEmitterService->>Client: "notification" 이벤트 전송<br/>(사용자 및 그룹 정보 포함)
    end

    rect rgba(255, 228, 225, 0.3)
    Note over NotificationService,Client: 목록 조회 응답
    Client->>NotificationService: 알림 목록 요청
    NotificationService->>NotificationRepository: findNotificationList()
    NotificationRepository->>Database: 쿼리 실행
    Database-->>NotificationRepository: NotificationItemResponse 목록
    NotificationRepository-->>NotificationService: 결과 반환
    NotificationService->>Client: NotificationListResponse<br/>(List<NotificationItemResponse> 포함)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 토끼가 알리노니, 알림이 새로워졌네!
사용자와 그룹 정보 함께 담고,
SSE의 메시지 이제 풍성하구나.
좌측 외부 조인으로 어떤 알림도 놓치지 않고,
클라이언트는 모든 정보를 한 번에 받네! 📬✨

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a662cb9 and 1d9f10d.

📒 Files selected for processing (9)
  • src/main/java/team/wego/wegobackend/auth/application/AuthService.java
  • src/main/java/team/wego/wegobackend/notification/application/NotificationService.java
  • src/main/java/team/wego/wegobackend/notification/application/SseEmitterService.java
  • src/main/java/team/wego/wegobackend/notification/application/dispatcher/NotificationDispatcher.java
  • src/main/java/team/wego/wegobackend/notification/application/dto/response/NotificationItemResponse.java
  • src/main/java/team/wego/wegobackend/notification/application/dto/response/NotificationListResponse.java
  • src/main/java/team/wego/wegobackend/notification/application/dto/response/NotificationTypeMapper.java
  • src/main/java/team/wego/wegobackend/notification/repository/NotificationRepositoryCustom.java
  • src/main/java/team/wego/wegobackend/notification/repository/NotificationRepositoryImpl.java

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.

@LimdaeIl LimdaeIl merged commit bc871a0 into main Jan 1, 2026
4 of 5 checks passed
@LimdaeIl LimdaeIl deleted the feat/noti branch January 1, 2026 05:43
@github-project-automation github-project-automation bot moved this from In progress to Done in WeGo-Together Backend Jan 1, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors the notification system to use a dedicated NotificationItemResponse DTO instead of the event-based payload approach, improving data encapsulation and consistency for client responses. The new DTO provides a cleaner structure with nested UserSummary and GroupSummary objects and maps internal notification types to client-facing string values.

Key Changes:

  • Introduced NotificationItemResponse DTO with nested summary objects for user and group data
  • Added NotificationTypeMapper to convert enum values to client-friendly string representations
  • Updated repository query to use QueryDSL projections and join group data when notifications are group-related

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
NotificationItemResponse.java New DTO with QueryDSL projection constructor and factory method to encapsulate notification data with nested user/group summaries
NotificationTypeMapper.java New mapper utility to convert NotificationType enum to client-facing string values (e.g., "group-join")
NotificationRepositoryImpl.java Updated query to project directly into NotificationItemResponse, added left join for group data, renamed method for clarity
NotificationRepositoryCustom.java Changed return type from NotificationResponse to NotificationItemResponse
NotificationListResponse.java Updated to use NotificationItemResponse list instead of NotificationResponse
NotificationDispatcher.java Modified to create and send NotificationItemResponse payload via SSE instead of NotificationEvent
SseEmitterService.java Added generic sendNotificationIfConnected(Long, Object) overload to support different payload types
NotificationService.java Updated to work with NotificationItemResponse instead of NotificationResponse
AuthService.java Removed outdated comment about user deletion order

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

.from(notification)
.leftJoin(notification.actor, user)
.leftJoin(groupV2).on(
notification.relatedType.eq("GROUP")
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

The hardcoded string "GROUP" in the query condition should be extracted as a constant or enum value. This magic string creates a tight coupling with the database schema and makes the code harder to maintain. Consider defining this as a constant in a shared location or using an enum.

Copilot uses AI. Check for mistakes.
Comment on lines +78 to +89
public boolean sendNotificationIfConnected(Long userId, Object payload) {
SseEmitter emitter = emitters.get(userId);
if (emitter == null) return false;

try {
emitter.send(SseEmitter.event().name("notification").data(payload));
return true;
} catch (IOException e) {
emitters.remove(userId);
return false;
}
}
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

The new generic sendNotificationIfConnected(Long userId, Object payload) method lacks proper logging compared to the existing NotificationEvent overload. The existing method logs debug information about the notification ID and provides more detailed logging on send failures. Consider adding similar logging to maintain consistent observability across both methods.

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +51
NotificationItemResponse payload = NotificationItemResponse.of(n, actor, group);

boolean ok = sseEmitterService.sendNotificationIfConnected(receiverId, payload);
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

This PR introduces inconsistency in how notifications are sent via SSE. The NotificationDispatcher now uses NotificationItemResponse.of(n, actor, group) while other notification handlers (e.g., GroupJoinNotificationHandler, GroupLeaveNotificationHandler, GroupKickNotificationHandler) still use NotificationEvent.of(saved, actor, group). This creates a situation where the same notification system sends differently structured payloads depending on the code path. All handlers should be updated to use the same DTO approach for consistency.

Copilot uses AI. Check for mistakes.

public record NotificationListResponse(List<NotificationResponse> notifications, Long nextCursor) {
public record NotificationListResponse(
// List<NotificationResponse> notifications,
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

The commented-out code should be removed. Instead of keeping List<NotificationResponse> notifications, as a comment, it should be deleted entirely to maintain code cleanliness.

Suggested change
// List<NotificationResponse> notifications,

Copilot uses AI. Check for mistakes.
NotificationType.GROUP_JOIN_REQUEST, "group-join-request",
NotificationType.GROUP_JOIN_APPROVED, "group-join-approved",
NotificationType.GROUP_JOIN_REJECTED, "group-join-rejected",
NotificationType.GROUP_JOIN_KICKED, "group-join-kicked"
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

The NotificationTypeMapper does not include a mapping for NotificationType.TEST. While the mapper falls back to type.name().toLowerCase() for unmapped types, it's better to be explicit. Either add NotificationType.TEST to the map or document why it's intentionally excluded.

Suggested change
NotificationType.GROUP_JOIN_KICKED, "group-join-kicked"
NotificationType.GROUP_JOIN_KICKED, "group-join-kicked",
NotificationType.TEST, "test"

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +48
.leftJoin(groupV2).on(
notification.relatedType.eq("GROUP")
.and(notification.relatedId.eq(groupV2.id))
)
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

The leftJoin on groupV2 only joins when relatedType equals "GROUP". However, if there are other notification types that should also include group information (or if the condition doesn't match), the group fields will be null. This could lead to unexpected null values in the response. Consider documenting this behavior or ensuring that all group-related notification types properly set relatedType to "GROUP".

Copilot uses AI. Check for mistakes.
LocalDateTime createdAt
) {
this.id = id;
this.user = (actorId == null ? null : new UserSummary(actorId, actorNickname));
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

The constructor creates UserSummary with potentially null actorNickname when actorId is not null. This could happen if the database contains an actor with a null nickname. The constructor should either validate that both values are non-null together, or handle the case where actorId exists but actorNickname is null.

Suggested change
this.user = (actorId == null ? null : new UserSummary(actorId, actorNickname));
this.user = (actorId == null || actorNickname == null
? null
: new UserSummary(actorId, actorNickname));

Copilot uses AI. Check for mistakes.
LocalDateTime createdAt
) {
this.id = id;
this.user = (actorId == null ? null : new UserSummary(actorId, actorNickname));
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

The constructor creates GroupSummary with potentially null groupTitle when groupId is not null. This could happen if the database contains a group with a null title. The constructor should either validate that both values are non-null together, or handle the case where groupId exists but groupTitle is null.

Suggested change
this.user = (actorId == null ? null : new UserSummary(actorId, actorNickname));
this.user = (actorId == null ? null : new UserSummary(actorId, actorNickname));
if (groupId != null && groupTitle == null) {
throw new IllegalArgumentException("groupTitle must not be null when groupId is not null");
}

Copilot uses AI. Check for mistakes.

import java.util.List;
import team.wego.wegobackend.notification.application.dto.response.NotificationItemResponse;
import team.wego.wegobackend.notification.application.dto.response.NotificationResponse;
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

The unused import NotificationResponse should be removed as the interface now returns NotificationItemResponse instead.

Suggested change
import team.wego.wegobackend.notification.application.dto.response.NotificationResponse;

Copilot uses AI. Check for mistakes.
}
}

public boolean sendNotificationIfConnected(Long userId, Object payload) {
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

Method SseEmitterService.sendNotificationIfConnected(..) could be confused with overloaded method sendNotificationIfConnected, since dispatch depends on static types.

Suggested change
public boolean sendNotificationIfConnected(Long userId, Object payload) {
public boolean sendPayloadIfConnected(Long userId, Object payload) {

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨enhancement New feature or request

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[FEAT] 알림 도메인 요구사항 개발

2 participants