refactor/98 :: Auth/User DTO 구조 정리 및 밴·언밴 흐름 리팩토링#16
Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ponse로 정리 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>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Auth/User 서비스 전반에서 DTO 패키지 구조를 command/query/result(/request/response) 기준으로 재정리하고, User 도메인의 밴/언밴 상태 전이 및 관리자 밴·언밴 API 흐름을 리팩토링하며, SonarCloud 분석을 위한 Java binaries 설정을 워크플로우에 추가한 PR입니다.
Changes:
- Auth/User DTO 및 UseCase 시그니처를 command/query/result 중심으로 재구성
- User 도메인에 BANNED 상태 및 언밴 가드/이벤트(UserUnbannedEvent) 추가, 관리자 unban API 추가
- SonarCloud 분석 워크플로우에 JDK 세팅/컴파일 및 sonar.java.binaries 주입 추가
Reviewed changes
Copilot reviewed 90 out of 103 changed files in this pull request and generated 8 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 | INACTIVE 제거, BANNED 추가 |
| services/user/src/main/java/kr/magicbox/user/domain/aggregate/User.java | 밴/언밴/삭제 상태 전이 메서드로 정리 |
| services/user/src/main/java/kr/magicbox/user/application/service/WithdrawUserService.java | Withdraw UseCase를 Command 기반으로 변경 및 락 조회 적용 |
| services/user/src/main/java/kr/magicbox/user/application/service/UserQueryService.java | Query/Result DTO로 패키지 및 시그니처 변경 |
| services/user/src/main/java/kr/magicbox/user/application/service/UserCommandService.java | Update 프로필 Command 시그니처로 변경 |
| services/user/src/main/java/kr/magicbox/user/application/service/UnbanUserService.java | 언밴 유스케이스 서비스 신규 추가 |
| services/user/src/main/java/kr/magicbox/user/application/service/ManageUserSessionService.java | 세션 시작/종료를 Command 기반으로 변경 |
| services/user/src/main/java/kr/magicbox/user/application/service/LoginService.java | DTO 패키지 및 UserRepositoryPort 메서드명 변경 반영 |
| services/user/src/main/java/kr/magicbox/user/application/service/CheckUserActiveService.java | isActive Query DTO 도입 |
| services/user/src/main/java/kr/magicbox/user/application/service/BanUserService.java | ban 처리 로직을 User.ban()/repo.update로 변경 |
| services/user/src/main/java/kr/magicbox/user/application/port/out/UserRepositoryPort.java | save/update 및 WithLock 조회 메서드 추가 |
| services/user/src/main/java/kr/magicbox/user/application/port/out/ReviewQueryPort.java | Result DTO 패키지 변경 반영 |
| services/user/src/main/java/kr/magicbox/user/application/port/in/WithdrawUserUseCase.java | Withdraw 시그니처를 Command로 변경 |
| services/user/src/main/java/kr/magicbox/user/application/port/in/UserQueryUseCase.java | getUserProfile 시그니처를 Query로 변경 |
| services/user/src/main/java/kr/magicbox/user/application/port/in/UserCommandUseCase.java | updateUserProfile 시그니처를 Command로 변경 |
| 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 | 세션 유스케이스를 Command 기반으로 변경 |
| services/user/src/main/java/kr/magicbox/user/application/port/in/LoadUserCredentialUseCase.java | Command/Result DTO 패키지 변경 반영 |
| services/user/src/main/java/kr/magicbox/user/application/port/in/CheckUserActiveUseCase.java | Query DTO로 시그니처 변경 |
| 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 | 기존 Command DTO 제거(패키지 이동) |
| services/user/src/main/java/kr/magicbox/user/application/dto/result/UserReviewResult.java | Result DTO 패키지로 이동 |
| services/user/src/main/java/kr/magicbox/user/application/dto/result/LoadUserCredentialResult.java | Result DTO 패키지로 이동 |
| services/user/src/main/java/kr/magicbox/user/application/dto/result/GetUserProfileResult.java | Result DTO 패키지로 이동 |
| 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 신규 추가(유저 ID 포함) |
| 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 DTO 패키지로 이동 |
| 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 | WithLock 조회 및 save/update 메서드명 변경 반영 |
| 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 | Result DTO 패키지 변경 반영 |
| services/user/src/main/java/kr/magicbox/user/adapter/in/web/UserQueryController.java | Query DTO로 유스케이스 호출 변경 |
| services/user/src/main/java/kr/magicbox/user/adapter/in/web/UserCommandController.java | Command DTO로 유스케이스 호출 변경 |
| services/user/src/main/java/kr/magicbox/user/adapter/in/web/exception/handler/GlobalExceptionHandler.java | Optimistic locking 예외 핸들러 제거 |
| services/user/src/main/java/kr/magicbox/user/adapter/in/web/dto/response/GetUserProfileResponse.java | web response DTO 패키지 정리 및 import 수정 |
| services/user/src/main/java/kr/magicbox/user/adapter/in/web/dto/request/UpdateUserProfileRequest.java | userId 포함 Command 생성으로 변경 |
| services/user/src/main/java/kr/magicbox/user/adapter/in/web/AdminUserCommandController.java | 관리자 unban API 추가 |
| services/user/src/main/java/kr/magicbox/user/adapter/in/kafka/AuthEventKafkaListener.java | 세션 유스케이스 호출을 Command로 변경 |
| services/user/src/main/java/kr/magicbox/user/adapter/in/grpc/UserGrpcService.java | CheckUserActive를 Query로 변경, DTO 패키지 정리 |
| 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 | RefreshToken을 Command 기반으로 변경 |
| services/auth/src/main/java/kr/magicbox/auth/application/service/LogoutService.java | Logout을 Command 기반으로 변경 및 예외 교체 |
| services/auth/src/main/java/kr/magicbox/auth/application/service/LoginService.java | Command/Result DTO 패키지 변경 반영 |
| services/auth/src/main/java/kr/magicbox/auth/application/service/HandleUserWithdrawnService.java | 이벤트 핸들링을 Command 기반으로 변경 |
| services/auth/src/main/java/kr/magicbox/auth/application/service/HandleUserBannedService.java | 이벤트 핸들링을 Command 기반으로 변경 |
| 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 | RefreshToken 시그니처를 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 | Login DTO 패키지 변경 반영 |
| services/auth/src/main/java/kr/magicbox/auth/application/port/in/HandleUserWithdrawnUseCase.java | Withdrawn 핸들러 시그니처를 Command로 변경 |
| services/auth/src/main/java/kr/magicbox/auth/application/port/in/HandleUserBannedUseCase.java | Banned 핸들러 시그니처를 Command로 변경 |
| services/auth/src/main/java/kr/magicbox/auth/application/dto/result/UserResult.java | Result DTO 패키지로 이동 |
| services/auth/src/main/java/kr/magicbox/auth/application/dto/result/TokenResult.java | Result DTO 패키지로 이동 |
| services/auth/src/main/java/kr/magicbox/auth/application/dto/result/IssueTokenResult.java | Result DTO 패키지로 이동 |
| services/auth/src/main/java/kr/magicbox/auth/application/dto/command/RefreshTokenCommand.java | Command DTO 신규 추가 |
| services/auth/src/main/java/kr/magicbox/auth/application/dto/command/LogoutCommand.java | Command DTO 신규 추가 |
| services/auth/src/main/java/kr/magicbox/auth/application/dto/command/LoginCommand.java | Command DTO 패키지로 이동 |
| 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 | web response DTO 패키지 정리 |
| services/auth/src/main/java/kr/magicbox/auth/adapter/in/web/dto/request/LoginRequest.java | web request DTO 패키지 정리 |
| services/auth/src/main/java/kr/magicbox/auth/adapter/in/web/AuthCommandController.java | Refresh/Logout 호출을 Command 기반으로 변경 |
| 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 기반으로 변경 |
| .github/workflows/sonarcloud-analyze.yml | JDK/컴파일 및 sonar.java.binaries 설정 추가 |
Comments suppressed due to low confidence (1)
services/user/src/main/java/kr/magicbox/user/application/service/UserCommandService.java:25
- UserEntity에서 @Version이 제거된 상태에서 프로필 업데이트는 getUserById()로 조회 후 update()로 저장하고 있어, 다른 쓰기(세션 갱신/밴/탈퇴 등)와 동시에 발생하면 변경이 덮어써질 수 있습니다. 동시성 충돌을 감지하지 못하게 된 만큼, 이 쓰기 트랜잭션에서도 getUserByIdWithLock()을 사용하거나 낙관적 락을 유지하는 등 일관된 동시성 전략을 적용해주세요.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| public enum UserStatus { | ||
| ACTIVE, | ||
| INACTIVE, | ||
| BANNED, | ||
| DELETED |
There was a problem hiding this comment.
UserStatus에서 INACTIVE enum 값을 제거하고 BANNED로 대체하면, DB에 기존에 저장된 "INACTIVE" 값을 JPA가 역직렬화하지 못해 엔티티 로딩 시 IllegalArgumentException이 발생할 수 있습니다. 운영 데이터가 존재한다면 (1) INACTIVE를 유지하되 의미만 재정의하거나, (2) DB 마이그레이션으로 INACTIVE -> BANNED로 변환, (3) Enum converter로 하위호환 처리를 추가하는 방식 중 하나로 호환성을 보장해주세요.
There was a problem hiding this comment.
운영데이터가 존재하지 않아서 완전 교체하였습니다.
| public void banUser(Nickname nickname) { | ||
| User user = userRepositoryPort.getUserByNickname(nickname) | ||
| .orElseThrow(UserNotFoundException::new); | ||
|
|
||
| user.ban(); | ||
| userRepositoryPort.update(user); | ||
|
|
There was a problem hiding this comment.
@Version(낙관적 락) 필드를 제거했는데, 이 PR에서 추가한 WithLock 조회 메서드가 밴 처리에 사용되지 않아 동시 업데이트 시 lost update(마지막 저장이 덮어쓰기) 가능성이 커집니다. 특히 밴 처리 후 이벤트 발행까지 포함하므로 동일 사용자에 대한 다른 업데이트와 경쟁하면 상태/이벤트가 불일치할 수 있어, getUserByNicknameWithLock(또는 id 기반 WithLock)로 조회하고 동일 트랜잭션에서 업데이트하도록 바꿔주세요.
| public void unbanUser(Nickname nickname) { | ||
| User user = userRepositoryPort.getUserByNickname(nickname) | ||
| .orElseThrow(UserNotFoundException::new); | ||
|
|
||
| user.unban(); | ||
| userRepositoryPort.update(user); | ||
|
|
There was a problem hiding this comment.
@Version 제거 이후 동시성 제어가 WithLock 조회로 전환된 것으로 보이는데, unban 흐름은 getUserByNickname()으로 조회해 다른 업데이트와 경쟁 시 상태가 덮어써질 수 있습니다. user.unban()은 상태 가드(현재 BANNED 여부)도 포함하므로, getUserByNicknameWithLock()을 사용해 같은 트랜잭션에서 읽기-검증-갱신이 원자적으로 수행되도록 변경해주세요.
| @Override | ||
| @Transactional | ||
| public void startSession(UserId userId, Instant loginAt) { | ||
| User user = userRepositoryPort.getUserById(userId).orElseThrow(UserNotFoundException::new); | ||
| public void startSession(StartSessionCommand command) { | ||
| User user = userRepositoryPort.getUserById(command.userId()).orElseThrow(UserNotFoundException::new); | ||
| if (user.isActive()) throw new UserAlreadyActiveException(); | ||
| user.startSession(loginAt); | ||
| userRepositoryPort.updateUser(user); | ||
| user.startSession(command.loginAt()); | ||
| userRepositoryPort.update(user); | ||
| } | ||
|
|
||
| @Override | ||
| @Transactional | ||
| public void endSession(UserId userId, Instant logoutAt) { | ||
| User user = userRepositoryPort.getUserById(userId).orElseThrow(UserNotFoundException::new); | ||
| if (!user.isActive()) throw new UserAlreadyInactiveException(); | ||
| user.endSession(logoutAt); | ||
| userRepositoryPort.updateUser(user); | ||
| public void endSession(EndSessionCommand command) { | ||
| User user = userRepositoryPort.getUserById(command.userId()).orElseThrow(UserNotFoundException::new); | ||
| if (!user.isActive()) throw new UserSessionNotActiveException(); | ||
| user.endSession(command.logoutAt()); | ||
| userRepositoryPort.update(user); | ||
| } |
There was a problem hiding this comment.
세션 시작/종료는 Kafka 이벤트 기반으로 연속/중복 호출될 수 있고(totalUsageTime 누적, isActive 토글 등) 동시 업데이트에 취약합니다. 그런데 조회가 getUserById()로 되어 있어 @Version 제거 이후에는 lost update 가능성이 있습니다. getUserByIdWithLock()을 사용하거나, 최소한 낙관적 락(버전) 복구 등으로 동시성 충돌을 감지/방지하도록 변경해주세요.
| @Builder | ||
| public record BanUserCommand( | ||
| Nickname nickname | ||
| ) { | ||
| public static BanUserCommand of(Nickname nickname) { | ||
| return new BanUserCommand(nickname); | ||
| } | ||
| } |
There was a problem hiding this comment.
BanUserCommand가 신규로 추가됐지만 현재 코드베이스 내에서 참조되는 곳이 없어(dead code) DTO 구조 정리 목적과도 어긋납니다. (1) BanUserUseCase 시그니처를 Command 기반으로 바꾸어 실제로 사용하거나, (2) 이번 PR 범위에서 사용하지 않는다면 클래스를 제거해 유지보수 비용을 줄여주세요.
| @Builder | ||
| public record UnbanUserCommand( | ||
| Nickname nickname | ||
| ) { | ||
| public static UnbanUserCommand of(Nickname nickname) { | ||
| return new UnbanUserCommand(nickname); | ||
| } | ||
| } |
There was a problem hiding this comment.
UnbanUserCommand가 추가됐지만 UnbanUserUseCase/Controller/Service가 Nickname 파라미터를 그대로 사용하고 있어 현재는 어디에서도 사용되지 않습니다. Command 기반으로 API를 맞추거나, 사용 계획이 없다면 이번 PR에서 제거해 불필요한 타입 증가를 막아주세요.
| - 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 |
There was a problem hiding this comment.
SonarCloud 분석을 위해 Java classes를 컴파일하는 단계가 auth 서비스만 대상으로 되어 있는데, SONAR_JAVA_BINARIES는 레포 전체에서 build/classes/java/main 경로를 수집합니다. user 등 다른 Java 모듈이 컴파일되지 않으면 sonar.java.binaries가 부분적으로만 채워져 분석이 누락되거나(규칙 비활성) 설정에 따라 실패할 수 있습니다. 최소한 변경된 Java 모듈(auth+user)을 모두 classes 빌드하거나, 멀티모듈 단위로 일괄 컴파일하는 스텝으로 조정해주세요.
| public class UserInactiveException extends BaseException { | ||
| public UserInactiveException() { | ||
| super("비활성 사용자입니다.", HttpStatus.BAD_REQUEST); | ||
| } |
There was a problem hiding this comment.
UserInactiveException의 HTTP status가 기존 InActiveUserException(FORBIDDEN) 대비 BAD_REQUEST로 변경되어, 비활성 사용자 로그아웃 요청에 대한 API 응답 코드가 바뀝니다. 클라이언트/게이트웨이에서 상태코드에 의존하고 있다면 breaking change가 될 수 있으니, 의도된 변경인지 확인하고 기존 정책(예: 403/409 등)에 맞춰 status를 유지하거나 마이그레이션 공지를 추가해주세요.
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>
|



📌 관련 이슈
📝 변경 사항 요약
작업 유형
(기능 단위 완료 후 PR)(버그 1개 = PR 1개)(작업 단위 완료 후 PR)(작업 단위 완료 후 PR)변경 내용
변경 이유
✅ 테스트 체크리스트
📸 스크린샷 / 로그
펼쳐보기
커밋 단위로 Auth/User 리팩토링 반영
🔄 동작 플로우 (Mermaid)
flowchart TD A[Auth User 요청 수신] --> B[Controller 요청 검증] B --> C[Command Query 변환] C --> D[UseCase 실행] D --> E{상태 전이 필요} E -->|예| F[도메인 상태 변경] E -->|아니오| G[조회 및 세션 처리] F --> H[Repository 반영] G --> H H --> I[이벤트 발행 또는 소비] I --> J[응답 반환]💬 리뷰어에게
Auth/User 리팩토링 범위(DTO 구조, UseCase 시그니처, 밴/언밴 도메인 흐름) 중심으로 확인 부탁드립니다.