-
Notifications
You must be signed in to change notification settings - Fork 1
feat: BookDetailResponse에 사용자 도서 상태 및 페이지 정보 추가 #72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…ice 인터페이스를 sealed로 변경, 불필요한 상수 제거
…archRequest의 메서드 이름 변경 및 파라미터 정리
… 이름 변경 및 유효성 검증 메시지 수정
…ndByIdAndUserId 메서드를 existsByIdAndUserId로 변경하여 존재 여부 확인 로직 개선
… 패턴 제거하고 ISBN13 패턴 추가
…dinSearchResponse 데이터 클래스 추가
…메서드 위치 변경 및 대소문자 수정
…이터 클래스의 필드 정리 및 ISBN 처리 로직 개선
…nse의 isbn13 필드를 nullable로 변경
…vice에 상태 조회 기능 구현
|
Warning Rate limit exceeded@hoonyworld has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 6 minutes and 28 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (9)
📝 WalkthroughWalkthrough이번 변경사항은 도서 상세 조회 API의 응답에 Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant BookController
participant BookUseCase
participant UserBookService
participant BookQueryService
participant AladinBookQueryService
participant ExternalAladinAPI
User->>BookController: GET /books/{isbn}
BookController->>BookUseCase: getBookDetail(request, userId)
BookUseCase->>BookQueryService: getBookDetail(request)
BookQueryService->>AladinBookQueryService: getBookDetail(request)
AladinBookQueryService->>ExternalAladinAPI: lookupBook(request)
ExternalAladinAPI-->>AladinBookQueryService: AladinBookDetailResponse
AladinBookQueryService-->>BookQueryService: BookDetailResponse (with totalPage)
BookQueryService-->>BookUseCase: BookDetailResponse
BookUseCase->>UserBookService: findUserBookStatusByIsbn(userId, isbn)
UserBookService-->>BookUseCase: BookStatus
BookUseCase-->>BookController: BookDetailResponse (with status, totalPage)
BookController-->>User: 응답 반환
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes(해당 이슈의 목적과 직접적으로 관련 없는 변경사항은 발견되지 않았습니다.) Possibly related PRs
Suggested labels
Suggested reviewers
✨ 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
Documentation and Community
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 12
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (35)
apis/src/main/kotlin/org/yapp/apis/book/constant/BookQueryServiceQualifier.kt(0 hunks)apis/src/main/kotlin/org/yapp/apis/book/controller/BookController.kt(1 hunks)apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookCreateRequest.kt(2 hunks)apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookDetailRequest.kt(1 hunks)apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookSearchRequest.kt(1 hunks)apis/src/main/kotlin/org/yapp/apis/book/dto/request/UpsertUserBookRequest.kt(1 hunks)apis/src/main/kotlin/org/yapp/apis/book/dto/request/UserBookRegisterRequest.kt(1 hunks)apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookDetailResponse.kt(1 hunks)apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookSearchResponse.kt(3 hunks)apis/src/main/kotlin/org/yapp/apis/book/exception/BookErrorCode.kt(1 hunks)apis/src/main/kotlin/org/yapp/apis/book/exception/UserBookException.kt(1 hunks)apis/src/main/kotlin/org/yapp/apis/book/helper/AladinApiHelper.kt(0 hunks)apis/src/main/kotlin/org/yapp/apis/book/service/AladinBookQueryService.kt(1 hunks)apis/src/main/kotlin/org/yapp/apis/book/service/BookQueryService.kt(1 hunks)apis/src/main/kotlin/org/yapp/apis/book/service/UserBookService.kt(3 hunks)apis/src/main/kotlin/org/yapp/apis/book/service/impl/AladinBookQueryService.kt(0 hunks)apis/src/main/kotlin/org/yapp/apis/book/usecase/BookUseCase.kt(2 hunks)apis/src/main/kotlin/org/yapp/apis/util/AuthorExtractor.kt(1 hunks)apis/src/main/kotlin/org/yapp/apis/util/IsbnConverter.kt(1 hunks)apis/src/main/resources/application.yml(1 hunks)domain/src/main/kotlin/org/yapp/domain/book/Book.kt(1 hunks)domain/src/main/kotlin/org/yapp/domain/userbook/UserBookDomainService.kt(3 hunks)domain/src/main/kotlin/org/yapp/domain/userbook/UserBookRepository.kt(1 hunks)domain/src/main/kotlin/org/yapp/domain/userbook/exception/UserBookErrorCode.kt(1 hunks)domain/src/main/kotlin/org/yapp/domain/userbook/exception/UserBookNotFoundException.kt(1 hunks)global-utils/src/main/kotlin/org/yapp/globalutils/util/RegexUtils.kt(1 hunks)infra/src/main/kotlin/org/yapp/infra/external/aladin/AladinApi.kt(2 hunks)infra/src/main/kotlin/org/yapp/infra/external/aladin/AladinRestClient.kt(3 hunks)infra/src/main/kotlin/org/yapp/infra/external/aladin/request/AladinBookLookupRequest.kt(1 hunks)infra/src/main/kotlin/org/yapp/infra/external/aladin/request/AladinBookSearchRequest.kt(4 hunks)infra/src/main/kotlin/org/yapp/infra/external/aladin/response/AladinDetailResponse.kt(1 hunks)infra/src/main/kotlin/org/yapp/infra/external/aladin/response/AladinResponseBase.kt(0 hunks)infra/src/main/kotlin/org/yapp/infra/external/aladin/response/AladinSearchResponse.kt(1 hunks)infra/src/main/kotlin/org/yapp/infra/userbook/repository/JpaUserBookRepository.kt(1 hunks)infra/src/main/kotlin/org/yapp/infra/userbook/repository/impl/UserBookRepositoryImpl.kt(1 hunks)
💤 Files with no reviewable changes (4)
- apis/src/main/kotlin/org/yapp/apis/book/helper/AladinApiHelper.kt
- apis/src/main/kotlin/org/yapp/apis/book/constant/BookQueryServiceQualifier.kt
- apis/src/main/kotlin/org/yapp/apis/book/service/impl/AladinBookQueryService.kt
- infra/src/main/kotlin/org/yapp/infra/external/aladin/response/AladinResponseBase.kt
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: hoonyworld
PR: YAPP-Github/Reed-Server#39
File: apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookSearchResponse.kt:45-45
Timestamp: 2025-07-17T15:09:36.267Z
Learning: Response DTO 클래스에서는 불변성을 지키기 위해 프로퍼티를 val로 선언하고, 업데이트가 필요한 경우 copy() 메서드를 사용해야 합니다.
⏰ 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-validation
🔇 Additional comments (60)
apis/src/main/resources/application.yml (1)
28-28: LGTM! 표준 Swagger UI 경로로 업데이트되었습니다.
.html확장자를 제거한/swagger-ui경로는 최신 SpringDoc 규칙에 맞는 적절한 변경입니다.apis/src/main/kotlin/org/yapp/apis/book/exception/UserBookException.kt (1)
5-8: 예외 계층 구조 개선이 잘 되었습니다.
UserBookNotFoundException에서UserBookException으로 이름 변경은 API 계층에서 더 일반적인 예외 타입을 제공하고, 도메인 계층의 구체적인 예외와 분리하는 좋은 설계입니다.apis/src/main/kotlin/org/yapp/apis/book/service/BookQueryService.kt (1)
8-11: sealed interface 사용이 우수한 설계 선택입니다.sealed interface로 변경하여 구현체를 같은 모듈 내로 제한함으로써 타입 안전성과 제어된 상속을 제공합니다. 이는 exhaustive type checking과 모듈화된 설계를 지원합니다.
infra/src/main/kotlin/org/yapp/infra/userbook/repository/JpaUserBookRepository.kt (1)
10-10: 존재 여부 확인을 위한 성능 최적화가 잘 되었습니다.
findByIdAndUserId에서existsByIdAndUserId로 변경하여 엔티티 조회 대신 존재 여부만 확인하도록 최적화되었습니다. 이는 메모리 사용량을 줄이고 검증 시나리오에서 쿼리 성능을 향상시킵니다.apis/src/main/kotlin/org/yapp/apis/book/controller/BookController.kt (1)
48-48: 사용자 컨텍스트 전달이 올바르게 구현되었습니다.인증된 사용자의 UUID를 use case 레이어로 전달하여 사용자별 도서 상태 조회가 가능하도록 하는 변경사항이 적절하게 구현되었습니다.
domain/src/main/kotlin/org/yapp/domain/userbook/exception/UserBookNotFoundException.kt (1)
6-9: 도메인별 예외 처리 구조가 적절합니다.
CommonException을 확장하여 일관된 예외 처리 패턴을 따르고 있으며,UserBookErrorCode를 통한 구조화된 에러 처리가 잘 구현되었습니다.infra/src/main/kotlin/org/yapp/infra/external/aladin/AladinApi.kt (2)
5-6: 패키지 구조 개선이 적절합니다.DTO 클래스들을
request패키지로 이동시켜 더 명확한 패키지 구조를 만든 것이 좋습니다.
25-26: 코드 중복 제거로 가독성이 향상되었습니다.
request.toMap()에서 이미 필요한 파라미터를 처리하도록 개선하여 수동으로 파라미터를 추가하는 중복 코드가 제거되었습니다. DRY 원칙을 잘 따른 리팩토링입니다.domain/src/main/kotlin/org/yapp/domain/userbook/UserBookRepository.kt (1)
13-13: 존재 여부 확인을 위한 메서드 시그니처 개선이 우수합니다.엔티티 전체를 반환하던
findByIdAndUserId를 존재 여부만 확인하는existsByIdAndUserId로 변경하여 성능을 개선하고 메서드의 의도를 더 명확하게 표현했습니다. 불필요한 엔티티 로딩을 방지하는 좋은 리팩토링입니다.apis/src/main/kotlin/org/yapp/apis/util/AuthorExtractor.kt (1)
8-20: 저자명 추출 로직이 한국어 텍스트 처리에 적합합니다.
(지은이)마커를 기준으로 저자명을 추출하는 로직이 적절하게 구현되었습니다. null/blank 입력 처리와 중첩된 괄호 처리도 고려되어 있습니다.infra/src/main/kotlin/org/yapp/infra/userbook/repository/impl/UserBookRepositoryImpl.kt (1)
28-30: 성능 최적화가 잘 적용되었습니다!단순 존재 여부 확인을 위해 전체 엔티티를 조회하고 도메인 객체로 변환하는 대신
existsByIdAndUserId를 사용하여 메모리 사용량과 성능을 개선했습니다. 데이터베이스 레벨의 EXISTS 쿼리를 활용하는 것이 더 효율적입니다.infra/src/main/kotlin/org/yapp/infra/external/aladin/AladinRestClient.kt (3)
12-14: 불필요한 별칭 제거로 코드가 개선되었습니다!
client별칭을 제거하고restClient를 직접 사용하는 것이 더 명확하고 간결합니다.
27-27: LGTM!
restClient를 직접 사용하여 코드가 더 읽기 쉬워졌습니다.Also applies to: 43-43
50-60: 확장 함수 리팩토링 및 private 적용은 좋습니다.
다만 58번 라인에서 쿼리 파라미터"output"을"Output"으로 변경한 부분이 Aladin API에서 대소문자를 구분하지 않는지 API 문서 또는 실제 호출 테스트를 통해 확인해 주세요.
- 파일: infra/src/main/kotlin/org/yapp/infra/external/aladin/AladinRestClient.kt
- 라인: 58
apis/src/main/kotlin/org/yapp/apis/book/dto/request/UserBookRegisterRequest.kt (3)
8-11: API 문서화가 잘 추가되었습니다!클래스 레벨
@Schema어노테이션으로 요청의 목적을 명확히 설명했습니다.
14-21: ISBN 필드 문서화가 상세하고 적절합니다!13자리 ISBN 요구사항과 예시값을 포함한 상세한 스키마 정의가 API 사용자에게 도움이 될 것입니다.
minLength와maxLength제약조건도 적절합니다.
23-31: 도서 상태 필드 검증과 문서화가 완벽합니다!
@NotNull검증 추가와 함께 허용 가능한 enum 값들을 명시한 스키마가 API 명세를 크게 개선했습니다.apis/src/main/kotlin/org/yapp/apis/util/IsbnConverter.kt (1)
12-21: 입력 검증 로직이 견고합니다!ISBN-10 형식의 입력값을 적절히 정규화하고 길이와 패턴을 검증하는 로직이 잘 구현되었습니다. null과 빈 문자열 처리도 안전합니다.
domain/src/main/kotlin/org/yapp/domain/userbook/UserBookDomainService.kt (3)
5-6: 도메인 예외 처리를 위한 적절한 import 추가새로운 예외 클래스들을 import하여 명시적인 예외 처리를 준비했습니다.
62-66: 사용자 도서 상태 조회 기능이 잘 구현되었습니다!ISBN으로 사용자 도서를 조회하고, 없을 경우 도메인 특화 예외를 던지는 로직이 PR의 핵심 목표인 "도서 상세 응답에 사용자 도서 상태 포함" 기능을 지원합니다. 예외 처리도 명시적이고 적절합니다.
79-81: 성능 최적화된 존재 여부 확인 메서드전체 엔티티 조회 대신 Boolean 반환으로 변경하여 성능을 개선했습니다. 메서드명도
existsByUserBookIdAndUserId로 명확하게 변경되어 의도가 잘 드러납니다.apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookSearchRequest.kt (5)
3-6: 새로운 import 추가가 적절합니다.validation과 OpenAPI documentation을 위한 필요한 import들이 추가되었습니다.
8-11: 클래스 레벨 Schema 문서화가 잘 되었습니다.API 문서에서 명확한 설명을 제공하는 좋은 개선사항입니다.
13-19: query 필드의 validation과 문서화가 적절합니다.
@NotBlankvalidation과 상세한 Schema 정보가 잘 추가되었습니다.
37-46: maxResults 필드의 범위 제한이 적절합니다.
@Min(1)과@Max(100)제약 조건이 합리적이며, Schema 문서와 일치합니다.
48-55: start 필드의 최소값 제한이 적절합니다.페이징을 위한 시작 인덱스가 1 이상이어야 한다는 제약이 합리적입니다.
domain/src/main/kotlin/org/yapp/domain/userbook/exception/UserBookErrorCode.kt (1)
1-16: 에러 코드 enum 클래스가 잘 구현되었습니다.BaseErrorCode 인터페이스를 구현하여 일관된 에러 처리 패턴을 따르고 있으며, 확장 가능한 구조로 설계되었습니다. 에러 코드 네이밍("USER_BOOK_001")과 한글 메시지도 적절합니다.
apis/src/main/kotlin/org/yapp/apis/book/service/UserBookService.kt (3)
11-11: Exception import 변경이 적절합니다.UserBookNotFoundException에서 UserBookException으로의 변경이 일관된 예외 처리 전략을 반영합니다.
35-42: validateUserBookExists 메서드 리팩토링이 개선되었습니다.존재 여부만 확인하는 boolean 기반 검증으로 변경되어 메서드의 목적이 더 명확해졌습니다. 파라미터 순서도 repository 메서드와 일치하도록 개선되었습니다.
52-55: 새로운 findUserBookStatusByIsbn 메서드가 잘 구현되었습니다.ISBN을 통해 사용자 책 상태를 조회하는 기능이 간결하고 명확하게 구현되었습니다. domain service 호출을 통해 적절한 레이어 분리도 유지되고 있습니다.
infra/src/main/kotlin/org/yapp/infra/external/aladin/request/AladinBookSearchRequest.kt (4)
1-1: 패키지 구조 개선이 적절합니다.
dto에서request로의 패키지 변경이 코드 구조를 더 명확하게 만듭니다.
11-11: 코드 스타일 개선이 좋습니다.categoryId의 명시적 nullable 처리와 trailing comma 추가가 코드 가독성을 향상시킵니다.
27-27: 팩토리 메서드명 변경이 일관성을 개선합니다.
create에서of로의 변경이 다른 DTO들과의 네이밍 일관성을 향상시킵니다.
43-44: cover 값 표준화가 합리적입니다.cover 파라미터를 "Big"으로 하드코딩하여 모든 알라딘 API 요청에서 일관된 커버 이미지 크기를 사용하도록 표준화한 것이 좋은 결정입니다.
apis/src/main/kotlin/org/yapp/apis/book/dto/request/UpsertUserBookRequest.kt (6)
3-6: 필요한 import들이 적절히 추가되었습니다.validation과 OpenAPI 문서화를 위한 필수 import들이 모두 포함되었습니다.
11-14: 클래스 레벨 문서화가 명확합니다.API의 목적과 사용 용도가 명확하게 설명되어 있어 개발자 경험을 향상시킵니다.
16-23: UUID 필드들의 validation과 문서화가 우수합니다.userId, bookId, bookIsbn 필드에 대한 validation과 Schema 정의가 일관되고 상세합니다. UUID format 지정도 적절합니다.
Also applies to: 25-32, 34-42
44-52: 텍스트 필드들의 크기 제한이 합리적입니다.제목(500자), 저자/출판사(200자)의 크기 제한이 실제 사용 환경에 적합하며, 한글 에러 메시지가 사용자 친화적입니다.
Also applies to: 54-62, 64-72
74-83: URL 필드의 validation이 적절합니다.2048자 제한과 URI format 지정이 URL 필드에 적합하며, 예시 URL도 실제적입니다.
85-93: BookStatus enum의 문서화가 완벽합니다.허용 가능한 값들이 명시되어 있고 enumAsRef 설정으로 OpenAPI 스펙이 정확하게 생성될 것입니다.
global-utils/src/main/kotlin/org/yapp/globalutils/util/RegexUtils.kt (1)
4-5: ISBN 정규식 패턴이 올바르게 정의되었습니다.ISBN-10과 ISBN-13 형식에 맞는 정확한 정규식 패턴입니다. ISBN-10은 9자리 숫자 + 체크 디지트(0-9 또는 X), ISBN-13은 표준 도서 접두사(978/979) + 10자리 숫자로 국제 ISBN 표준을 준수합니다.
apis/src/main/kotlin/org/yapp/apis/book/exception/BookErrorCode.kt (1)
13-14: 알라딘 API 에러 코드가 적절히 세분화되었습니다.검색과 상세 조회 실패를 구분하는 것은 디버깅과 에러 처리에 도움이 됩니다. 에러 코드와 메시지가 일관성 있고 명확합니다.
apis/src/main/kotlin/org/yapp/apis/book/service/AladinBookQueryService.kt (2)
23-43: 검색 메서드가 잘 구현되었습니다.Result 타입을 활용한 에러 처리와 성공/실패 케이스에 대한 상세한 로깅이 훌륭합니다. 도메인 요청을 알라딘 API 요청으로 변환하는 로직도 명확합니다.
45-58: 상세 조회 메서드의 구현이 우수합니다.검색 메서드와 일관된 패턴을 사용하여 코드의 일관성을 유지했습니다. 에러 처리와 로깅이 적절하게 구현되어 있습니다.
infra/src/main/kotlin/org/yapp/infra/external/aladin/request/AladinBookLookupRequest.kt (2)
3-7: 요청 클래스 구조가 효과적으로 단순화되었습니다.
cover속성 추가와itemIdType의 기본값 설정이 적절하며, ISBN13 표준화 방향에 부합합니다.
17-22: 팩토리 메서드가 잘 단순화되었습니다.
itemId만 받고 나머지는 고정값으로 설정하는 것이 API 사용을 단순화하고 일관성을 보장합니다. ISBN13과 "Big" 커버 사이즈로 고정하는 것이 합리적입니다.apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookCreateRequest.kt (3)
10-23: 스키마 어노테이션과 ISBN 검증이 잘 정의되었습니다.API 문서화를 위한 상세한 스키마 정보와 13자리 ISBN 요구사항이 명확하게 표현되었습니다.
55-63: 출간연도 검증 범위가 적절합니다.1000년부터 2100년까지의 범위는 실용적이고 합리적인 제한입니다.
90-93: ISBN13 전용 사용으로의 변경이 일관성 있습니다.
bookDetail.isbn13만 사용하도록 변경한 것이 PR의 ISBN13 표준화 방향과 일치합니다. 예외 처리도 적절합니다.infra/src/main/kotlin/org/yapp/infra/external/aladin/response/AladinDetailResponse.kt (1)
1-53: 외부 API 응답 모델이 잘 구성되어 있습니다!Aladin API 응답을 위한 데이터 모델이 적절히 설계되었습니다:
- Jackson 어노테이션으로 JSON 매핑이 명확히 정의됨
internal생성자로 캡슐화가 잘 되어있음- nullable 타입이 적절히 사용되어 외부 API의 불확실성을 잘 처리함
infra/src/main/kotlin/org/yapp/infra/external/aladin/response/AladinSearchResponse.kt (1)
1-42: 검색 응답 모델이 일관성 있게 구현되었습니다!상세 응답 모델과 동일한 패턴으로 잘 구현되었습니다. 외부 API 통합에 적합한 구조입니다.
apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookSearchResponse.kt (1)
20-22: 불변성 원칙이 잘 적용되었습니다!이전 학습 내용에 따라
copy()메서드를 사용하여 불변성을 유지하는 방식이 올바르게 구현되었습니다.apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookDetailResponse.kt (1)
23-25: 불변성 원칙이 잘 적용되었습니다!
copy()메서드를 활용한 상태 업데이트가 적절히 구현되었습니다.apis/src/main/kotlin/org/yapp/apis/book/usecase/BookUseCase.kt (7)
32-42: searchBooks 메서드 리팩토링이 잘 구현되었습니다.userId 매개변수를 추가하고 사용자별 책 상태를 병합하는 로직이 깔끔하게 분리되어 있습니다. 새로운 헬퍼 메서드를 통해 코드의 가독성과 재사용성이 향상되었습니다.
44-56: getBookDetail 메서드 개선사항이 우수합니다.사용자 ID를 받아서 실제 사용자의 책 상태를 조회하여 반환하는 로직이 잘 구현되었습니다. ISBN13가 없는 경우 기본값으로
BEFORE_REGISTRATION을 반환하는 처리도 적절합니다.
59-75: upsertBookToMyLibrary 메서드 시그니처 개선.메서드 시그니처가 더 명확해졌고,
BookDetailRequest.from()팩토리 메서드 사용으로 코드가 더 깔끔해졌습니다.
89-108: mergeWithUserBookStatus 헬퍼 메서드 구현이 효율적입니다.빈 목록에 대한 조기 반환 처리와 ISBN13 기반의 상태 매핑 로직이 잘 구현되었습니다. null 체크를 통해 안전하게 상태를 업데이트하거나 원본 객체를 반환하는 로직도 적절합니다.
110-121: getUserBookStatusMap 헬퍼 메서드의 성능 최적화.배치 조회를 통해 N+1 쿼리 문제를 방지하고, associate 함수를 사용하여 효율적으로 Map을 생성하는 방식이 우수합니다.
89-121: 헬퍼 메서드들의 역할 분리가 명확합니다.
mergeWithUserBookStatus와getUserBookStatusMap두 개의 private 메서드로 책임을 명확히 분리한 것이 좋습니다. 단일 책임 원칙(SRP)을 잘 준수하고 있으며, 테스트하기도 용이한 구조입니다.
26-26: Qualifier 어노테이션 문자열 리터럴 사용 검증 필요현재
BookQueryServiceQualifier객체가 삭제되면서,@Qualifier("aladinBookQueryService")로 상수 대신 문자열 리터럴이 사용되고 있습니다.
아래 사항을 반드시 확인해주세요:• 파일:
apis/src/main/kotlin/org/yapp/apis/book/usecase/BookUseCase.kt
라인 26 –@Qualifier("aladinBookQueryService")@Qualifier("aladinBookQueryService") private val bookQueryService: BookQueryService,• DI 설정(예:
@Bean메서드 또는@Component어노테이션)에서"aladinBookQueryService"라는 빈 이름이 실제로 정의되어 있는지
• 상수 기반 Qualifier에서 문자열 리터럴로 변경된 것이 의도된 사항인지위 사항들이 충족되지 않으면 빈 주입 오류가 발생할 수 있으니, 빈 정의 여부와 변경 의도를 검토해 주세요.
apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookDetailRequest.kt
Show resolved
Hide resolved
| optResult = this.optResult | ||
| ) | ||
| } | ||
| fun validIsbn(): String = isbn!! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안전하지 않은 non-null assertion 사용
validIsbn() 메서드에서 !! 연산자를 사용하는 것은 NPE를 발생시킬 위험이 있습니다.
더 안전한 방식으로 구현하는 것을 권장합니다:
- fun validIsbn(): String = isbn!!
+ fun validIsbn(): String = isbn ?: throw IllegalStateException("ISBN이 제공되지 않았습니다")📝 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.
| fun validIsbn(): String = isbn!! | |
| fun validIsbn(): String = isbn ?: throw IllegalStateException("ISBN이 제공되지 않았습니다") |
🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookDetailRequest.kt at
line 28, the validIsbn() method uses the non-null assertion operator (!!) on
isbn, which can cause a NullPointerException if isbn is null. To fix this,
modify the method to safely handle null values by either returning a nullable
String, providing a default value, or throwing a controlled exception with a
clear message instead of using !!.
| fun from( | ||
| isbn: String?, | ||
| ): BookDetailRequest { | ||
| return BookDetailRequest( | ||
| itemId = isbn, | ||
| itemIdType = "ISBN", | ||
| optResult = optResult | ||
| isbn = isbn, | ||
| ) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Factory 메서드의 입력 검증 부재
from 메서드가 nullable isbn을 받지만 검증 없이 그대로 전달합니다. 이는 잘못된 입력으로 인한 런타임 오류를 야기할 수 있습니다.
입력 검증을 추가하는 것을 권장합니다:
companion object {
fun from(
isbn: String?,
): BookDetailRequest {
+ requireNotNull(isbn) { "ISBN은 필수입니다" }
+ require(isbn.matches(Regex(RegexUtils.ISBN13_PATTERN))) { "유효한 13자리 ISBN 형식이 아닙니다" }
return BookDetailRequest(
isbn = isbn,
)
}
}🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookDetailRequest.kt
around lines 31 to 37, the from factory method accepts a nullable isbn parameter
but does not validate it before passing it to the constructor, which can cause
runtime errors. Add input validation to check if isbn is null or empty and throw
an appropriate exception or handle the error before creating the
BookDetailRequest instance.
| pubDate = item.pubDate ?: "", | ||
| description = item.description ?: "", | ||
| mallType = item.mallType, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
누락된 데이터에 대한 기본값 처리 재검토
빈 문자열을 기본값으로 사용하면 실제로 데이터가 없는 경우를 구분하기 어려울 수 있습니다.
nullable 타입을 유지하여 데이터 누락을 명시적으로 표현하는 것을 고려해보세요:
- val pubDate: String,
- val description: String,
+ val pubDate: String?,
+ val description: String?,
// ...
- pubDate = item.pubDate ?: "",
- description = item.description ?: "",
+ pubDate = item.pubDate,
+ description = item.description,
- publisher = item.publisher ?: "",
+ publisher = item.publisher,Also applies to: 43-43
🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookDetailResponse.kt at
lines 37-39 and line 43, the code assigns empty strings as default values for
missing data fields like pubDate and description. Instead of defaulting to empty
strings, modify these fields to be nullable types and assign null when data is
missing. This change explicitly represents missing data and avoids ambiguity
between empty and absent values.
apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookDetailResponse.kt
Show resolved
Hide resolved
domain/src/main/kotlin/org/yapp/domain/userbook/exception/UserBookNotFoundException.kt
Outdated
Show resolved
Hide resolved
…kupRequest, AladinBookSearchRequest에서 사용
…cover 필드 이름을 coverImageUrl로 변경
…lable로 변경하고 관련 메서드 수정
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (5)
apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookSearchResponse.kt (1)
76-76: 복잡한 ISBN 변환 로직 개선 제안현재 한 줄에 너무 많은 로직이 포함되어 있어 가독성이 떨어집니다.
다음과 같이 개선할 수 있습니다:
- isbn13 = isbn13 ?: IsbnConverter.toIsbn13(isbn) ?: throw IllegalArgumentException("Either isbn13 or isbn must be provided"), + isbn13 = when { + !isbn13.isNullOrBlank() -> isbn13 + !isbn.isNullOrBlank() -> IsbnConverter.toIsbn13(isbn) + ?: throw IllegalArgumentException("ISBN을 ISBN13으로 변환할 수 없습니다") + else -> throw IllegalArgumentException("ISBN13 또는 ISBN 중 하나는 반드시 제공되어야 합니다") + },apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookDetailResponse.kt (3)
37-38: 누락된 데이터에 대한 기본값 처리 재검토빈 문자열을 기본값으로 사용하면 실제로 데이터가 없는 경우를 구분하기 어려울 수 있습니다.
nullable 타입을 유지하여 데이터 누락을 명시적으로 표현하는 것을 고려해보세요:
- val pubDate: String, - val description: String, + val pubDate: String?, + val description: String?, // ... - pubDate = item.pubDate ?: "", - description = item.description ?: "", + pubDate = item.pubDate, + description = item.description, - publisher = item.publisher ?: "", + publisher = item.publisher,Also applies to: 43-43
40-40: ISBN 변환 로직의 일관성
BookSearchResponse와 동일한 ISBN 변환 로직이 사용되고 있습니다. 가독성 개선을 위해 동일한 리팩토링을 적용하는 것이 좋겠습니다.
44-44: 매직 넘버(4032) 상수 추출 필요기본 페이지 수로 사용되는
4032가 코드에 하드코딩되어 있어, 그 의미를 파악하기 어렵습니다. 상수로 분리하고 주석을 추가해 의도를 명확히 하는 것이 좋습니다.+ companion object { + // 알라딘 API에서 페이지 정보(itemPage)가 없을 때 사용하는 기본값 + private const val DEFAULT_TOTAL_PAGE = 4032 + } fun from(response: AladinBookDetailResponse, userBookStatus: BookStatus = BookStatus.BEFORE_REGISTRATION): BookDetailResponse { // … - totalPage = item.subInfo.itemPage ?: 4032, + totalPage = item.subInfo.itemPage ?: DEFAULT_TOTAL_PAGE,apis/src/main/kotlin/org/yapp/apis/book/usecase/BookUseCase.kt (1)
3-3: 불필요한 빈 줄 제거를 고려해보세요.코드 스타일 일관성을 위해 불필요한 빈 줄들을 제거하는 것을 권장합니다.
Also applies to: 9-9, 21-21
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (8)
apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookCreateRequest.kt(3 hunks)apis/src/main/kotlin/org/yapp/apis/book/dto/request/UserBookRegisterRequest.kt(1 hunks)apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookDetailResponse.kt(1 hunks)apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookSearchResponse.kt(3 hunks)apis/src/main/kotlin/org/yapp/apis/book/usecase/BookUseCase.kt(2 hunks)global-utils/src/main/kotlin/org/yapp/globalutils/book/BookCoverSize.kt(1 hunks)infra/src/main/kotlin/org/yapp/infra/external/aladin/request/AladinBookLookupRequest.kt(1 hunks)infra/src/main/kotlin/org/yapp/infra/external/aladin/request/AladinBookSearchRequest.kt(4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: hoonyworld
PR: YAPP-Github/Reed-Server#39
File: apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookSearchResponse.kt:45-45
Timestamp: 2025-07-17T15:09:36.267Z
Learning: Response DTO 클래스에서는 불변성을 지키기 위해 프로퍼티를 val로 선언하고, 업데이트가 필요한 경우 copy() 메서드를 사용해야 합니다.
⏰ 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-validation
🔇 Additional comments (18)
global-utils/src/main/kotlin/org/yapp/globalutils/book/BookCoverSize.kt (1)
3-10: 잘 설계된 enum 클래스입니다.API 값을 중앙화하고 타입 안전성을 제공하는 좋은 설계입니다. 외부 API 요청에서 일관된 커버 사이즈 처리를 가능하게 합니다.
infra/src/main/kotlin/org/yapp/infra/external/aladin/request/AladinBookSearchRequest.kt (2)
1-3: 패키지 구조 개선과 enum 사용이 적절합니다.
dto에서request패키지로의 이동과BookCoverSizeenum 사용이 코드 구조를 개선합니다.
29-48: 팩토리 메서드 호출 검증 완료
AladinBookSearchRequest.create호출이 전혀 없으며AladinBookSearchRequest.of(...)호출만 존재합니다.모든 호출부가 새로운 시그니처에 맞춰 업데이트된 것이 확인되었습니다. 그대로 머지 진행해주세요.
apis/src/main/kotlin/org/yapp/apis/book/dto/request/UserBookRegisterRequest.kt (2)
8-11: Swagger 문서화 개선이 좋습니다.API 문서화를 위한 상세한 스키마 어노테이션 추가가 적절합니다.
23-35: BookStatus 유효성 검증 구현이 좋습니다.enum 값에 대한 상세한 스키마 정의와 안전한 접근자 메서드 제공이 적절합니다.
infra/src/main/kotlin/org/yapp/infra/external/aladin/request/AladinBookLookupRequest.kt (2)
1-4: 일관된 패키지 구조 개선입니다.다른 request 클래스와 일관된 패키지 이동과
BookCoverSizeenum 사용이 좋습니다.
5-8: optResult → cover 변경사항 검증 완료
- infra/src/main/kotlin/org/yapp/infra/external/aladin/request/AladinBookSearchRequest.kt
• cover?.let { params["Cover"] = it } 로 파라미터 반영- infra/src/main/kotlin/org/yapp/infra/external/aladin/request/AladinBookLookupRequest.kt
• cover?.let { params["Cover"] = it } 로 파라미터 반영- infra/src/main/kotlin/org/yapp/infra/external/aladin/AladinRestClient.kt
• 기존 OptResult 처리 로직이 “Cover”를 일반 queryParam 처리로 수용하므로 별도 보완 불필요위 검증 결과, optResult 제거 후 cover 속성 추가가 모든 요청 경로에 정상 반영되어 기존 기능에 영향이 없습니다.
apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookCreateRequest.kt (4)
10-13: 상세한 API 문서화가 훌륭합니다.포괄적인 Swagger 스키마 어노테이션으로 API 문서화가 크게 개선되었습니다.
90-103: ISBN13 중심의 리팩토링이 PR 목표와 일치합니다.
bookDetail.isbn13만 사용하고coverImageUrl속성 매핑 변경이 전체 리팩토링 방향과 일관됩니다.
55-63: 출간연도 유효성 검증(@Min/@Max) 동작 확인 요청현재
publicationYear: Int?필드에만@Min/@Max를 적용하면 null 값은 검증 대상에서 제외되어 통과됩니다.
- 해당 필드가 선택값(optional)인 경우 null 허용이 비즈니스 요구사항에 부합하는지 재확인해주세요.
- 필수 입력값이어야 한다면
@NotNull을 추가하거나 기본값을 지정하고, Bean Validation이 의도대로 동작하는지 검증하는 테스트 코드를 작성해주세요.
16-23: ISBN 유효성 검증 제약 재검토 요청현재 BookCreateRequest.kt에서
minLength=13, maxLength=13제약이 설정되어 있어 10자리 ISBN이 배제됩니다.
시스템 전반에서 ISBN-10 지원 여부 및 다른 DTO 클래스와의 일관성을 검토해 주세요.
- 파일: apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookCreateRequest.kt (16–23줄)
- 문제:
@Schema어노테이션의minLength=13, maxLength=13제약이 ISBN-10을 허용하지 않음apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookSearchResponse.kt (2)
21-23: copy() 메서드를 활용한 불변성 유지가 잘 되었습니다!DTO의 불변성을 지키면서 books 리스트를 업데이트하는 구현이 적절합니다.
38-48: 외부 API 응답 매핑이 깔끔하게 구현되었습니다!
AuthorExtractor를 사용한 저자 정보 추출과BookSummary.of팩토리 메서드를 활용한 매핑이 적절합니다.apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookDetailResponse.kt (1)
23-25: 사용자 도서 상태 업데이트 메서드가 잘 구현되었습니다!불변성을 유지하면서 상태를 업데이트하는 적절한 구현입니다.
apis/src/main/kotlin/org/yapp/apis/book/usecase/BookUseCase.kt (4)
32-42: 사용자 도서 상태 병합 로직의 모듈화가 잘 되었습니다!별도의 헬퍼 메서드로 분리하여 가독성과 재사용성이 향상되었습니다.
44-58: 사용자별 도서 상태 조회 로직이 명확하게 구현되었습니다!ISBN13이 없거나 사용자 도서 정보가 없는 경우를 적절히 처리하고 있습니다.
91-110: 효율적인 상태 병합 로직입니다!빈 리스트에 대한 조기 반환과 불변성을 유지한 상태 업데이트가 적절합니다.
112-123: ISBN을 키로 하는 상태 맵 생성이 효율적입니다!
associate함수를 활용한 깔끔한 구현입니다.
apis/src/main/kotlin/org/yapp/apis/book/dto/request/BookCreateRequest.kt
Outdated
Show resolved
Hide resolved
apis/src/main/kotlin/org/yapp/apis/book/dto/request/UserBookRegisterRequest.kt
Show resolved
Hide resolved
infra/src/main/kotlin/org/yapp/infra/external/aladin/request/AladinBookLookupRequest.kt
Show resolved
Hide resolved
…rBookService 의존성 제거 및 예외 처리 로직 개선
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🔭 Outside diff range comments (1)
apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/request/CreateReadingRecordRequest.kt (1)
20-44: 널 가능 프로퍼티 +!!해제는 런타임 NPE 위험
Bean Validation이 반드시 실행된다는 보장이 없으면valid*()에서!!로 즉시 NPE가 발생할 수 있습니다. DTO 자체를 불변·안전하게 유지하려면 프로퍼티를 nullable이 아닌val로 두고 기본값을 제거하는 편이 안전합니다.- val pageNumber: Int? = null, + val pageNumber: Int, - val quote: String? = null, + val quote: String, - val review: String? = null, + val review: String,이렇게 하면
valid*()보조 메서드도 필요 없어집니다.
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (7)
apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/request/CreateReadingRecordRequest.kt(1 hunks)apis/src/main/kotlin/org/yapp/apis/readingrecord/service/ReadingRecordService.kt(0 hunks)apis/src/main/kotlin/org/yapp/apis/readingrecord/usecase/ReadingRecordUseCase.kt(2 hunks)domain/src/main/kotlin/org/yapp/domain/readingrecord/ReadingRecordDomainService.kt(2 hunks)domain/src/main/kotlin/org/yapp/domain/readingrecord/exception/ReadingRecordErrorCode.kt(1 hunks)domain/src/main/kotlin/org/yapp/domain/readingrecord/exception/ReadingRecordNotFoundException.kt(1 hunks)infra/src/main/kotlin/org/yapp/infra/readingrecord/repository/impl/ReadingRecordRepositoryImpl.kt(1 hunks)
💤 Files with no reviewable changes (1)
- apis/src/main/kotlin/org/yapp/apis/readingrecord/service/ReadingRecordService.kt
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: hoonyworld
PR: YAPP-Github/Reed-Server#39
File: apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookSearchResponse.kt:45-45
Timestamp: 2025-07-17T15:09:36.267Z
Learning: Response DTO 클래스에서는 불변성을 지키기 위해 프로퍼티를 val로 선언하고, 업데이트가 필요한 경우 copy() 메서드를 사용해야 합니다.
⏰ 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-validation
🔇 Additional comments (6)
domain/src/main/kotlin/org/yapp/domain/readingrecord/exception/ReadingRecordNotFoundException.kt (1)
1-8: 도메인 계층으로의 예외 클래스 이동 승인예외 클래스를 API 계층(
org.yapp.apis.readingrecord.exception)에서 도메인 계층(org.yapp.domain.readingrecord.exception)으로 이동한 것은 도메인 주도 설계 원칙에 부합하는 좋은 아키텍처 개선입니다. 구조화된 에러 코드(ReadingRecordErrorCode)를 사용하여 명확한 예외 처리를 제공합니다.apis/src/main/kotlin/org/yapp/apis/readingrecord/usecase/ReadingRecordUseCase.kt (2)
57-57: 매개변수 순서 일관성 확인Line 29와 동일하게 매개변수 순서가 변경되었습니다. 두 호출 지점 모두 일관되게 업데이트되어 좋습니다.
29-29: validateUserBookExists 매개변수 순서 일관성 확인 완료서비스 메서드 시그니처
fun validateUserBookExists(userBookId: UUID, userId: UUID)와 호출부의 매개변수 순서가 일치함을 확인했습니다. 다른 호출 지점도 모두(userBookId, userId)순서로 일관되게 반영되어 있으므로 변경 사항을 승인합니다.domain/src/main/kotlin/org/yapp/domain/readingrecord/ReadingRecordDomainService.kt (2)
5-6: 도메인 예외 처리 개선을 위한 임포트 추가새로운 에러 코드와 예외 클래스 임포트가 추가되어 구조화된 예외 처리를 지원합니다.
64-69: 예외 처리 확인 완료: GlobalExceptionHandler에서 CommonException(ReadingRecordNotFoundException) 처리
findReadingRecordById가 던지는ReadingRecordNotFoundException은CommonException의 하위 클래스이며, 전역 예외 처리기(GlobalExceptionHandler)의
@ExceptionHandler(CommonException::class)에서 이미 처리되고 있습니다. 추가적인 호출 지점별 예외 처리 코드는 필요하지 않습니다.domain/src/main/kotlin/org/yapp/domain/readingrecord/exception/ReadingRecordErrorCode.kt (1)
1-16: 구조화된 에러 코드 enum 구현 승인
ReadingRecordErrorCodeenum이 잘 설계되었습니다:
BaseErrorCode인터페이스를 올바르게 구현- 적절한 HTTP 상태 코드(404) 사용
- 명확한 한국어 에러 메시지 제공
- 향후 에러 코드 확장에 용이한 구조
- 프로젝트의 다른 에러 코드 enum들과 일관된 패턴
도메인 계층에서의 명시적 에러 처리를 위한 좋은 기반을 제공합니다.
apis/src/main/kotlin/org/yapp/apis/readingrecord/dto/request/CreateReadingRecordRequest.kt
Show resolved
Hide resolved
.../src/main/kotlin/org/yapp/infra/readingrecord/repository/impl/ReadingRecordRepositoryImpl.kt
Show resolved
Hide resolved
|


🔗 관련 이슈
📘 작업 유형
📙 작업 내역
📚 BookDetailResponse에 사용자 도서 상태 정보 추가
주요 변경사항*
BookDetailResponse 구조 개선
userBookStatus: BookStatus필드 추가로 사용자의 실제 도서 상태 제공totalPage: Int?필드를 통해 책의 총 페이지 수 정보 제공withUserBookStatus()메서드 추가로 상태 업데이트 지원from()메서드에userBookStatus매개변수 추가 (기본값:BEFORE_REGISTRATION)도메인 서비스 레이어 확장
UserBookDomainService.findByUserIdAndBookIsbn()메서드 추가null반환으로 자연스러운 처리서비스 레이어 기능 추가
UserBookService.findUserBookStatusByIsbn()메서드 추가null반환하여 정상적인 케이스로 처리UseCase 로직 개선
BookUseCase.getBookDetail()에서 실제 사용자 라이브러리 상태 확인 로직 추가BEFORE_REGISTRATION반환동작 방식
API 응답 예시
기대 효과
🧪 테스트 내역
✅ PR 체크리스트
💬 추가 설명 or 리뷰 포인트
🔍 주요 리뷰 포인트
예외 처리 방식 개선
UserBookNotFoundException예외 발생null반환으로 변경성능 고려사항
findUserBookStatusByIsbn)getUserBookStatusMap) 방식 유지API 스펙 변경 영향도
userBookStatus필드 추가totalPage필드 정보 제공 강화도메인 모델 설계
🚀 향후 개선 방향
Summary by CodeRabbit
신규 기능
기능 개선
/swagger-ui로 변경되었습니다.버그 수정
기타