Conversation
- 테스트를 위해 RoomJpaEntity 에 updateMemberCount 메서드 추가
|
""" Walkthrough유저가 참여한 모임방을 상태별(진행중, 모집중, 완료됨)로 조회하는 새로운 API가 추가되었습니다. 이를 위해 컨트롤러, 서비스, 포트, 어댑터, 레포지토리, 도메인 타입, 예외 코드, DTO, 통합 테스트가 대거 신설 및 확장되었습니다. 커서 기반 페이지네이션과 입력값 검증 로직도 포함됩니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Controller as RoomQueryController
participant Service as RoomShowMineService
participant Port as RoomQueryPort
participant Repo as RoomQueryRepository
Client->>Controller: GET /rooms/my (userId, type, cursorDate, cursorId)
Controller->>Service: getMyRooms(userId, type, cursorDate, cursorId)
Service->>Port: find*RoomsUserParticipated(userId, cursorDate, cursorId, pageSize)
Port->>Repo: find*RoomsUserParticipated(...)
Repo-->>Port: CursorSliceOfMyRoomView<MyRoom>
Port-->>Service: CursorSliceOfMyRoomView<MyRoom>
Service-->>Controller: RoomShowMineResponse
Controller-->>Client: BaseResponse<RoomShowMineResponse>
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changesNo out-of-scope changes detected. Suggested labels
Poem
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ 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)
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (13)
src/main/java/konkuk/thip/room/adapter/out/jpa/RoomJpaEntity.java (1)
72-76: 멤버 카운트 setter 공개는 과도합니다테스트 편의를 위한 메서드라면
public대신@VisibleForTesting(guava) 사용‧패키지 프라이빗으로 낮추거나 테스트 전용 Factory/Builder를 활용해 실제 코드 경로에서 오용되지 않도록 하는 편이 안전합니다.
또한 음수 값 방지를 위한 검사도 추가하는 것을 권장드립니다.src/main/java/konkuk/thip/room/application/port/in/RoomShowMineUseCase.java (1)
7-10: 파라미터 객체화 고려프리미티브 4개가 노출돼 있어 향후 요구사항 추가 시 메서드 시그니처가 잦은 변동을 겪을 수 있습니다.
RoomShowMineQuery같은 DTO로 묶으면 확장성과 테스트 가독성이 개선됩니다.src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java (1)
84-92:cursorDate파싱 포맷 명시 추천
LocalDate매핑은 ISO 형식이 기본이나, 명시적으로 지정하지 않으면 버전/지역 설정에 따라 실패할 위험이 있습니다.@RequestParam(value = "cursorDate", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate cursorDate추가로 Swagger 문서에도 포맷 예시(yyyy-MM-dd)를 표기해 주세요.
src/main/java/konkuk/thip/room/adapter/in/web/response/RoomShowMineResponse.java (2)
8-9: 주석 오탈자 수정 제안
현제→현재로 수정하면 가독성이 좋아집니다.
6-12: size 필드 중복 위험
size값은roomList.size()로 파생 가능합니다. 별도 필드를 유지하면 실제 리스트 크기와 불일치할 여지가 생기니 제거 또는 런타임 계산을 고려해보세요.src/main/java/konkuk/thip/room/application/service/RoomShowMineService.java (2)
28-31: XOR 검증 가독성 향상
cursorDate == null ^ cursorId == null표현은 한눈에 이해하기 어렵습니다.
아래처럼 괄호를 추가하거나 명시적 조건으로 풀어쓰면 가독성이 높아집니다.-if (cursorDate == null ^ cursorId == null) { +if ((cursorDate == null) ^ (cursorId == null)) {또는
boolean onlyOneNull = (cursorDate == null) != (cursorId == null); if (onlyOneNull) { ... }
21-22: 페이지 사이즈 하드코딩
PAGE_SIZE = 10은 요구사항 변동 시 서비스 재배포가 필요합니다.
프로퍼티(예:application.yml)로 빼두면 운영 중 조정이 수월합니다.src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java (1)
23-30: 메서드 시그니처 네이밍 일관성
lastDate,lastRoomStartDateCursor,dateCursor등이 혼용되고 있습니다.
통일된 네이밍(예:cursorDate,cursorId)을 사용하면 코드 검색과 유지보수가 쉬워집니다.src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java (1)
25-31: Port·Repository 네이밍 동기화 필요앞선 Repository 인터페이스와 동일하게, 포트에서도 파라미터 이름·순서를 일치시키는 편이 좋습니다.
API 설계 관점에서 작은 불일치가 추후 구현체 작성 시 혼란을 줄 수 있습니다.src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java (2)
297-331: 복합 조회 로직이 잘 구현되었습니다!진행중인 방과 모집중인 방을 우선순위에 따라 조회하는 로직이 적절합니다. 다만, 복잡한 로직이므로 주석을 더 추가하면 좋겠습니다.
390-399: 쿼리 성능 최적화를 고려하세요현재 쿼리는 participant, room, book 테이블을 조인하고 있습니다. 대량의 데이터에서 성능을 보장하기 위해 다음 인덱스가 필요합니다:
room_participant테이블의(user_id, room_id)복합 인덱스room테이블의(start_date, room_id)및(end_date, room_id)복합 인덱스src/test/java/konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.java (2)
107-117: 멤버 카운트 업데이트 로직 중복
saveSingleUserToRoom메서드에서 멤버 카운트를 업데이트하는데, 이미changeRoomMemberCount메서드가 있습니다.다음과 같이 리팩토링하는 것을 제안합니다:
private void saveSingleUserToRoom(RoomJpaEntity roomJpaEntity, UserJpaEntity userJpaEntity) { RoomParticipantJpaEntity roomParticipantJpaEntity = RoomParticipantJpaEntity.builder() .userJpaEntity(userJpaEntity) .roomJpaEntity(roomJpaEntity) .roomParticipantRole(RoomParticipantRole.MEMBER) .build(); roomParticipantJpaRepository.save(roomParticipantJpaEntity); - roomJpaEntity.updateMemberCount(roomJpaEntity.getMemberCount() + 1); - roomJpaRepository.save(roomJpaEntity); + changeRoomMemberCount(roomJpaEntity, roomJpaEntity.getMemberCount() + 1); }
389-442: 테스트 데이터 생성 로직 개선 필요12개의 방을 생성하는 코드가 매우 반복적입니다. 가독성과 유지보수성을 위해 개선이 필요합니다.
다음과 같은 방식으로 개선할 수 있습니다:
// 테스트 데이터 생성을 위한 헬퍼 메서드 private List<RoomJpaEntity> createMultipleRecruitingRooms(int count, UserJpaEntity user) { List<RoomJpaEntity> rooms = new ArrayList<>(); for (int i = 1; i <= count; i++) { RoomJpaEntity room = saveScienceRoom( "모집중인방-책-" + i, "isbn" + i, "과학-방-" + i + "일뒤-활동시작", LocalDate.now().plusDays(i), LocalDate.now().plusDays(30), 10 ); changeRoomMemberCount(room, 5 + (i % 4)); // 다양한 멤버 수 saveSingleUserToRoom(room, user); rooms.add(room); } return rooms; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
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/RoomShowMineResponse.java(1 hunks)src/main/java/konkuk/thip/room/adapter/out/jpa/RoomJpaEntity.java(1 hunks)src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java(2 hunks)src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java(2 hunks)src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java(10 hunks)src/main/java/konkuk/thip/room/application/port/in/RoomShowMineUseCase.java(1 hunks)src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java(2 hunks)src/main/java/konkuk/thip/room/application/port/out/dto/CursorSliceOfMyRoomView.java(1 hunks)src/main/java/konkuk/thip/room/application/service/RoomShowMineService.java(1 hunks)src/main/java/konkuk/thip/room/domain/MyRoomType.java(1 hunks)src/test/java/konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.java(1 hunks)
🧰 Additional context used
🧠 Learnings (6)
src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java (2)
Learnt from: buzz0331
PR: #78
File: src/main/java/konkuk/thip/user/application/port/out/FollowingQueryPort.java:3-3
Timestamp: 2025-07-14T18:22:56.538Z
Learning: THIP 프로젝트에서는 Query API(조회 API)에 한해서는 application 계층에서 adapter.in.web.response 패키지의 response DTO를 직접 참조하는 것을 허용함. 이는 CQRS 아키텍처에서 읽기 전용 작업의 효율성을 위한 팀 컨벤션임.
Learnt from: hd0rable
PR: #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/room/adapter/out/persistence/repository/RoomQueryRepository.java (2)
Learnt from: seongjunnoh
PR: #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로부터 조회하는 메서드를 추가함.
Learnt from: buzz0331
PR: #78
File: src/main/java/konkuk/thip/user/application/port/out/FollowingQueryPort.java:3-3
Timestamp: 2025-07-14T18:22:56.538Z
Learning: THIP 프로젝트에서는 Query API(조회 API)에 한해서는 application 계층에서 adapter.in.web.response 패키지의 response DTO를 직접 참조하는 것을 허용함. 이는 CQRS 아키텍처에서 읽기 전용 작업의 효율성을 위한 팀 컨벤션임.
src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java (2)
Learnt from: seongjunnoh
PR: #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로부터 조회하는 메서드를 추가함.
Learnt from: hd0rable
PR: #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/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java (1)
Learnt from: seongjunnoh
PR: #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/RoomShowMineApiTest.java (1)
Learnt from: hd0rable
PR: #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/room/application/port/out/RoomQueryPort.java (2)
Learnt from: seongjunnoh
PR: #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로부터 조회하는 메서드를 추가함.
Learnt from: buzz0331
PR: #78
File: src/main/java/konkuk/thip/user/application/port/out/FollowingQueryPort.java:3-3
Timestamp: 2025-07-14T18:22:56.538Z
Learning: THIP 프로젝트에서는 Query API(조회 API)에 한해서는 application 계층에서 adapter.in.web.response 패키지의 response DTO를 직접 참조하는 것을 허용함. 이는 CQRS 아키텍처에서 읽기 전용 작업의 효율성을 위한 팀 컨벤션임.
🧬 Code Graph Analysis (1)
src/test/java/konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.java (1)
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1)
TestEntityFactory(21-172)
⏰ 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)
120-122: 새 오류 코드 추가 👍도메인 예외 분리가 깔끔하며 코드 값 충돌도 없습니다.
추가 검증 로직과 매핑만 누락되지 않았는지 확인해 주세요.src/main/java/konkuk/thip/room/application/port/out/dto/CursorSliceOfMyRoomView.java (1)
10-21: 문제 없음 – 구현 적합커서 정보를 포함한 Slice 확장은 깔끔하며, 별다른 결함이 보이지 않습니다.
src/main/java/konkuk/thip/room/adapter/in/web/response/RoomShowMineResponse.java (1)
14-19: endDate 타입 신중 검토
endDate를String으로 두면 날짜 연산이 어려워집니다.
가능하다면LocalDate/LocalDateTime으로 유지하고, 포맷은 컨트롤러에서 처리하도록 고민해보세요.src/main/java/konkuk/thip/room/application/service/RoomShowMineService.java (1)
46-52: size 중복 계산
RoomShowMineResponse에size를 전달하면서slice.getContent().size()를 다시 넘깁니다.
응답 DTO에서 size 필드를 제거하거나, 생성자 내부에서 list 크기를 계산하도록 일관성을 맞춰주세요.src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java (2)
47-50: 구현이 적절합니다!어댑터 패턴에 맞게 리포지토리로 위임하는 구조가 잘 구현되었습니다.
52-66: 나머지 메서드들도 일관성 있게 구현되었습니다!모든 메서드가 동일한 패턴으로 리포지토리에 위임하고 있어 구조적 일관성이 유지되고 있습니다.
src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java (2)
41-41: 별칭 변경이 적절합니다!
participant가userRoom보다 엔티티의 의미를 더 명확하게 표현합니다.
252-272: 모집중인 방 조회 로직이 올바르게 구현되었습니다!필터링 조건과 정렬 기준이 요구사항에 맞게 잘 구현되었습니다.
src/test/java/konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.java (2)
63-71: 테스트 정리가 적절합니다!
@AfterEach에서 역순으로 데이터를 삭제하는 것이 참조 무결성을 고려한 좋은 구현입니다.
120-156: 테스트 검증 로직이 명확합니다!각 시나리오별로 예상 결과를 명확하게 검증하고 있으며, 정렬 순서와 필터링이 올바르게 동작하는지 확인하고 있습니다.
src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java
Outdated
Show resolved
Hide resolved
| public static MyRoomType from(String type) { | ||
| return Arrays.stream(MyRoomType.values()) | ||
| .filter(param -> param.getType().equals(type)) | ||
| .findFirst() | ||
| .orElseThrow( | ||
| () -> new InvalidStateException(ErrorCode.INVALID_MY_ROOM_TYPE) | ||
| ); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
대소문자·null 안전성 보강 필요
from()이 equals로만 비교해 "Playing"·PLAYING 등은 거부하며, null 입력 시 NPE가 발생합니다.
public static MyRoomType from(String type) {
- return Arrays.stream(values())
- .filter(t -> t.type.equals(type))
+ if (type == null) throw new InvalidStateException(ErrorCode.INVALID_MY_ROOM_TYPE);
+ return Arrays.stream(values())
+ .filter(t -> t.type.equalsIgnoreCase(type))
.findFirst()
.orElseThrow(() -> new InvalidStateException(ErrorCode.INVALID_MY_ROOM_TYPE));
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/room/domain/MyRoomType.java around lines 20 to 27,
the from() method currently uses equals for comparison, which is case-sensitive
and throws a NullPointerException if the input is null. Modify the method to
first check if the input string is null and throw an appropriate exception or
handle it safely. Then, perform a case-insensitive comparison (e.g., using
equalsIgnoreCase) to allow matching regardless of letter case.
- 클라이언트와 주고받는 커서는 현재 조회할 페이지의 첫번째 레코드의 정보를 의미하므로, 변수 네이밍도 의미를 잘 나타내도록 수정
| @GetMapping("/rooms/my") | ||
| public BaseResponse<RoomShowMineResponse> getMyRooms( | ||
| @UserId final Long userId, | ||
| @RequestParam(value = "type", required = false, defaultValue = "playingAndRecruiting") final String type, |
| } | ||
| } No newline at end of file | ||
|
|
||
| // 테스트 메서드 편의용 |
There was a problem hiding this comment.
@buzz0331 @hd0rable 저희가 RoomJpaEntity 에 memberCount 값을 추가하여서 기존 테스트 코드 작성시 특정 방에 어떤 유저가 속해있다 라는 상황을 가정할때 더이상 RoomParticipantJpaEntity 를 DB에 save 하지 않고, room의 memberCount 값을 수정함으로써 테스트 코드를 간결하게 작성할 수 있는 이점이 있지 않을까 생각하긴 합니다
이런 이유로 RoomJpaEntity에 updateMemberCount 라는 메서드를 추가하였고, jpa entity 는 외부로 노출되는 메서드가 아니니 테스트 메서드 편의용으로 괜찮지 않나라는 생각이긴 합니다
관련해서 어떻게 생각하시나요?
There was a problem hiding this comment.
updateMemberCount()를 통해 테스트 코드가 간결해질 수 있다는 점에는 공감합니다. 또한 테스트를 준비하는 데 필요한 반복적인 작업들을 줄일 수 있다는 이점도 분명하다고 생각했습니다.
다만 JPA 엔티티는 도메인-DB 매핑과 정합성을 책임지는 계층이기 때문에,
테스트를 위한 조작용 메서드를 엔티티 내부에 추가하는 방식은 설계적으로 다소 책임이 분산될 수 있다고 느꼈습니다.
가능하다면 테스트 전용 팩토리나 빌더 객체를 활용해서 memberCount를 설정하거나,
RoomParticipant를 실제로 생성해서 상태를 만드는 쪽이 더 명확한 구조가 될 것 같습니다.
그래도 간편성 차원에선 고려 해볼법한 방법이라 생각하며, 유지한다면 명확히 @VisibleForTesting 또는 JavaDoc 주석을 명시해두는 것도 좋을 것 같습니다.
There was a problem hiding this comment.
오 @VisibleForTesting 어노테이션을 통한 문서화도 좋은 방법인것 같습니다!!
@buzz0331 현준님 생각은 어떠신가요?
There was a problem hiding this comment.
테스트 편의를 위해 update 메서드 뚫어놓는 것은 좋은 것 같아요! 특정 테스트 환경을 만들기 위해서는 불가피할 것 같습니다.
저도 저번에 방 참여 api의 테스트코드를 작성하기 위해서 @VisibleForTesting을 도입해서 updateStartDate 메서드를 구현해두었는데 테스트에서도 접근이 가능하고, 서비스 코드에서도 접근이 가능하더라구요,, @VisibleForTesting이 하는 역할이 정확히 어떤건지 아시나요??
There was a problem hiding this comment.
@VisibleForTesting도 사실상 주석처럼 "테스트에서만 사용하겠다"라는걸 명시하는 어노테이션으로 사용한다고 하네요
There was a problem hiding this comment.
아하 접근 제한자를 실제로 변경해주는 것은 아니고 주석 정도의 역할이군요! 주석을 사용하는 것보다 조금더 명시적일 것 같아서 좋을 것 같네요!!
|
|
||
| CursorSliceOfMyRoomView<RoomShowMineResponse.MyRoom> findPlayingAndRecruitingRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize); | ||
|
|
||
| CursorSliceOfMyRoomView<RoomShowMineResponse.MyRoom> findExpiredRoomsUserParticipated(Long userId, LocalDate dateCursor, Long roomIdCursor, int pageSize); |
| private final QBookJpaEntity book = QBookJpaEntity.bookJpaEntity; | ||
| private final QRoomParticipantJpaEntity userRoom = QRoomParticipantJpaEntity.roomParticipantJpaEntity; | ||
|
|
||
| private final QRoomParticipantJpaEntity participant = QRoomParticipantJpaEntity.roomParticipantJpaEntity; |
| /** | ||
| * 공통 커서+페이징 처리 | ||
| */ | ||
| private CursorSliceOfMyRoomView<RoomShowMineResponse.MyRoom> sliceQuery( |
| OrderSpecifier<?>[] orders = new OrderSpecifier<?>[]{ | ||
| cursorExpr.asc(), room.roomId.asc() | ||
| }; | ||
| Function<Tuple,RoomShowMineResponse.MyRoom> mapper = t -> new RoomShowMineResponse.MyRoom( // tuple -> DTO 매핑 함수 |
There was a problem hiding this comment.
이함수도 매핑하는 함수로 중복 코드가 많은것같아서 나중에 리펙하면 좋을것같아욥
There was a problem hiding this comment.
오 예리하시네요
해당 response 로 mapping 하는 메서드 분리하는 식으로 수정하겠습니다!
| /** | ||
| * 공통 커서+페이징 처리 | ||
| */ | ||
| private CursorSliceOfMyRoomView<RoomShowMineResponse.MyRoom> sliceQuery( | ||
| BooleanExpression baseCondition, | ||
| DateExpression<LocalDate> cursorDateExpr, | ||
| Function<Tuple,RoomShowMineResponse.MyRoom> mapper, | ||
| LocalDate dateCursor, | ||
| Long roomIdCursor, | ||
| int pageSize, | ||
| boolean ascending, | ||
| OrderSpecifier<?>... orderSpecs | ||
| ) { | ||
| BooleanBuilder where = new BooleanBuilder(baseCondition); // baseCondition + 커서 기반으로 where 절 구성 | ||
| if (dateCursor != null && roomIdCursor != null) { | ||
| if (ascending) { // 진행중, 모집중, 통합 | ||
| where.and( | ||
| cursorDateExpr.gt(dateCursor) // dateCursor 보다 크거나 | ||
| .or( | ||
| cursorDateExpr.eq(dateCursor) | ||
| .and(room.roomId.goe(roomIdCursor)) // 같으면 id가 roomIdCursor 보다 크거나 같은 것 | ||
| ) | ||
| ); | ||
| } else { // 내림차순일 때는 반대로 (만료된 방) | ||
| where.and( | ||
| cursorDateExpr.lt(dateCursor) // dateCursor 보다 작거나 | ||
| .or( | ||
| cursorDateExpr.eq(dateCursor) | ||
| .and(room.roomId.loe(roomIdCursor)) // 같으면 id가 roomIdCursor 보다 작거나 같은 것 | ||
| ) | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| int fetchSize = pageSize + 1; | ||
| List<Tuple> tuples = queryFactory | ||
| .select(room.roomId, book.imageUrl, room.title, room.memberCount, cursorDateExpr) | ||
| .from(participant) | ||
| .join(participant.roomJpaEntity, room) | ||
| .join(room.bookJpaEntity, book) | ||
| .where(where) | ||
| .orderBy(orderSpecs) | ||
| .limit(fetchSize) | ||
| .fetch(); // 직접 tuple 결과를 조회하므로 lazy 로딩 적용 대상 X | ||
|
|
||
| boolean hasNext = tuples.size() == fetchSize; | ||
| List<RoomShowMineResponse.MyRoom> content = tuples.stream() | ||
| .limit(pageSize) | ||
| .map(mapper) | ||
| .toList(); // pageSize 만큼만 dto로 매핑 | ||
|
|
||
| // 커서 값 세팅 | ||
| LocalDate nextDate = null; | ||
| Long nextId = null; | ||
| if (hasNext) { | ||
| Tuple next = tuples.get(pageSize); // 다음 페이지의 첫번째 레코드 | ||
| nextDate = next.get(cursorDateExpr); | ||
| nextId = next.get(room.roomId); | ||
| } | ||
|
|
||
| return new CursorSliceOfMyRoomView<>( | ||
| content, | ||
| PageRequest.of(0, pageSize), | ||
| hasNext, | ||
| nextDate, | ||
| nextId | ||
| ); | ||
| } |
There was a problem hiding this comment.
현재 페이징 변수를 구하는 로직때문에 쿼리 볼륨이 커진 느낌입니다. 회의에서 말한 것처럼 limit를 1 높게 조회하여 adapter 또는 service에서 CursorBasedList를 만들때, nextCursor를 추출하면 쿼리 볼륨이 조금 줄어들 것 같습니다!
지금은 고치기 어려우니 추후 리팩토링 시에 고치도록 하죠!!
There was a problem hiding this comment.
pageSize + 1 개를 조회하여 next 페이지의 유무를 확인하고 커서값을 세팅하도록 구현하긴 했는데, 아무래도 해당 메서드에서
쿼리 필터 조건 처리 + DB 쿼리 조회 + 다음 페이지 유무 및 커서 생성 의 작업을 모두 수행하다 보니 볼륨이 좀 크긴 하네요
추후에 복합 커서용 CursorBasedList 같은 친구를 도입하는 식으로 리펙헤보겠습니다!
| @Test | ||
| @DisplayName("cursor 값을 기준으로 해당 페이지의 데이터를 반환한다.") | ||
| void get_my_rooms_page_2() throws Exception { | ||
| //given | ||
| RoomJpaEntity recruitingRoom1 = saveScienceRoom("모집중인방-책-1", "isbn1", "과학-방-1일뒤-활동시작", LocalDate.now().plusDays(1), LocalDate.now().plusDays(30), 10); | ||
| changeRoomMemberCount(recruitingRoom1, 5); | ||
|
|
||
| RoomJpaEntity recruitingRoom2 = saveScienceRoom("모집중인방-책-2", "isbn2", "과학-방-2일뒤-활동시작", LocalDate.now().plusDays(2), LocalDate.now().plusDays(30), 10); | ||
| changeRoomMemberCount(recruitingRoom2, 8); | ||
|
|
||
| RoomJpaEntity recruitingRoom3 = saveScienceRoom("모집중인방-책-3", "isbn3", "과학-방-3일뒤-활동시작", LocalDate.now().plusDays(3), LocalDate.now().plusDays(30), 10); | ||
| changeRoomMemberCount(recruitingRoom3, 8); | ||
|
|
||
| RoomJpaEntity recruitingRoom4 = saveScienceRoom("모집중인방-책-4", "isbn4", "과학-방-4일뒤-활동시작", LocalDate.now().plusDays(4), LocalDate.now().plusDays(30), 10); | ||
| changeRoomMemberCount(recruitingRoom4, 8); | ||
|
|
||
| RoomJpaEntity recruitingRoom5 = saveScienceRoom("모집중인방-책-5", "isbn5", "과학-방-5일뒤-활동시작", LocalDate.now().plusDays(5), LocalDate.now().plusDays(30), 10); | ||
| changeRoomMemberCount(recruitingRoom5, 8); | ||
|
|
||
| RoomJpaEntity recruitingRoom6 = saveScienceRoom("모집중인방-책-6", "isbn6", "과학-방-6일뒤-활동시작", LocalDate.now().plusDays(6), LocalDate.now().plusDays(30), 10); | ||
| changeRoomMemberCount(recruitingRoom6, 8); | ||
|
|
||
| RoomJpaEntity recruitingRoom7 = saveScienceRoom("모집중인방-책-7", "isbn7", "과학-방-7일뒤-활동시작", LocalDate.now().plusDays(7), LocalDate.now().plusDays(30), 10); | ||
| changeRoomMemberCount(recruitingRoom7, 8); | ||
|
|
||
| RoomJpaEntity recruitingRoom8 = saveScienceRoom("모집중인방-책-8", "isbn8", "과학-방-8일뒤-활동시작", LocalDate.now().plusDays(8), LocalDate.now().plusDays(30), 10); | ||
| changeRoomMemberCount(recruitingRoom8, 8); | ||
|
|
||
| RoomJpaEntity recruitingRoom9 = saveScienceRoom("모집중인방-책-9", "isbn9", "과학-방-9일뒤-활동시작", LocalDate.now().plusDays(9), LocalDate.now().plusDays(30), 10); | ||
| changeRoomMemberCount(recruitingRoom9, 8); | ||
|
|
||
| RoomJpaEntity recruitingRoom10 = saveScienceRoom("모집중인방-책-10", "isbn10", "과학-방-10일뒤-활동시작", LocalDate.now().plusDays(10), LocalDate.now().plusDays(30), 10); | ||
| changeRoomMemberCount(recruitingRoom10, 8); | ||
|
|
||
| RoomJpaEntity recruitingRoom11 = saveScienceRoom("모집중인방-책-11", "isbn11", "과학-방-11일뒤-활동시작", LocalDate.now().plusDays(11), LocalDate.now().plusDays(30), 10); | ||
| changeRoomMemberCount(recruitingRoom11, 8); | ||
|
|
||
| RoomJpaEntity recruitingRoom12 = saveScienceRoom("모집중인방-책-12", "isbn12", "과학-방-12일뒤-활동시작", LocalDate.now().plusDays(12), LocalDate.now().plusDays(30), 10); | ||
| changeRoomMemberCount(recruitingRoom12, 8); | ||
|
|
||
| AliasJpaEntity scienceAlias = aliasJpaRepository.save(TestEntityFactory.createScienceAlias()); | ||
| UserJpaEntity user = userJpaRepository.save(TestEntityFactory.createUser(scienceAlias)); | ||
|
|
||
| // user가 생성한 방에 참여한 상황 가정 | ||
| saveSingleUserToRoom(recruitingRoom1, user); | ||
| saveSingleUserToRoom(recruitingRoom2, user); | ||
| saveSingleUserToRoom(recruitingRoom3, user); | ||
| saveSingleUserToRoom(recruitingRoom4, user); | ||
| saveSingleUserToRoom(recruitingRoom5, user); | ||
| saveSingleUserToRoom(recruitingRoom6, user); | ||
| saveSingleUserToRoom(recruitingRoom7, user); | ||
| saveSingleUserToRoom(recruitingRoom8, user); | ||
| saveSingleUserToRoom(recruitingRoom9, user); | ||
| saveSingleUserToRoom(recruitingRoom10, user); | ||
| saveSingleUserToRoom(recruitingRoom11, user); | ||
| saveSingleUserToRoom(recruitingRoom12, user); | ||
|
|
| CursorSliceOfMyRoomView<RoomShowMineResponse.MyRoom> slice = switch (MyRoomType.from(type)) { | ||
| case RECRUITING -> roomQueryPort | ||
| .findRecruitingRoomsUserParticipated(userId, cursorDate, cursorId, PAGE_SIZE); | ||
| case PLAYING -> roomQueryPort | ||
| .findPlayingRoomsUserParticipated(userId, cursorDate, cursorId, PAGE_SIZE); | ||
| case PLAYING_AND_RECRUITING -> roomQueryPort | ||
| .findPlayingAndRecruitingRoomsUserParticipated(userId, cursorDate, cursorId, PAGE_SIZE); | ||
| case EXPIRED -> roomQueryPort | ||
| .findExpiredRoomsUserParticipated(userId, cursorDate, cursorId, PAGE_SIZE); | ||
| }; |
- String 타입의 cursor 로 수정 (내부에서 파싱)
- 수정된 페이징 처리 데이터 반영
- String cursor 를 주입받도록 수정
- CursorBasedList, 조회용 dto 도입 - Q클래스의 조회용 dto를 사용하여 조회 및 반환을 한번에 수행하도록 수정 - 현재 커서의 값을 배제하는 exclusive 방식으로 페이지네이션 로직 수정
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
src/main/java/konkuk/thip/room/application/port/in/RoomShowMineUseCase.java (1)
7-7: 타입 안정성을 위해 도메인 타입 사용을 고려해보세요
type과cursor파라미터가 String으로 정의되어 있어 런타임에 검증이 필요합니다.MyRoomTypeenum과Cursor객체를 직접 파라미터로 받는 것이 타입 안정성 측면에서 더 나을 수 있습니다.- RoomShowMineResponse getMyRooms(Long userId, String type, String cursor); + RoomShowMineResponse getMyRooms(Long userId, MyRoomType type, Cursor cursor);src/main/java/konkuk/thip/room/application/service/RoomShowMineService.java (1)
45-61: EXPIRED 타입 처리를 Mapper에서 수행하는 것을 고려해보세요현재 EXPIRED 타입에 대한 특별 처리가 서비스 레이어에서 이루어지고 있습니다. 이를
RoomQueryMapper에 위임하면 서비스 로직이 더 간결해질 수 있습니다.- boolean isExpiredType = myRoomType == MyRoomType.EXPIRED; List<RoomShowMineResponse.MyRoom> myRooms = result.contents().stream() - .map(dto -> { - RoomShowMineResponse.MyRoom r = roomQueryMapper.toShowMyRoomResponse(dto); - if (isExpiredType) { - return new RoomShowMineResponse.MyRoom( - r.roomId(), - r.bookImageUrl(), - r.roomName(), - r.memberCount(), - null - ); - } - return r; - }) + .map(dto -> roomQueryMapper.toShowMyRoomResponse(dto, myRoomType)) .toList();Mapper 인터페이스에 다음 메서드를 추가:
default RoomShowMineResponse.MyRoom toShowMyRoomResponse(RoomShowMineQueryDto dto, MyRoomType type) { RoomShowMineResponse.MyRoom room = toShowMyRoomResponse(dto); if (type == MyRoomType.EXPIRED) { return new RoomShowMineResponse.MyRoom( room.roomId(), room.bookImageUrl(), room.roomName(), room.memberCount(), null ); } return room; }src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java (1)
26-32: 단일 메서드로 통합하는 것을 고려해보세요현재 4개의 별도 메서드로 구현되어 있는데, 타입을 파라미터로 받는 단일 메서드로 통합하면 인터페이스가 더 간결해질 수 있습니다.
- CursorBasedList<RoomShowMineQueryDto> findRecruitingRoomsUserParticipated(Long userId, Cursor cursor); - - CursorBasedList<RoomShowMineQueryDto> findPlayingRoomsUserParticipated(Long userId, Cursor cursor); - - CursorBasedList<RoomShowMineQueryDto> findPlayingAndRecruitingRoomsUserParticipated(Long userId, Cursor cursor); - - CursorBasedList<RoomShowMineQueryDto> findExpiredRoomsUserParticipated(Long userId, Cursor cursor); + CursorBasedList<RoomShowMineQueryDto> findRoomsUserParticipatedByType(Long userId, MyRoomType type, Cursor cursor);다만, 현재 구현이 각 타입별로 다른 쿼리 로직을 가지고 있다면 현재 방식이 더 적절할 수 있습니다.
src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java (1)
50-51: Cursor 파라미터 추출 로직을 Cursor 클래스로 이동 고려커서에서 파라미터를 추출하는 로직이 반복되고 있습니다. 이를 Cursor 클래스의 메서드로 캡슐화하면 더 깔끔해질 수 있습니다.
// Cursor 클래스에 추가 public record CursorParams(LocalDate date, Long id) { public static CursorParams from(Cursor cursor) { if (cursor.isFirstRequest()) { return new CursorParams(null, null); } return new CursorParams(cursor.getLocalDate(0), cursor.getLong(1)); } }그러면 이렇게 사용할 수 있습니다:
- LocalDate lastLocalDate = cursor.isFirstRequest() ? null : cursor.getLocalDate(0); - Long lastId = cursor.isFirstRequest() ? null : cursor.getLong(1); + CursorParams params = CursorParams.from(cursor); int pageSize = cursor.getPageSize(); - List<RoomShowMineQueryDto> roomShowMineQueryDtos = roomJpaRepository.findRecruitingRoomsUserParticipated(userId, lastLocalDate, lastId, pageSize); + List<RoomShowMineQueryDto> roomShowMineQueryDtos = roomJpaRepository.findRecruitingRoomsUserParticipated(userId, params.date(), params.id(), pageSize);Also applies to: 67-68, 84-85, 101-102
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
src/main/java/konkuk/thip/common/exception/code/ErrorCode.java(1 hunks)src/main/java/konkuk/thip/common/util/Cursor.java(2 hunks)src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java(4 hunks)src/main/java/konkuk/thip/room/adapter/in/web/response/RoomShowMineResponse.java(1 hunks)src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java(2 hunks)src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java(2 hunks)src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java(9 hunks)src/main/java/konkuk/thip/room/application/mapper/RoomQueryMapper.java(1 hunks)src/main/java/konkuk/thip/room/application/port/in/RoomShowMineUseCase.java(1 hunks)src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java(2 hunks)src/main/java/konkuk/thip/room/application/port/out/dto/RoomShowMineQueryDto.java(1 hunks)src/main/java/konkuk/thip/room/application/service/RoomShowMineService.java(1 hunks)src/test/java/konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.java(1 hunks)
✅ Files skipped from review due to trivial changes (3)
- src/main/java/konkuk/thip/room/application/mapper/RoomQueryMapper.java
- src/main/java/konkuk/thip/common/util/Cursor.java
- src/main/java/konkuk/thip/room/application/port/out/dto/RoomShowMineQueryDto.java
🚧 Files skipped from review as they are similar to previous changes (6)
- src/main/java/konkuk/thip/room/adapter/in/web/RoomQueryController.java
- src/main/java/konkuk/thip/common/exception/code/ErrorCode.java
- src/main/java/konkuk/thip/room/adapter/in/web/response/RoomShowMineResponse.java
- src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepositoryImpl.java
- src/test/java/konkuk/thip/room/adapter/in/web/RoomShowMineApiTest.java
- src/main/java/konkuk/thip/room/adapter/out/persistence/repository/RoomQueryRepository.java
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#101
File: src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java:36-39
Timestamp: 2025-07-26T06:09:00.850Z
Learning: THIP 프로젝트에서는 "사용자가 방에 속하는지 검증" 로직을 RoomParticipantPolicy 도메인 서비스로 캡슐화하여 재사용성을 높이고 비즈니스 로직의 중복을 방지하는 방식을 선호한다.
src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java (1)
Learnt from: seongjunnoh
PR: #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/port/out/RoomQueryPort.java (1)
Learnt from: seongjunnoh
PR: #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/RoomShowMineService.java (1)
Learnt from: seongjunnoh
PR: #101
File: src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java:36-39
Timestamp: 2025-07-26T06:09:00.850Z
Learning: THIP 프로젝트에서는 "사용자가 방에 속하는지 검증" 로직을 RoomParticipantPolicy 도메인 서비스로 캡슐화하여 재사용성을 높이고 비즈니스 로직의 중복을 방지하는 방식을 선호한다.
⏰ 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 (3)
src/main/java/konkuk/thip/room/application/service/RoomShowMineService.java (2)
18-25: 서비스 구현이 깔끔합니다!의존성 주입과 읽기 전용 트랜잭션 설정이 적절합니다.
34-43: switch 표현식 사용이 우수합니다Java 14의 switch 표현식을 활용하여 타입별 분기 처리가 깔끔하게 구현되었습니다.
src/main/java/konkuk/thip/room/application/port/out/RoomQueryPort.java (1)
26-32: 메서드 명명 규칙이 일관되고 명확합니다사용자가 참여한 방을 상태별로 조회하는 메서드들의 이름이 의도를 명확하게 전달합니다.
src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java
Show resolved
Hide resolved
seongjunnoh
left a comment
There was a problem hiding this comment.
새로 도입된 CursorBasedList, Cursor 반영하여 api 의 전체적인 코드를 수정하였습니다.
또한 기존에는 request param으로 전달받은 커서의 범위부터 조회하는 방식으로 페이징처리를 구현하였지만, 팀 컨벤션인 exclusive 커서 페이징 방식에 따라 페이징 처리 로직을 수정하였습니다
그리고 조회용 dto 도입 및 QueryDSL 코드에서 Q클래스 dto를 활용하여 조회 및 반환을 한번에 할 수 있도록 수정하였습니다
-> "방과 관련된 조회용 데이터를 담는 dto" 를 정의할까 하다가, 해당 dto에는 현재 api 로직과는 상관없는 데이터들이 많을 것이고, 이로 인한 코드의 수정 과정이 효율적이지 않다고 생각하여 현재 api 전용 조회 dto인 RoomShowMineQueryDto 를 정의하였습니다
| RoomShowMineResponse.MyRoom r = roomQueryMapper.toShowMyRoomResponse(dto); | ||
| if (isExpiredType) { | ||
| return new RoomShowMineResponse.MyRoom( | ||
| r.roomId(), | ||
| r.bookImageUrl(), | ||
| r.roomName(), | ||
| r.memberCount(), | ||
| null | ||
| ); |
There was a problem hiding this comment.
p3: r->myRoomDto으로 명시하는 것 어떨까요? 명시하면 RoomShowMineResponse.MyRoom 타입을 사용하지 않고 var로 타입을 바꿔도 어떤 클래스인지 알 수 있을 것 같아요!
var myRoomDto = roomQueryMapper.toShowMyRoomResponse(dto);
if (isExpiredType) {
return new RoomShowMineResponse.MyRoom(
myRoomDto.roomId(),
...
);| MyRoomType myRoomType = MyRoomType.from(type); | ||
| CursorBasedList<RoomShowMineQueryDto> result = switch (myRoomType) { | ||
| case RECRUITING -> roomQueryPort | ||
| .findRecruitingRoomsUserParticipated(userId, cursorDate, cursorId, PAGE_SIZE); | ||
| .findRecruitingRoomsUserParticipated(userId, nextCursor); | ||
| case PLAYING -> roomQueryPort | ||
| .findPlayingRoomsUserParticipated(userId, cursorDate, cursorId, PAGE_SIZE); | ||
| .findPlayingRoomsUserParticipated(userId, nextCursor); | ||
| case PLAYING_AND_RECRUITING -> roomQueryPort | ||
| .findPlayingAndRecruitingRoomsUserParticipated(userId, cursorDate, cursorId, PAGE_SIZE); | ||
| .findPlayingAndRecruitingRoomsUserParticipated(userId, nextCursor); | ||
| case EXPIRED -> roomQueryPort | ||
| .findExpiredRoomsUserParticipated(userId, cursorDate, cursorId, PAGE_SIZE); | ||
| .findExpiredRoomsUserParticipated(userId, nextCursor); | ||
| }; |
- 함수형 인터페이스를 도입하여 중복되는 로직 추상화
| )); | ||
| return nextCursor.toEncodedString(); | ||
| }); | ||
| return findRooms(userId, cursor, roomJpaRepository::findRecruitingRoomsUserParticipated); |
CursorBasedList 도입은 매우 좋은 것 같습니다!!! 덕분에 커서 기반 조회 로직에 통일성이 생겨서 수정 및 리뷰가 편해진거 같아여 |
#️⃣ 연관된 이슈
📝 작업 내용
내가 참여한 목록 조회 api 를 개발하였습니다.
<api 플로우>
📸 스크린샷
💬 리뷰 요구사항
QueryDSL 관련 로직을 담당하는 RoomQueryRepositoryImpl 의 코드가 많이 복잡해지는 것 같아, 커서 기반 페이징 처리를 담당하는 내부 private 메서드를 정의하고, 이를 공유하여 반환값을 구성하도록 하였습니다.
하지만 이렇게 하더라도 여전히 RoomQueryRepositoryImpl 의 코드가 무겁고, 추후 조회 api 개발하면서 코드가 더 추가될텐데, 더 나은 방식은 없을까 고민해보면 좋을 것 같습니다!
api 통합 테스트 코드를 상세하게 작성하였습니다! 테스트 코드의 흐름을 먼저 보시는 것이 리뷰하기에 더 편할 것 같습니다!
📌 PR 진행 시 이러한 점들을 참고해 주세요
Summary by CodeRabbit
Summary by CodeRabbit
신규 기능
버그 수정
테스트