Skip to content

[Feat] 독서메이트 조회 api 개발#72

Merged
hd0rable merged 21 commits intodevelopfrom
feat/#70-get-room-member-list
Jul 14, 2025
Merged

[Feat] 독서메이트 조회 api 개발#72
hd0rable merged 21 commits intodevelopfrom
feat/#70-get-room-member-list

Conversation

@hd0rable
Copy link
Member

@hd0rable hd0rable commented Jul 13, 2025

#️⃣ 연관된 이슈

closes #70

📝 작업 내용

  • 모임에 참여하고있는 멤버들의 정보를 조회할수 있는 독서메이트 조회 api를 개발했습니다.
  • api 흐름은 다음과 같습니다.
controller :  request dto ->  Usecase로 query 전달
application : Persistence Port 호출 및 결과를 응답 DTO로 변환하여 반환
persistence adapter : 조회가 필요한 엔티티 조회 및 결과반환
  • join을 사용하지않고 해당 방에 참여하는 독서메이트 조회 흐름은 다음과 같습니다.
  1. roomId 로 room 검증 및 조회
  2. roomId 로 userRoom 조회 ( 방에 참여한 유저관계 테이블)
  3. 각 userRoom에서 userId로 각 유저의 팔로워 수 조회 및 유저정보 조회
  • 해당 조회결과는 최대 30개( 한 방에 참여할수있는 멤버의 수가 최대30)이라 페이징 처리는 진행하지않았습니다.
  • 통합,단위 테스트도 같이 작성했습니다.

📸 스크린샷

image

💬 리뷰 요구사항

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

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

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

Summary by CodeRabbit

Summary by CodeRabbit

  • 신규 기능

    • 방의 멤버 목록을 조회하는 새로운 API 엔드포인트(/rooms/{roomId}/users)가 추가되었습니다. 각 멤버의 사용자 ID, 닉네임, 이미지 URL, 별명, 구독자 수를 확인할 수 있습니다.
  • 버그 수정

    • 존재하지 않는 책 조회 시 반환되는 HTTP 상태 코드가 400(Bad Request)에서 404(Not Found)로 변경되었습니다.
  • 테스트

    • 방 멤버 목록 API의 정상 동작, 빈 목록 반환, 구독자 수 확인, 유효하지 않은 roomId 처리 등에 대한 통합 및 컨트롤러 테스트가 추가되었습니다.

@coderabbitai
Copy link

coderabbitai bot commented Jul 13, 2025

"""

Walkthrough

독서메이트(룸) 멤버 목록을 조회하는 새로운 API가 추가되었습니다. 이를 위해 컨트롤러, 서비스, 포트, 어댑터, JPA 레포지토리, DTO 등이 새로 생성되었고, 관련 통합 및 컨트롤러 테스트가 작성되었습니다. 또한 BOOK_NOT_FOUND 오류 코드의 HTTP 상태가 400에서 404로 변경되었습니다.

Changes

파일/경로 요약 변경 내용 요약
.../exception/code/ErrorCode.java BOOK_NOT_FOUND의 HTTP 상태를 400(BAD_REQUEST) → 404(NOT_FOUND)로 변경
.../room/adapter/in/web/RoomQueryController.java Room 멤버 목록 조회 API 엔드포인트(/rooms/{roomId}/users) 추가 및 관련 유스케이스 DI
.../room/adapter/in/web/response/RoomGetMemberListResponse.java 멤버 목록 응답 및 멤버 정보 DTO(레코드) 신설
.../room/application/port/in/RoomGetMemberListUseCase.java 룸 멤버 목록 조회 유스케이스 인터페이스 신설
.../room/application/service/RoomGetMemberListService.java 룸 멤버 목록 조회 서비스 구현체 신설(구현 포함)
.../user/adapter/out/persistence/FollowingJpaRepository.java 팔로잉 수 카운트용 JPA 레포지토리 신설 및 메서드 추가
.../user/adapter/out/persistence/FollowingQueryPersistenceAdapter.java 팔로잉 수 카운트용 쿼리 어댑터 신설
.../user/application/port/out/FollowingQueryPort.java 팔로잉 수 카운트용 포트 인터페이스 신설
.../user/adapter/out/persistence/FollowingQueryRepository.java 팔로잉 수 카운트용 커스텀 쿼리 인터페이스 신설
.../user/adapter/out/persistence/FollowingQueryRepositoryImpl.java 팔로잉 수 카운트용 커스텀 쿼리 구현체 신설
.../user/adapter/out/persistence/UserCommandPersistenceAdapter.java 다중 사용자 조회용 findByIds 메서드 추가
.../user/application/port/out/UserCommandPort.java 다중 사용자 조회용 findByIds 메서드 인터페이스 추가
.../common/util/TestEntityFactory.java FollowingJpaEntity 생성 팩토리 메서드 추가
.../room/adapter/in/web/RoomGetMemberListApiTest.java 룸 멤버 목록 API 통합 테스트 신설(여러 시나리오 포함)
.../room/adapter/in/web/RoomGetMemberListControllerTest.java 룸 멤버 목록 API 컨트롤러 테스트 신설(유효성 및 에러 응답 검증 포함)

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller as RoomQueryController
    participant Service as RoomGetMemberListService
    participant RoomRepo as RoomJpaRepository
    participant UserRoomRepo as UserRoomJpaRepository
    participant UserRepo as UserJpaRepository
    participant FollowingQuery as FollowingQueryPort

    Client->>Controller: GET /rooms/{roomId}/users
    Controller->>Service: getRoomMemberList(roomId)
    Service->>RoomRepo: findById(roomId)
    RoomRepo-->>Service: Room
    Service->>UserRoomRepo: findByRoom(room)
    UserRoomRepo-->>Service: List<UserRoom>
    Service->>UserRepo: findByIds(userIds)
    UserRepo-->>Service: Map<userId, User>
    Service->>FollowingQuery: countByFollowingUserIds(userIds)
    FollowingQuery-->>Service: Map<userId, followerCount>
    Service-->>Controller: RoomGetMemberListResponse
    Controller-->>Client: BaseResponse<RoomGetMemberListResponse>
Loading

Assessment against linked issues

Objective Addressed Explanation
독서메이트(룸) 멤버 목록 조회 API 개발 (#70)
Task1, Task2 (#70) 세부 Task1, Task2의 구체적 내용이 명시되지 않아 확인 불가

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
BOOK_NOT_FOUND HTTP 상태 코드 변경 (src/main/java/konkuk/thip/common/exception/code/ErrorCode.java) 오류 코드 상태 변경은 독서메이트 조회 API 개발과 직접적인 관련이 없으며 별도의 수정 사항임.

Suggested reviewers

  • seongjunnoh
  • buzz0331

Possibly related PRs

Poem

🐰
새로운 방에 멤버가 쏙,
조회 API로 명단이 쏙쏙!
팔로워 숫자도 척척 보여주니,
토끼도 깡총깡총 기쁘구나!
404로 책 오류도 똑똑~
오늘도 코드밭에 당근 한 움큼!
🥕
"""


📜 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 59991ff and 7598586.

📒 Files selected for processing (1)
  • src/main/java/konkuk/thip/test/TestExceptionController.java (0 hunks)
💤 Files with no reviewable changes (1)
  • src/main/java/konkuk/thip/test/TestExceptionController.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: 7

🧹 Nitpick comments (3)
src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingJpaRepository.java (1)

7-7: 메서드명을 더 간결하게 개선할 수 있습니다.

countByFollowingUserJpaEntity_UserId는 너무 길고 복잡합니다. JPA 네이밍 컨벤션을 유지하면서도 더 간결한 이름을 사용할 수 있습니다.

-    int countByFollowingUserJpaEntity_UserId(Long userId);
+    int countByFollowingUserId(Long userId);

또는 @Query 어노테이션을 사용하여 명시적으로 쿼리를 작성하는 것도 고려해볼 수 있습니다.

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

12-19: subscriberCount 필드 타입을 검토해보세요.

구독자 수는 항상 0 이상의 값이어야 하므로, int 대신 더 명시적인 타입을 고려해볼 수 있습니다.

-                int subscriberCount
+                long subscriberCount

또는 validation 어노테이션을 추가하여 음수 값을 방지할 수도 있습니다. 하지만 현재 비즈니스 로직상 음수가 나올 가능성이 없다면, 현재 구현도 충분합니다.

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

23-27: 테스트 어노테이션 구성 검토 필요

@SpringBootTest를 사용하면서 [단위] 테스트라고 명시되어 있는데, 이는 통합 테스트에 가깝습니다. 진정한 단위 테스트를 원한다면 @WebMvcTest(RoomQueryController.class)를 사용하고 의존성들을 모킹하는 것을 고려해보세요.

-@SpringBootTest
-@ActiveProfiles("test")
-@AutoConfigureMockMvc(addFilters = false)
+@WebMvcTest(RoomQueryController.class)
+@ActiveProfiles("test")
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between b85c869 and 24051bd.

📒 Files selected for processing (11)
  • src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (1 hunks)
  • src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java (3 hunks)
  • src/main/java/konkuk/thip/room/adapter/in/web/response/RoomGetMemberListResponse.java (1 hunks)
  • src/main/java/konkuk/thip/room/application/port/in/RoomGetMemberListUseCase.java (1 hunks)
  • src/main/java/konkuk/thip/room/application/service/RoomGetMemberListService.java (1 hunks)
  • src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingJpaRepository.java (1 hunks)
  • src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryPersistenceAdapter.java (1 hunks)
  • src/main/java/konkuk/thip/user/application/port/out/FollowingQueryPort.java (1 hunks)
  • src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1 hunks)
  • src/test/java/konkuk/thip/room/adapter/in/web/RoomGetMemberListApiTest.java (1 hunks)
  • src/test/java/konkuk/thip/room/adapter/in/web/RoomGetMemberListControllerTest.java (1 hunks)
🧰 Additional context used
🧠 Learnings (5)
src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryPersistenceAdapter.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/main/java/konkuk/thip/user/application/port/out/FollowingQueryPort.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/application/service/RoomGetMemberListService.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/RoomGetMemberListControllerTest.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을 사용해야 한다.
🧬 Code Graph Analysis (1)
src/test/java/konkuk/thip/room/adapter/in/web/RoomGetMemberListApiTest.java (1)
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1)
  • TestEntityFactory (14-143)
⏰ 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 (10)
src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (1)

54-54: HTTP 상태 코드 개선이 적절합니다.

BOOK_NOT_FOUND 에러에 대해 BAD_REQUEST(400)에서 NOT_FOUND(404)로 변경한 것이 의미적으로 올바릅니다. "존재하지 않는 BOOK"에 대해서는 404가 더 적절하며, 다른 NOT_FOUND 에러들과의 일관성도 유지됩니다.

src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1)

137-142: 테스트 팩토리 메서드 구현이 좋습니다.

새로 추가된 createFollowing 메서드가 기존 팩토리 메서드들과 일관된 패턴을 따르고 있으며, 파라미터명이 명확하여 가독성이 좋습니다. 팔로잉 관계 테스트 데이터 생성에 유용할 것 같습니다.

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

1-7: 유스케이스 인터페이스 설계가 적절합니다.

단일 책임 원칙을 잘 따르고 있으며, 메서드 시그니처가 명확합니다. 기존 프로젝트의 유스케이스 패턴과 일관성을 유지하고 있어 좋습니다.

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

6-6: 의존성 추가가 적절합니다.

새로운 기능을 위한 유스케이스 의존성이 올바르게 추가되었습니다. import문과 필드 주입이 기존 패턴과 일관성을 유지하고 있습니다.

Also applies to: 9-9, 28-28


58-62: REST API 엔드포인트 구현이 우수합니다.

독서메이트 조회를 위한 새로운 엔드포인트가 REST 컨벤션을 잘 따르고 있습니다. /rooms/{roomId}/users 경로가 의미적으로 명확하며, 기존 컨트롤러 메서드들과 일관된 패턴을 유지하고 있습니다.

src/main/java/konkuk/thip/user/application/port/out/FollowingQueryPort.java (1)

1-6: 쿼리 포트 인터페이스 설계가 우수합니다.

CQRS 패턴에 따른 QueryPort 분리가 적절하며, 팔로워 수 조회라는 단일 책임을 명확히 정의하고 있습니다. 메서드명이 직관적이고 반환 타입도 적절합니다. 프로젝트의 포트 컨벤션을 잘 따르고 있습니다.

src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryPersistenceAdapter.java (1)

7-17: LGTM! CQRS 패턴에 맞게 잘 구현되었습니다.

FollowingQueryPort를 구현하는 어댑터가 올바르게 작성되었습니다. 학습된 내용에 따르면 THIP 프로젝트에서는 QueryPort에 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가하는 컨벤션을 따르고 있으며, 이 구현체가 그 컨벤션을 잘 따르고 있습니다.

다만 앞에서 제안한 레포지토리 메서드명 변경을 적용한다면, 15번 라인도 함께 수정이 필요합니다.

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

22-25: CommandPort 사용에 대한 검토가 필요합니다.

학습된 내용에 따르면 THIP 프로젝트에서는 CommandPort에 findByXXX 메서드를 추가하고, QueryPort에는 조회 API의 response 데이터를 조회하는 메서드를 추가하는 컨벤션을 따릅니다.

현재 읽기 전용 서비스에서 여러 CommandPort를 사용하고 있는데, 이것이 컨벤션에 맞는지 확인이 필요합니다. 조회 목적이라면 별도의 QueryPort를 만드는 것을 고려해보세요.

src/test/java/konkuk/thip/room/adapter/in/web/RoomGetMemberListApiTest.java (2)

90-98: 팔로잉 관계 설정이 테스트 시나리오를 잘 설명하고 있습니다.

복잡한 팔로잉 관계를 명확한 주석과 함께 설정하여 각 사용자별 예상 팔로워 수를 쉽게 이해할 수 있도록 했습니다. 이는 156-172라인의 팔로워 수 검증 테스트와 완벽하게 연결됩니다.


174-188: 엣지 케이스 테스트가 잘 작성되었습니다.

팔로워가 없는 사용자에 대한 테스트 케이스를 추가하여 subscriberCount가 0으로 올바르게 처리되는지 확인하고 있습니다. 이는 실제 운영 환경에서 발생할 수 있는 상황을 잘 다루고 있습니다.

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.

수고하셨습니다~ 간단한 리뷰 남겼으니 확인부탁드릴게요

Comment on lines +155 to +172
@Test
@DisplayName("팔로워(구독자) 수가 올바르게 집계된다.")
void getRoomMemberList_subscriberCount() throws Exception {
//given
Long roomId = room1.getRoomId();

//when
ResultActions result = mockMvc.perform(get("/rooms/{roomId}/users", roomId));

//then
// user1: user3이 팔로우(1명)
// user2: user1이 팔로우(1명)
// user3: user1, user2가 팔로우(2명)
result.andExpect(status().isOk())
.andExpect(jsonPath("$.data.userList[?(@.userId==" + user1.getUserId() + ")].subscriberCount").value(contains(1)))
.andExpect(jsonPath("$.data.userList[?(@.userId==" + user2.getUserId() + ")].subscriberCount").value(contains(1)))
.andExpect(jsonPath("$.data.userList[?(@.userId==" + user3.getUserId() + ")].subscriberCount").value(contains(2)));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

LGTM

seongjunnoh
seongjunnoh previously approved these changes Jul 14, 2025
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: 1

🧹 Nitpick comments (2)
src/main/java/konkuk/thip/user/application/port/out/UserCommandPort.java (1)

12-12: batch 조회 메서드 추가가 프로젝트 컨벤션에 맞습니다.

Retrieved learnings에 따르면 THIP 프로젝트에서 CommandPort에 findByXXX 메서드를 추가하는 것이 올바른 접근입니다. 하지만 메서드에 대한 문서화와 예외 상황 명시를 고려해보세요.

다음과 같은 JavaDoc 추가를 고려해보세요:

+    /**
+     * 주어진 사용자 ID 목록으로 사용자들을 조회합니다.
+     * @param userIds 조회할 사용자 ID 목록
+     * @return 사용자 ID를 키로 하고 User 객체를 값으로 하는 맵
+     * @throws IllegalArgumentException userIds가 null인 경우
+     */
     Map<Long, User> findByIds(List<Long> userIds);
src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryPersistenceAdapter.java (1)

15-16: 사용하지 않는 FollowingMapper 의존성을 제거하세요.

현재 구현에서 FollowingMapper는 주입되지만 사용되지 않습니다. 리포지토리 메서드가 이미 적절한 타입을 반환하므로 매핑이 필요하지 않다면 해당 의존성을 제거하는 것이 좋습니다.

-    private final FollowingMapper followingMapper;
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1077995 and 4572dea.

📒 Files selected for processing (8)
  • src/main/java/konkuk/thip/room/application/service/RoomGetMemberListService.java (1 hunks)
  • src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingJpaRepository.java (1 hunks)
  • src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryPersistenceAdapter.java (1 hunks)
  • src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryRepository.java (1 hunks)
  • src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryRepositoryImpl.java (1 hunks)
  • src/main/java/konkuk/thip/user/adapter/out/persistence/UserCommandPersistenceAdapter.java (2 hunks)
  • src/main/java/konkuk/thip/user/application/port/out/FollowingQueryPort.java (1 hunks)
  • src/main/java/konkuk/thip/user/application/port/out/UserCommandPort.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/konkuk/thip/room/application/service/RoomGetMemberListService.java
🧰 Additional context used
🧠 Learnings (4)
src/main/java/konkuk/thip/user/application/port/out/UserCommandPort.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/user/application/port/out/FollowingQueryPort.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/user/adapter/out/persistence/UserCommandPersistenceAdapter.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/user/adapter/out/persistence/FollowingQueryPersistenceAdapter.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로부터 조회하는 메서드를 추가함.
🧬 Code Graph Analysis (1)
src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryPersistenceAdapter.java (1)
src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryRepositoryImpl.java (1)
  • Repository (13-38)
🔇 Additional comments (7)
src/main/java/konkuk/thip/user/application/port/out/UserCommandPort.java (1)

5-6: 새로운 import 문 추가가 적절합니다.

batch 조회 기능을 위한 필요한 타입들을 추가하였으며, 코드가 깔끔하게 정리되어 있습니다.

src/main/java/konkuk/thip/user/adapter/out/persistence/UserCommandPersistenceAdapter.java (1)

12-15: 필요한 import 문들이 적절히 추가되었습니다.

batch 조회 기능 구현을 위한 필수 클래스들이 모두 포함되어 있습니다.

src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingJpaRepository.java (1)

1-7: LGTM! 표준 JPA 리포지토리 패턴을 올바르게 따르고 있습니다.

Spring Data JPA와 커스텀 쿼리 리포지토리를 적절히 결합한 표준적인 구현입니다.

src/main/java/konkuk/thip/user/application/port/out/FollowingQueryPort.java (1)

6-8: LGTM! CQRS QueryPort 컨벤션을 올바르게 따르고 있습니다.

검색된 학습 내용에 따르면, 이 프로젝트에서는 QueryPort에 조회 API response에 해당하는 데이터를 DB로부터 조회하는 메서드를 추가하는 컨벤션을 따르고 있으며, 이 구현이 해당 컨벤션에 부합합니다.

src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryRepository.java (1)

6-8: LGTM! 커스텀 쿼리 메서드 분리가 적절합니다.

Spring Data JPA의 커스텀 리포지토리 패턴을 올바르게 따르고 있으며, 메서드 시그니처가 포트 인터페이스와 일치합니다.

src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryPersistenceAdapter.java (1)

18-21: LGTM! 포트 구현이 올바르게 리포지토리에 위임하고 있습니다.

어댑터가 포트 인터페이스를 적절히 구현하고 있으며, 리포지토리로의 위임도 올바릅니다.

src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryRepositoryImpl.java (1)

19-37: 팔로워 수 0 처리 및 빈 리스트 엣지 케이스 확인 완료

RoomGetMemberListService에서

  • 팔로워가 없는 사용자는 subscriberCountMap.getOrDefault(userId, 0)로 0을 반환
  • userIds 전달 시 빈 컬렉션 반환

두 가지 모두 안전하게 처리되고 있으므로 추가 수정은 필요 없습니다.

Comment on lines +46 to +52
@Override
public Map<Long, User> findByIds(List<Long> userIds) {
List<UserJpaEntity> entities = userJpaRepository.findAllById(userIds);
return entities.stream()
.map(userMapper::toDomainEntity)
.collect(Collectors.toMap(User::getId, Function.identity()));
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

구현 로직은 올바르나 예외 처리와 입력 검증을 강화해야 합니다.

현재 구현은 기본적인 기능은 수행하지만 몇 가지 중요한 개선사항이 필요합니다:

  1. 존재하지 않는 사용자 ID에 대한 처리가 없습니다
  2. null 입력에 대한 검증이 부족합니다
  3. 빈 리스트 처리에 대한 고려가 필요합니다

다음과 같은 개선된 구현을 제안합니다:

 @Override
 public Map<Long, User> findByIds(List<Long> userIds) {
+    if (userIds == null || userIds.isEmpty()) {
+        return Map.of();
+    }
+    
     List<UserJpaEntity> entities = userJpaRepository.findAllById(userIds);
+    
+    // 존재하지 않는 사용자 ID 검증 (선택사항)
+    if (entities.size() != userIds.size()) {
+        Set<Long> foundIds = entities.stream()
+                .map(UserJpaEntity::getUserId)
+                .collect(Collectors.toSet());
+        List<Long> missingIds = userIds.stream()
+                .filter(id -> !foundIds.contains(id))
+                .collect(Collectors.toList());
+        // 로깅 또는 예외 처리 고려
+    }
+    
     return entities.stream()
             .map(userMapper::toDomainEntity)
             .collect(Collectors.toMap(User::getId, Function.identity()));
 }

또는 더 간단한 방식으로 null 체크만 추가할 수도 있습니다:

 @Override
 public Map<Long, User> findByIds(List<Long> userIds) {
+    if (userIds == null || userIds.isEmpty()) {
+        return Map.of();
+    }
+    
     List<UserJpaEntity> entities = userJpaRepository.findAllById(userIds);
     return entities.stream()
             .map(userMapper::toDomainEntity)
             .collect(Collectors.toMap(User::getId, Function.identity()));
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
public Map<Long, User> findByIds(List<Long> userIds) {
List<UserJpaEntity> entities = userJpaRepository.findAllById(userIds);
return entities.stream()
.map(userMapper::toDomainEntity)
.collect(Collectors.toMap(User::getId, Function.identity()));
}
@Override
public Map<Long, User> findByIds(List<Long> userIds) {
if (userIds == null || userIds.isEmpty()) {
return Map.of();
}
List<UserJpaEntity> entities = userJpaRepository.findAllById(userIds);
// 존재하지 않는 사용자 ID 검증 (선택사항)
if (entities.size() != userIds.size()) {
Set<Long> foundIds = entities.stream()
.map(UserJpaEntity::getUserId)
.collect(Collectors.toSet());
List<Long> missingIds = userIds.stream()
.filter(id -> !foundIds.contains(id))
.collect(Collectors.toList());
// TODO: 로깅 또는 예외 처리 고려
}
return entities.stream()
.map(userMapper::toDomainEntity)
.collect(Collectors.toMap(User::getId, Function.identity()));
}
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/user/adapter/out/persistence/UserCommandPersistenceAdapter.java
around lines 46 to 52, the method findByIds lacks input validation and handling
for missing user IDs. Add a null check for the input list userIds to prevent
NullPointerException. Also, handle the case when the input list is empty by
returning an empty map immediately. Additionally, consider how to handle user
IDs that do not exist in the database, such as by returning only found users
without errors or by logging missing IDs. Implement these checks before
processing the list and ensure the method returns a consistent and safe result.

…member-list

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

User user = userCommandPort.findById(memberUserId);
Long userId = userRoom.getUserId();
User user = userMap.get(userId);
int subscriberCount = subscriberCountMap.getOrDefault(userId, 0);
Copy link
Contributor

Choose a reason for hiding this comment

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

LGTM

@hd0rable hd0rable merged commit 2fbc80e into develop Jul 14, 2025
3 checks passed
@hd0rable hd0rable deleted the feat/#70-get-room-member-list branch July 14, 2025 05:15
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-118] [feat] 독서메이트 조회 api 개발

3 participants