-
Notifications
You must be signed in to change notification settings - Fork 0
Refactor: 웨이팅 로직 버그 해결 및 로직 개선 #175
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Caution Review failedThe pull request is closed. Walkthrough이 변경 사항은 예약 대기 시스템의 Redis 및 RDB 동기화, 예약 번호 및 상태 타임스탬프 관리, 배치 데이터 조회, DTO 확장, 컨트롤러 응답 표준화, 레포지토리 메서드 추가 등 예약 대기/확정/취소 전반의 로직을 대폭 개선 및 확장합니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UserAPI as ReservationController (User)
participant UserService as ReservationService (User)
participant RedisRepo as WaitingUserRedisRepository
participant StoreRepo as StoreRepository
participant ImgRepo as StoreImageRepository
participant DeptRepo as DepartmentRepository
User->>UserAPI: GET /myWaitings
UserAPI->>UserService: getAllMyWaitings(userId)
UserService->>RedisRepo: getUserWaitingStoreIds(userId)
RedisRepo-->>UserService: [storeId1, storeId2, ...]
UserService->>StoreRepo: findAllWithDepartmentByStoreIdIn(storeIds)
UserService->>ImgRepo: findAllByStore_StoreIdInAndImageTypeIn(storeIds, [PROFILE, BANNER])
UserService->>RedisRepo: (pipeline) get rank, partySize, timestamp, status, reservationId (for each store)
RedisRepo-->>UserService: (pipeline results)
UserService->>UserAPI: List<MyWaitingQueueDto>
UserAPI-->>User: 응답 반환
sequenceDiagram
participant Admin
participant AdminAPI as ReservationController (Admin)
participant AdminService as ReservationService (Admin)
participant RedisRepo as WaitingRedisRepository
participant ReservationRepo as ReservationRepository
Admin->>AdminAPI: POST /reservation/entry-status
AdminAPI->>AdminService: processEntryStatus(storeId, userId, member, newStatus)
AdminService->>RedisRepo: get status, score, partySize, calledAt, reservationId
AdminService->>ReservationRepo: (필요시) find reservation by storeId, userId, status, date
AdminService->>RedisRepo: (필요시) set/delete status, calledAt, reservationId
AdminService->>ReservationRepo: (필요시) save/update Reservation (calledAt/confirmedAt/cancelledAt)
AdminService->>AdminAPI: EntryStatusResponseDto
AdminAPI-->>Admin: 응답 반환
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (6)
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
🔭 Outside diff range comments (1)
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java (1)
68-86: 상태 전환 메서드 일관성 개선 필요새로운 상태 전환 메서드들(
markCalling,markConfirmed,markCancelled)은 상태와 타임스탬프를 함께 업데이트하지만, 기존updateStatus메서드는 상태만 업데이트합니다. 이는 일관성 없는 상태 관리로 이어질 수 있습니다.
updateStatus메서드를 deprecated로 표시하거나 제거하여 상태 전환 시 항상 새로운 메서드를 사용하도록 하세요:+@Deprecated(since = "2025.01", forRemoval = true) public void updateStatus(ReservationStatus status) { this.status = status; }
🧹 Nitpick comments (4)
nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java (1)
41-47: 메서드 네이밍이 실제 기능과 일치하지 않습니다.
buildMenuKey()와buildMenuDateKey()메서드들은 실제로 키를 생성하지 않고 포맷 템플릿을 반환합니다. 메서드명이 혼란을 야기할 수 있습니다.더 명확한 네이밍을 고려해보세요:
-public static String buildMenuKey() { - return KEY_FMT; -} - -public static DateTimeFormatter buildMenuDateKey() { - return DTF; -} +public static String getMenuKeyFormat() { + return KEY_FMT; +} + +public static DateTimeFormatter getMenuDateFormatter() { + return DTF; +}nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java (1)
124-217: N+1 쿼리 문제 해결을 위한 성능 최적화 구현 👍
getAllMyWaitings메서드가 다음과 같이 개선되었습니다:
- 배치 쿼리를 통한 Store, Department, StoreImage 조회
- Redis 파이프라이닝을 통한 일괄 데이터 조회
- Stream API를 활용한 효율적인 데이터 변환
이러한 변경으로 N+1 쿼리 문제가 해결되고 성능이 크게 향상되었습니다.
124번 줄의 TODO 주석은 이미 성능 개선이 완료되었으므로 제거할 수 있습니다:
-//TODO 성능 개선 필요 public List<MyWaitingQueueDto> getAllMyWaitings(CustomOAuth2User customOAuth2User) {nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java (2)
107-108: TODO 주석에 언급된 우려사항을 해결해야 합니다.예약 번호(reservationId) 생성 시점과 createdAt의 정확성 문제는 데이터 일관성에 영향을 줄 수 있습니다. 예약 생성 시점부터 DB에 저장하는 방식을 고려하거나, Redis와 DB 간의 트랜잭션 일관성을 보장하는 방안을 마련하세요.
이 문제를 해결하기 위한 구체적인 방안을 제안해드릴까요?
55-56: TODO 주석이 있는 레거시 코드입니다.성능 비교가 완료되었다면 이 메서드를 제거하거나 개선된 버전으로 교체하세요.
성능 비교가 완료되었는지 확인이 필요합니다. 이 메서드를 제거해도 되는지 검토가 필요합니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java(2 hunks)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/EntryStatusResponseDto.java(4 hunks)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java(2 hunks)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java(4 hunks)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java(5 hunks)nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java(4 hunks)nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/MyWaitingQueueDto.java(3 hunks)nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/MyWaitingStoreInfo.java(1 hunks)nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/WaitingResponseDto.java(1 hunks)nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java(5 hunks)nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java(6 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java(3 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreImageRepository.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java(1 hunks)nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java(2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (7)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java (1)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java (1)
StorePaymentController(27-76)
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreImageRepository.java (2)
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/entity/StoreImage.java (1)
StoreImage(21-45)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreImageService.java (1)
StoreImageService(26-114)
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java (1)
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/department/repository/DepartmentRepository.java (1)
DepartmentRepository(8-10)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java (1)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/ReservationGetResponseDto.java (1)
ReservationGetResponseDto(11-44)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java (1)
nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java (1)
RedisKeyUtils(9-85)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java (1)
nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java (1)
RedisKeyUtils(9-85)
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java (3)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/ReservationGetResponseDto.java (1)
ReservationGetResponseDto(11-44)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/CallingWaitingResponseDto.java (1)
CallingWaitingResponseDto(10-35)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/CallGetResponseDto.java (1)
CallGetResponseDto(11-43)
🔇 Additional comments (28)
nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java (5)
3-7: 새로운 시간 관련 import들이 적절히 추가되었습니다.
expireAtNext03메서드에서 사용되는 시간대 처리를 위한 import들이 올바르게 추가되었습니다.
49-59: 대기열 관련 키 접두사 메서드들이 올바르게 구현되었습니다.일관된 패턴으로 구현되어 있고 네이밍도 명확합니다.
61-68: 예약 관련 키 생성 메서드들이 올바르게 구현되었습니다.storeId를 받아서 적절한 Redis 키를 생성하는 로직이 정확합니다.
70-75: 대기 호출 시각 키 접두사 메서드가 적절하게 구현되었습니다.한국어 주석과 함께 명확한 기능을 제공합니다.
77-84: 다음 새벽 3시 만료 시간 계산 로직이 올바르게 구현되었습니다.Asia/Seoul 시간대를 고려한 시간 계산이 정확하며, Redis 키의 TTL 설정에 유용한 기능입니다.
nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/WaitingResponseDto.java (1)
9-9: 예약 번호 필드가 적절하게 추가되었습니다.기존 필드들과 일관된 패턴으로 구현되어 있으며, PR의 예약 번호 생성 로직 개선 목표에 부합합니다.
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java (2)
40-46: API 응답 표준화가 올바르게 적용되었습니다.
ApiUtils.success()로 응답을 래핑하여 일관된 API 응답 형식을 제공하며, 코드 가독성도 향상되었습니다.
57-63: API 응답 표준화가 일관되게 적용되었습니다.다른 엔드포인트와 동일한 패턴으로 응답 래핑이 구현되어 코드베이스의 일관성을 유지합니다.
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreImageRepository.java (1)
24-24: 배치 조회를 위한 새로운 레포지토리 메서드가 적절하게 추가되었습니다.여러 매장 ID와 이미지 타입으로 동시에 필터링할 수 있는 메서드로, N+1 쿼리 문제 해결에 기여할 것으로 보입니다. Spring Data JPA 네이밍 규칙도 올바르게 따르고 있습니다.
nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java (4)
60-60: 코드 가독성을 위한 포맷팅 개선이 적용되었습니다.메서드 호출 시 쉼표 뒤 공백 추가로 표준 Java 코딩 컨벤션을 따르고 있습니다.
77-77: 일관된 포맷팅이 적용되었습니다.다른 메서드 호출과 동일한 스타일로 통일성을 유지하고 있습니다.
98-98: 포맷팅 일관성이 유지되었습니다.메서드 파라미터 사이의 공백 처리가 일관되게 적용되었습니다.
108-114: 응답 반환 구문의 가독성이 향상되었습니다.멀티라인 포맷팅으로 ResponseEntity 구성이 더 명확해졌으며, 동일한 컨트롤러 내 다른 메서드들과 일관된 스타일을 유지합니다.
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java (1)
40-47: 배치 조회를 위한 새 메서드가 잘 구현되었습니다.N+1 쿼리 문제를 해결하기 위한 배치 조회 메서드가 적절히 추가되었습니다. LEFT JOIN을 사용하여 Department 정보를 함께 가져오는 로직이 올바르게 구현되어 있습니다.
nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/MyWaitingQueueDto.java (2)
18-19: 예약 ID 필드 추가가 적절합니다.새로운 예약 ID 시스템과 일치하는 필드가 올바르게 추가되었습니다. Swagger 스키마 어노테이션도 적절히 작성되어 있습니다.
40-41: 배너 이미지 URL 필드 추가가 적절합니다.배치 이미지 조회 로직과 연계되는 배너 이미지 URL 리스트 필드가 올바르게 추가되었습니다.
nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/MyWaitingStoreInfo.java (1)
6-13: 불변 DTO 클래스가 잘 설계되었습니다.final 필드와 @requiredargsconstructor를 사용하여 불변성을 보장하는 깔끔한 DTO 클래스입니다. 대기 정보에 필요한 매장 관련 데이터를 효과적으로 캡슐화하고 있습니다.
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java (1)
25-26: 예약 상태 필터링 기능이 적절히 추가되었습니다.상태별 예약 조회 기능을 위해
statusIn파라미터가 올바르게 추가되었습니다. 메서드명도 새로운 기능을 반영하여 적절히 변경되었습니다.nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java (2)
19-20: 예약 ID 필드명 변경이 적절합니다.필드명이
id에서reservationId로 명확하게 변경되어 가독성이 향상되었습니다. 스키마 어노테이션의 예시도 새로운 예약 ID 형식을 반영하고 있습니다.
51-61: Redis 데이터를 위한 팩토리 메서드가 잘 구현되었습니다.Redis에서 가져온 데이터로 DTO를 생성하는 별도의 팩토리 메서드가 적절히 추가되었습니다. 모든 필요한 파라미터를 포함하고 있으며, score 필드도 올바르게 처리하고 있습니다.
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java (2)
59-69: 상태 검증을 통한 예약 번호 조회 로직 👍대기 상태가
WAITING또는CALLING인 경우에만 예약 번호를 반환하도록 구현한 것이 적절합니다. 이미 완료되거나 취소된 대기의 경우 예약 번호를 반환하지 않아 데이터 일관성을 유지합니다.
71-86: 포괄적인 Redis 데이터 정리 구현 👍
deleteWaiting메서드가 예약 번호와 호출 시각을 포함한 모든 관련 Redis 데이터를 삭제하도록 확장된 것이 좋습니다. 이는 데이터 일관성을 유지하고 메모리 누수를 방지합니다.nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/EntryStatusResponseDto.java (1)
52-71: Entity에서 DTO로의 변환 로직 구현 👍
fromEntity메서드가 모든 필드를 적절히 매핑하고, switch 표현식을 사용하여 상태별 메시지를 생성하는 것이 좋습니다. 새로운 예약 번호와 타임스탬프 필드들이 잘 반영되어 있습니다.nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java (1)
80-84: 예약 번호 발급 실패 시 적절한 예외 처리 👍예약 번호 발급 실패 시
IllegalStateException을 발생시키고 명확한 한글 메시지를 제공하는 것이 좋습니다.nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java (2)
114-172:keys()대신SCAN사용으로 성능 개선 👍
getUserWaitingStoreIds메서드가 블로킹 연산인keys()를 비블로킹SCAN으로 대체하고, 파이프라이닝을 통해 rank 조회를 일괄 처리하도록 개선되었습니다. 프로덕션 환경에서의 성능과 안정성이 크게 향상될 것입니다.
34-78: 일일 초기화 예약 번호 생성 로직 구현 👍예약 번호 생성이 다음과 같이 잘 구현되었습니다:
- 원자적 증가 연산을 통한 스레드 안전성 보장
- 날짜별 시퀀스 초기화로 일일 번호 체계 구현
- 4자리 0 패딩으로 일관된 형식 유지
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java (2)
3-18: Import 구성이 적절합니다.Redis 파이프라인과 시간대 처리를 위한 필요한 import들이 올바르게 추가되었습니다.
Also applies to: 41-41, 53-53
109-175: N+1 쿼리 문제가 효과적으로 해결되었습니다.배치 조회와 Redis 파이프라인을 사용하여 성능을 크게 개선했습니다. 빈 리스트 처리와 null 안전성도 잘 구현되었습니다.
...in-api/src/main/java/com/nowait/applicationadmin/reservation/dto/EntryStatusResponseDto.java
Outdated
Show resolved
Hide resolved
...admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java
Show resolved
Hide resolved
...src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java
Outdated
Show resolved
Hide resolved
| @Transactional | ||
| public EntryStatusResponseDto processEntryStatus( | ||
| Long storeId, | ||
| String userId, | ||
| MemberDetails member, | ||
| ReservationStatus newStatus | ||
| ) { | ||
| User manager = authorize(storeId, member); | ||
| User user = userRepository.findById(Long.valueOf(userId)).orElseThrow(UserNotFoundException::new); | ||
|
|
||
| String message = null; | ||
| Reservation reservation; | ||
| public EntryStatusResponseDto processEntryStatus(Long storeId, String userId, MemberDetails member, ReservationStatus newStatus) { | ||
|
|
||
| authorize(storeId, member); | ||
|
|
||
| String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; | ||
|
|
||
| // Redis에서 상태·score·partySize·calledAt 조회 | ||
| String reservationNumber = waitingRedisRepository.getReservationId(storeId, userId); | ||
| String currStatus = waitingRedisRepository.getWaitingStatus(storeId, userId); | ||
| Double score = redisTemplate.opsForZSet().score(queueKey, userId); | ||
| Integer partySize = waitingRedisRepository.getWaitingPartySize(storeId, userId); | ||
| Long calledMillis = waitingRedisRepository.getWaitingCalledAt(storeId, userId); | ||
|
|
||
| LocalDateTime requestedAt = score != null | ||
| ? Instant.ofEpochMilli(score.longValue()).atZone(ZoneId.of("Asia/Seoul")).toLocalDateTime() | ||
| : LocalDateTime.now(); | ||
| LocalDateTime calledAt = calledMillis != null | ||
| ? Instant.ofEpochMilli(calledMillis).atZone(ZoneId.of("Asia/Seoul")).toLocalDateTime() | ||
| : null; | ||
| LocalDateTime now = LocalDateTime.now(); | ||
|
|
||
| switch (newStatus) { | ||
| case CALLING: | ||
| // 1) Redis 상태 검사 & 변경 | ||
| String curr = waitingRedisRepository.getWaitingStatus(storeId, userId); | ||
| if (!ReservationStatus.WAITING.name().equals(curr)) { | ||
| throw new IllegalStateException("이미 호출되었거나 없는 예약입니다."); | ||
| if (!ReservationStatus.WAITING.name().equals(currStatus)) { | ||
| throw new IllegalStateException("WAITING 상태에서만 CALLING 가능합니다."); | ||
| } | ||
| waitingRedisRepository.setWaitingStatus(storeId, userId, ReservationStatus.CALLING.name()); | ||
| waitingRedisRepository.setWaitingCalledAt(storeId, userId, now.toInstant(ZoneOffset.ofHours(9)).toEpochMilli()); | ||
|
|
||
| // 2) 파티 인원, 호출 시각 | ||
| Integer partySize = waitingRedisRepository.getWaitingPartySize(storeId, userId); | ||
| LocalDateTime now = LocalDateTime.now(); | ||
|
|
||
| // 3) DB에 무조건 새로 저장 | ||
| Store store = storeRepository.getReferenceById(storeId); | ||
| reservation = Reservation.builder() | ||
| .store(store) | ||
| .user(user) | ||
| return EntryStatusResponseDto.builder() | ||
| .reservationNumber(reservationNumber) | ||
| .userId(userId) | ||
| .partySize(partySize) | ||
| .requestedAt(now) | ||
| .status(ReservationStatus.CALLING) | ||
| .userName(userRepository.getReferenceById(Long.valueOf(userId)).getNickname()) | ||
| .createdAt(requestedAt) | ||
| .status("CALLING") | ||
| .score(score) | ||
| .calledAt(now) | ||
| .message("호출되었습니다.") | ||
| .build(); | ||
| reservationRepository.save(reservation); | ||
|
|
||
| break; | ||
|
|
||
| case CONFIRMED: | ||
| // 1) 기존 대기 중이거나 호출 중일 때: Redis → DB 최초 저장 | ||
| if (ReservationStatus.WAITING.name().equals(currStatus) || ReservationStatus.CALLING.name().equals(currStatus)) { | ||
|
|
||
| // Redis 전부 삭제 | ||
| waitingRedisRepository.deleteWaiting(storeId, userId); | ||
|
|
||
| // 새 Reservation 생성 & 저장 | ||
| Reservation r = Reservation.builder() | ||
| .reservationNumber(reservationNumber) | ||
| .store(storeRepository.getReferenceById(storeId)) | ||
| .user(userRepository.getReferenceById(Long.valueOf(userId))) | ||
| .partySize(partySize) | ||
| .requestedAt(requestedAt) | ||
| .build(); | ||
| // 호출 시각 반영 | ||
| r.markCalling(calledAt != null ? calledAt : now); | ||
| if (newStatus == ReservationStatus.CONFIRMED) { | ||
| r.markConfirmed(now); | ||
| } else { | ||
| r.markCancelled(now); | ||
| } | ||
|
|
||
| Reservation saved = reservationRepository.save(r); | ||
| return EntryStatusResponseDto.fromEntity(saved); | ||
| } | ||
|
|
||
| // 2) 이미 취소(CANCELLED)된 경우: DB 레코드 찾아 바로 CONFIRMED 로 전환 | ||
| // (Redis에는 상태가 남아있지 않으므로 currStatus==null) | ||
| { | ||
| LocalDateTime start = LocalDate.now().atStartOfDay(); | ||
| LocalDateTime end = LocalDate.now().atTime(LocalTime.MAX); | ||
| Reservation existing = reservationRepository | ||
| .findFirstByStore_StoreIdAndUserIdAndStatusInAndRequestedAtBetweenOrderByRequestedAtDesc( | ||
| storeId, | ||
| Long.valueOf(userId), | ||
| List.of(ReservationStatus.CANCELLED), | ||
| start, | ||
| end | ||
| ).orElseThrow(() -> new IllegalStateException("취소된 예약이 없습니다.")); | ||
|
|
||
| existing.markConfirmed(now); | ||
| existing.updateStatus(ReservationStatus.CONFIRMED); | ||
| Reservation saved = reservationRepository.save(existing); | ||
| return EntryStatusResponseDto.fromEntity(saved); | ||
| } | ||
|
|
||
| case CANCELLED: | ||
| // 1) Redis에서 제거 | ||
| if (!(ReservationStatus.WAITING.name().equals(currStatus) | ||
| || ReservationStatus.CALLING.name().equals(currStatus))) { | ||
| throw new IllegalStateException("WAITING/CALLING 상태에서만 취소 가능합니다."); | ||
| } | ||
| waitingRedisRepository.deleteWaiting(storeId, userId); | ||
|
|
||
| // 2) 오늘 날짜 예약 조회 & 상태 변경 | ||
| reservation = findTodayReservation(storeId, userId); | ||
| reservation.updateStatus(newStatus); | ||
| reservationRepository.save(reservation); | ||
|
|
||
| // 3) 완료/취소 메시지 | ||
| message = String.format( | ||
| "%s님의 예약이 %s 처리되었습니다.", | ||
| user.getNickname(), | ||
| newStatus == ReservationStatus.CONFIRMED ? "입장 완료" : "입장 취소" | ||
| ); | ||
| break; | ||
| Reservation r = Reservation.builder() | ||
| .reservationNumber(reservationNumber) | ||
| .store(storeRepository.getReferenceById(storeId)) | ||
| .user(userRepository.getReferenceById(Long.valueOf(userId))) | ||
| .partySize(partySize) | ||
| .requestedAt(requestedAt) | ||
| .build(); | ||
| r.markCalling(calledAt != null ? calledAt : now); | ||
| r.markCancelled(now); | ||
| Reservation saved = reservationRepository.save(r); | ||
| return EntryStatusResponseDto.fromEntity(saved); | ||
|
|
||
| default: | ||
| throw new IllegalArgumentException("지원하지 않는 상태입니다: " + newStatus); | ||
| throw new IllegalArgumentException("지원하지 않는 상태: " + newStatus); | ||
| } | ||
|
|
||
| // 5) 공통 DTO 반환 | ||
| return EntryStatusResponseDto.builder() | ||
| .id(reservation.getId().toString()) | ||
| .userId(userId) | ||
| .partySize(reservation.getPartySize()) | ||
| .userName(user.getNickname()) | ||
| .createdAt(reservation.getRequestedAt()) | ||
| .status(reservation.getStatus().name()) | ||
| .message(message) | ||
| .build(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
메서드가 너무 복잡하고 중복 코드가 많습니다.
112줄에 달하는 긴 메서드로 가독성과 유지보수성이 떨어집니다. 다음과 같은 개선을 제안합니다:
- 각 상태별 처리를 별도 메서드로 분리
- 예약 생성 로직을 재사용 가능한 메서드로 추출
- Redis 데이터 조회 로직 분리
+private ReservationData fetchReservationData(Long storeId, String userId) {
+ String reservationNumber = waitingRedisRepository.getReservationId(storeId, userId);
+ String currStatus = waitingRedisRepository.getWaitingStatus(storeId, userId);
+ // ... 나머지 조회 로직
+ return new ReservationData(...);
+}
+
+private Reservation createReservation(Long storeId, String userId, ReservationData data) {
+ return Reservation.builder()
+ .reservationNumber(data.reservationNumber())
+ .store(storeRepository.getReferenceById(storeId))
+ .user(userRepository.getReferenceById(Long.valueOf(userId)))
+ .partySize(data.partySize())
+ .requestedAt(data.requestedAt())
+ .build();
+}
+
+private EntryStatusResponseDto processCalling(Long storeId, String userId, ReservationData data) {
+ // CALLING 처리 로직
+}
+
+private EntryStatusResponseDto processConfirmed(Long storeId, String userId, ReservationData data) {
+ // CONFIRMED 처리 로직
+}
+
+private EntryStatusResponseDto processCancelled(Long storeId, String userId, ReservationData data) {
+ // CANCELLED 처리 로직
+}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java
around lines 222 to 334, the processEntryStatus method is too long and contains
duplicated code, reducing readability and maintainability. Refactor by
extracting the Redis data retrieval logic into a separate method, create
individual methods for handling each ReservationStatus case (CALLING, CONFIRMED,
CANCELLED), and encapsulate the reservation creation logic into a reusable
helper method. This modular approach will simplify the main method and improve
code clarity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이슈 등록 해줘
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
@Jjiggu 리팩토링 제안에 대한 이슈를 생성하겠습니다.
이슈가 성공적으로 생성되었습니다! 🐰✨
🏁 Script executed:
#!/bin/bash
# Create GitHub issue for the refactoring suggestion
gh issue create \
--title "Refactor: ReservationService.processEntryStatus 메서드 복잡도 개선" \
--body "## 개요
ReservationService 클래스의 processEntryStatus 메서드가 112줄에 달하는 긴 메서드로 가독성과 유지보수성이 떨어집니다.
## 문제점
- 메서드가 너무 복잡하고 중복 코드가 많음
- 각 상태별 처리 로직이 하나의 메서드에 모두 포함되어 있음
- Redis 데이터 조회 로직과 예약 생성 로직의 중복
## 개선 제안
1. 각 상태별 처리를 별도 메서드로 분리
2. 예약 생성 로직을 재사용 가능한 메서드로 추출
3. Redis 데이터 조회 로직 분리
## 관련 링크
- PR: https://github.com/GTable/NoWait_BE_MultiModule/pull/175
- 리뷰 코멘트: https://github.com/GTable/NoWait_BE_MultiModule/pull/175#discussion_r2235821383
## 요청자
@Jjiggu" \
--assignee Jjiggu
Length of output: 626
@Jjiggu 이슈가 성공적으로 생성되었습니다!
📋 생성된 이슈 정보:
- 이슈 번호: Refactor: ReservationService.processEntryStatus 메서드 복잡도 개선 #176
- 제목: "Refactor: ReservationService.processEntryStatus 메서드 복잡도 개선"
- 담당자: Jjiggu
- 링크: Refactor: ReservationService.processEntryStatus 메서드 복잡도 개선 #176
이슈에는 현재 processEntryStatus 메서드의 문제점과 개선 제안사항이 포함되어 있으며, 관련 PR과 코멘트 링크도 함께 첨부되어 있습니다.
🐰💭
🧬 Code Graph Analysis Results
Relevant snippets for understanding nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java
1. nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationViewUnauthorizedException.java (lines 5-9)
public class ReservationViewUnauthorizedException extends RuntimeException {
public ReservationViewUnauthorizedException() {
super(ErrorMessage.RESERVATION_VIEW_UNAUTHORIZED.getMessage());
}
}- Custom runtime exception thrown when a user is unauthorized to view reservation information.
2. nowait-domain/domain-core-rdb/src/main/java/com/nowait.domaincorerdb/reservation/exception/ReservationUpdateUnauthorizedException.java (lines 5-9)
public class ReservationUpdateUnauthorizedException extends RuntimeException {
public ReservationUpdateUnauthorizedException() {
super(ErrorMessage.RESERVATION_UPDATE_UNAUTHORIZED.getMessage());
}
}- Custom runtime exception thrown when a user is unauthorized to update reservation information.
3. nowait-domain/domain-redis/src/main/java/com/nowait.domaincoreredis/common/util/RedisKeyUtils.java (lines 9-85)
Utility class for building Redis keys related to waiting queues and reservations.
Key methods and constants:
buildWaitingKeyPrefix(): returns prefix for waiting queue keys, e.g."waiting:"buildWaitingPartySizeKeyPrefix(): returns prefix for party size hash keys, e.g."waiting:party:"buildWaitingStatusKeyPrefix(): returns prefix for waiting status hash keys, e.g."waiting:status:"buildReservationNumberKey(Long storeId): returns reservation number key for a store, e.g."reservation:number:<storeId>"buildWaitingCalledAtKeyPrefix(): prefix for waiting calledAt hash keysexpireAtNext03(): returns a Date object representing next day 3 AM in Asia/Seoul timezone for Redis key expiration
This class is used extensively in ReservationService to build Redis keys for accessing waiting queue data.
4. nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java (lines 48-257)
This user-facing ReservationService contains methods related to user reservation and waiting queue management:
registerWaiting(Long storeId, CustomOAuth2User customOAuth2User, ReservationCreateRequestDto requestDto): Registers a user in the waiting queue for a store, interacting with Redis and generating a reservation number.myWaitingInfo(Long storeId, CustomOAuth2User customOAuth2User): Retrieves the current waiting info for a user at a store.cancelWaiting(Long storeId, CustomOAuth2User customOAuth2User): Cancels a user's waiting entry in Redis.getAllMyWaitings(CustomOAuth2User customOAuth2User): Retrieves all waiting entries for a user across stores.create(Long storeId, CustomOAuth2User customOAuth2User, ReservationCreateRequestDto requestDto): Creates a reservation entity in the database.
This service uses Redis and database repositories similarly to the admin ReservationService, but from the user perspective.
5. nowait-app-user-api/src/main/java/com/nowait/applicationuser/store/service/StoreServiceImpl.java (lines 47-348)
Contains methods related to store data and waiting queue counts:
getAllStoresByPageAndDeparments(Pageable pageable): Retrieves paginated store data with waiting counts from Redis.getStoreByStoreId(Long storeId, CustomOAuth2User customOAuth2User): Retrieves detailed store info including waiting count and user waiting status.searchByKeywordNative(String keyword): Searches stores by keyword and includes waiting counts.getStoresByWaitingCount(boolean desc): Retrieves stores sorted by waiting count from Redis.- Uses Redis keys with prefix
"waiting:"to get waiting queue sizes.
This service shows how waiting queue data is aggregated and used in other parts of the system.
Summary
The above snippets provide context on:
- Custom exceptions used in
ReservationServicefor authorization errors. - Redis key utilities used for managing waiting queue data.
- User-side reservation and waiting queue management service with similar Redis and DB interactions.
- Store service that aggregates waiting queue counts from Redis.
These are relevant to understanding the ReservationService in the admin API, especially its interaction with Redis waiting queues, authorization checks, and reservation status updates.
...in-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java
Outdated
Show resolved
Hide resolved
...in-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java
Outdated
Show resolved
Hide resolved
.../main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java
Outdated
Show resolved
Hide resolved
...n/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java
Outdated
Show resolved
Hide resolved
Refactor: #174 code review 반영
작업 요약
Issue Link
#174
문제점 및 어려움
해결 방안
Reference
Summary by CodeRabbit
신규 기능
기능 개선
버그 수정
기타