Conversation
There was a problem hiding this comment.
Pull request overview
크리에이터 전환(권한 요청) 프로세스의 시작점인 “크리에이터 인증 신청/취소/내역 조회” 기능을 creator 서비스에 추가하고, 연관된 user/auth 서비스의 상태/세션 처리 및 DTO 구조를 정리한 PR입니다.
Changes:
- creator 서비스에 크리에이터 인증 신청/취소/내역 조회 API 및 JPA 기반 저장소/도메인 모델 추가
- user/auth 서비스에 command/query/result DTO 분리 적용 및 사용자 상태(밴/언밴, 세션) 관련 로직/포트 시그니처 정리
- SonarCloud 분석 워크플로우에서 Java 바이너리 경로 설정 및 사전 컴파일 단계 추가
Reviewed changes
Copilot reviewed 109 out of 130 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| services/user/src/main/java/kr/magicbox/user/domain/exception/UserSessionNotActiveException.java | 세션 종료 시 비활성 예외 추가 |
| services/user/src/main/java/kr/magicbox/user/domain/exception/UserNotBannedException.java | 언밴 불가 케이스 예외 추가 |
| services/user/src/main/java/kr/magicbox/user/domain/exception/UserBannedException.java | 밴 상태 예외 추가 |
| services/user/src/main/java/kr/magicbox/user/domain/exception/UserAlreadyInactiveException.java | 기존 비활성 예외 제거 |
| services/user/src/main/java/kr/magicbox/user/domain/event/UserUnbannedEvent.java | 언밴 도메인 이벤트 추가 |
| services/user/src/main/java/kr/magicbox/user/domain/event/UserDomainEventType.java | USER_UNBANNED 타입 추가 |
| services/user/src/main/java/kr/magicbox/user/domain/enums/UserStatus.java | UserStatus에 BANNED 추가 및 상태 체계 변경 |
| services/user/src/main/java/kr/magicbox/user/domain/aggregate/User.java | activate/ban/unban/delete 도메인 메서드로 상태 전환 정리 |
| services/user/src/main/java/kr/magicbox/user/application/service/WithdrawUserService.java | WithdrawUserCommand 및 락 조회/포트 메서드명 변경 반영 |
| services/user/src/main/java/kr/magicbox/user/application/service/UserQueryService.java | GetUserProfileQuery 기반 조회로 시그니처 변경 |
| services/user/src/main/java/kr/magicbox/user/application/service/UserCommandService.java | UpdateUserProfileCommand 단일 인자 방식으로 변경 |
| services/user/src/main/java/kr/magicbox/user/application/service/UnbanUserService.java | 언밴 유스케이스/서비스 추가 |
| services/user/src/main/java/kr/magicbox/user/application/service/ManageUserSessionService.java | Start/EndSessionCommand 도입 및 예외 변경 |
| services/user/src/main/java/kr/magicbox/user/application/service/LoginService.java | command/result 패키지 분리 및 포트 메서드명 변경 반영 |
| services/user/src/main/java/kr/magicbox/user/application/service/CheckUserActiveService.java | CheckUserActiveQuery 기반 조회로 변경 |
| services/user/src/main/java/kr/magicbox/user/application/service/BanUserService.java | ban() 도메인 메서드 및 update() 포트 사용 |
| services/user/src/main/java/kr/magicbox/user/application/port/out/UserRepositoryPort.java | withLock 조회 및 save/update 메서드명 변경 |
| services/user/src/main/java/kr/magicbox/user/application/port/out/ReviewQueryPort.java | UserReviewResult 패키지 변경 반영 |
| services/user/src/main/java/kr/magicbox/user/application/port/in/WithdrawUserUseCase.java | WithdrawUserCommand 기반으로 변경 |
| services/user/src/main/java/kr/magicbox/user/application/port/in/UserQueryUseCase.java | GetUserProfileQuery 기반으로 변경 |
| services/user/src/main/java/kr/magicbox/user/application/port/in/UserCommandUseCase.java | UpdateUserProfileCommand 단일 인자 변경 |
| services/user/src/main/java/kr/magicbox/user/application/port/in/UnbanUserUseCase.java | 언밴 유스케이스 추가 |
| services/user/src/main/java/kr/magicbox/user/application/port/in/ManageUserSessionUseCase.java | Start/EndSessionCommand 도입 |
| services/user/src/main/java/kr/magicbox/user/application/port/in/LoadUserCredentialUseCase.java | command/result 패키지 분리 반영 |
| services/user/src/main/java/kr/magicbox/user/application/port/in/CheckUserActiveUseCase.java | CheckUserActiveQuery 기반으로 변경 |
| services/user/src/main/java/kr/magicbox/user/application/port/in/BanUserUseCase.java | 포맷 정리(기능 영향 없음) |
| services/user/src/main/java/kr/magicbox/user/application/dto/UpdateUserProfileCommand.java | 기존 dto 위치의 커맨드 제거 |
| services/user/src/main/java/kr/magicbox/user/application/dto/result/UserReviewResult.java | result 패키지로 이동 |
| services/user/src/main/java/kr/magicbox/user/application/dto/result/LoadUserCredentialResult.java | result 패키지로 이동 |
| services/user/src/main/java/kr/magicbox/user/application/dto/result/GetUserProfileResult.java | result 패키지로 이동 |
| services/user/src/main/java/kr/magicbox/user/application/dto/query/GetUserProfileQuery.java | 프로필 조회 Query DTO 추가 |
| services/user/src/main/java/kr/magicbox/user/application/dto/query/CheckUserActiveQuery.java | 활성 여부 조회 Query DTO 추가 |
| services/user/src/main/java/kr/magicbox/user/application/dto/command/WithdrawUserCommand.java | 탈퇴 Command DTO 추가 |
| services/user/src/main/java/kr/magicbox/user/application/dto/command/UpdateUserProfileCommand.java | 프로필 수정 Command DTO 추가(userId 포함) |
| services/user/src/main/java/kr/magicbox/user/application/dto/command/UnbanUserCommand.java | 언밴 Command DTO 추가(현재 사용처 제한적) |
| services/user/src/main/java/kr/magicbox/user/application/dto/command/StartSessionCommand.java | 세션 시작 Command DTO 추가 |
| services/user/src/main/java/kr/magicbox/user/application/dto/command/LoadUserCredentialCommand.java | command 패키지로 이동 |
| services/user/src/main/java/kr/magicbox/user/application/dto/command/EndSessionCommand.java | 세션 종료 Command DTO 추가 |
| services/user/src/main/java/kr/magicbox/user/application/dto/command/BanUserCommand.java | 밴 Command DTO 추가 |
| services/user/src/main/java/kr/magicbox/user/adapter/out/persistence/UserJpaAdapter.java | save/update 및 withLock 조회 구현 추가 |
| services/user/src/main/java/kr/magicbox/user/adapter/out/persistence/repository/UserJpaRepository.java | PESSIMISTIC_WRITE 락 조회 쿼리 추가 |
| services/user/src/main/java/kr/magicbox/user/adapter/out/persistence/entity/UserEntity.java | @Version 제거 및 status 업데이트 반영 |
| services/user/src/main/java/kr/magicbox/user/adapter/out/communication/grpc/ReviewQueryGrpcAdapter.java | UserReviewResult 패키지 변경 반영 |
| services/user/src/main/java/kr/magicbox/user/adapter/in/web/UserQueryController.java | GetUserProfileQuery 생성 방식으로 변경 |
| services/user/src/main/java/kr/magicbox/user/adapter/in/web/UserCommandController.java | update/withdraw에 Command DTO 적용 |
| services/user/src/main/java/kr/magicbox/user/adapter/in/web/exception/handler/GlobalExceptionHandler.java | Optimistic lock 예외 핸들러 제거 |
| services/user/src/main/java/kr/magicbox/user/adapter/in/web/dto/response/GetUserProfileResponse.java | response 패키지로 이동 |
| services/user/src/main/java/kr/magicbox/user/adapter/in/web/dto/request/UpdateUserProfileRequest.java | request 패키지로 이동 및 userId 포함 커맨드 생성 |
| services/user/src/main/java/kr/magicbox/user/adapter/in/web/AdminUserCommandController.java | 언밴 엔드포인트 추가 |
| services/user/src/main/java/kr/magicbox/user/adapter/in/kafka/AuthEventKafkaListener.java | 세션 관리 유스케이스 호출을 Command DTO로 변경 |
| services/user/src/main/java/kr/magicbox/user/adapter/in/grpc/UserGrpcService.java | checkUserActive 호출을 Query DTO로 변경 |
| services/creator/src/main/resources/application-prod.yml | creator 서비스 prod 프로필 설정 추가 |
| services/creator/src/main/resources/application-local.yml | creator 서비스 local 프로필에 grpc/kafka/jpa 설정 확장 |
| services/creator/src/main/resources/application-dev.yml | creator 서비스 dev 프로필 설정 추가 |
| services/creator/src/main/proto/user.proto | creator 측 user gRPC 인터페이스 proto 추가 |
| services/creator/src/main/java/kr/magicbox/creator/global/exception/SystemError.java | 5xx 전용 시스템 예외 추가 |
| services/creator/src/main/java/kr/magicbox/creator/global/exception/BusinessException.java | 4xx 전용 비즈니스 예외 추가 |
| services/creator/src/main/java/kr/magicbox/creator/global/exception/BaseException.java | 공통 BaseException 추가 |
| services/creator/src/main/java/kr/magicbox/creator/global/configuration/PropertiesConfiguration.java | @ConfigurationPropertiesScan 설정 추가 |
| services/creator/src/main/java/kr/magicbox/creator/domain/vo/UserId.java | creator 도메인 UserId VO 추가 |
| services/creator/src/main/java/kr/magicbox/creator/domain/vo/CreatorCertificationResult.java | 심사 결과 VO 추가 |
| services/creator/src/main/java/kr/magicbox/creator/domain/vo/CreatorCertificationRequest.java | 신청 정보 VO 추가 |
| services/creator/src/main/java/kr/magicbox/creator/domain/vo/CreatorCertificationId.java | 인증 ID VO 추가 |
| services/creator/src/main/java/kr/magicbox/creator/domain/exception/InvalidFieldException.java | 필드 검증 예외 추가 |
| services/creator/src/main/java/kr/magicbox/creator/domain/exception/CertificationPendingAlreadyExistsException.java | PENDING 중복 신청 예외 추가 |
| services/creator/src/main/java/kr/magicbox/creator/domain/enums/MagicGenre.java | 장르 enum 추가 |
| services/creator/src/main/java/kr/magicbox/creator/domain/enums/CreatorCertificationStatus.java | 인증 상태 enum 추가 |
| services/creator/src/main/java/kr/magicbox/creator/domain/aggregate/CreatorCertification.java | 인증 애그리거트 및 생성/취소 검증 로직 추가 |
| services/creator/src/main/java/kr/magicbox/creator/application/service/certification/GetMyCertificationsService.java | 내 인증 내역 조회 서비스 추가 |
| services/creator/src/main/java/kr/magicbox/creator/application/service/certification/CancelCreatorCertificationService.java | 인증 신청 취소 서비스 추가 |
| services/creator/src/main/java/kr/magicbox/creator/application/service/certification/ApplyCreatorCertificationService.java | 인증 신청 생성 서비스 추가 |
| services/creator/src/main/java/kr/magicbox/creator/application/port/out/CreatorCertificationRepositoryPort.java | 인증 저장소 포트 추가 |
| services/creator/src/main/java/kr/magicbox/creator/application/port/in/GetMyCreatorCertificationsUseCase.java | 내 인증 내역 유스케이스 추가 |
| services/creator/src/main/java/kr/magicbox/creator/application/port/in/CancelCreatorCertificationUseCase.java | 인증 취소 유스케이스 추가 |
| services/creator/src/main/java/kr/magicbox/creator/application/port/in/ApplyCreatorCertificationUseCase.java | 인증 신청 유스케이스 추가 |
| services/creator/src/main/java/kr/magicbox/creator/application/dto/result/CreatorCertificationInfoResult.java | 인증 조회 결과 DTO 추가 |
| services/creator/src/main/java/kr/magicbox/creator/application/dto/query/GetMyCertificationsQuery.java | 내 인증 조회 Query DTO 추가 |
| services/creator/src/main/java/kr/magicbox/creator/application/dto/command/CancelCreatorCertificationCommand.java | 인증 취소 Command DTO 추가 |
| services/creator/src/main/java/kr/magicbox/creator/application/dto/command/ApplyCertificationCommand.java | 인증 신청 Command DTO 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/vo/CreatorCertificationResultVO.java | 결과 Embeddable VO 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/vo/CreatorCertificationRequestVO.java | 요청 Embeddable VO 추가(장르 element collection 포함) |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/repository/CreatorCertificationJpaRepository.java | 인증 JPA 리포지토리 및 커서 쿼리 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/mapper/CreatorCertificationRequestMapper.java | 요청 VO 매퍼 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/mapper/CreatorCertificationMapper.java | 인증 엔티티/도메인 매퍼 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/mapper/CertificationResultMapper.java | 결과 매퍼 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/entity/CreatorCertificationEntity.java | 인증 엔티티 추가(@Version 포함) |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/entity/BaseEntity.java | sonyflake id + auditing 베이스 엔티티 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/CreatorCertificationJpaAdapter.java | 포트 구현 어댑터 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/configuration/JpaConfiguration.java | JPA Auditing 활성화 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/communication/ServiceHost.java | 외부 서비스 호스트 enum 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/in/web/validation/CursorSizeValidator.java | 커서 size validator 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/in/web/validation/CursorSize.java | 커서 size 커스텀 어노테이션 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/in/web/exception/handler/GlobalExceptionHandler.java | creator 글로벌 예외 처리 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/in/web/exception/handler/ErrorResponse.java | 에러 응답 DTO 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/in/web/dto/response/CursorResponse.java | 커서 기반 응답 래퍼 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/in/web/dto/response/CreatorCertificationInfoResponse.java | 인증 조회 응답 DTO 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/in/web/dto/request/ApplyCertificationRequest.java | 인증 신청 요청 DTO 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/in/web/CreatorCertificationQueryController.java | 내 인증 내역 조회 API 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/in/web/CreatorCertificationCommandController.java | 인증 신청/취소 API 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/in/web/constants/CursorConstants.java | 커서 기본값/범위 상수 추가 |
| services/creator/build.gradle | protobuf + spring grpc/클라우드 의존성 및 BOM 설정 추가 |
| services/auth/src/main/java/kr/magicbox/auth/domain/exception/UserInactiveException.java | 비활성 사용자 예외 추가 |
| services/auth/src/main/java/kr/magicbox/auth/domain/exception/UserBannedException.java | 정지 사용자 예외 추가 |
| services/auth/src/main/java/kr/magicbox/auth/domain/exception/InActiveUserException.java | 기존 예외 제거 |
| services/auth/src/main/java/kr/magicbox/auth/application/service/RefreshTokenService.java | RefreshTokenCommand 적용 및 result 패키지 반영 |
| services/auth/src/main/java/kr/magicbox/auth/application/service/LogoutService.java | LogoutCommand 적용 및 예외 변경 |
| services/auth/src/main/java/kr/magicbox/auth/application/service/LoginService.java | command/result 패키지 분리 반영 |
| services/auth/src/main/java/kr/magicbox/auth/application/service/HandleUserWithdrawnService.java | HandleUserWithdrawnCommand 적용 |
| services/auth/src/main/java/kr/magicbox/auth/application/service/HandleUserBannedService.java | HandleUserBannedCommand 적용 |
| services/auth/src/main/java/kr/magicbox/auth/application/port/out/UserCredentialPort.java | UserResult 패키지 변경 반영 |
| services/auth/src/main/java/kr/magicbox/auth/application/port/in/RefreshTokenUseCase.java | refresh 시그니처를 Command로 변경 |
| services/auth/src/main/java/kr/magicbox/auth/application/port/in/LogoutUseCase.java | logout 시그니처를 Command로 변경 |
| services/auth/src/main/java/kr/magicbox/auth/application/port/in/LoginUseCase.java | command/result 패키지 분리 반영 |
| services/auth/src/main/java/kr/magicbox/auth/application/port/in/HandleUserWithdrawnUseCase.java | Command 기반 시그니처로 변경 |
| services/auth/src/main/java/kr/magicbox/auth/application/port/in/HandleUserBannedUseCase.java | Command 기반 시그니처로 변경 |
| services/auth/src/main/java/kr/magicbox/auth/application/dto/result/UserResult.java | result 패키지로 이동 |
| services/auth/src/main/java/kr/magicbox/auth/application/dto/result/TokenResult.java | result 패키지로 이동 |
| services/auth/src/main/java/kr/magicbox/auth/application/dto/result/IssueTokenResult.java | result 패키지로 이동 |
| services/auth/src/main/java/kr/magicbox/auth/application/dto/command/RefreshTokenCommand.java | refresh Command DTO 추가 |
| services/auth/src/main/java/kr/magicbox/auth/application/dto/command/LogoutCommand.java | logout Command DTO 추가 |
| services/auth/src/main/java/kr/magicbox/auth/application/dto/command/LoginCommand.java | command 패키지로 이동 |
| services/auth/src/main/java/kr/magicbox/auth/application/dto/command/HandleUserWithdrawnCommand.java | 유저 탈퇴 이벤트 처리 Command DTO 추가 |
| services/auth/src/main/java/kr/magicbox/auth/application/dto/command/HandleUserBannedCommand.java | 유저 밴 이벤트 처리 Command DTO 추가 |
| services/auth/src/main/java/kr/magicbox/auth/adapter/out/communication/grpc/UserGrpcAdapter.java | UserResult 패키지 변경 반영 |
| services/auth/src/main/java/kr/magicbox/auth/adapter/in/web/dto/response/AccessTokenResponse.java | response 패키지로 이동 |
| services/auth/src/main/java/kr/magicbox/auth/adapter/in/web/dto/request/LoginRequest.java | request 패키지로 이동 |
| services/auth/src/main/java/kr/magicbox/auth/adapter/in/web/AuthCommandController.java | refresh/logout를 Command DTO로 변경 |
| services/auth/src/main/java/kr/magicbox/auth/adapter/in/security/oauth2/OAuth2LoginSuccessHandler.java | UserResult 패키지 변경 반영 |
| services/auth/src/main/java/kr/magicbox/auth/adapter/in/kafka/UserEventKafkaListener.java | 이벤트 핸들러 호출을 Command DTO로 변경 |
| .github/workflows/sonarcloud-analyze.yml | SonarCloud 분석 전 Java 컴파일 및 binaries 설정 추가 |
Comments suppressed due to low confidence (1)
services/user/src/main/java/kr/magicbox/user/adapter/in/web/dto/request/UpdateUserProfileRequest.java:25
nickname필드는 OptionalNotBlank로 null 허용인데, toCommand()에서 항상Nickname.of(nickname)을 호출하고 있어 nickname 미전달 PATCH 요청 시 NPE/검증 예외가 발생합니다. nickname이 null이면 Nickname 변환을 건너뛰도록 처리해야 합니다.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| CreatorCertification certification = certificationRepositoryPort.findById(command.creatorCertificationId()) | ||
| .orElseThrow(CertificationNotFoundException::new); | ||
|
|
||
| certification.validateCancellable(command.userId()); | ||
|
|
||
| certificationRepositoryPort.deleteById(command.creatorCertificationId()); | ||
| } |
There was a problem hiding this comment.
필드로 주입된 것은 certificationRepositoryPort인데, 삭제 호출에서 certificationRepositoryPort.deleteById(...)를 사용하고 있어 컴파일이 되지 않습니다. 동일한 필드명으로 호출하도록 수정이 필요합니다.
| public void applyCreatorCertification(ApplyCertificationCommand command) { | ||
| log.info("서비스 통과"); | ||
| List<CreatorCertification> existingCertifications = | ||
| certificationRepositoryPort.findAllByUserId(command.userId()); | ||
| log.info("서비스 통과1, {}", existingCertifications.size()); | ||
|
|
There was a problem hiding this comment.
현재 log.info("서비스 통과") / 서비스 통과1 는 디버그성 로그로 보이며, 프로덕션 로그 노이즈 및 비용을 유발합니다. 의미 있는 메시지로 변경하거나 debug 레벨로 낮추거나 제거하는 것이 좋습니다.
| public enum UserStatus { | ||
| ACTIVE, | ||
| INACTIVE, | ||
| BANNED, | ||
| DELETED |
There was a problem hiding this comment.
UserStatus enum에서 INACTIVE가 제거되고 BANNED가 추가되었습니다. 이 enum이 DB에 문자열로 저장되고 있다면(일반적인 JPA EnumType.STRING) 기존 INACTIVE 데이터가 역직렬화되지 않아 장애로 이어질 수 있어, 배포 전 데이터 마이그레이션/호환 전략이 필요합니다.
There was a problem hiding this comment.
Flyway를 도입하겠습니다. 추후
| - name: Set up JDK 21 | ||
| uses: actions/setup-java@v4 | ||
| with: | ||
| distribution: temurin | ||
| java-version: '21' | ||
|
|
||
| - name: Compile Java classes | ||
| run: ./services/auth/gradlew --no-daemon classes | ||
|
|
||
| - name: Set SonarCloud Project Key | ||
| run: | | ||
| REPO_NAME=$(echo $GITHUB_REPOSITORY | cut -d '/' -f 2) | ||
| ORG_NAME=$(echo $GITHUB_REPOSITORY | cut -d '/' -f 1) | ||
| SONAR_PROJECT_KEY="${ORG_NAME}_${REPO_NAME}" | ||
| echo "SONAR_PROJECT_KEY=$SONAR_PROJECT_KEY" >> $GITHUB_ENV | ||
| SONAR_JAVA_BINARIES=$(find . -type d -path "*/build/classes/java/main" | sed 's|^\./||' | paste -sd "," -) | ||
| echo "SONAR_PROJECT_KEY=$SONAR_PROJECT_KEY" >> $GITHUB_ENV | ||
| echo "SONAR_JAVA_BINARIES=$SONAR_JAVA_BINARIES" >> $GITHUB_ENV | ||
|
|
||
| - name: Analyze with SonarCloud | ||
| uses: SonarSource/sonarcloud-github-action@master | ||
| id: analyze-sonarcloud | ||
| continue-on-error: true | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.SECRET_GITHUB_BOT }} | ||
| SONAR_TOKEN: ${{ secrets.SECRET_SONARQUBE }} | ||
| with: | ||
| args: | ||
| -Dsonar.projectKey=${{ env.SONAR_PROJECT_KEY }} | ||
| -Dsonar.organization=f-lab-edu-1 | ||
| -Dsonar.java.binaries=${{ env.SONAR_JAVA_BINARIES }} |
There was a problem hiding this comment.
PR 설명은 '크리에이터 권한 요청 API' 중심인데, 이 워크플로우 변경(자바 컴파일 단계 추가 및 sonar.java.binaries 설정)처럼 CI/분석 파이프라인 변경도 포함되어 있습니다. 리뷰/배포 범위가 커지므로 PR 설명에 해당 변경 범위를 명시하거나 PR을 분리하는 것이 좋습니다.
There was a problem hiding this comment.
그 전에 했었는데, 적용이 안 되어서 그렇습니다.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
f-lab-ted
left a comment
There was a problem hiding this comment.
리뷰 요약
크리에이터 권한 인증 요청(신청/취소/내역 조회) API를 헥사고널 아키텍처 기반으로 구현한 PR입니다. Command/Query 분리(CQRS 패턴), 도메인 애그리거트(CreatorCertification)에 비즈니스 규칙 집중, 커서 기반 페이지네이션, 그리고 도메인 VO를 통한 유효성 검증 등의 구조를 중점적으로 리뷰했습니다.
헥사고널 아키텍처의 레이어 분리(adapter/application/domain)가 잘 지켜져 있고, 도메인 내에 비즈니스 불변식(validateNoPendingCertification, validateCancellable)을 응집시킨 점, VO에서 자기 유효성을 보장하는 설계가 좋습니다.
다만, 몇 가지 주요 보완 사항이 있습니다:
- [Critical]
CreatorCertificationResultVO의of()메서드에서result가 null일 경우 NPE가 발생할 수 있으며,CreatorCertificationEntity.updateFromDomain()에서 null result를 그대로 전달합니다. - [Major] 취소 시
deleteById를 사용하는데, 감사 추적(audit trail)이 불가능해지며 soft delete 또는 상태 변경 방식을 고려해야 합니다. - [Major]
ApplyCreatorCertificationService에서findAllByUserId로 전체 이력을 조회하는 것은 데이터가 쌓일수록 비효율적이며, PENDING 상태만 조회하는 쿼리로 개선이 필요합니다. - [Major]
@AuthenticationPrincipal UserId userId가 Spring Security에서 바로UserId도메인 객체로 resolve 되려면 커스텀ArgumentResolver또는UserDetails구현이 필요한데, 관련 설정이 보이지 않습니다.
Critical 이슈인 NPE 가능성에 대해서는 반드시 수정 바랍니다. Major 이슈들에 대해서도 검토 후 반영 여부 결정해주세요.
| } | ||
|
|
||
| public void updateFromDomain(CreatorCertification creatorCertification) { | ||
| this.status = creatorCertification.getStatus(); |
There was a problem hiding this comment.
[Critical] updateFromDomain에서 creatorCertification.getResult()가 null일 수 있습니다 (PENDING 상태). 이 경우 CreatorCertificationResultVO.of(null)이 호출되어 NPE가 발생합니다.
public void updateFromDomain(CreatorCertification creatorCertification) {
this.status = creatorCertification.getStatus();
this.request = CreatorCertificationRequestVO.of(creatorCertification.getRequest());
this.result = creatorCertification.getResult() != null
? CreatorCertificationResultVO.of(creatorCertification.getResult())
: null;
}null 체크를 추가하거나, Optional을 활용해주세요.
There was a problem hiding this comment.
넵 정말 심각하네요..ㅠㅠ 수정하였습니다!
|
|
||
| certification.validateCancellable(command.userId()); | ||
|
|
||
| creatorCertificationRepositoryPort.deleteById(command.creatorCertificationId()); |
There was a problem hiding this comment.
[Major] 취소 시 deleteById로 물리 삭제를 수행하면 인증 신청 이력이 완전히 사라져 감사 추적(audit trail)이 불가능합니다.
DDD 관점에서 인증 신청 취소는 상태 전이(PENDING → CANCELLED)로 모델링하는 것이 자연스럽습니다. CreatorCertificationStatus에 CANCELLED 상태를 추가하고, 도메인 메서드(cancel())를 통해 상태를 변경하는 방식을 권장합니다.
이렇게 하면:
- 사용자의 과거 신청/취소 이력을 추적할 수 있고
validateNoPendingCertification로직도 자연스럽게 동작하며- 향후 취소 사유 등 추가 요구사항에도 대응이 용이합니다.
There was a problem hiding this comment.
네 훨씬 좋은 방안인 것 같습니다.
이 방향대로 수정했습니다.
| @Transactional | ||
| @Override | ||
| public void applyCreatorCertification(ApplyCertificationCommand command) { | ||
| List<CreatorCertification> existingCertifications = |
There was a problem hiding this comment.
[Major] findAllByUserId로 해당 유저의 전체 인증 이력을 메모리에 로드한 후, 스트림으로 PENDING 존재 여부를 확인합니다. 이력이 쌓일수록 불필요한 데이터 로딩이 발생합니다.
CreatorCertificationRepositoryPort에 existsByUserIdAndStatus(UserId, CreatorCertificationStatus) 같은 메서드를 추가하여, DB 레벨에서 PENDING 상태 존재 여부만 확인하는 것이 효율적입니다.
// Port
boolean existsByUserIdAndStatus(UserId userId, CreatorCertificationStatus status);
// Service
if (certificationRepositoryPort.existsByUserIdAndStatus(command.userId(), PENDING)) {
throw new CertificationPendingAlreadyExistsException();
}There was a problem hiding this comment.
넵 잘못된 방식으로 코드를 짜고 있었네요 수정하였습니다!
|
|
||
| @PostMapping | ||
| public ResponseEntity<Void> apply( | ||
| @AuthenticationPrincipal UserId userId, |
There was a problem hiding this comment.
[Major] @AuthenticationPrincipal UserId userId로 도메인 VO를 직접 주입받고 있는데, Spring Security의 기본 AuthenticationPrincipal은 UserDetails 또는 Authentication.getPrincipal() 객체를 주입합니다.
UserId 도메인 객체를 직접 resolve하려면 커스텀 AuthenticationPrincipalArgumentResolver 또는 @AuthenticationPrincipal 어노테이션의 expression 속성 설정이 필요합니다. 현재 코드에서는 관련 설정이 보이지 않아, 런타임에 타입 캐스팅 오류가 발생할 수 있습니다.
Security 설정과 함께 UserId resolve 방식을 확인/추가해주세요.
| @RequestParam(required = false) Long cursor, | ||
| @RequestParam(defaultValue = CursorConstants.DEFAULT_SIZE) @CursorSize Integer size) { | ||
| List<CreatorCertificationInfoResponse> content = | ||
| getMyCreatorCertificationsUseCase.getMyCertifications(GetMyCertificationsQuery.of(userId, cursor, size + 1)) |
There was a problem hiding this comment.
[Minor] size + 1을 서비스 레이어가 아닌 컨트롤러에서 계산하여 쿼리에 전달하고 있습니다. "다음 페이지 존재 여부 확인을 위해 1개 더 조회"하는 것은 페이지네이션 인프라 로직인데, 이 책임이 adapter 레이어에 위치해 있습니다.
이 로직이 컨트롤러에 노출되면:
- 다른 adapter(gRPC, 이벤트 핸들러 등)에서 같은 UseCase를 호출할 때 동일한
+1로직을 반복해야 합니다. CursorResponse.of와 암묵적 계약이 생깁니다.
GetMyCertificationsService 내부에서 size + 1 조회 후, 페이지네이션 정보를 포함한 결과 DTO를 반환하는 방식을 고려해주세요.
|



📌 관련 이슈
📝 변경 사항 요약
작업 유형
(기능 단위 완료 후 PR)(버그 1개 = PR 1개)(작업 단위 완료 후 PR)(작업 단위 완료 후 PR)변경 내용
변경 이유
✅ 테스트 체크리스트
📸 스크린샷 / 로그
펼쳐보기
🔄 동작 플로우 (Mermaid)
%%{init: {'flowchart': {'useMaxWidth': true, 'htmlLabels': true}} }%% flowchart TD A[사용자<br/>권한 요청] --> B[요청 검증] B --> C[PENDING 저장] C --> D[요청 내역 조회] D --> E[심사 단계 전달]💬 리뷰어에게
크리에이터 권한 요청 API 구현 범위를 중심으로 리뷰 부탁드립니다.