Skip to content

Conversation

@move-hoon
Copy link
Member

@move-hoon move-hoon commented Jul 14, 2025

🔗 관련 이슈

📘 작업 유형

  • ✨ Feature (기능 추가)
  • 🐞 Bugfix (버그 수정)
  • 🔧 Refactor (코드 리팩토링)
  • ⚙️ Chore (환경 설정)
  • 📝 Docs (문서 작성 및 수정)
  • ✅ Test (기능 테스트)
  • 🎨 style (코드 스타일 수정)

📙 작업 내역

  • 인증 도메인 리팩토링 작업 완료했습니다.

(추가)

변경 사항

  • User, Book, UserBook 등 주요 도메인 모델의 핵심 식별자 및 속성들을 원시 타입(primitive type)에서 타입-안전한 값 객체(Value Object, VO)로 변경했습니다.
  • 적용 필드 예시: id, email, isbn 등
  • 구현 방식: Kotlin의 value class와 newInstance 정적 팩토리 메서드 패턴을 적용했습니다.

변경 내용

Domain Layer:

  • User, Book, UserBook 도메인 모델 내부에 Id, Email, Isbn 등의 value class를 정의했습니다.
  • 각 값 객체는 newInstance 팩토리 메서드를 통해 생성되며, 이 메서드 내에서 유효성 검증(e.g., require(...))을 수행합니다.

Infrastructure Layer:

  • JPA Entity(UserEntity 등)의 필드는 DB와의 호환성을 위해 원시 타입을 유지했습니다.
  • Entity의 toDomain/fromDomain 메서드를 수정하여, Entity(원시 타입)와 Domain(값 객체) 간의 매핑을 책임지도록 했습니다.

Application Layer & Presentation Layer:

  • 최종적으로 API 응답을 생성하는 Response DTO의 from() 메서드에서 값 객체의 .value 프로퍼티를 호출하여 실제 String, UUID 값을 추출하도록 수정했습니다

코드예시

data class UserProfileVO private constructor(
    val id: User.Id, // vo 필드 사용 - 응집도 및 타입 안정성 보장
    val email: User.Email, // vo 필드 사용 - 응집도 및 타입 안정성 보장
    val nickname: String,
    val provider: ProviderType
) {
    init {
        require(nickname.isNotBlank()) {"nickname은 비어 있을 수 없습니다."}
        require(provider.name.isNotBlank()) { "providerType은 비어 있을 수 없습니다." }
    }

    companion object {
        fun newInstance(user: User?): UserProfileVO {
            requireNotNull(user) { "User는 null일 수 없습니다." }

            return UserProfileVO(
                id = user.id,
                email = user.email,
                nickname = user.nickname,
                provider = user.providerType
            )
        }
    }
}
@Schema(
    name = "UserProfileResponse",
    description = "Response containing user profile information"
)
data class UserProfileResponse(

    @Schema(
        description = "Unique identifier of the user",
        example = "c4d46ff7-9f1b-4c5f-8262-9fa2f982a7f4"
    )
    val id: UUID,

    @Schema(
        description = "User email address",
        example = "[email protected]"
    )
    val email: String,

    @Schema(
        description = "User nickname",
        example = "HappyPanda"
    )
    val nickname: String,

    @Schema(
        description = "Social login provider type",
        example = "KAKAO"
    )
    val provider: ProviderType
) {
    companion object {
        fun from(userProfileVO: UserProfileVO): UserProfileResponse {
            return UserProfileResponse(
                id = userProfileVO.id.value, // value로 실제 타입을 꺼냄
                email = userProfileVO.email.value, // value로 실제 타입을 꺼냄
                nickname = userProfileVO.nickname,
                provider = userProfileVO.provider
            )
        }
    }
}

🧪 테스트 내역

  • 브라우저/기기에서 동작 확인
  • 엣지 케이스 테스트 완료
  • 기존 기능 영향 없음

🎨 스크린샷 또는 시연 영상 (선택)

기능 미리보기 기능 미리보기
기능 설명 기능 설명

✅ PR 체크리스트

  • 커밋 메시지가 명확합니다
  • PR 제목이 컨벤션에 맞습니다
  • 관련 이슈 번호를 작성했습니다
  • 기능이 정상적으로 작동합니다
  • 불필요한 코드를 제거했습니다

💬 추가 설명 or 리뷰 포인트 (선택)

도메인 서비스에 에러핸들링 도입

  • 도메인 서비스에 에러 핸들링을 도입한 이유는, 원래는 모든 예외 처리를 애플리케이션 서비스 단에서 수행하려 했으나, UserProfile, UserIdentity 등 VO를 생성하기 전에 도메인 객체의 유효성이 반드시 보장되어야 했기 때문입니다.
  • 도메인 서비스에서 User?를 그대로 반환하고 애플리케이션 계층에서 에러를 처리하면, VO 생성 시점에서 null인 값을 참조하게 되는 구조적 문제가 발생할 수 있습니다.
  • 따라서 도메인 내부에서 **“존재하지 않는 사용자”**와 같은 도메인 고유의 예외는 그 책임을 가진 도메인 서비스에서 직접 처리하는 것이 응집도 측면에서도 더 적합합니다.
  • 예를 들어, UserNotFoundException과 같은 예외는 사용자 존재 여부를 판단하고 있는 도메인 계층에서 발생시키는 것이 의미 있고,
    반대로 인증 실패, 권한 없음 등 AuthException은 애플리케이션 계층에서 처리하는 방식이 관심사의 분리를 명확하게 유지하면서도 책임 있는 설계라고 판단했습니다.

VO는 responseDTO의 필드만 갖도록 설계

  • 단순히 도메인의 모든 필드를 갖는 UserVO, BookVO 같은 범용 VO는 필드 확장에 유연하다는 장점은 있지만, 명확한 책임이나 목적이 없는 경우가 많아 역할이 흐려질 수 있습니다.
  • 반면, UserProfile, UserIdentity처럼 특정 용도(예: 토큰 발급 등)에 맞춘 목적성 있는 VO는 필드가 적고 명확한 책임을 가지므로 도메인의 변화에 따른 영향도 적고, 확장성 및 가독성이 뛰어난 구조를 유지할 수 있습니다.

누락된 스웨거 명세

  • 저희가 dto에 스키마 명세를 안하고 넘어간게 많았습니다. 따라서 API 문서의 정확성과 협업 편의성을 위해, DTO마다 @Schema(name, description)과 각 필드의 description, example 등을 명시적으로 추가하고 리팩터링했습니다.

Summary by CodeRabbit

Summary by CodeRabbit

  • 신규 기능

    • 사용자 인증 및 프로필 관련 신규 요청/응답 DTO가 추가되었습니다.
    • 토큰 관리용 요청/응답 DTO 및 관련 예외 처리 기능이 도입되었습니다.
    • 사용자, 도서, 사용자도서 도메인에 VO 및 인라인 클래스가 도입되어 타입 안정성과 검증이 강화되었습니다.
    • 사용자, 도서, 토큰, 도메인 예외 및 에러 코드가 추가되어 명확한 오류 처리가 가능해졌습니다.
  • 변경 사항

    • 소셜 로그인, 토큰 생성 및 재발급 로직이 요청/응답 DTO 기반으로 개선되어 데이터 흐름이 명확해졌습니다.
    • 사용자 조회, 생성, 복구 서비스 및 도메인 계층 반환 타입이 VO 및 DTO로 일원화되었습니다.
    • 사용자 엔터티에 소프트 삭제 필터가 적용되어 기본적으로 삭제된 사용자가 조회되지 않습니다.
    • JWT 인증 변환 로직이 개선되어 권한 처리 및 사용자 식별자가 UUID 타입으로 변경되었습니다.
    • 인증 관련 내부 호출 방식이 간소화되고, 토큰 검증 및 삭제 로직이 DTO 중심으로 재구성되었습니다.
    • 도서 도메인 서비스에서 도서 존재 여부 및 중복 체크 로직이 예외 기반으로 변경되었습니다.
    • 도서 및 사용자도서 관련 VO 및 엔티티 변환 로직이 개선되어 도메인 타입 일관성이 강화되었습니다.
    • 기존 사용자 생성 정보 및 소셜 사용자 프로필 관련 데이터 클래스가 제거되었습니다.
  • 버그 수정 및 개선

    • 사용자 조회 시 존재하지 않는 경우 예외를 발생시키도록 개선되어 안정성이 향상되었습니다.
    • 이메일 중복 및 존재 여부 확인 로직이 명확해지고, 토큰 검증 과정에 예외 처리 강화가 이루어졌습니다.
  • 문서화

    • API 요청/응답 객체에 Swagger 문서화 어노테이션이 추가되어 API 명세가 강화되었습니다.

@coderabbitai
Copy link

coderabbitai bot commented Jul 14, 2025

📝 Walkthrough

"""

Walkthrough

인증 도메인 전반에 걸쳐 리팩토링이 이루어졌습니다. 주요 변경 사항은 인증 및 사용자 관련 서비스, DTO, VO, 예외 처리, 리포지토리, 전략 패턴 등에서 요청/응답 객체 도입, 반환 타입 및 메서드 시그니처 일치화, 예외 명확화, soft-delete 적용, 불필요한 코드 제거 및 네이밍 명확화 등입니다.

Changes

파일/경로 그룹 변경 요약
apis/.../auth/controller/AuthController.kt, apis/.../auth/controller/AuthControllerApi.kt 인증 컨트롤러 내부 요청/응답 객체 전달 방식 단순화, 불필요한 import 및 어노테이션 제거
apis/.../auth/dto/UserCreateInfo.kt, domain/.../user/vo/SocialUserProfile.kt 기존 사용자 생성 정보/소셜 프로필 VO 삭제
apis/.../auth/dto/request/FindOrCreateUserRequest.kt, apis/.../auth/dto/request/GenerateTokenPairRequest.kt 사용자 찾기/생성 및 토큰 페어 생성용 신규 DTO 추가
apis/.../auth/dto/request/SocialLoginRequest.kt, apis/.../auth/dto/request/TokenRefreshRequest.kt import 경로 및 어노테이션 위치 정리, 포맷 변경(로직 영향 없음)
apis/.../auth/dto/response/CreateUserResponse.kt, apis/.../auth/dto/response/UserAuthInfoResponse.kt, apis/.../auth/dto/response/UserCreateInfoResponse.kt 사용자 생성/인증/정보 응답 DTO 신규 추가
apis/.../auth/dto/response/UserProfileResponse.kt 팩토리 메서드 파라미터를 VO로 변경 및 네이밍 변경(of→from)
apis/.../auth/service/UserAuthService.kt 서비스 계층에서 DTO 기반 입출력, VO 활용, 메서드 시그니처 및 내부 로직 리팩토링, 명확한 존재성 체크, 예외 처리 명확화
apis/.../auth/service/SocialAuthService.kt AuthCredentials import 경로 변경
apis/.../auth/strategy/AppleAuthStrategy.kt, apis/.../auth/strategy/KakaoAuthStrategy.kt, apis/.../auth/strategy/AuthStrategy.kt 소셜 인증 전략 반환 타입을 UserCreateInfo → UserCreateInfoResponse로 통일, 관련 메서드 시그니처 및 import 정리
apis/.../auth/strategy/AuthCredentials.kt 패키지 위치 변경
apis/.../auth/usecase/AuthUseCase.kt 인증 유스케이스에서 DTO 기반 흐름으로 리팩토링, 메서드 시그니처 및 내부 플로우 변경
apis/.../book/usecase/BookUseCase.kt 불필요한 공백 제거(로직 영향 없음)
domain/.../user/User.kt User 생성 팩토리 메서드 추가 및 UUID 생성 방식 통일, 주요 필드 inline value class 도입
domain/.../user/UserDomainService.kt VO 기반 반환, 예외 명확화, 메서드 네이밍 및 시그니처 일관화, save 메서드 제거
domain/.../user/UserRepository.kt, infra/.../user/repository/impl/UserRepositoryImpl.kt, infra/.../user/repository/JpaUserRepository.kt findByEmail 제거, findById 반환타입 nullable로 변경, existsByEmail 등 존재성 체크 메서드 추가
domain/.../user/exception/UserErrorCode.kt, domain/.../user/exception/UserNotFoundException.kt 사용자 도메인 전용 에러코드 enum 및 예외 클래스 추가
domain/.../user/vo/UserIdentityVO.kt, domain/.../user/vo/UserProfileVO.kt 사용자 인증/프로필 정보 VO 신규 추가
infra/.../user/entity/UserEntity.kt soft-delete를 위한 @SQLRestriction("deleted_at IS NULL") 추가, 도메인 변환 시 inline value class 적용
gateway/src/main/kotlin/org/yapp/gateway/config/JwtConfig.kt JwtAuthenticationConverter → Converter<Jwt, AbstractAuthenticationToken>로 변경, 권한 변환 및 principal UUID 처리 방식 변경
gateway/src/main/kotlin/org/yapp/gateway/security/SecurityConfig.kt jwtAuthenticationConverter 타입을 구체 클래스에서 인터페이스 타입으로 변경
apis/.../auth/dto/request/DeleteTokenRequest.kt, FindUserIdentityRequest.kt, TokenGenerateRequest.kt 리프레시 토큰 삭제, 사용자 아이디 조회, 토큰 저장용 신규 요청 DTO 추가
apis/.../auth/dto/response/RefreshTokenResponse.kt, UserIdResponse.kt 리프레시 토큰 및 사용자 아이디 응답 DTO 신규 추가
apis/.../auth/helper/AuthTokenHelper.kt 토큰 관련 메서드들이 요청/응답 DTO 기반으로 변경, 기존 원시 타입 파라미터 메서드 제거
apis/.../auth/service/TokenService.kt 토큰 저장, 삭제, 검증, 조회 메서드가 DTO 기반으로 변경, 예외 처리 위임 및 반환 타입 수정
domain/.../token/RefreshToken.kt RefreshToken 도메인에 inline value class(Id, Token, UserId) 도입 및 만료 체크 메서드 추가
domain/.../token/exception/TokenErrorCode.kt, TokenNotFoundException.kt 토큰 도메인 전용 에러코드 enum 및 예외 클래스 추가
infra/.../external/redis/entity/RefreshToken.kt RefreshTokenEntity가 data class → 일반 클래스로 변경, 도메인 객체 변환 시 inline value class 사용
apis/.../book/dto/response/BookCreateResponse.kt, UserBookResponse.kt VO 타입 변경에 따른 팩토리 메서드 파라미터 및 필드 접근 방식 변경
apis/.../book/service/UserBookService.kt VO 타입 변경에 따른 변수 및 매핑 타입 변경
domain/.../book/Book.kt ISBN 필드를 inline value class로 변경 및 검증 추가
domain/.../book/BookDomainService.kt 예외 처리 추가 및 VO 반환 타입 변경
domain/.../book/BookRepository.kt, infra/.../book/repository/impl/BookRepositoryImpl.kt findByIsbn → findById 메서드명 변경 및 existsById 메서드 추가
domain/.../book/exception/BookAlreadyExistsException.kt, BookErrorCode.kt, BookNotFoundException.kt 도서 도메인 전용 에러코드 enum 및 예외 클래스 추가
domain/.../book/vo/BookInfoVO.kt VO명 변경 및 ISBN 타입 변경
domain/.../userbook/UserBook.kt 주요 식별자 필드를 inline value class로 변경 및 검증 추가
domain/.../userbook/UserBookDomainService.kt 반환 타입 VO 변경
domain/.../userbook/vo/UserBookInfoVO.kt VO명 변경 및 식별자 타입 변경
infra/.../userbook/entity/UserBookEntity.kt 도메인 변환 시 inline value class 적용, 필드 순서 조정 및 equals/hashCode 위치 변경
infra/.../userbook/repository/JpaUserBookRepository.kt, impl/UserBookRepositoryImpl.kt 패키지명 변경 및 import 경로 수정
apis/.../book/service/BookManagementService.kt 도서 미존재 시 신규 생성 로직 제거, 단순 조회만 수행
apis/.../book/controller/BookController.kt 메서드 시그니처 포맷 변경(로직 영향 없음)
global-utils/src/main/kotlin/org/yapp/globalutils/validator/EmailValidator.kt 이메일 유효성 검사 유틸리티 추가
global-utils/src/main/kotlin/org/yapp/globalutils/validator/IsbnValidator.kt ISBN 유효성 검사 유틸리티 추가

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant AuthController
    participant AuthUseCase
    participant SocialAuthService
    participant AuthStrategy
    participant UserAuthService
    participant UserDomainService

    Client->>AuthController: 소셜 로그인 요청(SocialLoginRequest)
    AuthController->>AuthUseCase: signIn(socialLoginRequest)
    AuthUseCase->>SocialAuthService: resolveStrategy + authenticate
    SocialAuthService->>AuthStrategy: authenticate(credentials)
    AuthStrategy-->>SocialAuthService: UserCreateInfoResponse
    SocialAuthService-->>AuthUseCase: UserCreateInfoResponse
    AuthUseCase->>UserAuthService: findOrCreateUser(FindOrCreateUserRequest)
    UserAuthService->>UserDomainService: findUserByProviderTypeAndProviderId
    alt 사용자 없음
        UserAuthService->>UserDomainService: create(...)
    else 삭제된 사용자
        UserAuthService->>UserDomainService: restoreDeletedUser
    end
    UserDomainService-->>UserAuthService: UserIdentityVO
    UserAuthService-->>AuthUseCase: CreateUserResponse
    AuthUseCase->>AuthUseCase: GenerateTokenPairRequest 생성
    AuthUseCase-->>Client: TokenPairResponse
Loading

Assessment against linked issues

Objective Addressed Explanation
인증 도메인 리팩토링: 결정된 아키텍처에 맞게 인증 도메인 전반 리팩토링 (#40)

Assessment against linked issues: Out-of-scope changes

(해당 변경사항 내에서 명확히 범위를 벗어난 기능적 코드 변경은 발견되지 않았습니다.)
"""

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

Support

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

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

CodeRabbit Commands (Invoked using PR comments)

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

Other keywords and placeholders

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

Documentation and Community

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f4758b and becef68.

📒 Files selected for processing (27)
  • apis/src/main/kotlin/org/yapp/apis/auth/controller/AuthController.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/controller/AuthControllerApi.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/UserCreateInfo.kt (0 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/request/FindOrCreateUserRequest.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/request/GenerateTokenPairRequest.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/request/SocialLoginRequest.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/response/CreateUserResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/response/UserAuthInfoResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/response/UserCreateInfoResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/response/UserProfileResponse.kt (2 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/service/SocialAuthService.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/service/UserAuthService.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/strategy/AppleAuthStrategy.kt (3 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/strategy/AuthCredentials.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/strategy/AuthStrategy.kt (2 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/strategy/KakaoAuthStrategy.kt (3 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/usecase/AuthUseCase.kt (3 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/usecase/BookUseCase.kt (0 hunks)
  • domain/src/main/kotlin/org/yapp/domain/user/User.kt (1 hunks)
  • domain/src/main/kotlin/org/yapp/domain/user/UserDomainService.kt (1 hunks)
  • domain/src/main/kotlin/org/yapp/domain/user/UserRepository.kt (1 hunks)
  • domain/src/main/kotlin/org/yapp/domain/user/exception/UserErrorCode.kt (1 hunks)
  • domain/src/main/kotlin/org/yapp/domain/user/exception/UserNotFoundException.kt (1 hunks)
  • domain/src/main/kotlin/org/yapp/domain/user/vo/UserIdentity.kt (1 hunks)
  • domain/src/main/kotlin/org/yapp/domain/user/vo/UserProfile.kt (1 hunks)
  • infra/src/main/kotlin/org/yapp/infra/user/repository/JpaUserRepository.kt (1 hunks)
  • infra/src/main/kotlin/org/yapp/infra/user/repository/impl/UserRepositoryImpl.kt (1 hunks)
💤 Files with no reviewable changes (2)
  • apis/src/main/kotlin/org/yapp/apis/book/usecase/BookUseCase.kt
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/UserCreateInfo.kt
🧰 Additional context used
🪛 detekt (1.23.8)
domain/src/main/kotlin/org/yapp/domain/user/exception/UserNotFoundException.kt

[warning] 8-9: The class or object UserNotFoundException is empty.

(detekt.empty-blocks.EmptyClassBlock)

⏰ 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 (43)
apis/src/main/kotlin/org/yapp/apis/auth/strategy/AuthCredentials.kt (1)

1-1: 패키지 구조 개선이 적절합니다.

인증 전략 관련 클래스들을 dto 패키지에서 strategy 패키지로 이동한 것은 관심사 분리 원칙에 따라 코드 구조를 개선한 좋은 변경사항입니다.

apis/src/main/kotlin/org/yapp/apis/auth/service/SocialAuthService.kt (1)

4-4: import 경로 업데이트가 올바릅니다.

AuthCredentials의 패키지 이동에 따른 import 경로 업데이트가 정확하게 반영되었습니다.

apis/src/main/kotlin/org/yapp/apis/auth/dto/request/SocialLoginRequest.kt (1)

5-7: import 경로 업데이트가 일관되게 적용되었습니다.

인증 전략 관련 클래스들(AppleAuthCredentials, AuthCredentials, KakaoAuthCredentials)의 import 경로가 모두 올바르게 strategy 패키지로 업데이트되었습니다.

apis/src/main/kotlin/org/yapp/apis/auth/controller/AuthControllerApi.kt (1)

1-109: @RequestMapping 매핑이 AuthController 구현체에 포함되어 있습니다

인터페이스에서 제거된 @RequestMapping("/api/v1/auth") 애노테이션은 AuthController 클래스 상단에 선언되어 있어 URL 매핑이 정상적으로 동작합니다. 추가 조치는 필요하지 않습니다.

domain/src/main/kotlin/org/yapp/domain/user/exception/UserNotFoundException.kt (1)

1-10: 적절한 예외 클래스 구현입니다.

UserNotFoundExceptionCommonException을 상속받아 표준적인 예외 처리 패턴을 따르고 있습니다. 생성자에서 UserErrorCode와 선택적 메시지를 받아 부모 클래스에 전달하는 구조가 적절합니다.

정적 분석 도구의 "empty class block" 경고는 false positive입니다. 클래스가 생성자를 가지고 있어 실제로는 비어있지 않습니다.

apis/src/main/kotlin/org/yapp/apis/auth/dto/response/UserProfileResponse.kt (2)

5-5: 도메인 값 객체 import 추가 확인됨

UserProfile 값 객체를 사용하기 위한 import가 적절히 추가되었습니다.


39-46: 팩토리 메서드 리팩토링 승인

개별 파라미터를 받던 of 메서드를 UserProfile 객체를 받는 from 메서드로 변경한 것은 좋은 개선입니다. 이는 캡슐화를 향상시키고 파라미터 오류 가능성을 줄여줍니다.

apis/src/main/kotlin/org/yapp/apis/auth/controller/AuthController.kt (2)

25-25: 컨트롤러 로직 단순화 승인

중간 변환 단계를 제거하고 요청 객체를 직접 전달하는 것은 좋은 개선입니다. 이는 관심사의 분리를 향상시키고 컨트롤러의 복잡성을 줄여줍니다.


31-31: 토큰 갱신 로직 단순화 승인

validRefreshToken() 호출을 제거하고 전체 요청 객체를 직접 전달하는 것은 적절한 리팩토링입니다. 검증 로직이 유스케이스 레이어로 이동된 것으로 보입니다.

apis/src/main/kotlin/org/yapp/apis/auth/strategy/AuthStrategy.kt (2)

3-3: 응답 DTO로의 import 변경 승인

UserCreateInfo에서 UserCreateInfoResponse로의 import 변경은 리팩토링 목표와 일치합니다.


14-14: AuthStrategy authenticate 반환 타입 업데이트 완료 확인

  • AuthStrategy 인터페이스(AuthStrategy.kt)와 구현체(KakaoAuthStrategy.kt, AppleAuthStrategy.kt)의 authenticate 메서드 모두 UserCreateInfoResponse로 일관되게 변경된 것을 확인했습니다.
  • 더 이상의 추가 검증이나 수정은 필요하지 않습니다.
apis/src/main/kotlin/org/yapp/apis/auth/strategy/AppleAuthStrategy.kt (3)

5-5: 응답 DTO import 변경 승인

UserCreateInfoResponse로의 import 변경이 적절히 이루어졌습니다.


24-24: 인터페이스 구현 메서드 시그니처 변경 승인

AuthStrategy 인터페이스 변경에 맞게 반환 타입이 UserCreateInfoResponse로 적절히 변경되었습니다.


46-54: 헬퍼 메서드 리팩토링 승인

createUserInfo 메서드의 반환 타입과 구현이 새로운 DTO 구조에 맞게 잘 변경되었습니다. UserCreateInfoResponse.of() 팩토리 메서드 사용이 적절합니다.

apis/src/main/kotlin/org/yapp/apis/auth/strategy/KakaoAuthStrategy.kt (3)

5-5: 응답 DTO import 변경 승인

UserCreateInfoResponse로의 import 변경이 적절히 이루어졌습니다.


25-25: 인터페이스 구현 메서드 시그니처 변경 승인

AuthStrategy 인터페이스 변경에 맞게 반환 타입이 UserCreateInfoResponse로 적절히 변경되었습니다. AppleAuthStrategy와 일관된 구현입니다.


47-55: 헬퍼 메서드 리팩토링 승인

createUserInfo 메서드의 반환 타입과 구현이 새로운 DTO 구조에 맞게 잘 변경되었습니다. 다른 전략 구현체들과 일관된 패턴을 보여줍니다.

domain/src/main/kotlin/org/yapp/domain/user/vo/UserIdentity.kt (1)

16-24: 팩토리 메서드 구현이 적절합니다

nullable User 객체를 받아 UserIdentity를 생성하는 팩토리 메서드가 잘 구현되어 있습니다. null 체크와 객체 생성 로직이 명확합니다.

domain/src/main/kotlin/org/yapp/domain/user/User.kt (1)

67-68: 한국어 주석이 적절합니다

향후 다른 역할 부여 시 사용한다는 주석이 명확하고 적절합니다.

apis/src/main/kotlin/org/yapp/apis/auth/dto/response/CreateUserResponse.kt (3)

8-11: Swagger 스키마 문서화가 적절합니다

API 문서화를 위한 Swagger 애노테이션이 잘 작성되어 있습니다. 이름과 설명이 명확합니다.


12-18: DTO 설계 패턴이 우수합니다

private constructor와 companion object 패턴을 사용하여 객체 생성을 제어하는 설계가 훌륭합니다. 한국어 설명과 예시 값도 적절합니다.


19-26: 팩토리 메서드 구현이 깔끔합니다

UserIdentity로부터 CreateUserResponse를 생성하는 팩토리 메서드가 간결하고 명확합니다.

apis/src/main/kotlin/org/yapp/apis/auth/dto/response/UserCreateInfoResponse.kt (3)

6-9: Swagger 문서화가 상세하고 적절합니다

소셜 로그인을 통한 신규 사용자 정보 DTO에 대한 설명이 명확하고 구체적입니다.


10-41: 필드 설계가 적절합니다

nullable과 non-nullable 필드가 소셜 로그인 특성에 맞게 잘 구분되어 있습니다. 소셜 제공자마다 제공하는 정보가 다를 수 있음을 고려한 설계입니다.


42-58: 입력값 검증이 우수합니다

팩토리 메서드에서 providerId.trim()을 통해 공백을 제거하는 것은 훌륭한 방어적 프로그래밍 기법입니다. 외부 API로부터 받은 데이터의 품질을 보장합니다.

apis/src/main/kotlin/org/yapp/apis/auth/dto/response/UserAuthInfoResponse.kt (2)

12-18: 최소한의 정보 노출 설계가 우수합니다

인증에 필요한 최소한의 정보(ID와 역할)만을 노출하는 설계가 보안상 좋습니다. private constructor 패턴도 적절합니다.


19-26: 팩토리 메서드 구현이 일관성 있습니다

다른 Response DTO들과 동일한 패턴의 팩토리 메서드로 일관성이 있고 구현이 명확합니다.

domain/src/main/kotlin/org/yapp/domain/user/vo/UserProfile.kt (1)

7-12: UserProfile 클래스 구조가 잘 설계되었습니다

Value Object 패턴을 적절히 적용하여 불변성과 캡슐화를 보장했습니다. private constructor와 companion object factory method 조합이 좋습니다.

domain/src/main/kotlin/org/yapp/domain/user/exception/UserErrorCode.kt (1)

6-16: 에러 코드 enum 구현이 깔끔하고 확장 가능합니다

BaseErrorCode 인터페이스를 구현하여 일관성 있는 에러 처리 구조를 제공합니다. 향후 다른 사용자 관련 에러 코드 추가가 용이합니다.

infra/src/main/kotlin/org/yapp/infra/user/repository/JpaUserRepository.kt (1)

16-18: 논리적 삭제를 고려한 존재성 검사 메소드가 잘 구현되었습니다

deletedAt 필드가 null인 경우만 존재한다고 판단하는 로직이 논리적 삭제 패턴에 적합합니다. 메소드 명도 명확하게 의도를 전달합니다.

apis/src/main/kotlin/org/yapp/apis/auth/dto/request/GenerateTokenPairRequest.kt (1)

33-48: factory method 구현이 적절합니다

companion object의 factory method들이 잘 구현되어 있고, 서로 다른 response 타입에서 GenerateTokenPairRequest를 생성할 수 있어 유연합니다.

domain/src/main/kotlin/org/yapp/domain/user/UserRepository.kt (2)

11-11: nullable 반환 타입 변경이 적절합니다

findById 메소드가 User?를 반환하도록 변경된 것이 명시적인 null 처리를 가능하게 하여 더 안전합니다.


19-22: 논리적 삭제를 고려한 존재성 검사 메소드가 잘 설계되었습니다

메소드명이 명확하게 의도를 전달하고, deletedAt 필드가 null인 경우만 존재한다고 판단하는 로직이 논리적 삭제 패턴에 완벽히 부합합니다.

infra/src/main/kotlin/org/yapp/infra/user/repository/impl/UserRepositoryImpl.kt (2)

26-28: 적절한 nullable 반환 타입 변경입니다.

findById 메서드가 nullable User?를 반환하도록 변경된 것은 도메인 계층의 명시적인 예외 처리 방식과 일치합니다.


30-36: 활성 사용자 확인을 위한 명확한 메서드 추가

삭제되지 않은 사용자만을 확인하는 existsByEmailAndDeletedAtIsNullexistsByIdAndDeletedAtIsNull 메서드 추가가 적절합니다.

apis/src/main/kotlin/org/yapp/apis/auth/dto/request/FindOrCreateUserRequest.kt (1)

44-50: 기본값 생성 로직이 적절합니다

이메일과 닉네임의 기본값 생성 로직이 명확하고 안전하게 구현되었습니다.

apis/src/main/kotlin/org/yapp/apis/auth/service/UserAuthService.kt (2)

18-32: 도메인과 API 계층 간의 명확한 변환

Value Object를 DTO로 변환하는 메서드들이 적절하게 구현되었습니다. 명시적인 예외 처리도 좋습니다.


34-51: 사용자 찾기/생성 로직의 개선된 구조

기존 사용자, 삭제된 사용자 복원, 신규 사용자 생성의 순서가 논리적이고 명확합니다.

apis/src/main/kotlin/org/yapp/apis/auth/usecase/AuthUseCase.kt (2)

26-39: 명확한 DTO 체이닝 구조

각 단계별로 적절한 DTO 변환이 이루어지며, 책임이 명확하게 분리되어 있습니다.


42-54: 토큰 재발급 로직의 개선

TokenRefreshRequest DTO 사용과 명시적인 사용자 인증 정보 조회가 적절합니다.

domain/src/main/kotlin/org/yapp/domain/user/UserDomainService.kt (3)

16-24: Value Object와 명시적 예외 처리의 적절한 사용

UserProfileUserIdentity Value Object 반환 및 UserNotFoundException 예외 처리가 도메인 계층의 책임을 명확히 합니다.


36-42: 개선된 메서드 명명

existsActiveUserByIdAndDeletedAtIsNullexistsActiveUserByEmailAndDeletedAtIsNull 메서드명이 의도를 명확히 표현합니다.


44-63: 명시적 파라미터를 사용한 사용자 생성

SocialUserProfile 대신 개별 파라미터를 받는 방식이 더 명확하고 유연합니다.

import org.yapp.apis.auth.dto.response.UserProfileResponse
import org.yapp.globalutils.exception.ErrorResponse
import java.util.UUID
import java.util.*
Copy link

@coderabbitai coderabbitai bot Jul 14, 2025

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

wildcard import 사용을 재고해보세요.

java.util.* wildcard import 대신 java.util.UUID를 명시적으로 import하는 것이 더 명확하고 예측 가능합니다.

다음과 같이 수정하는 것을 권장합니다:

-import java.util.*
+import java.util.UUID
🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/auth/controller/AuthControllerApi.kt at
line 20, replace the wildcard import 'java.util.*' with an explicit import of
'java.util.UUID' to improve code clarity and maintainability.

Copy link
Member

Choose a reason for hiding this comment

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

이부분 반영되면 좋을 것 같아요!

Copy link

Choose a reason for hiding this comment

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

@minwoo1999 네, 맞습니다! 명시적인 import가 코드 가독성과 유지보수성에 더 좋겠네요.

이전에 학습한 내용에 따르면, IntelliJ IDEA에서도 와일드카드 임포트를 방지하는 설정을 통해 이런 상황을 미연에 방지할 수 있습니다. File > Settings > Editor > Code Style > Java > Imports 탭에서 "Class count to use import with '*'" 값을 높게 설정하시면 도움이 될 것 같습니다.

해당 변경사항을 적용해드리겠습니다.

Task: Replace wildcard import with explicit import

  • Replace java.util.* with java.util.UUID in AuthControllerApi.kt

🧠 Learnings used
Learnt from: hoonyworld
PR: YAPP-Github/Reed-Server#31
File: apis/src/main/kotlin/org/yapp/apis/book/controller/BookControllerApi.kt:13-13
Timestamp: 2025-07-08T14:24:44.340Z
Learning: IntelliJ IDEA에서 와일드카드 임포트를 방지하는 설정 방법: File > Settings > Editor > Code Style > Java > Imports 탭에서 "Class count to use import with '*'" 값을 높게 설정(예: 500)하면 해당 임계값 이하에서는 와일드카드 임포트가 자동으로 사용되지 않음. 와일드카드 임포트 관련 리뷰 시 이 IDE 설정 방법도 함께 안내하면 좋음.

Learnt from: hoonyworld
PR: YAPP-Github/Reed-Server#31
File: apis/src/main/kotlin/org/yapp/apis/book/controller/BookControllerApi.kt:13-13
Timestamp: 2025-07-08T14:24:44.340Z
Learning: hoonyworld가 와일드카드 임포트 관련하여 IDE 설정을 통한 자동 최적화 방법을 추천했습니다. 향후 임포트 관련 리뷰 시 IDE 설정 방법도 함께 안내하는 것이 좋겠습니다.

⏳ Preparing changes...

Copy link

Choose a reason for hiding this comment

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

A summary of the changes CodeRabbit can apply:

  • Update the AuthControllerApi.kt file by replacing the wildcard import of java.util.* with an explicit import of java.util.UUID to improve code clarity and maintainability.
  • ✅ Create PR with these edits
  • 📌 Create commit in current branch
  • 📋 Get copyable edits

Comment on lines 11 to 14
init {
requireNotNull(id) { "User ID must not be null." }
requireNotNull(role) { "User role must not be null." }
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

불필요한 null 체크 제거 필요

idrole 프로퍼티가 이미 non-nullable 타입으로 선언되어 있으므로, init 블록의 requireNotNull 체크는 불필요합니다. 컴파일러가 이미 null 값을 허용하지 않습니다.

-    init {
-        requireNotNull(id) { "User ID must not be null." }
-        requireNotNull(role) { "User role must not be null." }
-    }
📝 Committable suggestion

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

Suggested change
init {
requireNotNull(id) { "User ID must not be null." }
requireNotNull(role) { "User role must not be null." }
}
🤖 Prompt for AI Agents
In domain/src/main/kotlin/org/yapp/domain/user/vo/UserIdentity.kt at lines 11 to
14, remove the init block containing requireNotNull checks for id and role, as
these properties are already declared with non-nullable types, making the null
checks redundant and unnecessary.

Comment on lines 43 to 65
fun create(
email: String,
nickname: String,
profileImageUrl: String?,
providerType: ProviderType,
providerId: String,
createdAt: LocalDateTime,
updatedAt: LocalDateTime,
deletedAt: LocalDateTime? = null
): User {
return User(
id = UUID.randomUUID(),
email = email,
nickname = nickname,
profileImageUrl = profileImageUrl,
providerType = providerType,
providerId = providerId,
role = Role.USER,
createdAt = createdAt,
updatedAt = updatedAt,
deletedAt = deletedAt
)
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

기본 사용자 생성을 위한 팩토리 메서드 추가를 승인합니다

기본 Role.USER로 사용자를 생성하는 간편한 팩토리 메서드가 잘 구현되어 있습니다. 일반적인 사용자 생성 시나리오에서 유용할 것입니다.

createcreateWithRole 메서드 간의 코드 중복을 줄이기 위해 다음과 같이 개선할 수 있습니다:

 fun create(
     email: String,
     nickname: String,
     profileImageUrl: String?,
     providerType: ProviderType,
     providerId: String,
     createdAt: LocalDateTime,
     updatedAt: LocalDateTime,
     deletedAt: LocalDateTime? = null
 ): User {
-    return User(
-        id = UUID.randomUUID(),
-        email = email,
-        nickname = nickname,
-        profileImageUrl = profileImageUrl,
-        providerType = providerType,
-        providerId = providerId,
-        role = Role.USER,
-        createdAt = createdAt,
-        updatedAt = updatedAt,
-        deletedAt = deletedAt
-    )
+    return createWithRole(
+        email = email,
+        nickname = nickname,
+        profileImageUrl = profileImageUrl,
+        providerType = providerType,
+        providerId = providerId,
+        role = Role.USER,
+        createdAt = createdAt,
+        updatedAt = updatedAt,
+        deletedAt = deletedAt
+    )
 }
📝 Committable suggestion

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

Suggested change
fun create(
email: String,
nickname: String,
profileImageUrl: String?,
providerType: ProviderType,
providerId: String,
createdAt: LocalDateTime,
updatedAt: LocalDateTime,
deletedAt: LocalDateTime? = null
): User {
return User(
id = UUID.randomUUID(),
email = email,
nickname = nickname,
profileImageUrl = profileImageUrl,
providerType = providerType,
providerId = providerId,
role = Role.USER,
createdAt = createdAt,
updatedAt = updatedAt,
deletedAt = deletedAt
)
}
fun create(
email: String,
nickname: String,
profileImageUrl: String?,
providerType: ProviderType,
providerId: String,
createdAt: LocalDateTime,
updatedAt: LocalDateTime,
deletedAt: LocalDateTime? = null
): User {
return createWithRole(
email = email,
nickname = nickname,
profileImageUrl = profileImageUrl,
providerType = providerType,
providerId = providerId,
role = Role.USER,
createdAt = createdAt,
updatedAt = updatedAt,
deletedAt = deletedAt
)
}
🤖 Prompt for AI Agents
In domain/src/main/kotlin/org/yapp/domain/user/User.kt between lines 43 and 65,
the create method duplicates code similar to createWithRole. To reduce
duplication, refactor create to call createWithRole with Role.USER as the role
parameter, passing all other arguments through. This centralizes user creation
logic and avoids repeating the User constructor call.

Comment on lines 13 to 17
init {
require(email.isNotBlank()) { "email은 비어 있을 수 없습니다." }
require(nickname.isNotBlank()) {"nickname은 비어 있을 수 없습니다."}
require(provider.name.isNotBlank()) { "providerType은 비어 있을 수 없습니다." }
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

validation 로직에서 일관성 문제가 있습니다

provider.name으로 검증하는 것보다 enum 자체의 유효성을 검증하는 것이 더 적절할 수 있습니다.

- require(provider.name.isNotBlank()) { "providerType은 비어 있을 수 없습니다." }
+ // ProviderType은 enum이므로 이미 유효한 값이 보장됨

또한 에러 메시지 형식을 일관성 있게 맞춰주세요:

- require(nickname.isNotBlank()) {"nickname은 비어 있을 수 없습니다."}
+ require(nickname.isNotBlank()) { "nickname은 비어 있을 수 없습니다." }
📝 Committable suggestion

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

Suggested change
init {
require(email.isNotBlank()) { "email은 비어 있을 수 없습니다." }
require(nickname.isNotBlank()) {"nickname은 비어 있을 수 없습니다."}
require(provider.name.isNotBlank()) { "providerType은 비어 있을 수 없습니다." }
}
init {
require(email.isNotBlank()) { "email은 비어 있을 수 없습니다." }
require(nickname.isNotBlank()) { "nickname은 비어 있을 수 없습니다." }
// ProviderType은 enum이므로 이미 유효한 값이 보장됨
}
🤖 Prompt for AI Agents
In domain/src/main/kotlin/org/yapp/domain/user/vo/UserProfile.kt around lines 13
to 17, the validation logic inconsistently checks provider.name instead of
validating the enum provider directly. Change the validation to check the
provider enum's validity rather than its name string. Also, update the error
messages to have a consistent format matching the other require statements.

Comment on lines 19 to 31
companion object {
fun newInstance(user: User?): UserProfile {
requireNotNull(user) { "User는 null일 수 없습니다." }

return UserProfile(
id = user.id,
email = user.email,
nickname = user.nickname,
provider = user.providerType
)
}
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

factory method 구현이 적절하지만 개선이 필요합니다

requireNotNull 사용은 좋지만, 실제 사용 시 null인 경우 더 구체적인 예외 타입을 사용하는 것이 좋겠습니다.

  companion object {
-     fun newInstance(user: User?): UserProfile {
-         requireNotNull(user) { "User는 null일 수 없습니다." }
+     fun newInstance(user: User?): UserProfile {
+         user ?: throw IllegalArgumentException("User는 null일 수 없습니다.")

또는 null safety를 위해 non-null parameter로 변경하는 것도 고려해보세요:

- fun newInstance(user: User?): UserProfile {
-     requireNotNull(user) { "User는 null일 수 없습니다." }
+ fun newInstance(user: User): UserProfile {
📝 Committable suggestion

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

Suggested change
companion object {
fun newInstance(user: User?): UserProfile {
requireNotNull(user) { "User는 null일 수 없습니다." }
return UserProfile(
id = user.id,
email = user.email,
nickname = user.nickname,
provider = user.providerType
)
}
}
}
companion object {
fun newInstance(user: User?): UserProfile {
- requireNotNull(user) { "User는 null일 수 없습니다." }
+ user ?: throw IllegalArgumentException("User는 null일 수 없습니다.")
return UserProfile(
id = user.id,
email = user.email,
nickname = user.nickname,
provider = user.providerType
)
}
}
}
🤖 Prompt for AI Agents
In domain/src/main/kotlin/org/yapp/domain/user/vo/UserProfile.kt around lines 19
to 31, the factory method newInstance uses requireNotNull with a generic
exception for null user input. Improve this by either changing the user
parameter to be non-nullable to enforce null safety at compile time or throw a
more specific exception type when user is null to provide clearer error
handling.

private val code: String,
private val message: String
) : BaseErrorCode {
USER_NOT_FOUND(HttpStatus.NOT_FOUND, "USER_001", "유저 정보를 찾을 수 없습니다.");
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

세미콜론 제거를 권장합니다

Kotlin에서는 세미콜론이 필요하지 않으므로 제거하는 것이 좋습니다.

- USER_NOT_FOUND(HttpStatus.NOT_FOUND, "USER_001", "유저 정보를 찾을 수 없습니다.");
+ USER_NOT_FOUND(HttpStatus.NOT_FOUND, "USER_001", "유저 정보를 찾을 수 없습니다.")
📝 Committable suggestion

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

Suggested change
USER_NOT_FOUND(HttpStatus.NOT_FOUND, "USER_001", "유저 정보를 찾을 수 없습니다.");
USER_NOT_FOUND(HttpStatus.NOT_FOUND, "USER_001", "유저 정보를 찾을 수 없습니다.")
🤖 Prompt for AI Agents
In domain/src/main/kotlin/org/yapp/domain/user/exception/UserErrorCode.kt at
line 11, remove the semicolon at the end of the USER_NOT_FOUND enum entry, as
Kotlin does not require semicolons and it is recommended to omit them for
cleaner code.

Comment on lines +30 to +32
fun validUserId(): UUID = userId!!
fun validRole(): Role = role!!

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

!! 연산자 사용으로 인한 런타임 예외 위험

nullable 필드를 non-null로 강제 변환하는 !! 연산자는 런타임 KotlinNullPointerException을 발생시킬 수 있습니다.

위의 필드를 non-null로 변경하면 이 메소드들도 불필요해집니다:

- fun validUserId(): UUID = userId!!
- fun validRole(): Role = role!!
📝 Committable suggestion

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

Suggested change
fun validUserId(): UUID = userId!!
fun validRole(): Role = role!!
🤖 Prompt for AI Agents
In
apis/src/main/kotlin/org/yapp/apis/auth/dto/request/GenerateTokenPairRequest.kt
around lines 30 to 32, the functions validUserId() and validRole() use the !!
operator to force unwrap nullable fields, which risks runtime
KotlinNullPointerException. To fix this, change the userId and role fields to be
non-nullable types so that these functions become unnecessary and can be removed
safely.

Comment on lines 14 to 28
data class GenerateTokenPairRequest private constructor(
@field:NotBlank(message = "userId must not be null")
@Schema(
description = "User ID",
example = "a1b2c3d4-e5f6-7890-1234-56789abcdef0"
)
val userId: UUID? = null,

@field:NotBlank(message = "role must not be null")
@Schema(
description = "User role",
example = "USER"
)
val role: Role? = null
) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

validation annotation 사용이 부적절합니다

@notblank는 String에만 사용할 수 있는 annotation인데 UUID와 Role에 사용되었습니다. 또한 nullable 필드에 @notblank를 사용하는 것도 일관성이 없습니다.

- @field:NotBlank(message = "userId must not be null")
+ @field:NotNull(message = "userId must not be null")
  @Schema(
      description = "User ID",
      example = "a1b2c3d4-e5f6-7890-1234-56789abcdef0"
  )
- val userId: UUID? = null,
+ val userId: UUID,

- @field:NotBlank(message = "role must not be null")
+ @field:NotNull(message = "role must not be null")
  @Schema(
      description = "User role",
      example = "USER"
  )
- val role: Role? = null
+ val role: Role
📝 Committable suggestion

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

Suggested change
data class GenerateTokenPairRequest private constructor(
@field:NotBlank(message = "userId must not be null")
@Schema(
description = "User ID",
example = "a1b2c3d4-e5f6-7890-1234-56789abcdef0"
)
val userId: UUID? = null,
@field:NotBlank(message = "role must not be null")
@Schema(
description = "User role",
example = "USER"
)
val role: Role? = null
) {
data class GenerateTokenPairRequest private constructor(
@field:NotNull(message = "userId must not be null")
@Schema(
description = "User ID",
example = "a1b2c3d4-e5f6-7890-1234-56789abcdef0"
)
val userId: UUID,
@field:NotNull(message = "role must not be null")
@Schema(
description = "User role",
example = "USER"
)
val role: Role
) {
// ...
}
🤖 Prompt for AI Agents
In
apis/src/main/kotlin/org/yapp/apis/auth/dto/request/GenerateTokenPairRequest.kt
between lines 14 and 28, the @NotBlank annotation is incorrectly applied to UUID
and Role fields, which are not Strings, and also to nullable fields. Remove the
@NotBlank annotations from these fields and replace them with appropriate
validation annotations such as @NotNull to ensure the fields are not null, or
handle validation logic suitable for their types.

Comment on lines 32 to 42
@field:NotBlank(message = "providerType은 필수입니다.")
@Schema(
description = "소셜 로그인 제공자",
example = "KAKAO"
) val providerType: ProviderType? = null,

@field:NotBlank(message = "providerId는 필수입니다.")
@Schema(
description = "소셜 제공자에서 발급한 식별자",
example = "12345678901234567890"
) val providerId: String? = null
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Nullable 필드와 @notblank 검증의 잠재적 런타임 예외 위험

providerTypeproviderId 필드가 nullable로 선언되어 있으면서 @NotBlank 검증과 !! 연산자를 사용하는 것은 위험합니다. 검증이 실행되지 않거나 우회될 경우 NPE가 발생할 수 있습니다.

다음과 같이 non-nullable 필드로 변경하거나 안전한 접근 방식을 사용하세요:

-    @field:NotBlank(message = "providerType은 필수입니다.")
-    @Schema(
-        description = "소셜 로그인 제공자",
-        example = "KAKAO"
-    ) val providerType: ProviderType? = null,
+    @Schema(
+        description = "소셜 로그인 제공자",
+        example = "KAKAO"
+    ) val providerType: ProviderType,

-    @field:NotBlank(message = "providerId는 필수입니다.")
-    @Schema(
-        description = "소셜 제공자에서 발급한 식별자",
-        example = "12345678901234567890"
-    ) val providerId: String? = null
+    @field:NotBlank(message = "providerId는 필수입니다.")
+    @Schema(
+        description = "소셜 제공자에서 발급한 식별자",
+        example = "12345678901234567890"
+    ) val providerId: String

-    fun validProviderType(): ProviderType = providerType!!
-    fun validProviderId(): String = providerId!!
+    fun validProviderType(): ProviderType = providerType
+    fun validProviderId(): String = providerId

Also applies to: 52-53

🤖 Prompt for AI Agents
In
apis/src/main/kotlin/org/yapp/apis/auth/dto/request/FindOrCreateUserRequest.kt
around lines 32 to 42 and also lines 52 to 53, the fields providerType and
providerId are declared as nullable but annotated with @NotBlank, which can
cause runtime NullPointerExceptions if validation is bypassed. To fix this,
change these fields to be non-nullable by removing the '?' and ensure they are
always initialized with non-null values, or alternatively, implement safe null
checks before using them to avoid unsafe !! operators.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (4)
apis/src/main/kotlin/org/yapp/apis/auth/dto/response/UserAuthInfoResponse.kt (1)

8-11: Swagger 문서화 언어 일관성 검토 필요

이미 이전 리뷰에서 지적된 것처럼, 다른 DTO들은 한국어 description을 사용하는데 이 파일은 영어를 사용하고 있습니다. 프로젝트 전반의 일관성을 위해 한국어 사용을 고려해보세요.

apis/src/main/kotlin/org/yapp/apis/auth/dto/request/GenerateTokenPairRequest.kt (2)

19-20: 검증 annotation 사용이 부적절합니다

이전 리뷰에서 지적된 것처럼, @notblank는 String에만 사용할 수 있는 annotation인데 UUID와 Role에 사용되었습니다. 또한 nullable 필드에 @notblank를 사용하는 것도 일관성이 없습니다.

Also applies to: 26-27


30-32: !! 연산자 사용으로 인한 런타임 예외 위험

이전 리뷰에서 지적된 것처럼, nullable 필드를 non-null로 강제 변환하는 !! 연산자는 런타임 KotlinNullPointerException을 발생시킬 수 있습니다.

apis/src/main/kotlin/org/yapp/apis/auth/dto/request/FindOrCreateUserRequest.kt (1)

37-38: Nullable 필드와 @notblank 검증의 잠재적 런타임 예외 위험

이전 리뷰에서 지적된 것처럼, providerTypeproviderId 필드가 nullable로 선언되어 있으면서 @NotBlank 검증과 !! 연산자를 사용하는 것은 위험합니다.

Also applies to: 44-45

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between becef68 and c7ece94.

📒 Files selected for processing (7)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/request/FindOrCreateUserRequest.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/request/GenerateTokenPairRequest.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/request/SocialLoginRequest.kt (2 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/request/TokenRefreshRequest.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/response/AuthResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/response/CreateUserResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/response/UserAuthInfoResponse.kt (1 hunks)
⏰ 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 (9)
apis/src/main/kotlin/org/yapp/apis/auth/dto/request/SocialLoginRequest.kt (3)

5-7: 리팩토링에 따른 import 경로 변경이 적절합니다.

AuthCredentials 관련 클래스들을 strategy 패키지로 이동시킨 것이 인증 전략 패턴의 구조적 개선에 부합합니다. 패키지 구조가 더 명확해졌습니다.


17-21: Schema 어노테이션 포맷팅이 개선되었습니다.

단일 라인에서 다중 라인 형식으로 변경하여 가독성이 향상되었습니다. API 문서화에 필요한 모든 속성들이 명확하게 정의되어 있습니다.


25-29: OAuth 토큰 필드의 Schema 어노테이션이 잘 정의되었습니다.

다중 라인 형식으로 변경하여 가독성이 향상되었고, 설명과 예시가 적절하게 제공되어 API 문서의 품질이 개선되었습니다.

apis/src/main/kotlin/org/yapp/apis/auth/dto/response/CreateUserResponse.kt (1)

8-33: 훌륭한 구현입니다!

Response DTO 구현이 우수합니다:

  • Private constructor와 companion object factory method 패턴 사용
  • 한국어 Swagger 문서화로 프로젝트 일관성 유지
  • 도메인 객체에서 DTO로 올바른 데이터 변환
  • 명확한 책임 분리 (응답 전용 DTO)
apis/src/main/kotlin/org/yapp/apis/auth/dto/response/UserAuthInfoResponse.kt (1)

12-33: Response DTO 구현 구조가 우수합니다

CreateUserResponse와 동일한 패턴으로 일관성 있게 구현되었습니다:

  • Private constructor와 factory method 패턴 사용
  • 도메인 객체에서 적절한 데이터 추출
  • 명확한 책임 분리
apis/src/main/kotlin/org/yapp/apis/auth/dto/request/GenerateTokenPairRequest.kt (1)

34-47: Factory method 구현이 깔끔합니다

Response DTO들로부터 Request DTO를 생성하는 factory method들이 잘 구현되어 있습니다:

  • CreateUserResponse와 UserAuthInfoResponse 모두 지원
  • 명확한 데이터 변환 로직
  • 타입 안전성 보장
apis/src/main/kotlin/org/yapp/apis/auth/dto/response/AuthResponse.kt (1)

24-25: 메소드 시그니처 포맷팅 변경 승인

단순한 포맷팅 변경으로 기능에 영향을 주지 않습니다. 코드의 가독성도 유지됩니다.

apis/src/main/kotlin/org/yapp/apis/auth/dto/request/FindOrCreateUserRequest.kt (2)

47-53: 기본값 처리 로직이 우수합니다

getOrDefaultEmail()getOrDefaultNickname() 메소드가 잘 구현되어 있습니다:

  • Null 안전성을 고려한 기본값 생성
  • 빈 문자열 체크도 포함
  • 도메인 로직에 맞는 기본값 생성 (이메일 형식, 닉네임 생성기 사용)

58-71: Factory method 구현이 깔끔합니다

UserCreateInfoResponse로부터 Request DTO를 생성하는 factory method가 잘 구현되어 있습니다:

  • 모든 필드를 적절히 매핑
  • 명확한 데이터 변환 로직
  • 타입 안전성 보장

@field:NotBlank(message = "Refresh token is required")
val refreshToken: String? = null
) {
fun validRefreshToken(): String = refreshToken!!
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

불필요한 !! 제거로 NPE 위험 해소

위 필드가 non-null 로 변경되면 아래 메서드는 단순 반환으로 충분합니다.

-fun validRefreshToken(): String = refreshToken!!
+fun validRefreshToken(): String = refreshToken

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

🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/auth/dto/request/TokenRefreshRequest.kt at
line 19, the method validRefreshToken() uses the not-null assertion operator
(!!) on refreshToken, which can cause a NullPointerException. Since the
refreshToken field should be non-null, remove the !! operator and simply return
refreshToken directly to eliminate the unnecessary null check and avoid
potential NPE.

Comment on lines +16 to 17
@field:NotBlank(message = "Refresh token is required")
val refreshToken: String? = null
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

@NotBlank 와 nullable 타입이 상충됩니다.

@NotBlank 는 값이 null 이면 즉시 제약 위반이 발생하도록 설계되었습니다. 그런데 현재 refreshToken 타입이 String?(nullable) 이고 기본값까지 null 로 지정되어 있어, DTO 인스턴스가 생성되는 순간부터 제약 위반 상태가 됩니다. 또한 이후 !! 연산자 사용을 강제하게 되어 NPE 위험이 존재합니다.

-    @field:NotBlank(message = "Refresh token is required")
-    val refreshToken: String? = null
+    @field:NotBlank(message = "Refresh token is required")
+    val refreshToken: String

이렇게 변경하면

  1. 컴파일 단계에서 null 전달을 차단하여 불필요한 런타임 검증을 줄이고,
  2. 하단 validRefreshToken()!! 도 제거할 수 있습니다.
    DTO 생성 방식(예: @Builder, 정적 팩토리 등)이 있다면 해당 부분 역시 non-null 서명으로 맞춰주세요.
📝 Committable suggestion

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

Suggested change
@field:NotBlank(message = "Refresh token is required")
val refreshToken: String? = null
@field:NotBlank(message = "Refresh token is required")
val refreshToken: String
🤖 Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/auth/dto/request/TokenRefreshRequest.kt at
lines 16-17, the refreshToken property is marked as nullable String? with a
default null value but annotated with @NotBlank, which conflicts because
@NotBlank expects a non-null value. Change the type of refreshToken to a
non-nullable String without a default value to enforce non-null at compile time,
remove the default null, and update any related builder or factory methods to
match this non-null signature. This will eliminate the need for !! operators and
reduce runtime validation.

@move-hoon move-hoon changed the title refactor: 인증 도메인 리팩토링 작업 완료 refactor: 인증 도메인 리팩토링 Jul 14, 2025
Copy link
Member

@minwoo1999 minwoo1999 left a comment

Choose a reason for hiding this comment

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

"고생 많으셨습니다! 리뷰는 간단한 부분만 남겼으니 확인 부탁드릴게요 🙇‍♂️
동훈님의 생각 말씀 주시면 감사하겠습니다"

import org.yapp.apis.auth.dto.response.CreateUserResponse
import org.yapp.apis.auth.dto.response.UserAuthInfoResponse
import org.yapp.globalutils.auth.Role
import java.util.*
Copy link
Member

Choose a reason for hiding this comment

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

이부분도 반영되면 좋을 것 같네요!

import io.swagger.v3.oas.annotations.media.Schema
import org.yapp.domain.user.vo.UserIdentity
import org.yapp.globalutils.auth.Role
import java.util.*
Copy link
Member

Choose a reason for hiding this comment

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

이부분도 반영되면 좋을 것 같네요!

import io.swagger.v3.oas.annotations.media.Schema
import org.yapp.domain.user.vo.UserIdentity
import org.yapp.globalutils.auth.Role
import java.util.*
Copy link
Member

Choose a reason for hiding this comment

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

이부분도 반영되면 좋을 것 같네요!

import io.swagger.v3.oas.annotations.media.Schema
import org.yapp.domain.user.ProviderType
import org.yapp.domain.user.vo.UserProfile
import java.util.*
Copy link
Member

Choose a reason for hiding this comment

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

이부분도 반영되면 좋을 것 같네요!

import org.yapp.globalutils.auth.Role
import java.util.UUID

data class UserIdentity(
Copy link
Member

Choose a reason for hiding this comment

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

"해당 객체가 VO(Value Object)라는 점이 한눈에 잘 드러나지 않는 것 같아, 클래스 이름에 VO와 같은 접미사를 붙여 명시해보는 건 어떨까요? 유지보수 시 역할을 좀 더 명확히 구분하는 데 도움이 될 것 같습니다!"

import org.yapp.domain.user.User
import java.util.UUID

data class UserProfile private constructor(
Copy link
Member

Choose a reason for hiding this comment

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

"해당 객체가 VO(Value Object)라는 점이 한눈에 잘 드러나지 않는 것 같아, 클래스 이름에 VO와 같은 접미사를 붙여 명시해보는 건 어떨까요? 유지보수 시 역할을 좀 더 명확히 구분하는 데 도움이 될 것 같습니다!"

fun findByEmail(email: String): UserEntity?
fun existsByIdAndDeletedAtIsNull(id: UUID): Boolean

fun existsByEmailAndDeletedAtIsNull(email: String): Boolean
Copy link
Member

Choose a reason for hiding this comment

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

"DeletedAt이 null인지 반복적으로 체크하다 보면 중복된 코드가 계속 생길 수 있을 것 같아요.
@where(clause = "deleted_at IS NULL") 어노테이션을 entity 클래스에 적용하면, 기본적으로 해당 조건이 자동으로 적용돼 중복 없이 처리할 수 있어서 더 깔끔할 것 같습니다!"

Copy link
Member Author

Choose a reason for hiding this comment

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

Hibernate 6에서 @where에서 @SQLRestriction로 바뀌었다고 해서 해당 설정으로 적용하겠습니다 ㅎㅎ
감사합니다!!

Copy link
Member

Choose a reason for hiding this comment

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

좋아요~

fun findByProviderTypeAndProviderId(providerType: ProviderType, providerId: String): UserEntity?

fun findByEmail(email: String): UserEntity?
fun existsByIdAndDeletedAtIsNull(id: UUID): Boolean
Copy link
Member

Choose a reason for hiding this comment

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

"DeletedAt이 null인지 반복적으로 체크하다 보면 중복된 코드가 계속 생길 수 있을 것 같아요.
@where(clause = "deleted_at IS NULL") 어노테이션을 entity 클래스에 적용하면, 기본적으로 해당 조건이 자동으로 적용돼 중복 없이 처리할 수 있어서 더 깔끔할 것 같습니다!"

Copy link
Member Author

Choose a reason for hiding this comment

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

Hibernate 6에서 @where에서 @SQLRestriction로 바뀌었다고 해서 해당 설정으로 적용하겠습니다 ㅎㅎ
감사합니다!!

Copy link
Member

Choose a reason for hiding this comment

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

네넵~ 좋아요

import org.yapp.globalutils.auth.Role
import org.yapp.globalutils.util.TimeProvider
import java.util.UUID
import java.util.*
Copy link
Member

Choose a reason for hiding this comment

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

이부분도 UUID로 수정 되면 좋을 것 같아요!

deletedAt: LocalDateTime? = null
): User {
return User(
id = UUID.randomUUID(),
Copy link
Member

Choose a reason for hiding this comment

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

"랜덤 UUID를 직접 사용하는 것보다는, 저희 GlobalUtils 안에 있는 uuidV7을 사용해서 생성하면 더 좋을 것 같아요! 통일된 방식으로 관리할 수 있어서 추후 유지보수나 로그 추적 측면에서도 이점이 있을 것 같습니다."

Copy link
Member Author

Choose a reason for hiding this comment

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

오 제가 이 부분은 놓쳤네요!! 반영해두겠습니다!

Copy link
Member

Choose a reason for hiding this comment

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

네넵~

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (4)
apis/src/main/kotlin/org/yapp/apis/auth/dto/response/CreateUserResponse.kt (1)

14-23: Swagger 문서화 언어 일관성을 맞춰주세요.

과거 리뷰에서 지적된 것처럼 Swagger 어노테이션에서 한국어와 영어가 혼재되어 있습니다. 프로젝트 전반의 일관성을 위해 언어를 통일해주세요.

apis/src/main/kotlin/org/yapp/apis/auth/dto/response/UserAuthInfoResponse.kt (1)

8-11: Swagger 문서화 언어 일관성을 맞춰주세요.

과거 리뷰에서 지적된 것처럼 다른 DTO들과 언어 일관성을 맞춰야 합니다. 프로젝트 전반의 일관성을 위해 한국어 또는 영어로 통일해주세요.

domain/src/main/kotlin/org/yapp/domain/user/vo/UserProfileVO.kt (1)

14-14: 불필요한 provider.name 검증 제거 필요

ProviderType이 enum이므로 name이 항상 존재하여 이 검증은 불필요합니다.

domain/src/main/kotlin/org/yapp/domain/user/User.kt (1)

44-66: 팩토리 메서드의 코드 중복 개선 필요

이전 리뷰에서 지적된 create와 createWithRole 메서드 간의 코드 중복이 여전히 존재합니다.

다음과 같이 개선할 수 있습니다:

 fun create(
     email: String,
     nickname: String,
     profileImageUrl: String?,
     providerType: ProviderType,
     providerId: String,
     createdAt: LocalDateTime,
     updatedAt: LocalDateTime,
     deletedAt: LocalDateTime? = null
 ): User {
-    return User(
-        id = Id.newInstance(UuidGenerator.create()),
-        email = Email.newInstance(email),
-        nickname = nickname,
-        profileImageUrl = profileImageUrl,
-        providerType = providerType,
-        providerId = ProviderId.newInstance(providerId),
-        role = Role.USER,
-        createdAt = createdAt,
-        updatedAt = updatedAt,
-        deletedAt = deletedAt
-    )
+    return createWithRole(
+        email = email,
+        nickname = nickname,
+        profileImageUrl = profileImageUrl,
+        providerType = providerType,
+        providerId = providerId,
+        role = Role.USER,
+        createdAt = createdAt,
+        updatedAt = updatedAt,
+        deletedAt = deletedAt
+    )
 }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0250e3a and c8421a8.

📒 Files selected for processing (28)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/response/CreateUserResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/response/UserAuthInfoResponse.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/response/UserProfileResponse.kt (2 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/service/UserAuthService.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookCreateResponse.kt (2 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/dto/response/UserBookResponse.kt (2 hunks)
  • apis/src/main/kotlin/org/yapp/apis/book/service/UserBookService.kt (2 hunks)
  • domain/src/main/kotlin/org/yapp/domain/book/Book.kt (4 hunks)
  • domain/src/main/kotlin/org/yapp/domain/book/BookDomainService.kt (3 hunks)
  • domain/src/main/kotlin/org/yapp/domain/book/BookRepository.kt (1 hunks)
  • domain/src/main/kotlin/org/yapp/domain/book/exception/BookAlreadyExistsException.kt (1 hunks)
  • domain/src/main/kotlin/org/yapp/domain/book/exception/BookErrorCode.kt (1 hunks)
  • domain/src/main/kotlin/org/yapp/domain/book/exception/BookNotFoundException.kt (1 hunks)
  • domain/src/main/kotlin/org/yapp/domain/book/vo/BookInfoVO.kt (2 hunks)
  • domain/src/main/kotlin/org/yapp/domain/user/User.kt (6 hunks)
  • domain/src/main/kotlin/org/yapp/domain/user/vo/UserIdentityVO.kt (1 hunks)
  • domain/src/main/kotlin/org/yapp/domain/user/vo/UserProfileVO.kt (1 hunks)
  • domain/src/main/kotlin/org/yapp/domain/userbook/UserBook.kt (2 hunks)
  • domain/src/main/kotlin/org/yapp/domain/userbook/UserBookDomainService.kt (3 hunks)
  • domain/src/main/kotlin/org/yapp/domain/userbook/vo/UserBookInfoVO.kt (2 hunks)
  • infra/src/main/kotlin/org/yapp/infra/book/entity/BookEntity.kt (2 hunks)
  • infra/src/main/kotlin/org/yapp/infra/book/repository/JpaBookRepository.kt (0 hunks)
  • infra/src/main/kotlin/org/yapp/infra/book/repository/impl/BookRepositoryImpl.kt (2 hunks)
  • infra/src/main/kotlin/org/yapp/infra/external/redis/entity/RefreshToken.kt (2 hunks)
  • infra/src/main/kotlin/org/yapp/infra/user/entity/UserEntity.kt (3 hunks)
  • infra/src/main/kotlin/org/yapp/infra/userbook/entity/UserBookEntity.kt (2 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 (1)
  • infra/src/main/kotlin/org/yapp/infra/book/repository/JpaBookRepository.kt
🧰 Additional context used
🧬 Code Graph Analysis (1)
apis/src/main/kotlin/org/yapp/apis/auth/service/UserAuthService.kt (1)
domain/src/main/kotlin/org/yapp/domain/user/UserDomainService.kt (1)
  • createNewUser (44-63)
⏰ 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 (58)
infra/src/main/kotlin/org/yapp/infra/external/redis/entity/RefreshToken.kt (3)

15-15: 생성자를 private로 변경한 것은 좋은 설계입니다.

외부에서 직접 인스턴스를 생성할 수 없도록 하여 팩토리 메서드를 통한 객체 생성을 강제함으로써 캡슐화를 강화했습니다.


26-28: 도메인 모델 변환 시 인라인 value class 사용이 적절합니다.

newInstance 팩토리 메서드를 사용하여 primitive 타입을 도메인 특화 value class로 변환하는 것은 타입 안전성과 유효성 검증을 강화하는 좋은 접근입니다.


36-38: 도메인 newInstance 팩토리 메서드 예외 처리 확인 완료

  • RefreshToken.Id · UserId: non-nullable UUID를 받아 래핑하므로 별도 검증 불필요
  • RefreshToken.Token: require(value.isNotBlank())로 공백 검증 적용

도메인 객체의 primitive 추출 및 예외 처리가 모두 적절히 구현되었습니다.

infra/src/main/kotlin/org/yapp/infra/userbook/repository/JpaUserBookRepository.kt (2)

1-1: 패키지 구조 리팩토링이 올바르게 적용되었습니다.

userbook 도메인을 별도 패키지로 분리하는 것은 도메인 경계를 명확히 하는 좋은 접근입니다.


4-4: import 경로 변경이 적절합니다.

패키지 구조 변경에 맞춰 UserBookEntity의 import 경로가 올바르게 업데이트되었습니다.

infra/src/main/kotlin/org/yapp/infra/userbook/repository/impl/UserBookRepositoryImpl.kt (2)

1-1: 패키지 구조 변경이 일관되게 적용되었습니다.

repository 구현체의 패키지 경로가 인터페이스와 일관되게 업데이트되었습니다.


6-7: import 경로들이 새로운 패키지 구조에 맞게 올바르게 변경되었습니다.

UserBookEntity와 JpaUserBookRepository의 import 경로가 적절히 업데이트되었습니다.

domain/src/main/kotlin/org/yapp/domain/book/exception/BookNotFoundException.kt (1)

1-8: 도메인 레벨 예외 처리가 올바르게 구현되었습니다.

BookNotFoundException 클래스가 적절히 설계되었습니다:

  • CommonException을 상속받아 일관된 예외 계층 구조를 유지
  • BookErrorCode를 통한 명확한 에러 코드 관리
  • 선택적 메시지 파라미터로 유연성 제공

이는 PR 목적에 언급된 도메인 서비스 레벨에서의 에러 핸들링 강화와 일치합니다.

apis/src/main/kotlin/org/yapp/apis/book/dto/response/UserBookResponse.kt (3)

4-4: VO 타입 변경이 적절합니다.

UserBookVO에서 UserBookInfoVO로의 변경은 목적별 VO 설계 원칙에 부합합니다.


22-22: 파라미터 타입 변경이 올바르게 적용되었습니다.

from 메서드의 파라미터 타입이 UserBookInfoVO로 정확히 변경되었습니다.


25-27: 인라인 값 클래스 사용이 적절합니다.

id, userId, bookIsbn 프로퍼티에 .value 접근을 통해 인라인 값 클래스의 실제 값을 추출하는 것은 타입 안전성을 높이는 좋은 접근입니다.

domain/src/main/kotlin/org/yapp/domain/book/exception/BookAlreadyExistsException.kt (1)

1-8: 도메인 예외 클래스가 일관되게 구현되었습니다.

BookAlreadyExistsException 클래스가 적절히 설계되었습니다:

  • BookNotFoundException과 동일한 패턴을 따라 일관성 유지
  • CommonException 상속으로 예외 계층 구조 일관성 확보
  • BookErrorCode를 통한 명확한 에러 분류

이는 도메인 레벨에서의 체계적인 예외 관리를 위한 좋은 접근입니다.

apis/src/main/kotlin/org/yapp/apis/book/service/UserBookService.kt (2)

7-7: import 변경이 적절합니다.

UserBookVO에서 UserBookInfoVO로의 변경이 PR 목표인 "목적 지향적인 VO 설계"와 일치합니다. 더 구체적이고 명확한 책임을 가진 VO 네이밍입니다.


28-29: 타입 변경이 일관되게 적용되었습니다.

UserBookInfoVO로의 타입 변경이 일관되게 적용되어 도메인 리팩토링의 의도를 잘 반영하고 있습니다.

domain/src/main/kotlin/org/yapp/domain/user/vo/UserIdentityVO.kt (2)

6-9: 목적 지향적인 VO 설계가 잘 구현되었습니다.

사용자 신원 정보만을 담당하는 명확한 책임을 가진 VO로, PR 목표인 "응답 DTO에 필요한 필드만 포함하는 목적 지향적인 VO"의 좋은 예시입니다.


11-16: 팩토리 메서드 패턴이 적절하게 적용되었습니다.

도메인 엔티티에서 VO를 생성하는 팩토리 메서드가 깔끔하게 구현되어 도메인과 VO 간의 변환 로직이 명확합니다.

domain/src/main/kotlin/org/yapp/domain/book/vo/BookInfoVO.kt (2)

5-6: 강한 타입 적용이 도메인 설계를 개선했습니다.

isbn 필드의 타입을 String에서 Book.Isbn으로 변경하여 도메인 레벨에서 ISBN 검증과 타입 안전성을 확보한 것이 좋습니다.


21-24: 팩토리 메서드가 새로운 타입 시스템에 맞게 업데이트되었습니다.

BookInfoVO를 반환하도록 메서드가 적절히 수정되어 일관성이 유지되고 있습니다.

apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookCreateResponse.kt (2)

3-3: import 변경이 도메인 리팩토링과 일치합니다.

BookInfoVO로의 변경이 도메인 레이어의 VO 네이밍 변경과 일관되게 적용되었습니다.


13-15: 인라인 값 클래스 처리가 올바르게 구현되었습니다.

bookVO.isbn.value를 통해 Book.Isbn 인라인 값 클래스에서 실제 값을 추출하는 로직이 정확하게 구현되어 도메인 모델의 변경사항을 잘 반영했습니다.

infra/src/main/kotlin/org/yapp/infra/book/entity/BookEntity.kt (2)

56-56: 엔티티-도메인 변환에서 값 객체 캡슐화가 올바르게 구현되었습니다.

Book.Isbn.newInstance(this.isbn)을 통해 문자열을 도메인 값 객체로 적절히 래핑하여 도메인 경계를 유지하고 있습니다.


70-70: 도메인-엔티티 변환에서 값 추출이 정확하게 처리되었습니다.

book.isbn.value를 통해 도메인 값 객체에서 실제 값을 추출하는 로직이 올바르게 구현되어 영속성 레이어와 도메인 레이어 간의 일관성을 보장합니다.

apis/src/main/kotlin/org/yapp/apis/auth/dto/response/UserProfileResponse.kt (1)

39-46: 팩토리 메서드 패턴과 VO 사용이 잘 적용되었습니다.

of에서 from으로 메서드명 변경과 UserProfileVO를 사용하는 패턴이 도메인 분리와 가독성 향상에 도움이 됩니다. 도메인 값 객체를 통해 응답 DTO를 생성하는 방식이 일관성 있고 명확합니다.

apis/src/main/kotlin/org/yapp/apis/auth/dto/response/CreateUserResponse.kt (1)

12-32: 팩토리 메서드 패턴과 불변성 보장이 잘 구현되었습니다.

private constructor와 companion object의 from 메서드를 사용하여 객체 생성을 제어하고, UserIdentityVO를 통해 도메인 값 객체와 응답 DTO 간의 명확한 변환 로직을 제공합니다.

infra/src/main/kotlin/org/yapp/infra/book/repository/impl/BookRepositoryImpl.kt (1)

14-20: 인터페이스 변경사항에 맞춰 구현이 잘 되어 있습니다.

findByIdOrNull을 사용하여 안전한 조회를 제공하고, existsById 메서드도 JPA 레포지토리의 메서드를 적절히 위임하여 구현했습니다. 도메인 인터페이스와 일관성 있는 구현입니다.

apis/src/main/kotlin/org/yapp/apis/auth/dto/response/UserAuthInfoResponse.kt (1)

12-33: 일관된 응답 DTO 패턴이 잘 적용되었습니다.

CreateUserResponse와 유사한 패턴을 사용하여 private constructor와 companion object의 from 메서드로 객체 생성을 제어하고, UserIdentityVO를 통해 도메인 값 객체와의 명확한 변환을 제공합니다. 인증 도메인 전반에 걸쳐 일관된 패턴이 적용되어 있습니다.

domain/src/main/kotlin/org/yapp/domain/userbook/vo/UserBookInfoVO.kt (2)

7-10: 도메인 특화 타입 적용으로 타입 안전성 향상

primitive type에서 domain-specific inline value class로 변경하여 타입 안전성과 도메인 무결성이 크게 향상되었습니다. 이는 DDD 원칙을 잘 적용한 개선사항입니다.


32-32: 클래스명 변경으로 목적성 명확화

UserBookVO에서 UserBookInfoVO로 변경하여 해당 VO의 목적이 사용자 도서 정보 조회에 특화되어 있음을 명확히 표현했습니다.

domain/src/main/kotlin/org/yapp/domain/user/vo/UserProfileVO.kt (1)

17-28: companion object factory method 구현이 적절함

nullable User를 받고 requireNotNull로 검증하는 패턴이 적절하며, 도메인 객체에서 VO로의 변환 로직도 명확합니다.

domain/src/main/kotlin/org/yapp/domain/book/exception/BookErrorCode.kt (1)

6-17: 도메인별 에러 코드 enum 구현이 우수함

BaseErrorCode 인터페이스를 구현하여 일관된 에러 처리 구조를 제공하고, 도메인별로 분리된 에러 코드 관리가 잘 되어 있습니다. HTTP 상태 코드와 메시지도 적절합니다.

infra/src/main/kotlin/org/yapp/infra/user/entity/UserEntity.kt (3)

17-17: soft-delete 필터링 구현이 적절함

@SQLRestriction을 통해 전역적으로 soft-delete된 레코드를 제외하는 구현이 적절합니다. 이는 데이터 무결성과 비즈니스 로직을 보장합니다.


55-65: domain-specific inline value class 적용으로 타입 안전성 향상

Entity에서 Domain으로의 변환 시 inline value class를 사용하여 타입 안전성이 크게 향상되었습니다. newInstance() 패턴을 통해 validation도 함께 수행됩니다.


68-81: Domain에서 Entity로의 변환 로직 적절함

inline value class의 .value 접근을 통해 primitive type을 추출하는 로직이 적절하며, 변환 과정에서 데이터 무결성이 보장됩니다.

domain/src/main/kotlin/org/yapp/domain/userbook/UserBookDomainService.kt (3)

3-3: import 변경으로 일관성 유지

UserBookVO에서 UserBookInfoVO로 import 변경이 적절하며, 전체 코드베이스의 일관성을 유지합니다.


19-19: 메서드 리턴 타입 변경으로 명확성 향상

UserBookVO에서 UserBookInfoVO로 리턴 타입을 변경하여 메서드의 목적이 더욱 명확해졌습니다.

Also applies to: 35-35


32-32: factory method 호출 변경이 적절함

UserBookInfoVO.newInstance() 호출로 변경하여 새로운 VO 타입과 일관성을 유지하고 있습니다.

Also applies to: 37-37

domain/src/main/kotlin/org/yapp/domain/book/Book.kt (3)

9-9: ISBN 도메인 모델링 개선을 승인합니다

primitive String 타입을 Isbn inline value class로 변경한 것은 타입 안전성과 도메인 무결성을 높이는 좋은 개선사항입니다.


32-32: 팩토리 메서드의 일관성 있는 업데이트를 승인합니다

create 메서드에서 Isbn.newInstance를 사용하여 value object를 생성하는 것이 올바릅니다.


72-81: ISBN 검증 로직 개선을 승인합니다

ISBN의 10자리 또는 13자리 숫자 검증 로직이 정확하게 구현되었습니다. 정규식 패턴이 올바르고 에러 메시지도 명확합니다.

domain/src/main/kotlin/org/yapp/domain/book/BookDomainService.kt (3)

13-16: 도메인 서비스의 예외 기반 에러 처리 개선을 승인합니다

nullable 반환 대신 BookNotFoundException을 던지는 것은 더 명확한 에러 처리를 제공하고 호출자가 null 체크를 할 필요가 없어집니다.


27-29: 중복 생성 방지 로직 추가를 승인합니다

save 메서드에 existsById 검사를 추가하여 중복 생성을 방지하는 것은 데이터 무결성을 보장하는 좋은 개선사항입니다.


14-14: BookRepository.findById 사용에 문제 없습니다

BookRepository는 Spring Data JPA의 JpaRepository를 상속하고 있어 별도 선언 없이도 findById(id: String) 메서드를 제공합니다.
따라서 BookDomainService 내에서 findById 를 호출하도록 변경한 것은 의도한 대로 동작하며, 기존 findByIsbn 커스텀 메서드는 인터페이스에 남아있지 않습니다.

domain/src/main/kotlin/org/yapp/domain/user/User.kt (3)

55-55: UuidGenerator 사용 개선을 승인합니다

이전 리뷰 코멘트에서 제안된 UuidGenerator.create() 사용이 올바르게 반영되었습니다.


127-135: 이메일 검증 로직을 승인합니다

이메일 형식 검증을 위한 정규식이 올바르게 구현되었고, 에러 메시지도 명확합니다.


138-145: ProviderId 검증 로직을 승인합니다

ProviderId의 blank 검증 로직이 적절하게 구현되었습니다.

infra/src/main/kotlin/org/yapp/infra/userbook/entity/UserBookEntity.kt (3)

1-1: 패키지 구조 개선을 승인합니다

패키지 선언이 org.yapp.infra.userbook.entity로 변경되어 더 명확한 구조를 제공합니다.


57-68: 도메인 매핑 로직의 inline value class 적용을 승인합니다

toDomain() 메서드에서 primitive 타입을 inline value class로 래핑하는 로직이 올바르게 구현되었습니다.


73-75: 엔티티 매핑 로직의 value 추출을 승인합니다

fromDomain() 메서드에서 inline value class의 .value 속성을 추출하는 로직이 올바르게 구현되었습니다.

apis/src/main/kotlin/org/yapp/apis/auth/service/UserAuthService.kt (4)

4-12: DTO 기반 인터페이스 개선을 승인합니다

API 레이어에서 DTO를 사용하여 도메인 객체와의 분리를 명확히 한 것은 좋은 아키텍처 개선사항입니다.


35-52: 사용자 조회/생성 로직 개선을 승인합니다

findOrCreateUser 메서드에서 활성 사용자 조회, 삭제된 사용자 복원, 신규 사용자 생성의 순서로 처리하는 로직이 명확하고 적절합니다.


54-69: 사용자 생성 로직의 이메일 중복 검사를 승인합니다

createNewUser 메서드에서 활성 사용자의 이메일 중복을 검사하는 로직이 올바르게 구현되었습니다.


30-33: FindUserIdentityRequest.validUserId() 검증 로직 일관성 확인

  • FindUserIdentityRequest.validUserId()userId!!를 통해 null을 보장하는 간단한 Non-Null Assertion 패턴으로 구현되어 있습니다.
  • 이와 동일한 패턴이 TokenGenerateRequest, SocialLoginRequest 등 다른 DTO에서도 사용되고 있어, 별도 추가 검증 로직 없이 유지해도 무방합니다.
  • (참고) NPE 발생 시 HTTP 400으로 매핑되는 예외 처리 정책이 프로젝트 전반에 일관되게 적용되는지만 한 번 더 확인해 주세요.
domain/src/main/kotlin/org/yapp/domain/userbook/UserBook.kt (6)

8-10: 강력한 타입 안전성을 위한 value class 도입이 훌륭합니다.

원시 타입 대신 Id, UserId, BookIsbn과 같은 인라인 value class를 사용하는 것은 도메인 모델링에서 매우 좋은 접근입니다. 타입 안전성을 높이고 잘못된 매개변수 전달을 컴파일 타임에 방지할 수 있습니다.


36-38: 팩토리 메서드를 통한 value class 생성이 적절합니다.

create 메서드에서 newInstance 팩토리 메서드를 사용하여 value class 인스턴스를 생성하는 것은 캡슐화 원칙을 잘 따르고 있습니다. 특히 BookIsbn의 경우 내부 검증 로직을 통과하게 됩니다.


50-52: reconstruct 메서드의 매개변수 타입 일관성이 좋습니다.

reconstruct 메서드가 value class 타입을 직접 받도록 변경된 것은 일관성 있는 접근입니다. 이미 검증된 value class를 재구성할 때 사용하는 메서드의 특성상 적절한 선택입니다.


78-83: Id value class의 단순한 구현이 적절합니다.

UUID를 감싸는 Id value class는 @JvmInline 어노테이션으로 성능 최적화를 하고 있고, 단순한 팩토리 메서드를 제공합니다. 도메인 식별자로서의 역할을 명확히 하는 좋은 설계입니다.


85-90: UserId value class의 구현이 일관성 있습니다.

Id와 동일한 패턴을 따르는 UserId value class는 사용자 식별자의 타입 안전성을 보장합니다. 일관된 설계 패턴을 유지하고 있어 좋습니다.


92-100: BookIsbn의 검증 로직이 올바르게 구현되었습니다.

ISBN 형식 검증을 위한 정규식 ^(\\d{10}|\\d{13})$이 정확하게 10자리 또는 13자리 숫자를 검증하고 있습니다. 한국어 오류 메시지도 코드베이스의 언어 정책과 일치합니다.

Comment on lines +12 to +15
init {
require(nickname.isNotBlank()) {"nickname은 비어 있을 수 없습니다."}
require(provider.name.isNotBlank()) { "providerType은 비어 있을 수 없습니다." }
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

User.Email 타입 검증 누락

User.Email이 inline value class로 변경되었으므로 이미 내부에서 검증이 수행되지만, 일관성을 위해 email 검증도 고려해보세요.

 init {
+    require(email.value.isNotBlank()) { "email은 비어 있을 수 없습니다." }
     require(nickname.isNotBlank()) {"nickname은 비어 있을 수 없습니다."}
     require(provider.name.isNotBlank()) { "providerType은 비어 있을 수 없습니다." }
 }
📝 Committable suggestion

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

Suggested change
init {
require(nickname.isNotBlank()) {"nickname은 비어 있을 수 없습니다."}
require(provider.name.isNotBlank()) { "providerType은 비어 있을 수 없습니다." }
}
init {
require(email.value.isNotBlank()) { "email은 비어 있을 수 없습니다." }
require(nickname.isNotBlank()) {"nickname은 비어 있을 수 없습니다."}
require(provider.name.isNotBlank()) { "providerType은 비어 있을 수 없습니다." }
}
🤖 Prompt for AI Agents
In domain/src/main/kotlin/org/yapp/domain/user/vo/UserProfileVO.kt around lines
12 to 15, the init block validates nickname and provider name but lacks
validation for the email field. Since User.Email is now an inline value class
with its own validation, add an explicit require check for the email property
here to maintain consistency, ensuring the email is not blank or invalid
according to your domain rules.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
apis/src/main/kotlin/org/yapp/apis/auth/dto/request/FindUserIdentityRequest.kt (1)

20-20: !! 연산자 사용 시 NPE 위험이 있습니다.

validUserId() 메서드에서 !! 연산자를 사용하면 예상치 못한 NPE가 발생할 수 있습니다. 더 안전한 방법을 고려해보세요.

다음과 같이 개선할 수 있습니다:

-    fun validUserId(): UUID = userId!!
+    fun validUserId(): UUID = requireNotNull(userId) { "userId must not be null" }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6660280 and 0d428b4.

📒 Files selected for processing (1)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/request/FindUserIdentityRequest.kt (1 hunks)
⏰ 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 (3)
apis/src/main/kotlin/org/yapp/apis/auth/dto/request/FindUserIdentityRequest.kt (3)

1-6: 패키지 선언과 임포트가 적절합니다.

필요한 라이브러리들이 올바르게 임포트되었고, 패키지 구조가 인증 도메인 리팩토링 목적에 부합합니다.


8-11: Swagger 스키마 어노테이션이 잘 작성되었습니다.

API 문서화 개선을 위한 스키마 어노테이션이 명확하게 작성되어 있어 협업 효율성이 향상될 것입니다.


22-26: 팩토리 메서드가 잘 구현되었습니다.

UserIdResponse로부터 FindUserIdentityRequest를 생성하는 팩토리 메서드가 명확하고 간결하게 구현되었습니다.

Copy link
Member

@minwoo1999 minwoo1999 left a comment

Choose a reason for hiding this comment

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

고생 많으셨습니다! 🙇‍♂️
이번 변경 범위가 꽤 넓다 보니, 전체적으로 API 통합 테스트가 필요해 보입니다.
시나리오별로 전체 API 호출 흐름을 따라가며 테스트를 한번 진행해보면 좋을 것 같습니다.

시간 되실 때 한 번 확인 부탁드리며,
언제든 필요하시면 말씀 주세요. 감사합니다 😊

import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.NotNull
import org.yapp.apis.auth.dto.response.UserIdResponse
import java.util.*
Copy link
Member

Choose a reason for hiding this comment

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

여기 수정 부탁드릴게요!

import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.NotNull
import java.util.*
Copy link
Member

Choose a reason for hiding this comment

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

여기 수정 부탁드릴게요!

import org.yapp.apis.auth.dto.response.UserIdResponse
import org.yapp.domain.token.TokenDomainRedisService
import org.yapp.domain.token.RefreshToken
import java.util.*
Copy link
Member

Choose a reason for hiding this comment

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

여기 수정 부탁드릴게요!

value class Isbn(val value: String) {
companion object {
fun newInstance(value: String): Isbn {
require(value.matches(Regex("^(\\d{10}|\\d{13})$"))) { "ISBN은 10자리 또는 13자리 숫자여야 합니다." }
Copy link
Member

Choose a reason for hiding this comment

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

global-utils에 있느 validator에 추가해서 사용해도 좋을듯요!
ISBN의 패턴은 일정하니 전역 global-util의 validator로 빼도 좋아 보입니다~

Copy link
Member Author

Choose a reason for hiding this comment

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

너무 좋습니다!!

추가적으로 의견을 아래에 남겨보았습니다 ㅎㅎ

#42 (comment)

Comment on lines +72 to +74
@JvmInline
value class Isbn(val value: String) {
companion object {
Copy link
Member

Choose a reason for hiding this comment

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

별도의 클래스로 빼지않고 내부에 정의하신 이유가 궁금합니다~

Copy link
Member Author

Choose a reason for hiding this comment

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

간단하게 언급 드리자면, 도메인에 강하게 종속된 개념이기 때문에 inner class로 정의했습니다!

자세한 내용은 아래에서 적어놨습니다 ㅎㅎ
#42 (comment)

value class BookIsbn(val value: String) {
companion object {
fun newInstance(value: String): BookIsbn {
require(value.matches(Regex("^(\\d{10}|\\d{13})$"))) { "ISBN은 10자리 또는 13자리 숫자여야 합니다." }
Copy link
Member

Choose a reason for hiding this comment

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

이부분도 global-utils로 빼도 좋을 것 같아요~

import org.yapp.domain.userbook.BookStatus
import org.yapp.domain.userbook.UserBook
import java.sql.Types
import java.util.*
Copy link
Member

Choose a reason for hiding this comment

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

이부분도 한번 확인 부탁드릴게요!

import org.springframework.data.jpa.repository.JpaRepository
import org.yapp.infra.user.entity.UserBookEntity
import org.yapp.infra.userbook.entity.UserBookEntity
import java.util.*
Copy link
Member

Choose a reason for hiding this comment

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

이부분도 한번 확인 부탁드릴게요!

import org.yapp.infra.user.repository.JpaUserBookRepository
import org.yapp.infra.userbook.entity.UserBookEntity
import org.yapp.infra.userbook.repository.JpaUserBookRepository
import java.util.*
Copy link
Member

Choose a reason for hiding this comment

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

이부분도 한번 확인 부탁드릴게요!

Comment on lines +16 to +34
fun findUserProfileById(id: UUID): UserProfileVO {
val user = userRepository.findById(id) ?: throw UserNotFoundException(UserErrorCode.USER_NOT_FOUND)
return UserProfileVO.newInstance(user)
}

fun findById(id: UUID): User? =
userRepository.findById(id)
fun findUserIdentityById(id: UUID): UserIdentityVO {
val user = userRepository.findById(id) ?: throw UserNotFoundException(UserErrorCode.USER_NOT_FOUND)
return UserIdentityVO.newInstance(user)
}

fun findByEmail(email: String): User? =
userRepository.findByEmail(email)
fun findUserByProviderTypeAndProviderId(providerType: ProviderType, providerId: String): UserIdentityVO? {
return userRepository.findByProviderTypeAndProviderId(providerType, providerId)
?.let { UserIdentityVO.newInstance(it) }
}

fun findByProviderTypeAndProviderId(providerType: ProviderType, providerId: String): User? =
userRepository.findByProviderTypeAndProviderId(providerType, providerId)
fun findUserByProviderTypeAndProviderIdIncludingDeleted(providerType: ProviderType, providerId: String): UserIdentityVO? {
return userRepository.findByProviderTypeAndProviderIdIncludingDeleted(providerType, providerId)
?.let { UserIdentityVO.newInstance(it) }
}
Copy link
Member

Choose a reason for hiding this comment

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

위 내용과 동일합니다 . exception처리

@move-hoon
Copy link
Member Author

move-hoon commented Jul 16, 2025

  1. service 레이어에서 exception잡는줄 알았는데 전부 도메인으로 뺀 부분

개인톡으로 말씀드리긴 했는데, 아카이빙 하면 좋을 것 같아서 남깁니다 ㅎㅎ

결국 VO를 생성하려면, 해당 도메인이 존재함이 보장된 상태에서 newInstance 메서드에 인자로 전달되어야 한다고 생각합니다.

그렇지 않으면 VO 내부에서는 해당 도메인이 반드시 존재한다는 전제 하에 getter를 호출하게 되는데, 실제로 객체가 존재하지 않으면 null이 전달되어 init 블록의 require 조건에서 IllegalArgumentException이 발생하게 됩니다. 이 경우, 의미 있는 예외인 UserNotFoundException으로 처리되지 않아, 에러 핸들링이 어려워지는 문제가 있었습니다.

따라서 도메인의 존재 여부나 무결성에 대한 책임은 도메인 서비스 레이어에서,
비즈니스 로직 수행 중 발생할 수 있는 예외는 애플리케이션 서비스 레이어에서 처리하는 것이 책임의 분리에 있어 더 적절하다고 생각했습니다!!

  1. validator분리

좋습니다!
이메일이나 ISBN처럼 국제적으로 형식이 정해져 있고, 여러 도메인에서 동일한 규칙으로 검증할 수 있는 항목들은 전역적인 Validator로 분리하는 것이 좋아 보입니다.
반면, 문자열 길이처럼 도메인별 혹은 국가별로 달라질 수 있는 규칙들은 해당 도메인 내부에서 검증하는 것이 더 적절하다고 생각합니다.
이러한 검증을 전역 Validator로 두게 되면, 예를 들어 A 도메인에서 문자열 길이 제한이 10자에서 15자로 변경되었을 때, 같은 전역 Validator를 사용하는 B 도메인에도 그 변경이 원하지 않는 방식으로 전파될 수 있어 문제가 발생할 수 있습니다.
따라서, 앞으로 전역으로 분리할 지, 도메인 내부에서 검증할지 리뷰하면서 같이 얼라인 맞춰보면 좋을 것 같아요 ㅎㅎ

  1. util.*

IntelliJ에서 import문 관련 설정도 해봤는데, 여전히 같은 현상이 나타나네요 ㅠㅠ
아마 네이버 코드 포맷터 세팅 자체가 이렇게 되어 있어서 그런 걸 수도 있을 것 같습니다.
얘기했던대로 우선순위 row로 가져가고 추후 같이 보는걸로 하면 좋은 것 같아요~~

  1. value객체 inner로 선언한 이유

UserId, Isbn, Email 등의 값 객체를 각각 해당 도메인 클래스의 내부 클래스로 선언했습니다.
그중 하나의 예시로, Isbn을 Book 클래스의 내부 value class로 선언한 이유는 ISBN이라는 값이 Book 도메인에 강하게 종속된 개념이기 때문입니다.

ISBN은 일반적으로 Book 도메인 외부에서 단독으로 사용될 일이 거의 없으며, Book과 함께 관리되고 사용되는 식별자입니다.
따라서 이를 내부 클래스 형태로 선언함으로써 도메인 간의 경계를 명확히 하고, 관련 책임을 Book 내부로 응집시킬 수 있다고 판단했습니다.

물론, Isbn이 향후 다른 도메인에서도 재사용될 가능성이 있다면 별도의 클래스로 분리하는 것이 더 바람직할 수 있습니다.
다만 현재 도메인 모델 구조에서는 Book 내부에 정의하는 것이 응집력 측면에서 더 적절하다고 판단했습니다.

  1. 변경범위가 많아 전체적인 API통합테스트 필요해보임!
    로컬에서 API전체적으로 테스트하면 좋아보임
    (오늘 집에가서 POSTMAN 하나 만들어서 같이 돌려쓰면 좋을듯!)

이거 너무 좋아요~

@minwoo1999
Copy link
Member

  1. service 레이어에서 exception잡는줄 알았는데 전부 도메인으로 뺀 부분

개인톡으로 말씀드리긴 했는데, 아카이빙 하면 좋을 것 같아서 남깁니다 ㅎㅎ

결국 VO를 생성하려면, 해당 도메인이 존재함이 보장된 상태에서 newInstance 메서드에 인자로 전달되어야 한다고 생각합니다.

그렇지 않으면 VO 내부에서는 해당 도메인이 반드시 존재한다는 전제 하에 getter를 호출하게 되는데, 실제로 객체가 존재하지 않으면 null이 전달되어 init 블록의 require 조건에서 IllegalArgumentException이 발생하게 됩니다. 이 경우, 의미 있는 예외인 UserNotFoundException으로 처리되지 않아, 에러 핸들링이 어려워지는 문제가 있었습니다.

따라서 도메인의 존재 여부나 무결성에 대한 책임은 도메인 서비스 레이어에서, 비즈니스 로직 수행 중 발생할 수 있는 예외는 애플리케이션 서비스 레이어에서 처리하는 것이 책임의 분리에 있어 더 적절하다고 생각했습니다!!

  1. validator분리

좋습니다! 이메일이나 ISBN처럼 국제적으로 형식이 정해져 있고, 여러 도메인에서 동일한 규칙으로 검증할 수 있는 항목들은 전역적인 Validator로 분리하는 것이 좋아 보입니다. 반면, 문자열 길이처럼 도메인별 혹은 국가별로 달라질 수 있는 규칙들은 해당 도메인 내부에서 검증하는 것이 더 적절하다고 생각합니다. 이러한 검증을 전역 Validator로 두게 되면, 예를 들어 A 도메인에서 문자열 길이 제한이 10자에서 15자로 변경되었을 때, 같은 전역 Validator를 사용하는 B 도메인에도 그 변경이 원하지 않는 방식으로 전파될 수 있어 문제가 발생할 수 있습니다. 따라서, 앞으로 전역으로 분리할 지, 도메인 내부에서 검증할지 리뷰하면서 같이 얼라인 맞춰보면 좋을 것 같아요 ㅎㅎ

  1. util.*

IntelliJ에서 import문 관련 설정도 해봤는데, 여전히 같은 현상이 나타나네요 ㅠㅠ 아마 네이버 코드 포맷터 세팅 자체가 이렇게 되어 있어서 그런 걸 수도 있을 것 같습니다. 얘기했던대로 우선순위 row로 가져가고 추후 같이 보는걸로 하면 좋은 것 같아요~~

  1. value객체 inner로 선언한 이유

UserId, Isbn, Email 등의 값 객체를 각각 해당 도메인 클래스의 내부 클래스로 선언했습니다. 그중 하나의 예시로, Isbn을 Book 클래스의 내부 value class로 선언한 이유는 ISBN이라는 값이 Book 도메인에 강하게 종속된 개념이기 때문입니다.

ISBN은 일반적으로 Book 도메인 외부에서 단독으로 사용될 일이 거의 없으며, Book과 함께 관리되고 사용되는 식별자입니다. 따라서 이를 내부 클래스 형태로 선언함으로써 도메인 간의 경계를 명확히 하고, 관련 책임을 Book 내부로 응집시킬 수 있다고 판단했습니다.

물론, Isbn이 향후 다른 도메인에서도 재사용될 가능성이 있다면 별도의 클래스로 분리하는 것이 더 바람직할 수 있습니다. 다만 현재 도메인 모델 구조에서는 Book 내부에 정의하는 것이 응집력 측면에서 더 적절하다고 판단했습니다.

  1. 변경범위가 많아 전체적인 API통합테스트 필요해보임!
    로컬에서 API전체적으로 테스트하면 좋아보임
    (오늘 집에가서 POSTMAN 하나 만들어서 같이 돌려쓰면 좋을듯!)

이거 너무 좋아요~

  1. 동의합니다. VO로 감싸서 반환해야 하는 경우, 예외는 도메인 영역에서 던지는 방향이 적절하다고 생각합니다.

  2. 이메일이나 ISBN처럼 국제적으로 형식이 정해져 있고 여러 도메인에서 공통으로 사용되는 항목은 전역 Validator로 분리하는 것이 좋아 보입니다. 반면, 문자열 길이처럼 도메인별 또는 국가별로 달라질 수 있는 규칙은 해당 도메인 내부에서 검증하는 것이 더 적절하다는 점에 동의합니다.

  3. 네 ㅎㅎ 이 부분은 급하지 않으니, 추후에 천천히 수정하시죠!

  4. 그러면 일단은 각 도메인 내부에서 정의해 사용하고, 추후 재사용 가능성이 생기거나 확장 필요성이 생기면 별도로 분리하는 방향으로 가는 건 어떨까요?

  5. 네! 좋습니다 :) 이 부분 테스트 한 번 진행해주시고, 이상 없으면 머지해주세요!

Copy link
Member

@minwoo1999 minwoo1999 left a comment

Choose a reason for hiding this comment

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

고생하셨습니다 ~~

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
0.0% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (3)
domain/src/main/kotlin/org/yapp/domain/book/Book.kt (1)

73-81: 도메인 값 객체 구현이 올바릅니다

Isbn 인라인 값 클래스의 구현이 잘 되어 있습니다:

  • @JvmInline 어노테이션으로 런타임 오버헤드 최소화
  • newInstance 팩토리 메서드로 검증 로직 캡슐화
  • 적절한 에러 메시지 제공

과거 리뷰 댓글에서 논의된 바와 같이, 도메인에 강하게 종속된 개념으로 inner class 설계가 적절합니다.

domain/src/main/kotlin/org/yapp/domain/userbook/UserBook.kt (1)

4-4: IsbnValidator 활용이 적절합니다

UserBook 도메인에서도 동일한 ISBN 검증 로직을 재사용하여 일관성을 유지하고 있습니다.

과거 리뷰 댓글에서 언급된 바와 같이 global-utils로 추출된 것이 올바른 접근입니다.

domain/src/main/kotlin/org/yapp/domain/user/User.kt (1)

45-67: 팩토리 메서드 중복 코드 개선 필요

이전 리뷰에서 지적된 createcreateWithRole 메서드 간의 코드 중복 문제가 여전히 남아있습니다.

 fun create(
     email: String,
     nickname: String,
     profileImageUrl: String?,
     providerType: ProviderType,
     providerId: String,
     createdAt: LocalDateTime,
     updatedAt: LocalDateTime,
     deletedAt: LocalDateTime? = null
 ): User {
-    return User(
-        id = Id.newInstance(UuidGenerator.create()),
-        email = Email.newInstance(email),
-        nickname = nickname,
-        profileImageUrl = profileImageUrl,
-        providerType = providerType,
-        providerId = ProviderId.newInstance(providerId),
-        role = Role.USER,
-        createdAt = createdAt,
-        updatedAt = updatedAt,
-        deletedAt = deletedAt
-    )
+    return createWithRole(
+        email = email,
+        nickname = nickname,
+        profileImageUrl = profileImageUrl,
+        providerType = providerType,
+        providerId = providerId,
+        role = Role.USER,
+        createdAt = createdAt,
+        updatedAt = updatedAt,
+        deletedAt = deletedAt
+    )
 }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d428b4 and 75d80e8.

📒 Files selected for processing (6)
  • apis/src/main/kotlin/org/yapp/apis/book/controller/BookController.kt (1 hunks)
  • domain/src/main/kotlin/org/yapp/domain/book/Book.kt (4 hunks)
  • domain/src/main/kotlin/org/yapp/domain/user/User.kt (6 hunks)
  • domain/src/main/kotlin/org/yapp/domain/userbook/UserBook.kt (2 hunks)
  • global-utils/src/main/kotlin/org/yapp/globalutils/validator/EmailValidator.kt (1 hunks)
  • global-utils/src/main/kotlin/org/yapp/globalutils/validator/IsbnValidator.kt (1 hunks)
⏰ 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 (14)
global-utils/src/main/kotlin/org/yapp/globalutils/validator/IsbnValidator.kt (1)

6-8: 유효성 검사 메서드 구현이 올바릅니다

기본적인 ISBN 형식 검증 로직이 정확하게 구현되었습니다. 전역 유틸리티로 추출하여 재사용성을 높인 것도 좋은 설계입니다.

apis/src/main/kotlin/org/yapp/apis/book/controller/BookController.kt (1)

28-30: 코드 포맷팅 개선이 적절합니다

메서드 시그니처를 멀티라인으로 변경하여 가독성을 향상시켰습니다. 클래스 내 다른 메서드들과 일관성 있는 포맷팅을 유지하고 있습니다.

domain/src/main/kotlin/org/yapp/domain/book/Book.kt (3)

3-3: IsbnValidator 도입이 적절합니다

전역 유틸리티에서 ISBN 유효성 검사 로직을 가져와 도메인 레벨에서 활용하는 것이 좋은 설계입니다. 검증 로직의 재사용성과 일관성을 보장합니다.


10-10: 타입 안전성 강화가 우수합니다

기존 String 타입을 Isbn 인라인 값 클래스로 교체하여 타입 안전성을 크게 향상시켰습니다. 컴파일 타임에 타입 오류를 잡을 수 있게 되었습니다.


33-33: 팩토리 메서드 업데이트가 정확합니다

create 메서드에서 Isbn.newInstance(isbn)를 사용하여 문자열을 검증된 Isbn 객체로 변환하는 것이 올바른 접근입니다.

domain/src/main/kotlin/org/yapp/domain/userbook/UserBook.kt (4)

9-11: 도메인 식별자 타입 안전성 강화

id, userId, bookIsbn 프로퍼티를 각각 전용 인라인 값 클래스로 교체하여 타입 안전성을 크게 향상시켰습니다. 잘못된 식별자 타입 전달을 컴파일 타임에 방지할 수 있습니다.


37-39: 팩토리 메서드의 값 객체 생성이 정확합니다

각 값 객체의 newInstance 팩토리 메서드를 올바르게 활용하여 검증된 도메인 객체를 생성하고 있습니다.


94-101: BookIsbn 검증 로직이 우수합니다

BookIsbn 인라인 값 클래스에서 IsbnValidator를 활용한 검증 로직이 잘 구현되어 있습니다:

  • 팩토리 메서드에서 검증 수행
  • 명확한 에러 메시지 제공
  • 도메인 무결성 보장

79-91: 단순 래퍼 값 객체들이 적절합니다

IdUserId 인라인 값 클래스들이 간단하면서도 효과적으로 구현되어 있습니다. 별도의 검증 로직이 필요 없는 UUID 타입에 대해서는 단순 래퍼로 충분합니다.

domain/src/main/kotlin/org/yapp/domain/user/User.kt (5)

4-5: 글로벌 유틸리티 통합 승인

UuidGenerator와 EmailValidator의 통합이 잘 이루어졌습니다. 이전 리뷰 의견이 적절히 반영되어 통일된 방식으로 UUID 생성과 이메일 검증을 관리할 수 있게 되었습니다.


24-25: Value Object 도입 승인

primitive 타입을 type-safe한 value object로 변경한 것이 좋습니다. 컴파일 타임에 타입 안정성을 보장하고 도메인 로직을 명확하게 표현할 수 있습니다.

Also applies to: 29-29


124-129: Id Value Object 구현 승인

UUID를 래핑하는 Id value class의 구현이 깔끔합니다. newInstance 팩토리 메서드를 통해 일관된 생성 패턴을 유지하고 있습니다.


131-139: Email Value Object 검증 로직 승인

EmailValidator와의 통합이 잘 이루어졌습니다. 이메일 형식 검증을 value object 생성 시점에 수행하여 도메인 무결성을 보장하고 있습니다.


95-119: Reconstruct 메서드 승인

이미 검증된 value object들을 받아 User를 재구성하는 메서드가 잘 구현되어 있습니다. 데이터베이스에서 조회한 데이터를 도메인 객체로 변환할 때 유용할 것입니다.

package org.yapp.globalutils.validator

object IsbnValidator {
private val ISBN_REGEX = Regex("^(\\d{10}|\\d{13})$")
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

ISBN 유효성 검사 로직 개선 제안

현재 구현은 단순히 10자리 또는 13자리 숫자인지만 확인하지만, 실제 ISBN 유효성 검사에는 체크 디지트(check digit) 검증이 포함되어야 합니다.

더 정확한 ISBN 유효성 검사를 위해 다음과 같은 개선을 고려해보세요:

object IsbnValidator {
-    private val ISBN_REGEX = Regex("^(\\d{10}|\\d{13})$")
+    private val ISBN_10_REGEX = Regex("^\\d{9}[\\dX]$")
+    private val ISBN_13_REGEX = Regex("^\\d{13}$")

    fun isValidIsbn(isbn: String): Boolean {
-        return isbn.matches(ISBN_REGEX)
+        val cleanIsbn = isbn.replace("-", "").replace(" ", "")
+        return when (cleanIsbn.length) {
+            10 -> isValidIsbn10(cleanIsbn)
+            13 -> isValidIsbn13(cleanIsbn)
+            else -> false
+        }
+    }
+
+    private fun isValidIsbn10(isbn: String): Boolean {
+        if (!isbn.matches(ISBN_10_REGEX)) return false
+        // ISBN-10 체크 디지트 검증 로직 추가
+        return true
+    }
+
+    private fun isValidIsbn13(isbn: String): Boolean {
+        if (!isbn.matches(ISBN_13_REGEX)) return false
+        // ISBN-13 체크 디지트 검증 로직 추가
+        return true
    }
}
📝 Committable suggestion

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

Suggested change
private val ISBN_REGEX = Regex("^(\\d{10}|\\d{13})$")
object IsbnValidator {
private val ISBN_10_REGEX = Regex("^\\d{9}[\\dX]$")
private val ISBN_13_REGEX = Regex("^\\d{13}$")
fun isValidIsbn(isbn: String): Boolean {
val cleanIsbn = isbn.replace("-", "").replace(" ", "")
return when (cleanIsbn.length) {
10 -> isValidIsbn10(cleanIsbn)
13 -> isValidIsbn13(cleanIsbn)
else -> false
}
}
private fun isValidIsbn10(isbn: String): Boolean {
if (!isbn.matches(ISBN_10_REGEX)) return false
// ISBN-10 체크 디지트 검증 로직 추가
return true
}
private fun isValidIsbn13(isbn: String): Boolean {
if (!isbn.matches(ISBN_13_REGEX)) return false
// ISBN-13 체크 디지트 검증 로직 추가
return true
}
}
🤖 Prompt for AI Agents
In global-utils/src/main/kotlin/org/yapp/globalutils/validator/IsbnValidator.kt
at line 4, the current ISBN_REGEX only checks if the input is 10 or 13 digits
long without validating the check digit. To improve accuracy, implement a
function that calculates and verifies the ISBN check digit according to ISBN-10
and ISBN-13 standards, and update the validation logic to use this function
instead of just the regex.

Comment on lines +6 to +8
fun isValidEmail(email: String): Boolean {
return email.matches(EMAIL_REGEX)
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

이메일 검증 로직 개선 제안

현재 구현은 기본적인 검증만 수행하고 있습니다. 더 robust한 검증을 위해 다음과 같은 개선을 고려해보세요:

 fun isValidEmail(email: String): Boolean {
-    return email.matches(EMAIL_REGEX)
+    return email.isNotBlank() && 
+           email.length <= 254 && // RFC 5321 제한
+           email.matches(EMAIL_REGEX) &&
+           !email.startsWith(".") &&
+           !email.endsWith(".") &&
+           !email.contains("..")
 }

또는 더 포괄적인 검증을 위해 Apache Commons Validator와 같은 검증된 라이브러리 사용을 고려해보세요.

📝 Committable suggestion

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

Suggested change
fun isValidEmail(email: String): Boolean {
return email.matches(EMAIL_REGEX)
}
fun isValidEmail(email: String): Boolean {
return email.isNotBlank() &&
email.length <= 254 && // RFC 5321 제한
email.matches(EMAIL_REGEX) &&
!email.startsWith(".") &&
!email.endsWith(".") &&
!email.contains("..")
}
🤖 Prompt for AI Agents
In global-utils/src/main/kotlin/org/yapp/globalutils/validator/EmailValidator.kt
around lines 6 to 8, the current email validation uses a simple regex that may
not cover all valid email formats. Improve the validation by either enhancing
the regex pattern to cover more edge cases or replace the custom validation with
a well-tested library like Apache Commons Validator to ensure more robust and
comprehensive email format checking.

package org.yapp.globalutils.validator

object EmailValidator {
private val EMAIL_REGEX = "^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$".toRegex()
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

이메일 정규식 패턴 검증 필요

현재 사용된 정규식 패턴이 RFC 5322 표준을 완전히 준수하는지 확인이 필요합니다. 특히 다음과 같은 케이스들을 고려해야 합니다:

  • 국제화 도메인 이름 (IDN) 지원
  • 더 복잡한 이메일 형식 (예: 따옴표로 감싸진 로컬 파트)
  • 도메인 검증 로직

다음 스크립트로 이메일 정규식의 유효성을 검증해보세요:


🏁 Script executed:

#!/bin/bash
# 이메일 정규식 패턴 테스트를 위한 검증 스크립트

# 테스트 케이스들
test_emails=(
    "[email protected]"
    "[email protected]"
    "[email protected]"
    "[email protected]"
    "test@localhost"
    "invalid.email"
    "test@"
    "@example.com"
    "[email protected]"
    "한글@example.com"
)

# Kotlin 코드로 테스트 실행
cat > EmailValidatorTest.kt << 'EOF'
object EmailValidator {
    private val EMAIL_REGEX = "^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$".toRegex()

    fun isValidEmail(email: String): Boolean {
        return email.matches(EMAIL_REGEX)
    }
}

fun main() {
    val testEmails = arrayOf(
        "[email protected]",
        "[email protected]", 
        "[email protected]",
        "[email protected]",
        "test@localhost",
        "invalid.email",
        "test@",
        "@example.com",
        "[email protected]",
        "한글@example.com"
    )
    
    testEmails.forEach { email ->
        println("$email: ${EmailValidator.isValidEmail(email)}")
    }
}
EOF

# kotlinc로 컴파일하고 실행
if command -v kotlinc &> /dev/null; then
    kotlinc EmailValidatorTest.kt -include-runtime -d EmailValidatorTest.jar
    java -jar EmailValidatorTest.jar
else
    echo "Kotlin compiler not available - manual testing recommended"
fi

Length of output: 365


아래 Python3 스크립트로 동일한 정규식 패턴을 테스트해 주세요. sandbox에 kotlinc가 없어 Kotlin에서 테스트할 수 없으므로, Python을 이용해 결과를 확인하겠습니다.


🏁 Script executed:

#!/bin/bash
python3 << 'EOF'
import re

# Kotlin EmailValidator와 동일한 정규식 패턴
pattern = re.compile(r"^[A-Za-z0-9_!#$%&'*+/=?`{|}~^.\-]+@[A-Za-z0-9.\-]+$")

# 테스트 케이스 리스트
test_emails = [
    "[email protected]",
    "[email protected]",
    "[email protected]",
    "[email protected]",
    "test@localhost",
    "invalid.email",
    "test@",
    "@example.com",
    "[email protected]",
    "한글@example.com"
]

# 실행 및 결과 출력
for email in test_emails:
    print(f"{email}: {bool(pattern.match(email))}")
EOF

Length of output: 249


이메일 정규식 패턴 수정 필요

현재 패턴은 연속된 점("..")을 허용하고, 도메인 검증이 약하며, 국제화(IDN)나 따옴표 로컬 파트를 지원하지 않습니다. 아래 항목을 검토·수정해주세요:

• 위치

  • global-utils/src/main/kotlin/org/yapp/globalutils/validator/EmailValidator.kt
    – 4행 private val EMAIL_REGEX = …

• 제안 수정 예시

- private val EMAIL_REGEX = "^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$".toRegex()
+ // 연속된 점 차단, 도메인에 최소 한 개의 점 요구, TLD 최소 2글자
+ private val EMAIL_REGEX = (
+   "^(?!.*\\.\\.)" +                                  // 연속된 점 금지
+   "[A-Za-z0-9_!#$%&'*+/=?`{|}~^.-]+" +
+   "@[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?" +
+   "(?:\\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+$"  // 도메인에 최소 1개 ‘.’ + TLD
+ ).toRegex()

• 대안 라이브러리 사용 고려

  • Apache Commons Validator
    EmailValidator.getInstance(true).isValid(email)
  • JavaMail InternetAddress
📝 Committable suggestion

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

Suggested change
private val EMAIL_REGEX = "^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$".toRegex()
// 연속된 점 차단, 도메인에 최소 한 개의 점 요구, TLD 최소 2글자
private val EMAIL_REGEX = (
"^(?!.*\\.\\.)" + // 연속된 점 금지
"[A-Za-z0-9_!#$%&'*+/=?`{|}~^.-]+" + // 로컬 파트
"@[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?" + // 도메인 레이블
"(?:\\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+$" // 최소 1개 도메인 ‘.’ + TLD
).toRegex()
🤖 Prompt for AI Agents
In global-utils/src/main/kotlin/org/yapp/globalutils/validator/EmailValidator.kt
at line 4, the current email regex pattern is insufficient as it allows
consecutive dots, has weak domain validation, and lacks support for
internationalized domain names and quoted local parts. To fix this, replace the
regex with a more robust pattern that disallows consecutive dots, properly
validates domain parts, and supports IDN and quoted local parts, or
alternatively, replace the regex validation entirely by integrating a
well-tested library such as Apache Commons Validator's EmailValidator or
JavaMail's InternetAddress for email validation.

Comment on lines +141 to +149
@JvmInline
value class ProviderId(val value: String) {
companion object {
fun newInstance(value: String): ProviderId {
require(value.isNotBlank()) { "Provider ID는 필수입니다." }
return ProviderId(value)
}
}
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

ProviderId 검증 로직 개선 제안

현재 단순히 blank 체크만 하고 있습니다. Provider별로 다른 검증 규칙이 필요할 수 있습니다.

 @JvmInline
 value class ProviderId(val value: String) {
     companion object {
         fun newInstance(value: String): ProviderId {
             require(value.isNotBlank()) { "Provider ID는 필수입니다." }
+            require(value.length <= 255) { "Provider ID는 255자를 초과할 수 없습니다." }
             return ProviderId(value)
         }
     }
 }

추후 특정 Provider에 대한 ID 형식 검증이 필요하다면 ProviderType과 함께 검증하는 방식도 고려해볼 수 있습니다.

📝 Committable suggestion

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

Suggested change
@JvmInline
value class ProviderId(val value: String) {
companion object {
fun newInstance(value: String): ProviderId {
require(value.isNotBlank()) { "Provider ID는 필수입니다." }
return ProviderId(value)
}
}
}
@JvmInline
value class ProviderId(val value: String) {
companion object {
fun newInstance(value: String): ProviderId {
require(value.isNotBlank()) { "Provider ID는 필수입니다." }
require(value.length <= 255) { "Provider ID는 255자를 초과할 수 없습니다." }
return ProviderId(value)
}
}
}
🤖 Prompt for AI Agents
In domain/src/main/kotlin/org/yapp/domain/user/User.kt around lines 141 to 149,
the ProviderId validation currently only checks for blank values. To improve
this, enhance the validation logic to support different rules based on the
ProviderType by passing ProviderType as a parameter to the newInstance method
and implementing conditional validation for each provider's ID format. This will
allow flexible and extensible validation for various providers in the future.

Copy link
Member

@minwoo1999 minwoo1999 left a comment

Choose a reason for hiding this comment

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

LGTM 고생하셨습니다

@move-hoon move-hoon merged commit bdebb66 into develop Jul 17, 2025
3 of 4 checks passed
@move-hoon move-hoon deleted the BOOK-140-refactor/#40 branch August 12, 2025 06:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BOOK-140/refactor] 인증도메인 리팩토링

3 participants