Skip to content

Conversation

@Jjiggu
Copy link
Contributor

@Jjiggu Jjiggu commented Jul 28, 2025

작업 요약

  • 예약 번호 생성 로직 추가
  • 기존 로직 개선
  • N + 1 문제 해결

Issue Link

#174

문제점 및 어려움

해결 방안

Reference

Summary by CodeRabbit

  • 신규 기능

    • 예약 대기열에 고유한 예약 번호(reservationNumber)와 호출/확정/취소 시각 정보가 추가되었습니다.
    • 사용자 대기 목록 조회 시 매장 정보, 부서명, 위치, 프로필/배너 이미지, 예약 ID 등 상세 정보가 제공됩니다.
    • 신규 DTO 및 응답 필드가 추가되어 예약 및 대기 상태 정보를 더욱 풍부하게 전달합니다.
  • 기능 개선

    • 대기열 및 예약 관련 데이터 조회가 대량 처리 및 Redis 파이프라인을 통해 더욱 빨라졌습니다.
    • 예약 상태 변경(호출, 확정, 취소) 시 일관된 상태 전환 및 시간 기록이 적용됩니다.
    • API 응답이 표준화된 성공 포맷으로 반환됩니다.
    • Redis 키 만료 시간이 매일 오전 3시 기준으로 조정되어 관리 효율성이 향상되었습니다.
    • Redis 대기열 관리 기능이 개선되어 예약 번호 관리 및 호출 시간 기록이 추가되었습니다.
  • 버그 수정

    • 대기열 삭제 시 관련된 예약 번호 및 호출 시간 정보도 함께 삭제됩니다.
  • 기타

    • 코드 가독성 및 유지보수성을 위한 포맷팅 및 리팩터링이 일부 적용되었습니다.

@Jjiggu Jjiggu self-assigned this Jul 28, 2025
@Jjiggu Jjiggu added bug Something isn't working enhancement New feature or request refactor 리팩토링 labels Jul 28, 2025
@coderabbitai
Copy link

coderabbitai bot commented Jul 28, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

이 변경 사항은 예약 대기 시스템의 Redis 및 RDB 동기화, 예약 번호 및 상태 타임스탬프 관리, 배치 데이터 조회, DTO 확장, 컨트롤러 응답 표준화, 레포지토리 메서드 추가 등 예약 대기/확정/취소 전반의 로직을 대폭 개선 및 확장합니다.

Changes

Cohort / File(s) Change Summary
Admin API 컨트롤러 응답 표준화
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java
컨트롤러의 응답을 ApiUtils.success()로 래핑하여 표준화된 성공 응답 형식 적용.
Admin API DTO 확장
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/EntryStatusResponseDto.java,
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/dto/WaitingUserResponse.java
예약 ID 필드명 변경 및 예약 번호, 호출/확정/취소 시각 필드 추가, 엔티티 변환용 정적 팩토리 메서드 추가.
Admin API Redis 리포지토리 확장
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/repository/WaitingRedisRepository.java
예약 번호 조회, 호출 시각 저장/조회 기능 추가, 대기 삭제 시 관련 키 동시 삭제 기능 확장.
Admin API 서비스 로직 리팩토링
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java
대기 사용자 상세 조회 시 N+1 제거 및 Redis 파이프라인 활용, 예약 상태 전이 로직 통합 및 타임스탬프/예약 번호 동기화, 예외 처리 및 상태별 처리 개선.
User API 컨트롤러 포맷팅
nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/controller/ReservationController.java
메서드 호출 파라미터 구분을 위한 공백 추가 및 응답 래핑 방식 변경 등 코드 포맷팅 개선, 로직 변경 없음.
User API DTO 확장 및 추가
nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/MyWaitingQueueDto.java,
nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/MyWaitingStoreInfo.java,
nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/dto/WaitingResponseDto.java
예약 ID, 예약 번호, 배너 이미지 URL 필드 추가 및 매장 정보 DTO 신설.
User API Redis 리포지토리 확장
nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java
대기 큐 추가 시 예약 번호 생성 및 저장, 매일 3시 만료 TTL 적용, SCAN 기반 매장 조회, 예약 번호 조회 및 대기 삭제 시 예약 번호 키 삭제 기능 추가.
User API 서비스 리팩토링
nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/service/ReservationService.java
대기 등록, 내 대기 정보 조회, 전체 대기 목록 조회 시 예약 번호 및 이미지, 부서명 등 추가 정보 포함, Redis 파이프라인 및 배치 쿼리 활용으로 성능 개선.
RDB 예약 엔티티 확장
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java
예약 번호, 호출/확정/취소 시각 필드 및 상태 전이 메서드 추가.
RDB 예약 레포지토리 메서드 변경
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java
예약 상태 리스트 필터링 추가로 메서드 시그니처 변경.
RDB 매장/이미지 레포지토리 확장
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreImageRepository.java,
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/repository/StoreRepository.java
매장 및 이미지 다중 조회용 메서드 추가, JPQL 쿼리 도입.
Redis Key 유틸리티 확장
nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java
예약 관련 Redis 키, 프리픽스, 시퀀스, 만료일 생성 메서드 다수 추가 및 기존 메서드 멀티라인화.

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: 응답 반환
Loading
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: 응답 반환
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • #129: User API의 대기 목록 조회 및 관련 DTO 확장과 본 PR의 getAllMyWaitings 리팩토링이 직접적으로 연결됨.
  • #122: Redis 기반 대기 큐 처리 및 예약 응답 DTO 확장 등, 예약 서비스/리포지토리 계층의 Redis 통합과 직접 관련.
  • #160: Admin API의 processEntryStatus 예약 상태 전이 로직 리팩토링 및 상태별 타임스탬프 관리와 본 PR의 로직 통합이 밀접하게 연관됨.

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d509b60 and 2c29716.

📒 Files selected for processing (6)
  • 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/repository/WaitingUserRedisRepository.java (5 hunks)
  • nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java (3 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/#174-waiting-refactoring

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 generate unit tests to generate unit tests for 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.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

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.

@github-actions github-actions bot requested a review from HyemIin July 28, 2025 10:38
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: 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

📥 Commits

Reviewing files that changed from the base of the PR and between b331d3e and d509b60.

📒 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 안전성도 잘 구현되었습니다.

Comment on lines 222 to 334
@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();
}
Copy link

@coderabbitai coderabbitai bot Jul 28, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

메서드가 너무 복잡하고 중복 코드가 많습니다.

112줄에 달하는 긴 메서드로 가독성과 유지보수성이 떨어집니다. 다음과 같은 개선을 제안합니다:

  1. 각 상태별 처리를 별도 메서드로 분리
  2. 예약 생성 로직을 재사용 가능한 메서드로 추출
  3. 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.

Copy link
Contributor Author

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.

🧩 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 이슈가 성공적으로 생성되었습니다!

📋 생성된 이슈 정보:

이슈에는 현재 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 keys
  • expireAtNext03(): 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 ReservationService for 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.

@Jjiggu Jjiggu merged commit e444d99 into develop Jul 28, 2025
1 check was pending
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working enhancement New feature or request refactor 리팩토링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants