Skip to content

[feat] 진행중인 방 상세보기 api 개발#75

Merged
buzz0331 merged 8 commits intodevelopfrom
feat/#74-room-playing-detail
Jul 15, 2025
Merged

[feat] 진행중인 방 상세보기 api 개발#75
buzz0331 merged 8 commits intodevelopfrom
feat/#74-room-playing-detail

Conversation

@seongjunnoh
Copy link
Collaborator

@seongjunnoh seongjunnoh commented Jul 14, 2025

#️⃣ 연관된 이슈

closes #74

📝 작업 내용

진행중인 방 상세보기 api를 개발하였습니다

  • api 주요 로직

    1. Room 과 연관된 UserRoom list 조회 -> 일급 컬렉션 생성 후 필요한 로직 수행 (일급 컬렉션 내 메서드 추가)
    2. 현재 모임방의 투표 중 참여율이 높은 투표 최대 3개 반환 -> QueryDSL 활용
  • api 통합 테스트 코드, 일급 컬렉션에 추가한 메서드의 단위 테스트 코드 추가하였습니다

📸 스크린샷

💬 리뷰 요구사항

📌 PR 진행 시 이러한 점들을 참고해 주세요

* P1 : 꼭 반영해 주세요 (Request Changes) - 이슈가 발생하거나 취약점이 발견되는 케이스 등
* P2 : 반영을 적극적으로 고려해 주시면 좋을 것 같아요 (Comment)
* P3 : 이런 방법도 있을 것 같아요~ 등의 사소한 의견입니다 (Chore)

Summary by CodeRabbit

  • 신규 기능

    • 진행 중인 모임방의 상세 정보를 조회할 수 있는 API가 추가되었습니다. 해당 API는 방 정보, 책 정보, 참여자 현황, 사용자의 현재 페이지 및 읽기 비율, 그리고 참여율이 높은 투표 목록(최대 3개)을 제공합니다.
    • 모임방에 속하지 않은 사용자에 대한 새로운 오류 코드가 추가되어, 상세 조회 시 적절한 오류 메시지를 제공합니다.
  • 버그 수정

    • 모임방에 속하지 않은 사용자가 상세 정보를 조회할 경우, 적절한 오류 메시지와 함께 요청이 거부됩니다.
  • 테스트

    • 진행 중인 모임방 상세 조회 API 및 관련 도메인 메서드에 대한 통합 및 단위 테스트가 추가되었습니다. 다양한 사용자 상황(방장, 일반 멤버, 미가입자 등)과 투표 데이터 조건에 대한 검증이 포함되어 있습니다.

@seongjunnoh seongjunnoh linked an issue Jul 14, 2025 that may be closed by this pull request
2 tasks
@coderabbitai
Copy link

coderabbitai bot commented Jul 14, 2025

"""

Walkthrough

진행중인 방 상세보기 API가 새롭게 추가되었습니다. 이를 위해 컨트롤러, 서비스, 포트, 어댑터, 응답 DTO, 도메인 메서드, 투표 쿼리 로직, 예외 코드 등이 확장 및 신설되었으며, 통합 테스트와 도메인 테스트도 보강되었습니다.

Changes

파일/그룹 변경 요약
.../exception/code/ErrorCode.java USER_NOT_BELONG_TO_ROOM 에러 코드 추가
.../room/adapter/in/web/RoomQueryController.java 진행중인 방 상세보기 엔드포인트 및 의존성 추가
.../room/adapter/in/web/response/RoomPlayingDetailViewResponse.java 진행중인 방 상세보기 응답 DTO 및 중첩 레코드 신설
.../room/application/port/in/RoomShowPlayingDetailViewUseCase.java 진행중인 방 상세보기 유스케이스 인터페이스 신설
.../room/application/service/RoomShowPlayingDetailViewService.java 진행중인 방 상세보기 서비스 구현 및 응답 빌더 추가
.../user/domain/RoomParticipants.java 사용자별 현재 페이지/퍼센트 조회 메서드 2개 추가
.../vote/adapter/out/persistence/VoteQueryPersistenceAdapter.java
.../vote/adapter/out/persistence/VoteQueryRepository.java
.../vote/adapter/out/persistence/VoteQueryRepositoryImpl.java
.../vote/application/port/out/VoteQueryPort.java
방 내 참여율 상위 투표 조회 메서드 일괄 추가 및 구현
.../room/adapter/in/web/RoomPlayingDetailViewApiTest.java 진행중인 방 상세보기 API 통합 테스트 신설 (정상/예외/경계 케이스 포함)
.../user/domain/RoomParticipantsTest.java 사용자별 현재 페이지/퍼센트 조회 관련 단위 테스트 추가

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller as RoomQueryController
    participant Service as RoomShowPlayingDetailViewService
    participant RoomRepo
    participant BookRepo
    participant UserRoomRepo
    participant VotePort

    Client->>Controller: GET /rooms/{roomId}/playing?userId={userId}
    Controller->>Service: getPlayingRoomDetailView(userId, roomId)
    Service->>RoomRepo: findById(roomId)
    Service->>BookRepo: findById(room.bookId)
    Service->>UserRoomRepo: findAllByRoom(room)
    Service->>VotePort: findTopParticipationVotesByRoom(room, 3)
    Service-->>Controller: RoomPlayingDetailViewResponse
    Controller-->>Client: BaseResponse<RoomPlayingDetailViewResponse>
Loading

Assessment against linked issues

Objective Addressed Explanation
진행중인 방 상세보기 API 개발 (#74)
API 정상/예외/경계 케이스 테스트 추가 (#74)
도메인 및 응답 DTO 확장 (#74)

Suggested labels

🛠️ feat, 👻 성준

Suggested reviewers

  • seongjunnoh

Poem

방의 비밀을 살짝 열어
투표와 책장, 모두 다 보여
토끼는 깡총, 코드를 검토
예외도 꼼꼼, 테스트도 완벽
오늘도 방 상세, 한눈에 쏙!
🐇✨
"""


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d1a1ec6 and 659adec.

📒 Files selected for processing (2)
  • src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (1 hunks)
  • src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/konkuk/thip/common/exception/code/ErrorCode.java
  • src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
✨ 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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • 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

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@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

🧹 Nitpick comments (6)
src/main/java/konkuk/thip/user/domain/RoomParticipants.java (2)

40-46: 코드 중복 가능성과 성능 최적화 검토

getCurrentPageOfUser 메서드의 구현이 올바르지만, getUserPercentageOfUser와 매우 유사한 패턴을 보입니다.

두 메서드 모두 동일한 필터링 로직을 사용하므로, 공통 헬퍼 메서드로 리팩토링을 고려해볼 수 있습니다:

+    private UserRoom findUserRoomByUserId(Long userId) {
+        return participants.stream()
+                .filter(userRoom -> userRoom.getUserId().equals(userId))
+                .findFirst()
+                .orElseThrow(() -> new InvalidStateException(USER_NOT_BELONG_TO_ROOM));
+    }
+
     public int getCurrentPageOfUser(Long userId) {
-        return participants.stream()
-                .filter(userRoom -> userRoom.getUserId().equals(userId))
-                .map(UserRoom::getCurrentPage)
-                .findFirst()
-                .orElseThrow(() -> new InvalidStateException(USER_NOT_BELONG_TO_ROOM));
+        return findUserRoomByUserId(userId).getCurrentPage();
     }

48-54: 동일한 패턴의 메서드 구현 확인

getUserPercentageOfUser 메서드도 위의 메서드와 동일한 패턴으로 구현되어 있습니다. 로직은 정확하지만 위에서 제안한 공통 헬퍼 메서드를 사용하면 코드 중복을 줄일 수 있습니다.

src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java (1)

5-5: 새로운 응답 DTO와 와일드카드 import 추가

새로운 응답 DTO가 추가되고 import가 와일드카드로 변경되었습니다. 다만 와일드카드 import는 코드 가독성을 해칠 수 있습니다.

가독성을 위해 구체적인 import를 유지하는 것을 고려해보세요:

-import konkuk.thip.room.application.port.in.*;
+import konkuk.thip.room.application.port.in.RoomSearchUseCase;
+import konkuk.thip.room.application.port.in.RoomVerifyPasswordUseCase;
+import konkuk.thip.room.application.port.in.RoomShowRecruitingDetailViewUseCase;
+import konkuk.thip.room.application.port.in.RoomGetHomeJoinedListUseCase;
+import konkuk.thip.room.application.port.in.RoomShowPlayingDetailViewUseCase;

Also applies to: 9-9

src/main/java/konkuk/thip/room/application/service/RoomShowPlayingDetailViewService.java (1)

40-41: 변수명을 더 명확하게 개선하세요.

findByRoomId보다는 userRooms 또는 userRoomList와 같은 더 서술적인 변수명을 사용하는 것이 좋습니다.

-List<UserRoom> findByRoomId = userRoomCommandPort.findAllByRoomId(roomId);
-RoomParticipants roomParticipants = RoomParticipants.from(findByRoomId);
+List<UserRoom> userRooms = userRoomCommandPort.findAllByRoomId(roomId);
+RoomParticipants roomParticipants = RoomParticipants.from(userRooms);
src/test/java/konkuk/thip/room/adapter/in/web/RoomPlayingDetailViewApiTest.java (2)

36-36: 사용하지 않는 import를 제거하세요.

assertThrows는 import되었지만 실제로 사용되지 않습니다.

-import static org.junit.jupiter.api.Assertions.assertThrows;

284-286: 테스트 의도를 명확히 하기 위해 주석을 추가하세요.

하드코딩된 userId가 존재하지 않는 사용자임을 명시하면 테스트 의도가 더 명확해집니다.

 //when //then
 mockMvc.perform(get("/rooms/{roomId}/playing", room.getRoomId())
-                .requestAttr("userId", 1000L))      // 방에 속하지 않는 유저
+                .requestAttr("userId", 1000L))      // 존재하지 않는 userId로 방에 속하지 않는 유저 시뮬레이션
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 78e0066 and d1a1ec6.

📒 Files selected for processing (12)
  • src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (1 hunks)
  • src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java (4 hunks)
  • src/main/java/konkuk/thip/room/adapter/in/web/response/RoomPlayingDetailViewResponse.java (1 hunks)
  • src/main/java/konkuk/thip/room/application/port/in/RoomShowPlayingDetailViewUseCase.java (1 hunks)
  • src/main/java/konkuk/thip/room/application/service/RoomShowPlayingDetailViewService.java (1 hunks)
  • src/main/java/konkuk/thip/user/domain/RoomParticipants.java (2 hunks)
  • src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteQueryPersistenceAdapter.java (2 hunks)
  • src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteQueryRepository.java (1 hunks)
  • src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteQueryRepositoryImpl.java (3 hunks)
  • src/main/java/konkuk/thip/vote/application/port/out/VoteQueryPort.java (1 hunks)
  • src/test/java/konkuk/thip/room/adapter/in/web/RoomPlayingDetailViewApiTest.java (1 hunks)
  • src/test/java/konkuk/thip/user/domain/RoomParticipantsTest.java (2 hunks)
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/test/java/konkuk/thip/user/domain/RoomParticipantsTest.java (1)
Learnt from: hd0rable
PR: THIP-TextHip/THIP-Server#57
File: src/test/java/konkuk/thip/room/domain/RoomTest.java:0-0
Timestamp: 2025-07-08T16:30:33.771Z
Learning: Room 도메인에서 startDate는 현재 날짜 이후여야 하는 도메인 규칙이 있어서, 테스트에서 만료된 상태를 시뮬레이션하려면 reflection을 사용해야 한다.
src/main/java/konkuk/thip/vote/application/port/out/VoteQueryPort.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java (1)
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
src/test/java/konkuk/thip/room/adapter/in/web/RoomPlayingDetailViewApiTest.java (1)
Learnt from: hd0rable
PR: THIP-TextHip/THIP-Server#57
File: src/test/java/konkuk/thip/room/domain/RoomTest.java:0-0
Timestamp: 2025-07-08T16:30:33.771Z
Learning: Room 도메인에서 startDate는 현재 날짜 이후여야 하는 도메인 규칙이 있어서, 테스트에서 만료된 상태를 시뮬레이션하려면 reflection을 사용해야 한다.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (22)
src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (1)

101-101: 새로운 에러 코드 추가가 적절합니다.

USER_NOT_BELONG_TO_ROOM 에러 코드가 기존 컨벤션을 잘 따르고 있습니다. userRoom 섹션의 순차적 번호 체계(140001)와 명확한 한국어 메시지가 적절합니다.

src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteQueryRepository.java (1)

12-12: 메서드명과 매개변수가 명확합니다.

findTopParticipationVotesByRoom 메서드명이 기능을 잘 표현하고 있고, count 매개변수로 제한할 수 있어 좋습니다.

src/main/java/konkuk/thip/vote/application/port/out/VoteQueryPort.java (2)

3-6: CQRS 패턴을 올바르게 따르고 있습니다.

학습된 CQRS 컨벤션에 따라 QueryPort에 조회 API 응답 데이터를 반환하는 메서드를 추가한 것이 적절합니다.


12-12: 도메인 객체 사용이 좋습니다.

매개변수로 Room 도메인 객체를 사용한 것이 원시 타입보다 의미가 명확하고 좋습니다. 다만 웹 레이어 DTO 반환에 대한 의존성 검토는 여전히 필요합니다.

src/main/java/konkuk/thip/room/application/port/in/RoomShowPlayingDetailViewUseCase.java (1)

1-8: 클린 아키텍처 원칙을 잘 따른 유스케이스 인터페이스입니다.

단일 책임을 가진 명확한 인터페이스로, 메서드명과 매개변수가 직관적입니다. 클린 아키텍처의 유스케이스 레이어 설계가 적절합니다.

src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteQueryPersistenceAdapter.java (2)

3-4: 적절한 import 추가입니다.

Room 도메인 객체와 응답 DTO import가 메서드 구현에 필요한 최소한의 의존성만 추가되었습니다.


25-28: 어댑터 패턴을 올바르게 구현했습니다.

도메인 객체에서 ID를 추출하여 JPA 레포지토리로 위임하는 간단하고 명확한 구현입니다. 어댑터의 책임을 적절히 수행하고 있습니다.

src/test/java/konkuk/thip/user/domain/RoomParticipantsTest.java (5)

3-3: 적절한 예외 처리 테스트를 위한 import 추가

새로 추가된 메서드들의 예외 처리 테스트를 위해 InvalidStateException import가 적절히 추가되었습니다.


66-78: 정상 케이스 테스트 로직 확인

getCurrentPageOfUser 메서드의 정상 동작을 확인하는 테스트입니다. createUserRoom에서 currentPage를 0으로 설정하고 이를 검증하는 로직이 정확합니다.


80-89: 예외 케이스 테스트 로직 확인

존재하지 않는 userId(999L)로 호출 시 InvalidStateException이 발생하는지 확인하는 테스트입니다. 예외 처리가 올바르게 테스트되고 있습니다.


91-103: 정상 케이스 테스트 로직 확인

getUserPercentageOfUser 메서드의 정상 동작을 확인하는 테스트입니다. createUserRoom에서 userPercentage를 0.0으로 설정하고 이를 검증하는 로직이 정확합니다.


105-114: 예외 케이스 테스트 로직 확인

존재하지 않는 userId로 호출 시 InvalidStateException이 발생하는지 확인하는 테스트입니다. 두 메서드 모두 동일한 예외 처리 로직을 가지고 있어 일관성 있게 테스트되었습니다.

src/main/java/konkuk/thip/user/domain/RoomParticipants.java (1)

3-3: 예외 처리를 위한 적절한 import 추가

새로 추가된 메서드들의 예외 처리를 위해 InvalidStateExceptionUSER_NOT_BELONG_TO_ROOM 에러 코드가 적절히 import되었습니다.

Also applies to: 10-10

src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteQueryRepositoryImpl.java (2)

5-5: 응답 DTO import 추가

새로운 메서드에서 사용할 RoomPlayingDetailViewResponseQVoteItemJpaEntity가 적절히 import되었습니다.

Also applies to: 7-7


21-23: QueryDSL 엔티티 필드 추출로 코드 개선

기존 메서드에서 로컬 변수로 선언하던 QueryDSL 엔티티들을 클래스 레벨 필드로 추출하여 재사용성을 높였습니다. 좋은 리팩토링입니다.

src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java (3)

27-27: 새로운 use case 의존성 추가

진행중인 방 상세보기 기능을 위한 새로운 use case가 적절히 추가되었습니다.


47-47: 가독성을 위한 주석 추가

기존 엔드포인트에 명확한 주석이 추가되어 코드 가독성이 개선되었습니다.


65-72: 새로운 API 엔드포인트 구현

진행중인 방 상세보기 API 엔드포인트가 올바르게 구현되었습니다. 기존 패턴과 일관성 있게 작성되었고, 필요한 파라미터들이 적절히 정의되었습니다.

src/main/java/konkuk/thip/room/adapter/in/web/response/RoomPlayingDetailViewResponse.java (2)

7-26: 잘 설계된 응답 DTO 구조

진행중인 방 상세보기에 필요한 모든 필드들이 논리적으로 잘 구성되어 있습니다. record 클래스와 Builder 패턴의 조합이 적절합니다.


27-36: 적절한 중첩 record 구조

투표 관련 데이터를 위한 중첩 record 구조가 잘 설계되었습니다. CurrentVoteVoteItem의 계층 구조가 도메인 로직을 잘 반영하고 있습니다.

src/main/java/konkuk/thip/room/application/service/RoomShowPlayingDetailViewService.java (1)

50-70: 빌더 패턴을 활용한 명확한 응답 구성이 좋습니다!

도메인 객체에서 응답 DTO로의 변환이 깔끔하게 구현되었습니다.

src/test/java/konkuk/thip/room/adapter/in/web/RoomPlayingDetailViewApiTest.java (1)

169-378: 포괄적인 테스트 시나리오 구성이 훌륭합니다!

호스트/멤버 구분, 권한 검증, 투표 정렬 및 개수 제한 등 주요 비즈니스 로직을 모두 검증하고 있습니다. 특히 투표 참여율 정렬 검증이 세밀하게 구현되어 있네요.

@@ -1,10 +1,13 @@
package konkuk.thip.vote.adapter.out.persistence;

import konkuk.thip.room.adapter.in.web.response.RoomPlayingDetailViewResponse;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

웹 레이어 DTO에 대한 의존성을 검토해보세요.

Repository 레이어가 web response DTO(RoomPlayingDetailViewResponse.CurrentVote)에 직접 의존하고 있습니다. 이는 의존성 방향이 역전된 것으로, 더 안정적인 도메인 객체나 별도의 DTO를 사용하는 것을 고려해보세요.

🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteQueryRepository.java
at line 3, the repository layer improperly depends on a web response DTO
(RoomPlayingDetailViewResponse.CurrentVote), causing an inversion of dependency.
To fix this, remove the import and usage of the web layer DTO in this repository
class and instead define and use a dedicated domain-level DTO or entity that
represents the required data, ensuring the repository depends only on domain or
persistence models.

Comment on lines +50 to +83
@Override
public List<RoomPlayingDetailViewResponse.CurrentVote> findTopParticipationVotesByRoom(Long roomId, int count) {
// 1. Fetch top votes by total participation count
List<VoteJpaEntity> topVotes = jpaQueryFactory
.select(vote)
.from(vote)
.leftJoin(voteItem).on(voteItem.voteJpaEntity.eq(vote))
.where(vote.roomJpaEntity.roomId.eq(roomId))
.groupBy(vote)
.orderBy(voteItem.count.sum().desc()) // 해당 투표에 참여한 총 참여자 수 기준 내림차순 정렬
.limit(count)
.fetch();

// 2. Map to DTOs including vote items
return topVotes.stream()
.map(vote -> {
List<RoomPlayingDetailViewResponse.CurrentVote.VoteItem> voteItems = jpaQueryFactory
.select(voteItem)
.from(voteItem)
.where(voteItem.voteJpaEntity.eq(vote))
.orderBy(voteItem.count.desc())
.fetch()
.stream()
.map(item -> new RoomPlayingDetailViewResponse.CurrentVote.VoteItem(item.getItemName()))
.toList();
return new RoomPlayingDetailViewResponse.CurrentVote(
vote.getContent(),
vote.getPage(),
vote.isOverview(),
voteItems
);
})
.toList();
}
Copy link

@coderabbitai coderabbitai bot Jul 14, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

복잡한 QueryDSL 쿼리의 N+1 문제 가능성

findTopParticipationVotesByRoom 메서드에서 N+1 쿼리 문제가 발생할 수 있습니다. 상위 투표를 조회한 후 각 투표마다 별도의 쿼리로 투표 아이템들을 조회하고 있습니다.

성능을 개선하기 위해 단일 쿼리로 모든 데이터를 조회하는 방식을 고려해보세요:

 @Override
 public List<RoomPlayingDetailViewResponse.CurrentVote> findTopParticipationVotesByRoom(Long roomId, int count) {
-    // 1. Fetch top votes by total participation count
-    List<VoteJpaEntity> topVotes = jpaQueryFactory
-            .select(vote)
-            .from(vote)
-            .leftJoin(voteItem).on(voteItem.voteJpaEntity.eq(vote))
-            .where(vote.roomJpaEntity.roomId.eq(roomId))
-            .groupBy(vote)
-            .orderBy(voteItem.count.sum().desc())
-            .limit(count)
-            .fetch();
-
-    // 2. Map to DTOs including vote items
-    return topVotes.stream()
-            .map(vote -> {
-                List<RoomPlayingDetailViewResponse.CurrentVote.VoteItem> voteItems = jpaQueryFactory
-                        .select(voteItem)
-                        .from(voteItem)
-                        .where(voteItem.voteJpaEntity.eq(vote))
-                        .orderBy(voteItem.count.desc())
-                        .fetch()
-                        .stream()
-                        .map(item -> new RoomPlayingDetailViewResponse.CurrentVote.VoteItem(item.getItemName()))
-                        .toList();
-                return new RoomPlayingDetailViewResponse.CurrentVote(
-                        vote.getContent(),
-                        vote.getPage(),
-                        vote.isOverview(),
-                        voteItems
-                );
-            })
-            .toList();
+    // Fetch all data in a single query with join fetch
+    List<VoteJpaEntity> topVotesWithItems = jpaQueryFactory
+            .selectFrom(vote)
+            .leftJoin(vote.voteItemJpaEntities, voteItem).fetchJoin()
+            .where(vote.roomJpaEntity.roomId.eq(roomId))
+            .orderBy(voteItem.count.sum().desc())
+            .limit(count)
+            .fetch();
+            
+    // Group and transform data
+    return topVotesWithItems.stream()
+            .collect(Collectors.groupingBy(Function.identity()))
+            .entrySet().stream()
+            .map(entry -> {
+                VoteJpaEntity voteEntity = entry.getKey();
+                List<RoomPlayingDetailViewResponse.CurrentVote.VoteItem> voteItems = 
+                    voteEntity.getVoteItemJpaEntities().stream()
+                        .sorted((a, b) -> Integer.compare(b.getCount(), a.getCount()))
+                        .map(item -> new RoomPlayingDetailViewResponse.CurrentVote.VoteItem(item.getItemName()))
+                        .toList();
+                return new RoomPlayingDetailViewResponse.CurrentVote(
+                        voteEntity.getContent(),
+                        voteEntity.getPage(),
+                        voteEntity.isOverview(),
+                        voteItems
+                );
+            })
+            .toList();
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteQueryRepositoryImpl.java
between lines 50 and 83, the method findTopParticipationVotesByRoom currently
fetches top votes first and then executes separate queries for each vote's
items, causing an N+1 query problem. To fix this, refactor the method to
retrieve all necessary vote and vote item data in a single query using a join
and grouping, then map the results to the DTOs accordingly, eliminating the
multiple queries inside the stream mapping.

Copy link
Contributor

Choose a reason for hiding this comment

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

@seongjunnoh @hd0rable 현재 위쪽에서 이미 vote 테이블과 voteItem 테이블을 조인하지 않고 vote만 조회한 후, 각 Vote에 대해 개별적으로 voteItem을 조회하고 있습니다. 만약 양방향 매핑이 되어 있다면 getVoteItemJpaEntities()를 통해 접근할 수 있지만, 현 구조에서는 각 Vote에 대해 별도의 select 쿼리가 발생하므로 결과적으로 N+1 문제가 발생할 수 있는 구조라고 생각됩니다.

현재는 상위 3개의 투표만 조회하므로 총 4개의 쿼리로 끝나지만, 이후 다른 기능들(특히 기록장 조회 API)에서도 Vote와 VoteItem은 자주 함께 사용될 가능성이 높아 보입니다. 따라서 성능 측면에서도 양방향 매핑을 추가한 뒤 fetch join을 고려하는 방식이 더 나을 수 있지 않을까 조심스럽게 제안드립니다..!

Copy link

Choose a reason for hiding this comment

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

@buzz0331 정말 좋은 분석입니다! 맞습니다. 현재 구조에서는 N+1 문제가 발생하고 있으며, 양방향 매핑과 fetch join을 통한 해결 방안이 더 나은 접근법이라고 생각합니다.

특히 기록장 조회 API 등 다른 기능에서도 Vote와 VoteItem이 함께 사용될 가능성이 높다는 점을 고려하면, 장기적으로 보았을 때 양방향 매핑을 추가하는 것이 성능상 더 유리할 것 같습니다.

현재 3개의 투표만 조회하므로 4개의 쿼리로 제한되지만, 확장성을 고려한다면 좋은 개선 방향이라고 생각합니다!


✏️ Learnings added
Learnt from: buzz0331
PR: THIP-TextHip/THIP-Server#75
File: src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteQueryRepositoryImpl.java:50-83
Timestamp: 2025-07-14T14:19:38.762Z
Learning: Vote와 VoteItem 엔티티는 자주 함께 사용되므로, N+1 문제를 방지하기 위해 양방향 매핑과 fetch join을 고려하는 것이 좋습니다. 특히 기록장 조회 API 등에서도 함께 사용될 가능성이 높습니다.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

buzz0331
buzz0331 previously approved these changes Jul 14, 2025
Copy link
Contributor

@buzz0331 buzz0331 left a comment

Choose a reason for hiding this comment

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

수고하셨습니다~ 간단한 리뷰 남겼는데 추후에 리팩토링할 내용인 것 같습니다~!

buzz0331 added 2 commits July 14, 2025 23:44
…ing-detail

# Conflicts:
#	src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java
@sonarqubecloud
Copy link

Copy link
Member

@hd0rable hd0rable left a comment

Choose a reason for hiding this comment

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

수고하셨습니닷!! 현준님 리뷰확인했는데 추후에 리펙토링할 내용인것같아 주석처리만 해주시면 될것같스빈다₩~~

return BaseResponse.ok(roomVerifyPasswordUseCase.verifyRoomPassword(roomVerifyPasswordRequest.toQuery(roomId)));
}

// 모집중인 방 상세보기
Copy link
Member

Choose a reason for hiding this comment

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

👍🏻

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[THIP2025-121] [feat] 진행중인 방 상세보기 api 개발

3 participants