Conversation
|
""" Walkthrough최근 검색어 조회 및 삭제 기능이 도입되었습니다. 주요 변경 사항으로는 최근 검색어 조회/삭제 API 컨트롤러 및 서비스 계층 추가, 관련 포트 및 어댑터 확장, 도메인 및 JPA 엔티티의 타입 시스템 개선, 오류 코드 및 스웨거 응답 설명 확장, 그리고 관련 통합 테스트 코드가 작성되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant RecentSearchQueryController
participant RecentSearchGetService
participant RecentSearchQueryPort
participant RecentSearchQueryMapper
Client->>RecentSearchQueryController: GET /recent-searches?type=TYPE
RecentSearchQueryController->>RecentSearchGetService: getRecentSearches(type, userId)
RecentSearchGetService->>RecentSearchQueryPort: findRecentSearchesByTypeAndUserId(type, userId, 5)
RecentSearchQueryPort-->>RecentSearchGetService: List<RecentSearch>
RecentSearchGetService->>RecentSearchQueryMapper: toResponseList(List<RecentSearch>)
RecentSearchQueryMapper-->>RecentSearchGetService: List<RecentSearchDto>
RecentSearchGetService-->>RecentSearchQueryController: RecentSearchGetResponse
RecentSearchQueryController-->>Client: BaseResponse<RecentSearchGetResponse>
sequenceDiagram
participant Client
participant RecentSearchCommandController
participant RecentSearchDeleteService
participant RecentSearchCommandPort
Client->>RecentSearchCommandController: DELETE /recent-searches/{id}
RecentSearchCommandController->>RecentSearchDeleteService: deleteRecentSearch(id, userId)
RecentSearchDeleteService->>RecentSearchCommandPort: getByIdOrThrow(id)
RecentSearchDeleteService->>RecentSearchCommandPort: delete(id)
RecentSearchDeleteService-->>RecentSearchCommandController: Void
RecentSearchCommandController-->>Client: BaseResponse<Void>
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes해당 PR에서 요구사항과 무관한 변경 사항은 발견되지 않았습니다. Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
🧪 Generate unit tests
🪧 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: 6
🧹 Nitpick comments (7)
src/main/java/konkuk/thip/recentSearch/application/port/in/RecentSearchDeleteUseCase.java (1)
4-4: 반환 타입 재검토를 권장합니다.삭제 작업에서
Void래퍼 클래스를 반환하는 것은 일반적이지 않습니다. 일반적으로 삭제 메서드는void원시 타입을 사용하거나 아무것도 반환하지 않습니다. 특별한 프레임워크 요구사항이 있다면 주석으로 설명을 추가하는 것을 고려해보세요.- Void deleteRecentSearch(Long recentSearchId, Long userId); + void deleteRecentSearch(Long recentSearchId, Long userId);src/main/java/konkuk/thip/recentSearch/application/service/RecentSearchDeleteService.java (1)
18-27: 삭제 로직이 잘 구현되었지만 반환 타입 개선을 고려해보세요.삭제 서비스의 핵심 로직이 올바르게 구현되었습니다:
@Transactional어노테이션으로 데이터 일관성 보장- 소유권 검증으로 무단 삭제 방지
- 적절한 에러 코드로 예외 처리
다만
return null대신 메서드 반환 타입을void로 변경하는 것을 고려해보세요:-public Void deleteRecentSearch(Long recentSearchId, Long userId) { +public void deleteRecentSearch(Long recentSearchId, Long userId) { // ... 로직 ... - return null; }src/main/java/konkuk/thip/user/adapter/in/web/UserQueryController.java (1)
12-20: import문 정리 필요중복된 import문들이 있습니다. 12번째 줄의 와일드카드 import와 개별 import문들이 중복되고 있고, 32번째 줄에도 중복된 import가 있습니다.
다음과 같이 정리하세요:
-import konkuk.thip.user.adapter.in.web.response.*; -import konkuk.thip.user.adapter.in.web.response.UserFollowersResponse; -import konkuk.thip.user.adapter.in.web.response.UserFollowingResponse; -import konkuk.thip.user.adapter.in.web.response.UserVerifyNicknameResponse; -import konkuk.thip.user.adapter.in.web.response.UserIsFollowingResponse; -import konkuk.thip.user.adapter.in.web.response.UserViewAliasChoiceResponse; +import konkuk.thip.user.adapter.in.web.response.*;그리고:
-import org.springframework.web.bind.annotation.*;이미 개별 import들이 있으므로 와일드카드 import는 제거하세요.
Also applies to: 32-32
src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java (1)
15-15: 상수를 enum으로 관리하는 것을 고려해보세요."사용자 검색" 문자열이 하드코딩되어 있습니다. RecentSearchType enum을 확장하여 타입 관리를 일관성 있게 하는 것이 좋겠습니다.
src/main/java/konkuk/thip/recentSearch/domain/RecentSearchType.java (1)
23-30: enum 구현이 깔끔하고 올바릅니다.정적 팩토리 메서드와 예외 처리가 잘 구현되어 있습니다. enum 값이 적어서 현재 구현으로 충분하지만, 성능 최적화를 원한다면 Map을 사용하는 방법도 고려해볼 수 있습니다.
성능 최적화를 원한다면 다음과 같이 Map을 사용할 수 있습니다:
+private static final Map<String, RecentSearchType> PARAM_MAP = + Arrays.stream(values()).collect(Collectors.toMap(RecentSearchType::getParam, Function.identity())); + public static RecentSearchType from(String param) { - for (RecentSearchType recentSearchType : RecentSearchType.values()) { - if (recentSearchType.getParam().equals(param)) { - return recentSearchType; - } - } - throw new InvalidStateException(INVALID_SEARCH_TYPE); + RecentSearchType type = PARAM_MAP.get(param); + if (type == null) { + throw new InvalidStateException(INVALID_SEARCH_TYPE); + } + return type; }src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchGetApiTest.java (1)
56-82: 테스트 데이터 설정이 적절하지만 타임스탬프 순서 확인이 필요합니다.JPQL을 사용한 modifiedAt 강제 업데이트 방식은 JPA의 자동 타임스탬프 덮어쓰기 문제를 해결하는 창의적인 접근입니다. 하지만
now().minusMinutes(i)로직에서 i 값이 클수록 더 이른 시간이 되므로, 예상되는 정렬 순서와 혼동될 수 있습니다.타임스탬프 로직을 더 명확하게 하려면 다음과 같이 개선할 수 있습니다:
// JPQL update로 modifiedAt을 강제로 원하는 값으로 덮기 em.createQuery("update RecentSearchJpaEntity r set r.modifiedAt = :time where r.id = :id") - .setParameter("time", LocalDateTime.now().minusMinutes(i)) + .setParameter("time", LocalDateTime.now().minusMinutes(7-i)) // 최신이 앞에 오도록 .setParameter("id", saved.getRecentSearchId()) .executeUpdate();src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java (1)
47-52: delete 메서드 성능 최적화가 필요합니다.현재 구현에서는
findById를 두 번 호출하게 됩니다 (한 번은 존재 확인, 한 번은 삭제를 위해).다음과 같이 개선할 수 있습니다:
@Override public void delete(Long id) { - recentSearchJpaRepository.delete( - recentSearchJpaRepository.findById(id) - .orElseThrow(() -> new EntityNotFoundException(RECENT_SEARCH_NOT_FOUND)) - ); + RecentSearchJpaEntity entity = recentSearchJpaRepository.findById(id) + .orElseThrow(() -> new EntityNotFoundException(RECENT_SEARCH_NOT_FOUND)); + recentSearchJpaRepository.delete(entity); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (53)
src/main/java/konkuk/thip/book/application/service/BookSearchService.java(1 hunks)src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java(2 hunks)src/main/java/konkuk/thip/common/exception/code/ErrorCode.java(1 hunks)src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java(2 hunks)src/main/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchCommandController.java(1 hunks)src/main/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchQueryController.java(1 hunks)src/main/java/konkuk/thip/recentSearch/adapter/in/web/response/DummyResponse.java(0 hunks)src/main/java/konkuk/thip/recentSearch/adapter/in/web/response/RecentSearchGetResponse.java(1 hunks)src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/RecentSearchJpaEntity.java(2 hunks)src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/SearchType.java(3 hunks)src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java(2 hunks)src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchQueryPersistenceAdapter.java(1 hunks)src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchJpaRepository.java(1 hunks)src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepository.java(1 hunks)src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepositoryImpl.java(1 hunks)src/main/java/konkuk/thip/recentSearch/application/RecentSearchQueryMapper.java(1 hunks)src/main/java/konkuk/thip/recentSearch/application/port/in/DummyUseCase.java(0 hunks)src/main/java/konkuk/thip/recentSearch/application/port/in/RecentSearchDeleteUseCase.java(1 hunks)src/main/java/konkuk/thip/recentSearch/application/port/in/RecentSearchGetUseCase.java(1 hunks)src/main/java/konkuk/thip/recentSearch/application/port/out/RecentSearchCommandPort.java(1 hunks)src/main/java/konkuk/thip/recentSearch/application/port/out/RecentSearchQueryPort.java(1 hunks)src/main/java/konkuk/thip/recentSearch/application/service/RecentSearchDeleteService.java(1 hunks)src/main/java/konkuk/thip/recentSearch/application/service/RecentSearchGetService.java(1 hunks)src/main/java/konkuk/thip/recentSearch/application/service/RecentSearchService.java(0 hunks)src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java(1 hunks)src/main/java/konkuk/thip/recentSearch/domain/RecentSearch.java(2 hunks)src/main/java/konkuk/thip/recentSearch/domain/RecentSearchType.java(1 hunks)src/main/java/konkuk/thip/user/adapter/in/web/UserQueryController.java(4 hunks)src/main/java/konkuk/thip/user/adapter/in/web/response/UserFollowersResponse.java(1 hunks)src/main/java/konkuk/thip/user/adapter/in/web/response/UserFollowingResponse.java(1 hunks)src/main/java/konkuk/thip/user/adapter/in/web/response/UserSearchResponse.java(1 hunks)src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryPersistenceAdapter.java(3 hunks)src/main/java/konkuk/thip/user/adapter/out/persistence/UserQueryPersistenceAdapter.java(2 hunks)src/main/java/konkuk/thip/user/adapter/out/persistence/repository/UserQueryRepository.java(1 hunks)src/main/java/konkuk/thip/user/adapter/out/persistence/repository/UserQueryRepositoryImpl.java(2 hunks)src/main/java/konkuk/thip/user/adapter/out/persistence/repository/following/FollowingQueryRepository.java(2 hunks)src/main/java/konkuk/thip/user/adapter/out/persistence/repository/following/FollowingQueryRepositoryImpl.java(5 hunks)src/main/java/konkuk/thip/user/application/mapper/FollowQueryMapper.java(1 hunks)src/main/java/konkuk/thip/user/application/mapper/UserQueryMapper.java(1 hunks)src/main/java/konkuk/thip/user/application/port/in/UserSearchUsecase.java(1 hunks)src/main/java/konkuk/thip/user/application/port/in/dto/UserSearchQuery.java(1 hunks)src/main/java/konkuk/thip/user/application/port/out/FollowingQueryPort.java(1 hunks)src/main/java/konkuk/thip/user/application/port/out/UserQueryPort.java(1 hunks)src/main/java/konkuk/thip/user/application/port/out/dto/UserQueryDto.java(1 hunks)src/main/java/konkuk/thip/user/application/service/UserSearchService.java(1 hunks)src/main/java/konkuk/thip/user/application/service/following/UserFollowService.java(2 hunks)src/main/java/konkuk/thip/user/application/service/following/UserGetFollowService.java(3 hunks)src/main/java/konkuk/thip/user/domain/Following.java(1 hunks)src/main/java/konkuk/thip/user/domain/User.java(1 hunks)src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchDeleteApiTest.java(1 hunks)src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchGetApiTest.java(1 hunks)src/test/java/konkuk/thip/user/adapter/in/web/UserSearchApiTest.java(1 hunks)src/test/java/konkuk/thip/user/domain/FollowingTest.java(1 hunks)
💤 Files with no reviewable changes (3)
- src/main/java/konkuk/thip/recentSearch/adapter/in/web/response/DummyResponse.java
- src/main/java/konkuk/thip/recentSearch/application/service/RecentSearchService.java
- src/main/java/konkuk/thip/recentSearch/application/port/in/DummyUseCase.java
🧰 Additional context used
🧠 Learnings (27)
📓 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/main/java/konkuk/thip/book/application/service/BookSearchService.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/user/adapter/in/web/UserQueryController.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/user/application/port/out/UserQueryPort.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/user/application/port/in/dto/UserSearchQuery.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/user/application/service/following/UserGetFollowService.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/user/application/port/in/UserSearchUsecase.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/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepository.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/recentSearch/application/port/out/RecentSearchQueryPort.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/user/application/mapper/UserQueryMapper.java (1)
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/recentSearch/application/service/RecentSearchDeleteService.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/recentSearch/application/RecentSearchQueryMapper.java (1)
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/recentSearch/application/service/manager/RecentSearchCreateManager.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/recentSearch/adapter/in/web/RecentSearchDeleteApiTest.java (1)
Learnt from: hd0rable
PR: #101
File: src/test/java/konkuk/thip/comment/adapter/in/web/CommentControllerTest.java:118-265
Timestamp: 2025-07-23T17:41:55.507Z
Learning: CommentControllerTest는 댓글 생성 API의 검증 로직과 예외 상황만을 테스트하는 단위 테스트이며, 성공 케이스는 별도의 통합 테스트(CommentCreateAPITest)에서 다룬다.
src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryPersistenceAdapter.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: 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/recentSearch/adapter/out/persistence/RecentSearchQueryPersistenceAdapter.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/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepositoryImpl.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/recentSearch/application/port/out/RecentSearchCommandPort.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/user/application/port/out/FollowingQueryPort.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/user/adapter/out/persistence/repository/following/FollowingQueryRepositoryImpl.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: 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/user/application/mapper/FollowQueryMapper.java (1)
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/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java (1)
Learnt from: seongjunnoh
PR: #36
File: src/main/java/konkuk/thip/user/adapter/out/persistence/UserJpaRepository.java:7-7
Timestamp: 2025-06-29T09:47:31.299Z
Learning: Spring Data JPA에서 findBy{FieldName} 패턴의 메서드는 명시적 선언 없이 자동으로 생성되며, Optional 반환 타입을 사용하는 것이 null 안전성을 위해 권장됩니다.
src/main/java/konkuk/thip/user/adapter/in/web/response/UserSearchResponse.java (1)
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/recentSearch/adapter/out/jpa/RecentSearchJpaEntity.java (1)
Learnt from: seongjunnoh
PR: #36
File: src/main/java/konkuk/thip/user/adapter/out/persistence/UserJpaRepository.java:7-7
Timestamp: 2025-06-29T09:47:31.299Z
Learning: Spring Data JPA에서 findBy{FieldName} 패턴의 메서드는 명시적 선언 없이 자동으로 생성되며, Optional 반환 타입을 사용하는 것이 null 안전성을 위해 권장됩니다.
src/main/java/konkuk/thip/user/adapter/out/persistence/UserQueryPersistenceAdapter.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/user/application/service/UserSearchService.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/recentSearch/adapter/in/web/response/RecentSearchGetResponse.java (1)
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 (8)
src/main/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchCommandController.java (1)
src/main/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchQueryController.java (1)
Tag(15-32)
src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchJpaRepository.java (3)
src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java (1)
Repository(19-62)src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchQueryPersistenceAdapter.java (1)
Repository(15-35)src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepositoryImpl.java (1)
Repository(15-49)
src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchDeleteApiTest.java (1)
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1)
TestEntityFactory(29-293)
src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryPersistenceAdapter.java (1)
src/main/java/konkuk/thip/common/util/DateUtil.java (1)
DateUtil(12-62)
src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchQueryPersistenceAdapter.java (2)
src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java (1)
Repository(19-62)src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepositoryImpl.java (1)
Repository(15-49)
src/test/java/konkuk/thip/user/adapter/in/web/UserSearchApiTest.java (1)
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1)
TestEntityFactory(29-293)
src/test/java/konkuk/thip/user/domain/FollowingTest.java (1)
src/test/java/konkuk/thip/user/application/service/UserFollowServiceTest.java (2)
Nested(38-100)Nested(102-151)
src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchGetApiTest.java (1)
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1)
TestEntityFactory(29-293)
⏰ 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 (57)
src/main/java/konkuk/thip/book/application/service/BookSearchService.java (1)
75-75: 메서드 시그니처 개선 승인
RecentSearch도메인 객체에 이미userId가 포함되어 있으므로 중복 파라미터를 제거한 것은 좋은 리팩토링입니다. DDD 원칙에 따라 도메인 객체 내에 데이터를 캡슐화하는 방식으로 개선되었습니다.src/main/java/konkuk/thip/user/application/port/out/UserQueryPort.java (1)
15-15: CQRS 컨벤션을 잘 따른 메서드 추가
UserQueryPort에 새로운 조회 메서드가 추가되었습니다. 메서드명이 기능을 명확히 표현하고 있으며, 학습된 CQRS 컨벤션에 따라 QueryPort에 조회 API 응답 데이터를 위한 메서드를 적절히 배치했습니다.src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/RecentSearchJpaEntity.java (1)
34-38: 도메인 객체로부터 엔티티 업데이트 메서드 추가
updateFrom메서드가 잘 구현되었습니다. 도메인 객체의 상태를 JPA 엔티티에 동기화하는 일반적인 패턴을 따르고 있으며, 필요한 필드들을 적절히 업데이트하고 있습니다.SearchType.from()변환과setModifiedAt()호출도 올바르게 구현되었습니다.src/main/java/konkuk/thip/user/application/port/in/UserSearchUsecase.java (1)
1-8: 사용자 검색 Use Case 인터페이스 잘 설계됨헥사고날 아키텍처 원칙을 잘 따른 깔끔한 인터페이스입니다. 사용자 검색 기능에 대한 명확한 계약을 정의하고 있으며, 적절한 입력/출력 타입을 사용하고 있습니다. 네이밍도 일관성 있게 잘 되어 있습니다.
src/main/java/konkuk/thip/recentSearch/application/port/in/RecentSearchGetUseCase.java (1)
1-9: LGTM! 인터페이스 설계가 적절합니다.Use Case 인터페이스가 올바르게 정의되어 있고, THIP 프로젝트의 CQRS 컨벤션에 따라 조회 API에서 web adapter의 response DTO를 직접 참조하는 것이 허용되므로 아키텍처적으로도 문제없습니다.
src/main/java/konkuk/thip/user/adapter/out/persistence/repository/UserQueryRepository.java (1)
11-11: LGTM! 검색 기능을 위한 메서드가 적절히 설계되었습니다.메서드명이 명확하고, 파라미터 구성도 합리적입니다. THIP 프로젝트의 CQRS 컨벤션에 따라 QueryRepository에서 DTO를 반환하는 것도 올바른 패턴입니다.
src/main/java/konkuk/thip/user/application/port/in/dto/UserSearchQuery.java (1)
3-11: LGTM! 잘 설계된 쿼리 DTO입니다.Record 클래스를 활용한 불변 DTO 설계가 적절하고, static factory method
of를 제공하여 가독성을 향상시킨 점이 좋습니다. 사용자 검색에 필요한 모든 파라미터가 포함되어 있습니다.src/main/java/konkuk/thip/user/application/mapper/UserQueryMapper.java (1)
9-14: LGTM! MapStruct 매퍼가 올바르게 구현되었습니다.Spring 컴포넌트 모델 설정이 적절하고, THIP 프로젝트의 조회 API 컨벤션에 따라 application 계층에서 web response DTO를 직접 참조하는 것이 허용됩니다. 매핑 방향을 명시한 주석도 좋습니다.
src/main/java/konkuk/thip/recentSearch/application/port/in/RecentSearchDeleteUseCase.java (1)
3-5: LGTM! 소유권 검증을 고려한 좋은 인터페이스 설계입니다.
recentSearchId와userId모두를 파라미터로 받아 삭제 권한을 검증할 수 있도록 설계한 점이 보안 측면에서 우수합니다.src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchJpaRepository.java (1)
5-8: LGTM! CQRS 패턴을 올바르게 적용했습니다.
@Repository어노테이션 추가와RecentSearchQueryRepository확장이 적절하게 구현되었습니다. 이를 통해 QueryDSL을 활용한 커스텀 쿼리 메서드들이 JPA 레포지토리와 통합되어 사용할 수 있게 되었습니다.src/main/java/konkuk/thip/user/domain/User.java (1)
40-45: 적절한 가시성 변경입니다.
decreaseFollowerCount()메서드의 가시성을 public으로 변경한 것이 적절합니다.UserFollowService에서 직접 호출할 수 있게 되어 코드가 더 명확해졌고, 기존의 유효성 검증 로직(팔로워 수가 0일 때 예외 발생)도 그대로 유지되어 안전합니다.src/main/java/konkuk/thip/recentSearch/domain/RecentSearch.java (3)
7-7: 필요한 import 추가가 적절합니다.
LocalDateTimeimport가updateModifiedAt메서드에서 사용되므로 올바르게 추가되었습니다.
21-27: 잘 설계된 정적 팩토리 메서드입니다.
withoutId정적 팩토리 메서드가 적절하게 구현되었습니다. ID 없이 새로운 최근 검색어 엔티티를 생성하는 명확한 의도를 보여주며, 도메인 객체 생성의 일관성을 제공합니다.
29-31: 수정 시간 업데이트 메서드가 적절합니다.
updateModifiedAt메서드가 올바르게 구현되었습니다.BaseDomainEntity의 setter를 활용하여 수정 시간을 업데이트하는 방식이 적절하며, 최근 검색어의 타임스탬프 관리 요구사항을 잘 충족합니다.src/main/java/konkuk/thip/user/adapter/out/persistence/UserQueryPersistenceAdapter.java (2)
4-4: import 정리가 적절합니다.새로운 기능에 필요한 import들이 올바르게 추가되었습니다.
Also applies to: 7-7, 11-11
36-39: CQRS 패턴을 올바르게 따른 구현입니다.
findUsersByNicknameOrderByAccuracy메서드가 적절하게 구현되었습니다. QueryPort에서 API 응답용 데이터를 조회하는 메서드로, 학습된 CQRS 컨벤션을 잘 따르고 있습니다. 레포지토리로의 단순한 위임도 적절합니다.src/main/java/konkuk/thip/user/application/port/out/dto/UserQueryDto.java (2)
8-14: record 이름 변경과 필드 추가가 적절합니다.
FollowQueryDto에서UserQueryDto로의 이름 변경이 더 명확한 의미를 제공합니다.aliasColor필드 추가로 사용자 정보를 더 풍부하게 표현할 수 있게 되었습니다.
22-22: 적절한 유효성 검증 추가입니다.새로운
aliasColor필드에 대한 null 체크가 다른 필드들과 일관성 있게 추가되었습니다.src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java (2)
26-28: 새로운 사용자 검색 API 문서화가 적절합니다.사용자 검색 기능에 대한 Swagger 응답 설명이 올바르게 추가되었습니다.
USER_NOT_FOUND에러 코드와의 연결이 적절합니다.
197-201: 최근 검색어 삭제 API 문서화가 완전합니다.최근 검색어 삭제 기능에 대한 에러 시나리오들이 모두 문서화되었습니다. 권한 검증(
RECENT_SEARCH_NOT_ADDED_BY_USER)과 존재하지 않는 검색어(RECENT_SEARCH_NOT_FOUND) 에러 케이스가 적절히 포함되어 있습니다.src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (1)
75-76: 최근 검색어 관련 에러 코드가 적절히 정의되었습니다.새로 추가된 에러 코드들이 올바르게 구현되었습니다:
RECENT_SEARCH_NOT_FOUND: 404 상태 코드 사용이 적절함RECENT_SEARCH_NOT_ADDED_BY_USER: 권한 관련 비즈니스 로직 위반에 대해 400 상태 코드 사용이 적절함- 에러 코드 번호 체계(90001, 90002)와 한국어 메시지가 기존 패턴과 일관성 있음
src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/SearchType.java (2)
13-15: 검색 타입 확장과 설명 개선이 잘 되었습니다.
ROOM_SEARCH추가로 방 검색 기능을 지원합니다.BOOK_SEARCH설명에서 언더스코어 제거(책_검색→책 검색)로 일관성이 개선되었습니다.
4-4: 예외 타입 변경이 의미적으로 더 적절합니다.
BusinessException에서InvalidStateException으로 변경한 것이 enum 검증 실패에 더 적합합니다. 잘못된 상태나 입력값에 대한 예외 처리가 더 명확해졌습니다.Also applies to: 29-29
src/main/java/konkuk/thip/user/application/service/UserSearchService.java (1)
22-35: 사용자 검색과 최근 검색어 저장의 통합이 잘 구현되었습니다.구현이 올바르게 되어 있습니다:
@Transactional어노테이션으로 검색과 최근 검색어 저장 간의 일관성 보장- 키워드 소문자 변환으로 검색 정규화 처리
RecentSearchCreateManager를 통한 최근 검색어 저장 로직 분리트랜잭션 범위가 적절히 설정되어 사용자 검색 결과 반환과 최근 검색어 저장이 원자적으로 처리됩니다.
src/main/java/konkuk/thip/user/application/service/following/UserGetFollowService.java (1)
7-7: 타입 변경이 일관되게 적용되었습니다.
FollowQueryDto에서UserQueryDto로의 타입 변경이 import문부터 메서드 반환 타입, 매퍼 호출까지 일관되게 적용되어 좋습니다. CQRS 컨벤션에 맞게 Query API에서 response DTO를 직접 참조하는 방식도 적절합니다.Also applies to: 32-34, 37-37, 52-54, 57-57
src/main/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchCommandController.java (1)
16-31: 최근 검색어 삭제 API가 적절히 구현되었습니다.RESTful 원칙을 따른 DELETE 엔드포인트 구현이 좋습니다. Swagger 문서화, 경로 변수 처리, 사용자 인증 처리가 모두 적절하게 되어 있으며, 비즈니스 로직을 서비스 계층에 위임하는 구조도 올바릅니다.
src/main/java/konkuk/thip/recentSearch/adapter/in/web/response/RecentSearchGetResponse.java (1)
5-17: 응답 DTO 구조가 적절합니다.Record 클래스를 사용한 불변 응답 DTO 구현이 좋습니다. 중첩된
RecentSearchDto와 정적 팩토리 메서드of()를 통해 명확하고 간결한 구조를 제공합니다.src/main/java/konkuk/thip/user/adapter/in/web/response/UserFollowersResponse.java (1)
9-9: DTO 구조 개선이 적절합니다.
Follower에서FollowerDto로의 네이밍 변경과aliasColor필드 추가가 일관되게 적용되었습니다. DTO 네이밍 컨벤션을 따르고 있어 좋습니다.Also applies to: 14-20
src/main/java/konkuk/thip/user/adapter/in/web/response/UserFollowingResponse.java (1)
9-9: LGTM! DTO 구조 개선과 기능 확장중첩 레코드 이름을
Following에서FollowingDto로 변경하여 명확성을 높이고,aliasColor필드를 추가하여 사용자 별칭 색상 정보를 지원하도록 개선되었습니다.Also applies to: 14-20
src/main/java/konkuk/thip/recentSearch/application/RecentSearchQueryMapper.java (1)
1-17: LGTM! Query API 컨벤션에 부합하는 매퍼 구현application 계층에서 adapter.in.web.response 패키지의 response DTO를 직접 참조하고 있는데, 이는 학습된 THIP 프로젝트 컨벤션에 따라 Query API(조회 API)에서는 허용되는 방식입니다. MapStruct 매퍼도 적절하게 구현되었습니다.
src/main/java/konkuk/thip/user/adapter/in/web/response/UserSearchResponse.java (1)
1-21: LGTM! 잘 구조화된 response DTO사용자 검색 결과를 담는 response DTO가 잘 구조화되어 있고, static factory 메서드도 적절하게 제공되어 있습니다. 필드들도 사용자 검색 기능에 필요한 정보들을 적절히 포함하고 있습니다.
src/main/java/konkuk/thip/user/adapter/in/web/UserQueryController.java (1)
111-122: LGTM! 잘 구현된 사용자 검색 엔드포인트새로운 사용자 검색 엔드포인트가 잘 구현되었습니다:
- 적절한 파라미터 검증 (
@NotBlank,@Min,@Max)- 사용자 ID 자동 주입
- 명확한 API 문서화
- 적절한 응답 구조
src/main/java/konkuk/thip/recentSearch/application/service/RecentSearchGetService.java (1)
1-33: LGTM! 깔끔한 서비스 구현입니다.최근 검색어 조회 로직이 CQRS 패턴을 올바르게 따르고 있으며, 읽기 전용 트랜잭션 설정과 의존성 주입이 적절히 구현되어 있습니다.
src/main/java/konkuk/thip/user/domain/Following.java (1)
30-37: 도메인 로직이 잘 구현되었습니다.팔로우 상태 검증 로직을 static 메서드로 분리하여 부작용 없는 검증을 수행하고, 명확한 예외 처리와 반환값을 제공합니다. 도메인 주도 설계 원칙에 잘 부합합니다.
src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java (1)
17-18: CQRS 컨벤션 준수 여부를 확인해주세요.학습된 THIP 프로젝트 컨벤션에 따르면, CommandPort에 findByXXX 메서드를 추가하는 것이 원칙입니다. 현재 QueryPort를 사용하고 있는데, RecentSearchCommandPort에 findRecentSearchByKeywordAndUserId 메서드를 추가하는 것이 컨벤션에 더 부합할 것 같습니다.
src/main/java/konkuk/thip/user/application/service/following/UserFollowService.java (1)
37-47: 리팩토링이 잘 수행되었습니다.도메인 엔티티의 검증 메서드를 활용하여 서비스 로직이 간소화되고, 팔로우/언팔로우 분기 처리가 명확해졌습니다. 하드 삭제 방식으로의 변경도 적절히 반영되어 있습니다.
src/main/java/konkuk/thip/user/application/mapper/FollowQueryMapper.java (1)
5-13: DTO 타입 변경이 적절히 반영되었습니다.UserQueryDto로의 타입 변경과 메서드명 개선이 일관성 있게 적용되어 코드 가독성이 향상되었습니다. THIP 프로젝트의 Query API 컨벤션도 올바르게 준수하고 있습니다.
src/main/java/konkuk/thip/user/application/port/out/FollowingQueryPort.java (1)
4-8: LGTM! DTO 타입 리팩토링이 올바르게 적용되었습니다.
FollowQueryDto에서UserQueryDto로의 타입 변경이 일관성 있게 적용되었고, CQRS QueryPort 컨벤션을 잘 따르고 있습니다.src/main/java/konkuk/thip/user/adapter/out/persistence/repository/UserQueryRepositoryImpl.java (2)
24-24: 필드명 변경이 일관성을 개선했습니다.
jpaQueryFactory에서queryFactory로의 변경으로 코드 일관성이 향상되었습니다.
42-73: 사용자 검색 로직이 잘 설계되었습니다.닉네임 검색의 정확도 기반 정렬 로직이 우수합니다:
- 정확한 일치 > 접두사 일치 > 부분 일치 순서의 우선순위
- 자기 자신 제외 및 활성 사용자만 포함하는 적절한 필터링
- alias 테이블과의 LEFT JOIN으로 완전한 사용자 정보 제공
구현이 QueryDSL의 장점을 잘 활용하고 있으며, 사용자 경험을 고려한 정렬 순서가 적절합니다.
src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchGetApiTest.java (2)
1-37: 테스트 클래스 구조가 잘 설계되어 있습니다.통합 테스트를 위한 적절한 어노테이션과 임포트를 사용하고 있으며, 한글 DisplayName도 프로젝트 컨벤션을 잘 따르고 있습니다.
84-105: 테스트 메서드가 포괄적이고 정확합니다.API 응답과 데이터베이스 상태를 모두 검증하는 것은 좋은 테스트 관행입니다. 5개 제한과 정렬 순서 검증이 적절히 구현되어 있습니다.
src/main/java/konkuk/thip/recentSearch/application/port/out/RecentSearchCommandPort.java (2)
4-18: CQRS Command Port 설계가 우수합니다.
findById의 Optional 반환과getByIdOrThrow기본 메서드 제공은 좋은 설계 패턴입니다. 학습된 CQRS 컨벤션을 잘 따르고 있으며, 일관된 예외 처리를 제공합니다.
20-24: 완전한 CRUD 작업을 제공하는 명확한 메서드 시그니처입니다.
save메서드에서 userId 매개변수 제거는 도메인 객체에 사용자 정보가 포함되어 있다면 적절한 설계입니다. 전체적으로 Command 패턴 컨벤션을 잘 따르고 있습니다.src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepositoryImpl.java (2)
1-34: QueryDSL 구현이 우수하고 적절한 필터링을 제공합니다.여러 조건을 사용한 where 절과 ACTIVE 상태 필터링이 적절합니다. Optional 반환은 null-safe 처리를 위한 좋은 관행입니다.
36-49: 최근 검색어 조회 요구사항을 정확히 구현했습니다.
modifiedAt기준 내림차순 정렬과 5개 제한, ACTIVE 상태 필터링이 비즈니스 요구사항에 맞게 구현되어 있습니다.SearchType.from()변환도 타입 안전성을 보장합니다.src/main/java/konkuk/thip/user/adapter/out/persistence/repository/following/FollowingQueryRepository.java (1)
4-4: DTO 타입 통합 리팩토링이 일관되게 적용되었습니다.
FollowQueryDto에서UserQueryDto로의 변경이 import문과 메서드 반환 타입에 일관되게 적용되어 있습니다. 사용자 관련 DTO 통합 리팩토링의 일부로 보이며 적절합니다.Also applies to: 13-14
src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchDeleteApiTest.java (3)
1-72: 테스트 설정이 포괄적인 시나리오 검증을 위해 잘 구성되었습니다.두 명의 사용자(요청자, 다른유저)를 생성하여 소유권 검증 테스트를 가능하게 하는 설계가 우수합니다. 테스트 데이터가 최소한이지만 충분합니다.
74-86: 성공 시나리오 테스트가 적절히 구현되었습니다.하드 삭제 방식을 DB 상태 확인으로 검증하는 것이 적절하며, 테스트가 명확하고 집중적입니다.
88-113: 오류 시나리오 테스트가 보안과 오류 처리를 포괄적으로 검증합니다.소유권 검증 테스트는 무단 삭제를 방지하기 위해 특히 중요하며, HTTP 응답과 데이터베이스 상태를 모두 검증하는 것이 적절합니다. 오류 코드 검증도 정확합니다.
src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryPersistenceAdapter.java (1)
5-5: DTO 통합화 작업이 적절히 수행되었습니다.
FollowQueryDto에서UserQueryDto로의 전환이 일관성 있게 적용되었으며, 기존 로직에는 변경이 없어 안전한 리팩토링입니다.Also applies to: 21-21, 23-23, 33-33, 35-35
src/main/java/konkuk/thip/user/adapter/out/persistence/repository/following/FollowingQueryRepositoryImpl.java (1)
10-11: DTO 변경과 새로운 필드 추가가 올바르게 구현되었습니다.
UserQueryDto로의 전환과 함께alias.color필드가 QueryDSL projection에 적절히 추가되었습니다. 쿼리 로직과 조인 구조도 기존과 동일하게 유지되어 안전합니다.Also applies to: 40-40, 50-50, 59-59, 75-83
src/test/java/konkuk/thip/user/domain/FollowingTest.java (1)
12-70: 도메인 로직 분리에 따른 테스트 구조 개선이 잘 되었습니다.기존의 엔티티 직접 조작 방식에서 정적 메서드
validateFollowingState를 테스트하는 방식으로 변경되어, 테스트가 더 명확하고 도메인 로직에 집중할 수 있게 되었습니다. 예외 처리와 반환값 검증도 적절합니다.src/test/java/konkuk/thip/user/adapter/in/web/UserSearchApiTest.java (1)
64-93: 테스트 승인 – 검색 결과 순서 보장 여부 확인 필요통합 테스트 구조가 명확하고, API 응답과 DB 상태를 모두 검증하고 있어 좋습니다.
다만src/test/java/konkuk/thip/user/adapter/in/web/UserSearchApiTest.java(Lines 64–93)에서 검색 결과 순서(thipalpha,thipbeta,thipgamma,123thip)를 하드코딩하여 검증하고 있는데,
실제 검색 로직이 해당 순서를 보장하는지 꼭 확인해주세요.
- 확인 대상: 검색 서비스 구현부의 정렬 처리 로직
- 검증 방법: 코드 내 메서드 이름이나 쿼리 정렬 조건(예:
ORDER BY)을 찾아 실제 보장 여부 확인src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java (3)
29-32: 적절한 findById 구현입니다.Optional을 반환하여 null 안전성을 보장하고, 도메인 엔티티로의 매핑도 올바르게 처리되었습니다.
35-44: save 메서드 시그니처 개선이 좋습니다.도메인 객체에서 직접 userId를 추출하는 방식으로 변경되어 더 명확해졌습니다. 사용자 존재 여부 검증도 적절합니다.
47-61: 서비스 계층에서 권한 검증 로직 위치 확인 필요
delete/update호출 전 현재 로그인한 사용자가 해당 최근 검색어를 추가한 주체인지 검증하는 예외 처리(RECENT_SEARCH_NOT_ADDED_BY_USER)가 서비스 계층에서 실제로 구현되어 있는지 확인해주세요.– 점검 대상(예시)
• src/main/java/konkuk/thip/recentSearch/application/RecentSearchCommandServiceImpl.java
• 해당 클래스의delete(…)및update(…)메서드 내부에 권한 검사 및 예외 발생 로직 존재 여부위 위치에 권한 검증 로직이 없다면 서비스 계층에 추가 구현이 필요합니다.
| @Setter | ||
| private LocalDateTime modifiedAt; |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
modifiedAt 필드에 대한 Setter 추가 검토
modifiedAt 필드에 @Setter를 추가한 것은 최근 검색어 기능에서 타임스탬프를 수동으로 업데이트하기 위해 필요한 변경으로 보입니다. 하지만 기본 엔티티에 setter를 노출하는 것은 의도하지 않은 부작용을 일으킬 수 있습니다.
다음 스크립트로 setModifiedAt 메서드의 사용처를 확인해보겠습니다:
🏁 Script executed:
#!/bin/bash
# setModifiedAt 메서드 사용처 검증
rg -A 3 "setModifiedAt|updateModifiedAt" --type javaLength of output: 1364
modifiedAt 필드 Setter 사용처 검증 및 액세스 제한 제안
검증 결과 setModifiedAt 메서드는 아래 세 곳에서만 호출됩니다:
RecentSearch#updateModifiedAt(LocalDateTime)(도메인 로직)RecentSearchCreateManager(서비스 레이어에서 수정일시 갱신)RecentSearchJpaEntity(도메인 → JPA 엔티티 매핑)
현재 의도된 기능 외에 다른 호출은 발견되지 않았으므로, 직접적인 부작용 우려는 낮아 보입니다.
다만, 불필요한 외부 노출을 줄이기 위해 다음과 같이 접근 수준을 제한할 것을 권장합니다:
--- a/src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java
+++ b/src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java
@@
- @Setter
+ @Setter(AccessLevel.PROTECTED)
private LocalDateTime modifiedAt;이렇게 수정하면 서브클래스(JPA 엔티티 및 도메인 로직)에서만 Setter가 노출되어 캡슐화 수준을 높일 수 있습니다.
📝 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.
| @Setter | |
| private LocalDateTime modifiedAt; | |
| @Setter(AccessLevel.PROTECTED) | |
| private LocalDateTime modifiedAt; |
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java around lines 15
to 16, the setter for the modifiedAt field is currently public but is only used
within domain logic, service layer, and JPA entity mapping. To improve
encapsulation and reduce unnecessary external access, change the setter's access
modifier from public to protected. This limits the setter's visibility to
subclasses, maintaining necessary access while preventing broader exposure.
| public BaseResponse<RecentSearchGetResponse> showRecentSearches( | ||
| @Parameter(description = "최근 검색어 유형 (사용자 검색 : USER / 방 검색 : ROOM / 책 검색 : BOOK)", example = "USER") | ||
| @RequestParam(value = "type") String type, | ||
| @Parameter(hidden = true) @UserId final Long userId | ||
| ) { | ||
| return BaseResponse.ok(recentSearchGetUseCase.getRecentSearches(type, userId)); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
타입 파라미터에 대한 검증이 필요합니다.
type 파라미터에 대한 검증이 없어 잘못된 값이 전달될 수 있습니다. RecentSearchType enum의 유효한 값들로만 제한하는 것이 좋겠습니다.
다음과 같이 검증을 추가하는 것을 제안합니다:
@GetMapping("/recent-searches")
public BaseResponse<RecentSearchGetResponse> showRecentSearches(
@Parameter(description = "최근 검색어 유형 (사용자 검색 : USER / 방 검색 : ROOM / 책 검색 : BOOK)", example = "USER")
- @RequestParam(value = "type") String type,
+ @RequestParam(value = "type") @Pattern(regexp = "^(USER|ROOM|BOOK)$", message = "유효하지 않은 검색 타입입니다") String type,
@Parameter(hidden = true) @UserId final Long userId
) {📝 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.
| public BaseResponse<RecentSearchGetResponse> showRecentSearches( | |
| @Parameter(description = "최근 검색어 유형 (사용자 검색 : USER / 방 검색 : ROOM / 책 검색 : BOOK)", example = "USER") | |
| @RequestParam(value = "type") String type, | |
| @Parameter(hidden = true) @UserId final Long userId | |
| ) { | |
| return BaseResponse.ok(recentSearchGetUseCase.getRecentSearches(type, userId)); | |
| } | |
| @GetMapping("/recent-searches") | |
| public BaseResponse<RecentSearchGetResponse> showRecentSearches( | |
| @Parameter(description = "최근 검색어 유형 (사용자 검색 : USER / 방 검색 : ROOM / 책 검색 : BOOK)", example = "USER") | |
| @RequestParam(value = "type") | |
| @Pattern(regexp = "^(USER|ROOM|BOOK)$", message = "유효하지 않은 검색 타입입니다") | |
| String type, | |
| @Parameter(hidden = true) @UserId final Long userId | |
| ) { | |
| return BaseResponse.ok(recentSearchGetUseCase.getRecentSearches(type, userId)); | |
| } |
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchQueryController.java
around lines 24 to 30, the 'type' request parameter lacks validation and can
accept invalid values. To fix this, validate the 'type' parameter against the
RecentSearchType enum by converting the input string to the enum and handling
invalid values appropriately, such as throwing an exception or returning a bad
request response. This ensures only valid enum values are processed.
...va/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchQueryPersistenceAdapter.java
Show resolved
Hide resolved
...konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepository.java
Outdated
Show resolved
Hide resolved
|
|
||
| public interface RecentSearchQueryPort { | ||
|
|
||
| Optional<RecentSearch> findRecentSearchByKeywordAndUserId(String keyword, Long userId); |
There was a problem hiding this comment.
CQRS 컨벤션 위반: QueryPort에서 도메인 엔티티 반환
학습된 THIP 프로젝트 컨벤션에 따르면, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가해야 합니다. 현재 도메인 엔티티 RecentSearch를 반환하고 있어 컨벤션에 맞지 않습니다.
QueryPort에서는 response DTO를 반환하거나, 이 메서드를 CommandPort로 이동시키는 것을 고려해보세요.
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/recentSearch/application/port/out/RecentSearchQueryPort.java
at line 10, the method returns the domain entity RecentSearch, violating the
CQRS convention that QueryPort methods should return response DTOs. To fix this,
change the method to return a DTO representing the query response instead of the
domain entity, or move this method to the CommandPort if it fits better there.
|
|
||
| Optional<RecentSearch> findRecentSearchByKeywordAndUserId(String keyword, Long userId); | ||
|
|
||
| List<RecentSearch> findRecentSearchesByTypeAndUserId(String type, Long userId); |
There was a problem hiding this comment.
CQRS 컨벤션 위반: QueryPort에서 도메인 엔티티 반환
이 메서드도 도메인 엔티티 RecentSearch를 반환하고 있어 THIP 프로젝트의 CQRS 컨벤션에 맞지 않습니다.
응답용 DTO를 반환하거나 CommandPort로 이동시키는 것을 검토해주세요.
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/recentSearch/application/port/out/RecentSearchQueryPort.java
at line 12, the method returns the domain entity RecentSearch, violating the
CQRS convention. Refactor this method to return a response DTO instead of the
domain entity, or consider moving this method to a CommandPort if it involves
state changes. Define an appropriate DTO that contains only the necessary data
for queries and update the method signature accordingly.
seongjunnoh
left a comment
There was a problem hiding this comment.
궁금한 점 및 변경 요청 사항에 대해 리뷰 달았습니다! 확인부탁드립니다!
| @Operation(summary = "최근 검색어 조회", description = "사용자의 최근 검색어를 조회합니다. 최신순으로 최대 5개까지 조회됩니다.") | ||
| @GetMapping("/recent-searches") | ||
| public BaseResponse<RecentSearchGetResponse> showRecentSearches( | ||
| @Parameter(description = "최근 검색어 유형 (사용자 검색 : USER / 방 검색 : ROOM / 책 검색 : BOOK)", example = "USER") |
| @Getter | ||
| public enum RecentSearchType { | ||
|
|
||
| USER_SEARCH("USER","사용자 검색"), | ||
| BOOK_SEARCH("BOOK","책 검색"), | ||
| ROOM_SEARCH("ROOM","방 검색"); |
There was a problem hiding this comment.
p2 : recentSearch/adapter/out/jpa/SearchType 존재하는데, 해당 enum 클래스를 새로 추가하신 이유가 있나요??
There was a problem hiding this comment.
앞 pr에서 코멘트 주신것 확인했습니다! service에서 adapter/out 의 의존성을 가지는것이 적절하지 않다고 생각하시는 건 동의하지만, 이렇게 사실 1개인 enum을 도메인 <-> 영속성 을 분리하여 생각한다면 수정 발생 시 일을 2배로 해야하니
enum에 대해서는 유연하게 서비스에서의 의존성을 허용하는건 어떨까 싶습니다!
enum은 runtime 시점에 변경이 일어나는 것은 아니니 문제될 건 없다고 생각합니다!
There was a problem hiding this comment.
넵넵 그렇네요! 너무 의존성에 집착하지 말아야겠어요 하핳
...va/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchQueryPersistenceAdapter.java
Show resolved
Hide resolved
...konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepository.java
Outdated
Show resolved
Hide resolved
| recentSearchJpaEntity.status.eq(ACTIVE) | ||
| ) | ||
| .orderBy(recentSearchJpaEntity.modifiedAt.desc()) | ||
| .limit(5) |
There was a problem hiding this comment.
p3 : 몇 개를 찾는다 라는 것을 기존에는 전부 서비스에서 지정했었는데, 여기서도 마찬가지로 5개를 조회한다는 것을 서비스에서 지정하는건 어떨까 싶습니다! 코드 통일성 + 유지보수 용이 의 이점이 있을 것 같습니다!
| @Mapping(source = "id", target = "recentSearchId") | ||
| RecentSearchGetResponse.RecentSearchDto toDto(RecentSearch recentSearch); | ||
|
|
||
| List<RecentSearchGetResponse.RecentSearchDto> toResponseList(List<RecentSearch> recentSearchQueryDtos); |
There was a problem hiding this comment.
오호 list끼리 바로 매핑 해주나 보네요! 저도 참고하겠습니다!
| existingRecentSearch -> { | ||
| // 이미 존재하면 createdAt만 갱신 | ||
| existingRecentSearch.updateModifiedAt(LocalDateTime.now()); | ||
| recentSearchCommandPort.update(existingRecentSearch); | ||
| }, |
There was a problem hiding this comment.
p2 : 지금 이미 존재하는 최근검색어에 대해서 modifiedAt 값을 변경하기 위해
- 도메인 엔티티의 modifiedAt 변경
- 영속성 adapter의 update 메서드 호출
- update 메서드 내부에서 도메인 엔티티에 해당하는 최근 검색어 jpa entity 조회 (findById)
- 도메인 엔티티 -> jpa entity 로의 업데이트 (updateFrom 메서드 활용)
- 업데이트한 jpa entity save
하는 로직인데,
modifiedAt 은 BaseJpaEntity 에 명시되어 있듯 AuditingEntityListener 에 의해 영속성 컨텍스트가 flush 될때마다 modifiedAt 필드가 NOW() 로 업데이트가 되긴 합니다.
따라서 아래와 같이 코드를 수정하는건 어떤가 제안드립니다!
@Service
@RequiredArgsConstructor
public class RecentSearchCreateManager {
private final RecentSearchCommandPort recentSearchCommandPort;
private final RecentSearchQueryPort recentSearchQueryPort;
public void saveRecentSearchByUser(Long userId, String keyword) {
recentSearchQueryPort.findRecentSearchByKeywordAndUserId(keyword, userId)
.ifPresentOrElse(
recentSearchCommandPort::update, // Auditing으로 modifiedAt 자동 반영
() -> recentSearchCommandPort.save(
RecentSearch.withoutId(keyword, SearchType.USER_SEARCH.getSearchType(), userId) // 여기서도 enum.value 를 주입하는 식으로 수정하는 건 어떤가요??
)
);
}
}
public RecentSearchCommand영속성adapter {
,,,
@Override
public void update(RecentSearch recentSearch) {
RecentSearchJpaEntity entity = recentSearchJpaRepository.findById(recentSearch.getId())
.orElseThrow(() -> new EntityNotFoundException(RECENT_SEARCH_NOT_FOUND));
// 필드 수정 없이 save 호출만으로 Auditing이 modifiedAt을 최신화
recentSearchJpaRepository.save(entity);
}
}위와 같이 update 메서드를 수정하여도
- findById로 최근 검색어 jpa entity 조회 -> 영속성 컨택스트에 올라감
- 곧바로 jpa entity save -> 영속성 컨택스트에 존재하는 jpa entity와 병합
- 트랜잭션 종료 이후 영속성 컨택스트의 dirty checking 과정에서 modifiedAt 필드의 변경이 감지되어서 update sql 날라감
(-> update 메서드가 하나의 트랜잭션 범위에서 실행될 경우의 동작입니다. update 메서드에 트랜잭션이 걸려있지 않으면 findById 메서드로 조회한 jpa entity는 반환 즉시 영속성 detached 상태가 되는 것으로 알고 있습니다.)
이런 플로우로 동작하므로 문제없이 modifiedAt 값이 수정될 것 같습니다! (직접 해보지는 않았습니다ㅎ)
- 만약 update 메서드에 트랜잭션이 걸리지 않더라도 save 메서드 실행시 쓰기용 트랜잭션이 열리는데, 이때 JPA가 detached 엔티티를 전달받아 위와 동일한 작업(= merge -> auditing -> dirty checking -> update sql)을 수행한다고 합니다
There was a problem hiding this comment.
오호 처음 필드 수정 없이도 modifiedAt이 수정이 되는군요!! @seongjunnoh 음 근데 혹시 성준님이 추천하신 방법이 현재 제 코드에서 성능적인 이점이 있나요? 제가 생각했을 때 현재 제 코드에서 updateModifiedAt이 실제로 동작하지 않더라도 기존에 저희가 필드를 수정했던 것처럼 modifiedAt 필드를 업데이트한다는 것을 명시적으로 작성해두면 나중에 해당 코드를 읽을 때 더 수월하게 읽히지 않을까 싶은데,,,
물론 성능적인 이점이 있다면 성준님의 방식이 더 좋은 방법인 것 같습니다!!
There was a problem hiding this comment.
음 굳이 도메인 -> jpa 로의 매핑 과정이 없다 라는 점에서 성능적이 이점이 있지 않나 싶습니다!!
기존 코드에서도 마찬가지로 현준님이 임의로 변경하신 modifiedAt 값이 아니라, BaseJpaEntity 에서 modifiedAt을 현재 시각으로 업데이트 해주는데, 이러면 영속성 adapter 의 update 메서드 내부에서 도메인 -> jpa 의 매핑 과정은 필요없는 과정이지 않나 라는 생각입니다
There was a problem hiding this comment.
가독성을 생각한다면 또 다른 관점인 거 같은데, 저는 Auditing 을 활용하는 이상 createdAt, modifiedAt 의 책임은 저 친구들에게 몰아주도록 통일하는 것에서 얻는 이점이 더 크지 않나 라고 생각합니다!
There was a problem hiding this comment.
코드 적용해보니 findById 후 바로 save()만 하는 경우에는 영속성 컨텍스트에서 변경 사항이 없어서 dirty checking이 일어나지 않아 modifiedAt이 갱신되지 않는 것 같아요 ㅠ 특정 필드 변경이 있어야 update 쿼리와 함께 Auditing이 동작하는 것 같습니다! 이 부분은 좀더 찾아봐야될 것 같네요 🤔
There was a problem hiding this comment.
그러게요 dirty checking 이 auditing 보다 선행작업인 거 같네요 흠 jpa 는 알다가도 모르겠는
|
@seongjunnoh 리뷰 반영했습니다! (update관련해서는 리뷰 답변 남겨주시면 감사하겠습니다!!)
|
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
src/main/java/konkuk/thip/recentSearch/application/port/out/RecentSearchQueryPort.java (1)
11-11: CQRS 컨벤션 위반: QueryPort에서 도메인 엔티티 반환이전 리뷰에서 지적된 내용과 동일한 문제가 여전히 존재합니다. THIP 프로젝트의 CQRS 컨벤션에 따르면 QueryPort는 응답 DTO를 반환해야 하는데, 현재 도메인 엔티티
RecentSearch를 반환하고 있습니다.QueryPort에서는 response DTO를 반환하거나, 도메인 엔티티가 필요한 경우 해당 메서드들을 CommandPort로 이동시키는 것을 검토해주세요.
Also applies to: 13-13
🧹 Nitpick comments (1)
src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/RecentSearchType.java (1)
23-30: 성능 최적화 제안: Map 기반 조회로 개선현재 구현은 올바르게 작동하지만, 향후 열거형 값이 추가될 경우를 고려하여 Map 기반 조회를 사용하는 것을 권장합니다.
+import java.util.Map; +import java.util.stream.Collectors; +import java.util.Arrays; @Getter public enum RecentSearchType { // ... enum constants ... + private static final Map<String, RecentSearchType> TYPE_MAP = + Arrays.stream(values()) + .collect(Collectors.toMap(RecentSearchType::getSearchType, type -> type)); public static RecentSearchType from(String searchType) { - for (RecentSearchType type : RecentSearchType.values()) { - if (type.getSearchType().equals(searchType)) { - return type; - } - } - throw new InvalidStateException(INVALID_SEARCH_TYPE); + RecentSearchType type = TYPE_MAP.get(searchType); + if (type == null) { + throw new InvalidStateException(INVALID_SEARCH_TYPE); + } + return type; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
src/main/java/konkuk/thip/book/application/service/BookSearchService.java(2 hunks)src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/RecentSearchJpaEntity.java(1 hunks)src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/RecentSearchType.java(1 hunks)src/main/java/konkuk/thip/recentSearch/adapter/out/mapper/RecentSearchMapper.java(2 hunks)src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchQueryPersistenceAdapter.java(2 hunks)src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepository.java(1 hunks)src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepositoryImpl.java(2 hunks)src/main/java/konkuk/thip/recentSearch/application/port/out/RecentSearchQueryPort.java(1 hunks)src/main/java/konkuk/thip/recentSearch/application/service/RecentSearchGetService.java(1 hunks)src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java(2 hunks)src/main/java/konkuk/thip/recentSearch/domain/RecentSearch.java(2 hunks)src/main/java/konkuk/thip/user/application/service/UserSearchService.java(2 hunks)src/test/java/konkuk/thip/book/adapter/in/web/BookQueryControllerTest.java(3 hunks)src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchDeleteApiTest.java(1 hunks)src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchGetApiTest.java(1 hunks)src/test/java/konkuk/thip/user/adapter/in/web/UserSearchApiTest.java(2 hunks)
✅ Files skipped from review due to trivial changes (2)
- src/test/java/konkuk/thip/user/adapter/in/web/UserSearchApiTest.java
- src/test/java/konkuk/thip/book/adapter/in/web/BookQueryControllerTest.java
🚧 Files skipped from review as they are similar to previous changes (11)
- src/main/java/konkuk/thip/book/application/service/BookSearchService.java
- src/main/java/konkuk/thip/user/application/service/UserSearchService.java
- src/main/java/konkuk/thip/recentSearch/application/service/RecentSearchGetService.java
- src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java
- src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchDeleteApiTest.java
- src/main/java/konkuk/thip/recentSearch/domain/RecentSearch.java
- src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepository.java
- src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchGetApiTest.java
- src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/RecentSearchJpaEntity.java
- src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepositoryImpl.java
- src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchQueryPersistenceAdapter.java
🧰 Additional context used
🧠 Learnings (1)
src/main/java/konkuk/thip/recentSearch/application/port/out/RecentSearchQueryPort.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/main/java/konkuk/thip/recentSearch/adapter/out/jpa/RecentSearchType.java (1)
src/main/java/konkuk/thip/recentSearch/domain/RecentSearch.java (1)
Getter(10-33)
🔇 Additional comments (2)
src/main/java/konkuk/thip/recentSearch/adapter/out/mapper/RecentSearchMapper.java (1)
14-14: 매핑 로직 개선을 통한 코드 단순화
SearchType을 거치지 않고RecentSearchType열거형을 직접 할당하도록 변경한 것이 좋습니다. 이는 중간 변환 과정을 제거하여 코드를 더욱 명확하고 유지보수하기 쉽게 만듭니다.Also applies to: 23-23
src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/RecentSearchType.java (1)
10-15: 깔끔한 열거형 구현검색 타입별로 명확한 상수 정의와 문자열 매핑이 잘 구현되어 있습니다. 패키지 위치도 JPA 관련 타입으로 적절합니다.
update 관련해서 답변 남겼습니다!! |
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java(0 hunks)src/main/java/konkuk/thip/common/entity/BaseJpaEntity.java(0 hunks)src/main/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntity.java(0 hunks)src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/RecentSearchJpaEntity.java(1 hunks)src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java(3 hunks)src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchJpaRepository.java(1 hunks)src/main/java/konkuk/thip/recentSearch/application/port/out/RecentSearchCommandPort.java(1 hunks)src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java(1 hunks)src/main/java/konkuk/thip/recentSearch/domain/RecentSearch.java(2 hunks)
💤 Files with no reviewable changes (3)
- src/main/java/konkuk/thip/common/entity/BaseJpaEntity.java
- src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java
- src/main/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntity.java
🚧 Files skipped from review as they are similar to previous changes (3)
- src/main/java/konkuk/thip/recentSearch/application/port/out/RecentSearchCommandPort.java
- src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java
- src/main/java/konkuk/thip/recentSearch/domain/RecentSearch.java
🧰 Additional context used
🧠 Learnings (2)
src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java (4)
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: seongjunnoh
PR: #113
File: src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java:38-44
Timestamp: 2025-07-30T14:05:04.945Z
Learning: seongjunnoh는 코드 최적화 제안에 대해 구체적인 기술적 근거와 효율성 차이를 이해하고 싶어하며, 성능 개선 방식에 대한 상세한 설명을 선호한다.
Learnt from: seongjunnoh
PR: #112
File: src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepositoryImpl.java:272-272
Timestamp: 2025-07-30T10:44:34.115Z
Learning: seongjunnoh는 피드 커서 페이지네이션에서 LocalDateTime 단일 커서 방식을 선호하며, 복합 키 기반 커서보다 구현 단순성과 성능을 우선시한다.
Learnt from: seongjunnoh
PR: #93
File: src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java:49-114
Timestamp: 2025-07-28T16:44:31.224Z
Learning: seongjunnoh는 코드 중복 문제에 대한 리팩토링 제안을 적극적으로 수용하고 함수형 인터페이스를 활용한 해결책을 선호한다.
src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/RecentSearchJpaEntity.java (1)
Learnt from: seongjunnoh
PR: #36
File: src/main/java/konkuk/thip/user/adapter/out/persistence/UserJpaRepository.java:7-7
Timestamp: 2025-06-29T09:47:31.299Z
Learning: Spring Data JPA에서 findBy{FieldName} 패턴의 메서드는 명시적 선언 없이 자동으로 생성되며, Optional 반환 타입을 사용하는 것이 null 안전성을 위해 권장됩니다.
⏰ 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 (7)
src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java (5)
3-3: 새로운 enum 타입 도입 승인.
RecentSearchTypeenum을 도입하여 기존의 문자열 기반 타입 처리를 개선한 점이 좋습니다. 타입 안전성과 코드 가독성이 향상됩니다.
17-17: 메서드 시그니처 개선 승인.
RecentSearchType type파라미터를 추가하여 검색 타입을 동적으로 받도록 개선한 점이 좋습니다. 이는 PR 목표에서 언급한 "검색 타입을 동적으로 파라미터로 받을 수 있도록 조정"과 일치합니다.
20-20: 쿼리 메서드 호출 개선 승인.기존 조건에
type파라미터를 추가하여 더 정확한 중복 검색이 가능해졌습니다.userId + keyword + type조합으로 중복을 확인하는 것이 논리적으로 올바릅니다.
22-22:touch메서드 사용 승인.과거 리뷰 코멘트에서 논의된 내용을 반영하여
update대신touch메서드를 사용한 점이 좋습니다.modifiedAt갱신의 목적이 명확하게 드러나며, 메서드명이 의도를 잘 표현합니다.
23-23: 도메인 객체 생성 개선 승인.
RecentSearch.withoutId메서드 호출에RecentSearchType type파라미터를 전달하도록 수정한 점이 좋습니다. 타입 안전성이 보장되고 일관성 있는 도메인 객체 생성이 가능합니다.src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchJpaRepository.java (1)
12-14: JPQL 업데이트 쿼리 구현이 적절합니다.
updateModifiedAt메서드의 구현이 올바릅니다:
@Modifying어노테이션을 사용한 업데이트 작업이 적절함- JPQL 구문과 파라미터 바인딩이 정확함
- 전체 엔티티 업데이트보다 효율적인 접근 방식
단,
@Modifying쿼리는 일반적으로 트랜잭션 내에서 실행되어야 하므로, 서비스 계층에서@Transactional처리가 되어 있는지 확인해 주세요.src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/RecentSearchJpaEntity.java (1)
27-27: 열거형 이름 변경이 적절합니다.
SearchType에서RecentSearchType으로의 변경은 다음과 같은 장점이 있습니다:
- 더 구체적이고 명확한 의미 전달
- 다른 검색 관련 파라미터와의 혼동 방지
- 도메인 모델링 관점에서 개선된 명명
PR 목표에서 언급한 대로 네이밍 혼동을 줄이는 좋은 개선사항입니다.
| @Enumerated(EnumType.STRING) | ||
| @Column(nullable = false) | ||
| private SearchType type; | ||
| private RecentSearchType type; |
There was a problem hiding this comment.
RecentSearchType 임포트 누락으로 컴파일 에러 발생.
RecentSearchType 열거형을 사용하고 있지만 해당 클래스의 임포트 구문이 누락되어 컴파일 에러가 발생합니다.
다음 임포트를 추가해 주세요:
package konkuk.thip.recentSearch.adapter.out.jpa;
import jakarta.persistence.*;
import konkuk.thip.common.entity.BaseJpaEntity;
+import konkuk.thip.recentSearch.domain.RecentSearchType;
import konkuk.thip.user.adapter.out.jpa.UserJpaEntity;
import lombok.*;📝 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.
| private RecentSearchType type; | |
| package konkuk.thip.recentSearch.adapter.out.jpa; | |
| import jakarta.persistence.*; | |
| import konkuk.thip.common.entity.BaseJpaEntity; | |
| import konkuk.thip.recentSearch.domain.RecentSearchType; | |
| import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; | |
| import lombok.*; |
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/RecentSearchJpaEntity.java
at line 27, the RecentSearchType enum is used but not imported, causing a
compilation error. Add the appropriate import statement for RecentSearchType at
the top of the file to resolve the missing import issue.
| void delete(Long id); | ||
|
|
||
| void update(RecentSearch recentSearch); | ||
| void touch(RecentSearch recentSearch); // modifiedAt 갱신용 메서드 |
There was a problem hiding this comment.
ㅋㅋㅋㅋ많이 사용되는 메서드 네임이라고 하더라구요 👍🏻
| @Modifying | ||
| @Query("UPDATE RecentSearchJpaEntity r SET r.modifiedAt = CURRENT_TIMESTAMP WHERE r.recentSearchId = :recentSearchId") | ||
| void updateModifiedAt(@Param("recentSearchId") Long recentSearchId); |
There was a problem hiding this comment.
LGTM
엔티티의 필드값 변경이 아니라 modifiedAt 만 명시적으로 수정하는 건 이 방식이 가장 깔끔한 거 같습니다!
#️⃣ 연관된 이슈
📝 작업 내용
📸 스크린샷
💬 리뷰 요구사항
통합 테스트에서 최근 검색어 조회시 modifiedAt을 update해서 정렬 제대로 나오는지 확인하려고 했는데 JPA 특성상 persist 하는 순간에 수정된 modifiedAt을 덮어씌워버리기 때문에 이를 강제로 업데이트 시키기 위해서 JDBC를 사용했습니다. 이점 참고해서 테스트 코드 봐주세요!
📌 PR 진행 시 이러한 점들을 참고해 주세요
Summary by CodeRabbit
신규 기능
버그 수정
개선 사항
테스트