Feat: 주점 매출 순위 등락 기능 추가#109
Conversation
|
""" Walkthrough이 변경사항은 통계 및 매장 랭킹 기능을 도입하기 위한 대규모 리팩토링 및 기능 추가입니다. 기존 주문 관련 통계 API는 OrderController에서 제거되고, 새로운 StatisticsController로 이동되었습니다. Redis를 활용한 실시간 매장 랭킹 캐시 및 조회 구조가 추가되었으며, 관련 도메인, DTO, 서비스, 스케줄러, 예외 및 빌드 설정이 대폭 확장되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant StatisticsController
participant OrderService
participant RankingService
participant StatisticCustomRepository
participant RankingQueryService
participant RankingQueryRepository (Redis)
participant DB
Client->>StatisticsController: GET /admin/statistics/sales
StatisticsController->>OrderService: getSaleSumByStoreId(memberDetails)
OrderService->>StatisticCustomRepository: findSalesSumByStoreId(storeId)
StatisticCustomRepository->>DB: 쿼리 실행
DB-->>StatisticCustomRepository: 매출 합계 반환
StatisticCustomRepository-->>OrderService: 매출 합계 반환
OrderService-->>StatisticsController: 매출 합계 반환
StatisticsController-->>Client: 응답
Client->>StatisticsController: GET /admin/statistics/top-sales
StatisticsController->>RankingService: getStatisticsRankings(memberDetails)
RankingService->>RankingQueryService: getRankings(userStoreId, topN)
RankingQueryService->>RankingQueryRepository (Redis): findTopStoresWithUser(...)
RankingQueryRepository (Redis)-->>RankingQueryService: 랭킹 리스트 반환
RankingQueryService->>RankingQueryRepository (Redis): findPrevRank(...) (각 store)
RankingQueryRepository (Redis)-->>RankingQueryService: 이전 랭킹 반환
RankingQueryService-->>RankingService: 랭킹+변동 리스트 반환
RankingService->>StatisticCustomRepository: findStoreInfoByIds(storeIds)
StatisticCustomRepository->>DB: 쿼리 실행
DB-->>StatisticCustomRepository: 매장 정보 반환
StatisticCustomRepository-->>RankingService: 매장 정보 반환
RankingService-->>StatisticsController: StoreRankingDto 리스트 반환
StatisticsController-->>Client: 응답
Note over StatisticsController,RankingRefreshScheduler: 별도 스케줄러가 5분마다<br>DB에서 매출 집계 후 Redis 랭킹 스냅샷 갱신
Possibly related PRs
Suggested reviewers
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
✨ Finishing Touches
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.
Actionable comments posted: 9
🔭 Outside diff range comments (1)
nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepositoryImpl.java (1)
235-235: 잘못된 참조 사용
zeroTuple은QStore s에서 조회한 결과인데,u.store.departmentId를 사용하고 있습니다. 이는 컴파일 에러를 발생시킬 수 있습니다.다음과 같이 수정하세요:
-Long departmentId = zeroTuple.get(u.store.departmentId); +Long departmentId = zeroTuple.get(s.departmentId);
🧹 Nitpick comments (12)
nowait-domain/domain-admin-rdb/build.gradle (1)
47-49: Jackson 의존성 버전 통일을 검토해보세요.Jackson 관련 의존성들이 명시적으로 버전을 지정하고 있습니다. Spring Boot BOM에서 관리하는 버전을 사용하는 것이 일관성 측면에서 더 좋을 수 있습니다.
- api 'com.fasterxml.jackson.core:jackson-databind:2.15.2' - api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.2' + api 'com.fasterxml.jackson.core:jackson-databind' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/OrderSalesSumDetail.java (1)
7-21: @builder와 생성자가 중복됩니다.Lombok의 @builder 어노테이션이 있으면 생성자를 별도로 정의할 필요가 없습니다. 중복 코드를 제거하는 것이 좋겠습니다.
- public OrderSalesSumDetail(Long storeId, Integer todaySalesSum, Integer yesterdaySalesSum, - Integer cumulativeSalesBeforeYesterday) { - this.storeId = storeId; - this.todaySalesSum = todaySalesSum; - this.yesterdaySalesSum = yesterdaySalesSum; - this.cumulativeSalesBeforeYesterday = cumulativeSalesBeforeYesterday; - } + // @Builder 어노테이션으로 자동 생성됨nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/TopSalesStoresDetail.java (1)
7-25: @builder와 생성자가 중복됩니다.Lombok의 @builder 어노테이션이 있으면 생성자를 별도로 정의할 필요가 없습니다. 중복 코드를 제거하는 것이 좋겠습니다.
- public TopSalesStoresDetail(Long storeId, String storeName, Long departmentId, String departmentName, Integer totalSales, - Long storeRank) { - this.storeId = storeId; - this.storeName = storeName; - this.departmentId = departmentId; - this.departmentName = departmentName; - this.totalSales = totalSales; - this.storeRank = storeRank; - } + // @Builder 어노테이션으로 자동 생성됨nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/StoreSales.java (1)
10-13: 생성자에서 null 값 검증 추가를 고려해보세요.매출 데이터를 다루는 중요한 DTO에서 null 값이 전달될 경우 예상치 못한 오류가 발생할 수 있습니다.
public StoreSales(Long storeId, Integer totalSales) { + if (storeId == null) { + throw new IllegalArgumentException("storeId cannot be null"); + } + if (totalSales == null) { + throw new IllegalArgumentException("totalSales cannot be null"); + } this.storeId = storeId; this.totalSales = totalSales; }nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/config/RedisConfig.java (1)
25-29: 빈 비밀번호에 대한 처리를 고려해보세요.비밀번호가 빈 문자열이거나 null인 경우에 대한 처리가 있으면 더 안전할 것입니다.
RedisStandaloneConfiguration redisConfiguration = new RedisStandaloneConfiguration(); redisConfiguration.setHostName(host); redisConfiguration.setPort(port); -redisConfiguration.setPassword(password); +if (password != null && !password.trim().isEmpty()) { + redisConfiguration.setPassword(password); +} return new LettuceConnectionFactory(redisConfiguration);nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java (1)
5-7: Redis 키를 설정 파일로 외부화하는 것을 권장합니다.하드코딩된 Redis 키는 환경별 설정이나 키 네임스페이스 변경 시 유연성을 제한할 수 있습니다.
+@Component +@ConfigurationProperties(prefix = "redis.ranking") +@Data public class RedisKeyUtils { - private static final String KEY_CURRENT = "nowait:store:rank:current"; - private static final String KEY_PREVIOUS = "nowait:store:rank:previous"; - private static final String KEY_NEXT = "nowait:store:rank:next"; + private static String keyCurrent = "nowait:store:rank:current"; + private static String keyPrevious = "nowait:store:rank:previous"; + private static String keyNext = "nowait:store:rank:next";application.yml에서 설정 가능하도록:
redis: ranking: key-current: "nowait:store:rank:current" key-previous: "nowait:store:rank:previous" key-next: "nowait:store:rank:next"nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/RankingQueryServiceImpl.java (1)
24-24: 주석의 "4위까지"가 매직 넘버로 보입니다.주석에서 언급하는 "4위까지"가 topN 매개변수와 일치하지 않을 수 있습니다.
- // 2) 사용자 주점이 topN 밖이면 4위까지 + 사용자 주점 + // 2) 사용자 주점이 topN 밖이면 상위 topN개 + 사용자 주점nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepository.java (1)
12-23: JavaDoc 문서화를 추가하는 것을 권장합니다.각 메서드의 목적, 매개변수, 반환값에 대한 문서화가 있으면 유지보수성이 향상됩니다.
+/** + * 통계 관련 커스텀 repository + */ public interface StatisticCustomRepository { + /** + * 특정 매장의 매출 합계 상세 정보를 조회합니다. + * @param storeId 매장 ID + * @return 매출 합계 상세 정보 + */ OrderSalesSumDetail findSalesSumByStoreId(Long storeId); + /** + * 상위 4개 매장과 지정된 매장의 매출 정보를 조회합니다. + * @param storeId 매장 ID + * @return 상위 매장 및 지정 매장의 매출 정보 리스트 + */ List<TopSalesStoresDetail> getTop4PlusMine(Long storeId); + /** + * 모든 매장의 총 매출 정보를 조회합니다. (Redis 랭킹 갱신용) + * @return 매장별 총 매출 리스트 + */ List<StoreSales> findTotalSales(); + /** + * 주어진 매장 ID들의 상세 정보를 조회합니다. + * @param storeIds 매장 ID 리스트 + * @return 매장 상세 정보 리스트 + */ List<StoreInfo> findStoreInfoByIds(List<Long> storeIds);nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java (1)
48-52: 대량의 Redis 작업에 대한 성능 최적화를 권장합니다.현재 forEach로 개별 Redis 작업을 수행하는 것보다 배치 처리를 사용하면 성능이 향상됩니다.
- salesList.forEach(s -> - rankingQueryRepository.addToRanking(nextKey, s.getStoreId(), s.getTotalSales()) - ); + // 배치 처리로 성능 최적화 + redis.execute(connection -> { + salesList.forEach(s -> + rankingQueryRepository.addToRanking(nextKey, s.getStoreId(), s.getTotalSales()) + ); + return null; + });또는 RankingQueryRepository에 배치 메서드 추가:
+ // RankingQueryRepository에 추가 + void addBatchToRanking(String key, List<StoreSales> salesList);nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/repository/RankingQueryRepositoryImpl.java (2)
35-42: 성능 최적화 제안: 중복 연산 제거
reverseRangeWithScores로 이미 순위 정보를 가져왔는데, 다시reverseRank를 호출하여 중복 연산이 발생합니다.다음과 같이 최적화할 수 있습니다:
List<RankingEntry> entries = new ArrayList<>(); long rank = 1; for (var tuple : topTuples) { entries.add(new RankingEntry( Long.parseLong(tuple.getValue()), tuple.getScore().intValue(), rank++, 0 )); }
61-61: 파라미터 재할당으로 인한 혼란메서드 파라미터
entries를 재할당하면 코드 가독성이 떨어지고 의도를 파악하기 어려워집니다.지역 변수를 사용하여 명확하게 만드세요:
if (userZero != null && userZero >= topN) { List<RankingEntry> top4 = entries.subList(0, topN - 1); Double userScore = redisTemplate.opsForZSet().score(key, userStoreId.toString()); Integer userSales = userScore != null ? userScore.intValue() : 0; top4.add(new RankingEntry( userStoreId, userSales, userZero + 1, 0 )); - entries = top4; + return top4; } -return entries; +return entries;nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepositoryImpl.java (1)
297-297: 일관성 문제: static import와 인스턴스 참조 혼용Line 4에서
static import로store를 가져왔는데, 일부 곳에서는s.name, 다른 곳에서는store.departmentId를 사용하여 일관성이 없습니다.일관되게 static import를 사용하세요:
-.select(s.storeId, s.name, store.departmentId, d.name) +.select(store.storeId, store.name, store.departmentId, d.name)또는 모든 곳에서 인스턴스 참조를 사용하세요:
-.select(s.storeId, s.name, store.departmentId, d.name) +.select(s.storeId, s.name, s.departmentId, d.name)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (29)
nowait-app-admin-api/build.gradle(2 hunks)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java(0 hunks)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java(4 hunks)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/controller/StatisticsController.java(1 hunks)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/dto/StoreRankingDto.java(1 hunks)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java(1 hunks)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/RankingService.java(1 hunks)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/impl/RankingServiceImpl.java(1 hunks)nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java(1 hunks)nowait-domain/domain-admin-rdb/build.gradle(1 hunks)nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/config/QueryDslConfig.java(1 hunks)nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/OrderSalesSumDetail.java(1 hunks)nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/StoreInfo.java(1 hunks)nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/StoreSales.java(1 hunks)nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/TopSalesStoresDetail.java(1 hunks)nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/exception/StatisticViewUnauthorizedException.java(1 hunks)nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepository.java(1 hunks)nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepositoryImpl.java(3 hunks)nowait-domain/domain-core-rdb/build.gradle(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepository.java(0 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java(1 hunks)nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java(1 hunks)nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/config/RedisConfig.java(2 hunks)nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/dto/RankingEntry.java(1 hunks)nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/repository/RankingQueryRepository.java(1 hunks)nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/repository/RankingQueryRepositoryImpl.java(1 hunks)nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/RankingQueryService.java(1 hunks)nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/RankingQueryServiceImpl.java(1 hunks)nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/service/StoreRankCacheService.java(0 hunks)
💤 Files with no reviewable changes (3)
- nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderCustomRepository.java
- nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/service/StoreRankCacheService.java
- nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/controller/OrderController.java
🧰 Additional context used
🧬 Code Graph Analysis (1)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/controller/StatisticsController.java (1)
nowait-app-user-api/src/main/java/com/nowait/applicationuser/security/jwt/JwtAuthorizationFilter.java (1)
RequiredArgsConstructor(24-99)
🔇 Additional comments (26)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/RankingService.java (1)
8-10: 인터페이스 설계가 깔끔합니다.단일 책임 원칙을 잘 따르고 있으며, 메서드 이름도 명확합니다. 통계 랭킹 조회 기능을 위한 적절한 인터페이스 설계입니다.
nowait-domain/domain-admin-rdb/build.gradle (1)
30-42: 의존성 추가가 적절합니다.새로운 통계 및 랭킹 기능을 위한 필요한 의존성들이 잘 추가되었습니다. Spring Security, QueryDSL, Swagger 등이 기능 구현에 필요한 라이브러리들입니다.
nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/config/QueryDslConfig.java (2)
1-1: 패키지 리팩토링이 적절합니다.도메인 분리를 위한 패키지 구조 변경이 잘 이루어졌습니다. 통계 관련 기능을 별도 모듈로 분리하는 것이 좋은 설계입니다.
11-20: 표준적인 QueryDSL 설정입니다.JPAQueryFactory 빈 생성과 EntityManager 주입이 올바르게 구성되어 있습니다. 복잡한 쿼리 작성을 위한 기본 설정이 완료되었습니다.
nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/OrderSalesSumDetail.java (1)
1-1: 패키지 이동이 적절합니다.통계 관련 DTO를 전용 패키지로 이동한 것이 좋은 구조 개선입니다.
nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/TopSalesStoresDetail.java (1)
1-1: 패키지 이동이 적절합니다.통계 관련 DTO를 전용 패키지로 이동한 것이 좋은 구조 개선입니다.
nowait-app-admin-api/build.gradle (2)
24-24: Redis 도메인 모듈 의존성 추가 확인새로운 랭킹 기능을 위한 Redis 도메인 모듈 의존성이 적절히 추가되었습니다.
40-42: Redis 의존성 추가 확인Spring Boot Redis 스타터 의존성이 적절히 추가되어 Redis 연동이 가능합니다. 주석과 함께 명확하게 구성되어 있습니다.
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java (1)
12-12: Repository 인터페이스 리팩토링 적절주문 관련 통계 기능을 별도의 통계 도메인으로 분리하는 것은 단일 책임 원칙에 맞는 좋은 리팩토링입니다. OrderRepository가 핵심 주문 기능에만 집중할 수 있게 되었습니다.
nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java (1)
61-62: 통계 권한 에러 메시지 추가 적절새로운 통계 기능을 위한 권한 에러 메시지가 기존 패턴에 맞게 잘 추가되었습니다. 에러 코드와 메시지가 명확하고 일관성이 있습니다.
nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/exception/StatisticViewUnauthorizedException.java (1)
1-9: 통계 권한 예외 클래스 구현 적절통계 권한 관련 예외 클래스가 적절하게 구현되었습니다. RuntimeException을 상속받아 권한 실패에 맞는 처리가 가능하고, 중앙화된 에러 메시지를 사용하여 일관성이 유지됩니다.
nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/RankingQueryService.java (1)
7-13: 랭킹 쿼리 서비스 인터페이스 설계 적절랭킹 조회를 위한 서비스 인터페이스가 잘 설계되었습니다. 메소드 시그니처가 명확하고 JavaDoc 주석으로 동작이 명확히 설명되어 있습니다. 사용자 주점 포함 및 상위 N개 조회 로직이 잘 정의되어 있습니다.
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java (4)
12-14: 패키지 재구조화가 적절히 반영되었습니다.통계 관련 DTO들이 전용 패키지로 이동되어 도메인 분리가 잘 이루어졌습니다.
34-34: 새로운 통계 레포지토리 의존성이 적절히 추가되었습니다.통계 관련 쿼리를 전담하는 레포지토리 분리가 단일 책임 원칙을 잘 따르고 있습니다.
72-72: 메서드 시그니처 일치 확인됨
- OrderService.java (72행)
statisticCustomRepository.findSalesSumByStoreId(storeId)호출의 파라미터(Long storeId)와 반환 타입(OrderSalesSumDetail)이- domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepository.java
OrderSalesSumDetail findSalesSumByStoreId(Long storeId);- domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepositoryImpl.java
public OrderSalesSumDetail findSalesSumByStoreId(Long storeId) { … }모두 일치하므로 추가 수정이 필요 없습니다.
84-84: OrderRepository에는getTop4PlusMine메서드가 존재하지 않습니다.
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/repository/OrderRepository.java인터페이스 및 구현부를 확인한 결과, 해당 메서드 정의가 전혀 없습니다.- 따라서 이전
orderRepository호출과 비교할 대상이 없으므로, 메서드 결과 일치 여부 검증은 불필요합니다.리뷰 코멘트는 무시해 주세요.
Likely an incorrect or invalid review comment.
nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/config/RedisConfig.java (1)
20-21: Redis 인증 기능 추가가 보안 향상에 도움이 됩니다.비밀번호 기반 인증으로 Redis 보안이 강화되었습니다.
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/dto/StoreRankingDto.java (1)
5-14: 불변 객체 설계가 잘 되어 있습니다.final 필드를 사용한 불변 객체 설계는 스레드 안전성과 데이터 무결성을 보장하는 좋은 방식입니다.
nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java (1)
3-11: 유틸리티 클래스 패턴이 올바르게 구현되었습니다.private 생성자와 UnsupportedOperationException을 사용한 인스턴스화 방지는 좋은 방식입니다.
nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepository.java (1)
12-23: 인터페이스 설계가 명확하고 잘 구조화되어 있습니다.메서드 이름이 직관적이고 책임이 명확하게 분리되어 있습니다.
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java (1)
30-34: 초기화 시점에서 refresh 호출이 적절합니다.애플리케이션 시작 시 즉시 랭킹 데이터를 초기화하는 것은 좋은 방식입니다.
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/service/impl/RankingServiceImpl.java (1)
41-43: 권한 검사 로직에 논리적 오류가 있습니다.
userStoreId가 이미user.getStoreId()로 설정되어 있어서!user.getStoreId().equals(userStoreId)조건이 항상 false가 됩니다. SUPER_ADMIN이 아닌 사용자는 항상 자신의 매장 정보만 조회할 수 있어야 하므로 이 조건은 불필요합니다.다음과 같이 수정하세요:
-if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(userStoreId)) { +if (!Role.SUPER_ADMIN.equals(user.getRole())) { + // SUPER_ADMIN이 아닌 경우 자신의 매장 정보만 조회 가능 + // userStoreId는 이미 user.getStoreId()로 설정되어 있음 +}또는 더 명확하게:
-if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(userStoreId)) { - throw new StatisticViewUnauthorizedException(); -} +// SUPER_ADMIN이 아닌 경우 자신의 매장 정보만 조회 가능하므로 별도 검증 불필요 +// userStoreId는 이미 user.getStoreId()로 설정됨Likely an incorrect or invalid review comment.
nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/repository/RankingQueryRepository.java (1)
7-16: 인터페이스 설계가 잘 되어 있습니다.Redis 기반 랭킹 시스템의 핵심 기능들이 명확한 메서드 시그니처로 잘 정의되어 있습니다. 메서드명과 파라미터가 직관적이며 기능을 잘 표현하고 있습니다.
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/controller/StatisticsController.java (1)
24-61: 컨트롤러 구현이 우수합니다.REST API 표준을 잘 따르고 있으며, Spring Security 인증, Swagger 문서화, 일관된 응답 구조 등이 적절하게 구현되어 있습니다. 코드가 깔끔하고 가독성이 좋습니다.
nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/repository/StatisticCustomRepositoryImpl.java (2)
269-292: 새로운 Redis 지원 메서드 잘 구현되었습니다.
findTotalSales메서드는 Redis 랭킹 시스템을 위한 데이터를 효율적으로 조회하도록 잘 구현되었습니다. 날짜 범위 설정과 null 안전성 처리가 적절합니다.
295-311: 상점 정보 조회 메서드 잘 구현되었습니다.
findStoreInfoByIds메서드는 여러 상점 ID에 대한 상세 정보를 효율적으로 조회하도록 잘 구현되었습니다. 조인을 사용하여 한 번의 쿼리로 필요한 정보를 가져오는 것이 효율적입니다.
| //QueryDsl | ||
| implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' | ||
| annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" | ||
| annotationProcessor "jakarta.annotation:jakarta.annotation-api" | ||
| annotationProcessor "jakarta.persistence:jakarta.persistence-api" | ||
|
|
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
중복 의존성 및 버전 정합성 확인 필요
QueryDSL 의존성을 이 위치로 이동한 것은 문제없지만,
① 루트 build.gradle 또는 다른 서브모듈에도 동일한 의존성이 남아 있다면 Gradle dependency resolution-rule 에 따라 클래스패스 충돌이나 불필요한 중복 다운로드가 발생할 수 있습니다.
② com.querydsl:querydsl-jpa:5.0.0:jakarta 는 Spring Boot 3.x 기준으로 올바른 선택이지만, 전역적으로 BOM(예: spring-boot-dependencies)를 사용 중이라면 버전을 고정하기보다 BOM에 위임하는 편이 유지보수에 유리합니다.
③ annotationProcessor "jakarta.annotation:jakarta.annotation-api" 와 jakarta.persistence-api 는 이미 spring-boot-starter-data-jpa가 transitively 제공하므로 중복일 수 있습니다.
필요 시 아래 스크립트로 전체 모듈을 스캔해 중복 정의 여부를 확인해보세요.
🏁 Script executed:
#!/bin/bash
# QueryDSL 의존성 중복 여부 확인
rg --no-heading --line-number $'querydsl-jpa' | cut -d: -f1 | sort | uniq -cLength of output: 178
아래 스크립트를 실행해서 루트 레벨 Gradle 파일에서 BOM(spring-boot-dependencies 등) 사용 여부를 확인해주세요.
🏁 Script executed:
#!/bin/bash
# 루트 레벨 Gradle 파일에서 BOM 사용 여부 확인
for file in *.gradle; do
echo "=== $file ==="
grep -n -E 'enforcedPlatform|platform\(|dependencyManagement|spring-boot-dependencies|io.spring.dependency-management' "$file" \
|| echo "No matches in $file"
doneLength of output: 719
QueryDSL 의존성 중앙 관리 및 중복 제거 필요
-
대상 파일
- nowait-domain/domain-admin-rdb/build.gradle
- nowait-domain/domain-core-rdb/build.gradle
-
문제 요약
- 두 모듈에서 동일한 QueryDSL 의존성(
com.querydsl:querydsl-jpa:5.0.0:jakarta,com.querydsl:querydsl-apt:5.0.0:jakarta)을 직접 버전 고정하여 선언하고 있어 중복 및 버전 불일치 위험이 있습니다. - 현재 루트
build.gradle의 Spring Boot BOM(org.springframework.boot:spring-boot-dependencies:3.5.0)에는 QueryDSL 의존성이 포함되어 있지 않습니다. annotationProcessor "jakarta.annotation:jakarta.annotation-api"와annotationProcessor "jakarta.persistence:jakarta.persistence-api"는 이미spring-boot-starter-data-jpa가 transitively 제공하므로 별도 선언이 불필요합니다.
- 두 모듈에서 동일한 QueryDSL 의존성(
-
개선 제안
- 루트
build.gradle의dependencyManagement블록에 QueryDSL 버전을 한 번만 정의하거나,
QueryDSL이 제공하는 BOM(com.querydsl:querydsl-bom:5.0.0)을platform(...)으로 import하여 하위 모듈에서 버전 표기를 제거하세요. - 아래 불필요한
annotationProcessor선언을 삭제하세요:- annotationProcessor "jakarta.annotation:jakarta.annotation-api" - annotationProcessor "jakarta.persistence:jakarta.persistence-api"
- 루트
🤖 Prompt for AI Agents
In nowait-domain/domain-core-rdb/build.gradle around lines 41 to 46, remove the
explicit version declarations of QueryDSL dependencies and the redundant
annotationProcessor entries for jakarta.annotation-api and
jakarta.persistence-api. Instead, manage the QueryDSL versions centrally by
importing the QueryDSL BOM (com.querydsl:querydsl-bom:5.0.0) as a platform in
the root build.gradle's dependencyManagement block, then remove version
specifications from submodules to avoid duplication and version conflicts.
There was a problem hiding this comment.
@Jjiggu 네, 이해했습니다. 현재 PR에서는 기능 구현에 집중하고 QueryDSL 의존성 중앙 관리는 추후 개선하시는 것이 좋겠네요.
혹시 이 개선 사항을 추적하기 위해 별도 이슈를 생성해 드릴까요? 나중에 기술 부채 정리 시 참고하실 수 있도록 말입니다.
There was a problem hiding this comment.
🧩 Analysis chain
이슈를 생성해드리겠습니다.
@Jjiggu QueryDSL 의존성 중앙 관리 개선에 대한 이슈를 생성했습니다. 나중에 기술 부채 정리할 때 참고하실 수 있습니다.
🏁 Script executed:
#!/bin/bash
# Create GitHub issue for QueryDSL dependency management improvement
gh issue create \
--title "QueryDSL 의존성 중앙 관리 개선" \
--body "## 문제 설명
현재 여러 모듈에서 QueryDSL 의존성을 중복 선언하고 있어 버전 불일치 위험이 있습니다.
### 영향 받는 파일
- nowait-domain/domain-admin-rdb/build.gradle
- nowait-domain/domain-core-rdb/build.gradle
### 개선 필요 사항
1. 루트 build.gradle에서 QueryDSL 의존성 중앙 관리
2. 서브모듈에서 중복 의존성 제거
3. 불필요한 annotationProcessor 선언 정리
### 관련 PR 및 코멘트
- PR: https://github.com/GTable/NoWait_BE_MultiModule/pull/109
- 코멘트: https://github.com/GTable/NoWait_BE_MultiModule/pull/109#discussion_r2210094211
- 요청자: @Jjiggu" \
--assignee Jjiggu
Length of output: 594
@Jjiggu 이슈가 성공적으로 생성되었습니다!
생성된 이슈: #111
QueryDSL 의존성 중앙 관리 개선 사항이 이슈 #111로 등록되었으며, 담당자로 지정되었습니다. 추후 기술 부채 정리할 때 참고하시면 됩니다.
| public RankingEntry(Long storeId, Integer totalSales, Long currentRank, Integer delta) { | ||
| this.storeId = storeId; | ||
| this.totalSales = totalSales; | ||
| this.currentRank = currentRank; | ||
| this.delta = delta; | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
생성자에서 필수 필드의 null 값 검증을 추가하세요.
랭킹 데이터의 핵심 필드들에 대한 null 검증이 필요합니다. 특히 storeId와 currentRank는 필수값이어야 합니다.
public RankingEntry(Long storeId, Integer totalSales, Long currentRank, Integer delta) {
+ if (storeId == null) {
+ throw new IllegalArgumentException("storeId cannot be null");
+ }
+ if (currentRank == null) {
+ throw new IllegalArgumentException("currentRank cannot be null");
+ }
this.storeId = storeId;
this.totalSales = totalSales;
this.currentRank = currentRank;
this.delta = delta;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public RankingEntry(Long storeId, Integer totalSales, Long currentRank, Integer delta) { | |
| this.storeId = storeId; | |
| this.totalSales = totalSales; | |
| this.currentRank = currentRank; | |
| this.delta = delta; | |
| } | |
| public RankingEntry(Long storeId, Integer totalSales, Long currentRank, Integer delta) { | |
| if (storeId == null) { | |
| throw new IllegalArgumentException("storeId cannot be null"); | |
| } | |
| if (currentRank == null) { | |
| throw new IllegalArgumentException("currentRank cannot be null"); | |
| } | |
| this.storeId = storeId; | |
| this.totalSales = totalSales; | |
| this.currentRank = currentRank; | |
| this.delta = delta; | |
| } |
🤖 Prompt for AI Agents
In
nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/dto/RankingEntry.java
around lines 12 to 17, the constructor lacks null checks for essential fields
storeId and currentRank. Add explicit null validation for these fields at the
start of the constructor and throw an appropriate exception if they are null to
ensure these required values are always provided.
There was a problem hiding this comment.
@Jjiggu 알겠습니다. 현재 PR에서는 해당 개선사항을 나중에 처리하시는 것으로 이해했습니다.
이 개선사항을 추적하기 위해 후속 이슈를 생성해드릴까요? RankingEntry 생성자의 null 검증 추가에 대한 내용으로 이슈를 만들어두시면 나중에 작업하실 때 도움이 될 것 같습니다.
| public StoreInfo(Long storeId, String storeName, Long departmentId, String departmentName) { | ||
| this.storeId = storeId; | ||
| this.storeName = storeName; | ||
| this.departmentId = departmentId; | ||
| this.departmentName = departmentName; | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
필수 필드에 대한 null 검증을 추가하세요.
매장 정보는 중요한 데이터이므로 필수 필드들에 대한 null 검증이 필요합니다.
public StoreInfo(Long storeId, String storeName, Long departmentId, String departmentName) {
+ if (storeId == null) {
+ throw new IllegalArgumentException("storeId cannot be null");
+ }
+ if (storeName == null || storeName.trim().isEmpty()) {
+ throw new IllegalArgumentException("storeName cannot be null or empty");
+ }
this.storeId = storeId;
this.storeName = storeName;
this.departmentId = departmentId;
this.departmentName = departmentName;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public StoreInfo(Long storeId, String storeName, Long departmentId, String departmentName) { | |
| this.storeId = storeId; | |
| this.storeName = storeName; | |
| this.departmentId = departmentId; | |
| this.departmentName = departmentName; | |
| } | |
| public StoreInfo(Long storeId, String storeName, Long departmentId, String departmentName) { | |
| if (storeId == null) { | |
| throw new IllegalArgumentException("storeId cannot be null"); | |
| } | |
| if (storeName == null || storeName.trim().isEmpty()) { | |
| throw new IllegalArgumentException("storeName cannot be null or empty"); | |
| } | |
| this.storeId = storeId; | |
| this.storeName = storeName; | |
| this.departmentId = departmentId; | |
| this.departmentName = departmentName; | |
| } |
🤖 Prompt for AI Agents
In
nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/StoreInfo.java
between lines 12 and 17, the constructor lacks null checks for essential fields.
Add null validation for storeId, storeName, departmentId, and departmentName at
the start of the constructor, throwing IllegalArgumentException if any are null
to ensure these critical fields are always provided.
There was a problem hiding this comment.
@Jjiggu 네, 이해했습니다. 현재 PR의 범위를 벗어나는 개선사항이니 나중에 처리하는 것이 좋겠습니다.
추후 작업을 위해 GitHub 이슈로 등록해 드릴까요? 이렇게 하면 나중에 null 검증 추가 작업을 놓치지 않을 수 있습니다.
| public StoreRankingDto(Long storeId, String storeName, Long departmentId, String departmentName, Integer totalSales, | ||
| Long currentRank, Integer delta) { | ||
| this.storeId = storeId; | ||
| this.storeName = storeName; | ||
| this.departmentId = departmentId; | ||
| this.departmentName = departmentName; | ||
| this.totalSales = totalSales; | ||
| this.currentRank = currentRank; | ||
| this.delta = delta; | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
생성자에 null 체크 및 검증 로직 추가를 권장합니다.
현재 생성자에서 매개변수에 대한 null 체크나 유효성 검증이 없어 런타임 오류가 발생할 수 있습니다.
public StoreRankingDto(Long storeId, String storeName, Long departmentId, String departmentName, Integer totalSales,
Long currentRank, Integer delta) {
+ Objects.requireNonNull(storeId, "storeId는 null일 수 없습니다");
+ Objects.requireNonNull(storeName, "storeName은 null일 수 없습니다");
+ Objects.requireNonNull(departmentId, "departmentId는 null일 수 없습니다");
+ Objects.requireNonNull(departmentName, "departmentName은 null일 수 없습니다");
+ Objects.requireNonNull(totalSales, "totalSales는 null일 수 없습니다");
+ Objects.requireNonNull(currentRank, "currentRank는 null일 수 없습니다");
+ Objects.requireNonNull(delta, "delta는 null일 수 없습니다");
this.storeId = storeId;
this.storeName = storeName;
this.departmentId = departmentId;
this.departmentName = departmentName;
this.totalSales = totalSales;
this.currentRank = currentRank;
this.delta = delta;
}추가로 java.util.Objects import가 필요합니다:
+import java.util.Objects;🤖 Prompt for AI Agents
In
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/dto/StoreRankingDto.java
around lines 15 to 24, the constructor lacks null checks and validation for its
parameters, which can lead to runtime errors. Add null checks using
java.util.Objects.requireNonNull for parameters that must not be null, and
include any necessary validation logic to ensure data integrity. Also, import
java.util.Objects to support these checks.
There was a problem hiding this comment.
@Jjiggu 네, 이해했습니다. 현재 PR 범위를 벗어난 개선사항으로 추후 해결하실 계획이시군요.
혹시 이 null 체크 및 검증 로직 추가 작업을 추적하기 위해 새로운 이슈를 생성하시겠습니까? 추후 작업 시 참조하실 수 있도록 도와드릴 수 있습니다.
There was a problem hiding this comment.
@Jjiggu 알겠습니다! 현재 PR의 핵심 기능에 집중하시는 것이 좋겠네요.
추후 이 개선사항을 추적하기 위해 별도 이슈를 생성해드릴까요? 아니면 나중에 직접 처리하실 예정인가요?
🐰
/| /|
( ✌️ ✌️ )
) (
( () )
( ( ) ) )
())
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Create GitHub issue for StoreRankingDto null check improvement
gh issue create \
--title "StoreRankingDto 생성자에 null 체크 및 검증 로직 추가" \
--body "## 개요
StoreRankingDto 생성자에서 매개변수에 대한 null 체크나 유효성 검증이 없어 런타임 오류가 발생할 수 있습니다.
## 상세 설명
현재 StoreRankingDto 클래스의 생성자는 다음 매개변수들에 대한 null 체크가 없습니다:
- storeId
- storeName
- departmentId
- departmentName
- totalSales
- currentRank
- delta
## 해결 방안
java.util.Objects.requireNonNull을 사용하여 각 매개변수에 대한 null 체크를 추가하고, 필요한 유효성 검증 로직을 구현해야 합니다.
## 관련 링크
- PR: https://github.com/GTable/NoWait_BE_MultiModule/pull/109
- Comment: https://github.com/GTable/NoWait_BE_MultiModule/pull/109#discussion_r2210094285
## 요청자
@Jjiggu" \
--assignee "Jjiggu"
Length of output: 668
@Jjiggu 이슈가 성공적으로 생성되었습니다!
이슈 #110: StoreRankingDto 생성자에 null 체크 및 검증 로직 추가
🔗 #110
이슈에는 현재 PR과 코멘트 링크가 포함되어 있어 나중에 작업하실 때 참조하실 수 있습니다. 담당자로 설정해드렸습니다.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java (1)
72-85: Redis 키 교체 작업의 원자성 문제가 여전히 존재합니다.이전 리뷰에서 지적된 Redis 키 교체의 원자성 문제가 해결되지 않았습니다. 현재 구현에서는 여전히 race condition이 발생할 수 있습니다.
Redis 파이프라인이나 트랜잭션을 사용하여 원자적으로 처리해야 합니다:
private void rotateKeys(String currentKey, String previousKey, String nextKey) { try { - if (redis.hasKey(currentKey)) { - redis.rename(currentKey, previousKey); - } - if (redis.hasKey(nextKey)) { - redis.rename(nextKey, currentKey); - } + redis.executePipelined(connection -> { + // 이전 키 삭제 + if (redis.hasKey(previousKey)) { + redis.delete(previousKey); + } + // 현재 키를 이전 키로 이동 + if (redis.hasKey(currentKey)) { + redis.rename(currentKey, previousKey); + } + // 다음 키를 현재 키로 이동 + if (redis.hasKey(nextKey)) { + redis.rename(nextKey, currentKey); + } + return null; + }); log.info("Keys rotated: current -> {}, previous -> {}, next -> {}", currentKey, previousKey, nextKey); } catch (Exception e) { log.error("Redis 키 교체 중 오류 발생", e); throw new RuntimeException("랭킹 데이터 갱신 실패", e); } }
🧹 Nitpick comments (2)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java (2)
30-34: 초기화 시점에서 랭킹 갱신 실행을 재고해야 합니다.애플리케이션 시작 시점에
refresh()메서드를 호출하면 DB 조회와 Redis 작업으로 인해 시작 시간이 지연될 수 있습니다.@PostConstruct public void init() { - // 초기화 작업 - refresh(); + log.info("RankingRefreshScheduler 초기화 완료"); + // 필요시 별도 스레드에서 초기 갱신 수행 + // CompletableFuture.runAsync(this::refresh); }
64-66: 성능 최적화를 위한 배치 처리 고려가 필요합니다.현재 각 매장별로 개별적으로 Redis에 추가하고 있는데, 대량의 데이터가 있을 경우 성능 이슈가 발생할 수 있습니다.
-salesList.forEach(s -> - rankingQueryRepository.addToRanking(nextKey, s.getStoreId(), s.getTotalSales()) -); +// 배치 처리로 성능 개선 +Map<String, Double> rankingData = salesList.stream() + .collect(Collectors.toMap( + s -> s.getStoreId().toString(), + StoreSales::getTotalSales + )); +rankingQueryRepository.addBatchToRanking(nextKey, rankingData);
RankingQueryRepository에 배치 처리 메서드 추가를 고려하세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java(1 hunks)nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/StoreInfo.java(1 hunks)nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/repository/RankingQueryRepositoryImpl.java(1 hunks)nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/RankingQueryServiceImpl.java(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/service/RankingQueryServiceImpl.java
- nowait-domain/domain-admin-rdb/src/main/java/com/nowait/domainadminrdb/statistic/dto/StoreInfo.java
- nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/repository/RankingQueryRepositoryImpl.java
🔇 Additional comments (2)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java (2)
36-46: 예외 처리가 적절히 개선되었습니다.이전 리뷰에서 지적된 예외 처리 문제가 잘 해결되었습니다. try-catch 블록을 통해 예외를 적절히 처리하고 있습니다.
59-62: 빈 데이터 처리 로직이 개선되었습니다.매출 데이터가 없을 때 적절한 경고 로그와 함께 early return하는 로직이 잘 구현되어 있습니다.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java (2)
42-48: 예외 처리가 개선되었으나 더 구체적인 처리가 필요합니다.전체적인 예외 처리 구조는 개선되었지만, 다양한 예외 유형에 대한 구체적인 처리가 부족합니다.
try { doRefresh(); } catch (Exception e) { log.error("랭킹 데이터 갱신 중 오류 발생", e); - // 예외 발생 시 알림 또는 로깅 처리 + // 데이터베이스 연결 오류와 Redis 연결 오류를 구분하여 처리 + if (e.getCause() instanceof org.springframework.dao.DataAccessException) { + log.error("데이터베이스 연결 오류로 인한 랭킹 갱신 실패", e); + } else if (e.getCause() instanceof org.springframework.data.redis.RedisConnectionFailureException) { + log.error("Redis 연결 오류로 인한 랭킹 갱신 실패", e); + } }
74-96: Redis 키 교체 로직의 원자성이 개선되었습니다.이전 리뷰에서 지적된 원자성 문제가
RedisCallback을 사용하여 해결되었습니다. 하지만 여전히 개선할 점이 있습니다.키 존재 여부 확인과 작업을 더 효율적으로 처리할 수 있습니다:
redis.execute((RedisCallback<Object>)connection -> { - // 현재 스냅샷 키를 이전 스냅샷 키로 이동하고, 다음 스냅샷 키를 현재 스냅샷 키로 이동 - if (redis.hasKey(previousKey)) { - redis.delete(previousKey); - } - // 현재 스냅샷 키를 다음 스냅샷 키로 이동 - if (redis.hasKey(currentKey)) { - redis.rename(currentKey, previousKey); - } - // 다음 스냅샷 키가 존재하면 현재 스냅샷 키로 이동 - if (redis.hasKey(nextKey)) { - redis.rename(nextKey, currentKey); - } + // 더 효율적인 키 교체 로직 + redis.delete(previousKey); // 이전 키 삭제 (존재하지 않아도 오류 없음) + + if (redis.hasKey(currentKey)) { + redis.rename(currentKey, previousKey); + } + if (redis.hasKey(nextKey)) { + redis.rename(nextKey, currentKey); + } return null; });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java(1 hunks)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- nowait-app-admin-api/src/main/java/com/nowait/ApiAdminApplication.java
🔇 Additional comments (3)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/statistic/scheduler/RankingRefreshScheduler.java (3)
59-64: 빈 데이터 처리 로직이 개선되었습니다.빈 매출 데이터에 대한 적절한 처리와 로깅이 구현되어 있어 안정성이 향상되었습니다.
66-68: 람다 표현식 사용이 적절합니다.
forEach와 람다를 사용한 코드가 간결하고 읽기 쉽습니다.
32-36: @PostConstruct 시점의 refresh() 호출은 안전합니다.검토 결과,
@PostConstruct는 스프링이 의존성 주입을 완료한 후 호출되므로
StatisticCustomRepository,RankingQueryRepository,RedisTemplate등의 빈이 모두 준비된 상태에서refresh()가 실행됩니다.
별도의@DependsOn또는@Order설정 없이도 현재 구현대로 유지해도 무방합니다.
작업 요약
Issue Link
#108
문제점 및 어려움
해결 방안
Reference
Summary by CodeRabbit
신규 기능
버그 수정