Skip to content

Conversation

@yeonsu00
Copy link
Collaborator

@yeonsu00 yeonsu00 commented Jan 1, 2026

๐Ÿ“Œ Summary

  • Spring Batch ๊ตฌํ˜„ (commerce-batch)
    • ํŒŒ๋ผ๋ฏธํ„ฐ ๊ธฐ๋ฐ˜ Job: rankingType(WEEKLY/MONTHLY)๊ณผ targetDate(yyyyMMdd)๋ฅผ JobParameters๋กœ ๋ฐ›์•„ ํ•˜๋‚˜์˜ Job์œผ๋กœ ์ฃผ๊ฐ„/์›”๊ฐ„ ๋žญํ‚น์„ ์ฒ˜๋ฆฌ
    • rankingStep
      • ์ „๋‚  product_metrics ๋ฐ์ดํ„ฐ๋ฅผ ์ฒญํฌ ๋‹จ์œ„(100๊ฑด)๋กœ ์ฝ์–ด ์ ์ˆ˜ ๊ณ„์‚ฐ ํ›„ MV ํ…Œ์ด๋ธ”์— UPSERT (ranking=null)
    • rankingCalculationStep
      • MV ํ…Œ์ด๋ธ”์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•ด score ๊ธฐ์ค€ ์ •๋ ฌ ํ›„ TOP 100 ์ถ”์ถœ ๋ฐ ranking ์—…๋ฐ์ดํŠธ
    • ์Šค์ผ€์ค„๋Ÿฌ: ๋งค์ผ ์ƒˆ๋ฒฝ 3์‹œ์— ์ฃผ๊ฐ„ ๋žญํ‚น, 3์‹œ 30๋ถ„์— ์›”๊ฐ„ ๋žญํ‚น ๋ฐฐ์น˜ ์‹คํ–‰
    • Materialized View: mv_product_rank_weekly, mv_product_rank_monthly ํ…Œ์ด๋ธ”์— ์ฃผ๊ฐ„/์›”๊ฐ„ TOP 100 ๋žญํ‚น ์ €์žฅ
  • Ranking API ๊ตฌํ˜„ (commerce-api)
    • ํ†ตํ•ฉ API: GET /api/v1/rankings?date=yyyyMMdd&type=DAILY|WEEKLY|MONTHLY&page=1&size=20
    • ๋ฐ์ดํ„ฐ ์†Œ์Šค ๋ถ„๋ฆฌ:
      • ์ผ๊ฐ„ ๋žญํ‚น: Redis์—์„œ ์กฐํšŒ
      • ์ฃผ๊ฐ„/์›”๊ฐ„ ๋žญํ‚น: ๊ฐ๊ฐ์˜ MV ํ…Œ์ด๋ธ”์—์„œ ์กฐํšŒ
      • ํŽ˜์ด์ง• ์ง€์›

๐Ÿ’ฌ Review Points

1. ๋ฐฐ์น˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ API ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐ„์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ณต์œ  ๋ฐฉ์‹

๋ฐฐ์น˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ commerce-batch๋กœ ๋”ฐ๋กœ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.
commerce-batch์—์„œ MV ํ…Œ์ด๋ธ”์— ๋žญํ‚น์„ ๊ณ„์‚ฐํ•ด์„œ ์ €์žฅํ•˜๊ณ , API ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜(commerce-api)์€ MV ํ…Œ์ด๋ธ”์—์„œ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
์ด๋ ‡๊ฒŒ commerce-batch์™€ commerce-api์—์„œ ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๊ณต์œ ํ•˜๊ณ  ์žˆ๋Š”๋ฐ ์ด๊ฑธ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•ด์•ผ ํ• ์ง€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค.

ํ˜„์žฌ๋Š” ๊ฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— (๊ฐ™์€ ํ…Œ์ด๋ธ”์„ ๊ฐ€๋ฆฌํ‚ค์ง€๋งŒ) ๋ณ„๋„ ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค๋ฅผ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.
modules/jpa ๋ชจ๋“ˆ์˜ ๊ณตํ†ต ์„ค์ •์œผ๋กœ ํ•˜๋‚˜์˜ ํด๋ž˜์Šค๋งŒ ๊ตฌํ˜„ํ•ด์•ผ ํ• ๊นŒ์š”?


โœ… Checklist

๐Ÿงฑ Spring Batch

  • Spring Batch Job ์„ ์ž‘์„ฑํ•˜๊ณ , ํŒŒ๋ผ๋ฏธํ„ฐ ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์ž‘์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.
  • Chunk Oriented Processing (Reader/Processor/Writer or Tasklet) ๊ธฐ๋ฐ˜์˜ ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ๋ฅผ ๊ตฌํ˜„ํ–ˆ๋‹ค.
  • ์ง‘๊ณ„ ๊ฒฐ๊ณผ๋ฅผ ์ €์žฅํ•  Materialized View ์˜ ๊ตฌ์กฐ๋ฅผ ์„ค๊ณ„ํ•˜๊ณ  ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ ์žฌํ–ˆ๋‹ค.

๐Ÿงฉ Ranking API

  • API ๊ฐ€ ์ผ๊ฐ„, ์ฃผ๊ฐ„, ์›”๊ฐ„ ๋žญํ‚น์„ ์ œ๊ณตํ•˜๋ฉฐ ์กฐํšŒํ•ด์•ผ ํ•˜๋Š” ํ˜•ํƒœ์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋žญํ‚น์„ ์ œ๊ณตํ•œ๋‹ค.

Summary by CodeRabbit

Release Notes

  • New Features

    • ์ƒํ’ˆ ๋žญํ‚น ์กฐํšŒ ๊ธฐ๋Šฅ ํ™•๋Œ€: ์ผ๊ฐ„ ๋žญํ‚น์—์„œ ์ฃผ๊ฐ„, ์›”๊ฐ„ ๋žญํ‚น์œผ๋กœ ํ™•์žฅ
    • ๋žญํ‚น ์กฐํšŒ API์— ๋žญํ‚น ํƒ€์ž… ํŒŒ๋ผ๋ฏธํ„ฐ ์ถ”๊ฐ€ (์ผ๊ฐ„/์ฃผ๊ฐ„/์›”๊ฐ„ ์„ ํƒ ๊ฐ€๋Šฅ)
    • ๋žญํ‚น ์กฐํšŒ ์‘๋‹ต์— ์ƒํ’ˆ ๊ฐ€๊ฒฉ, ์ข‹์•„์š” ์ˆ˜, ์ˆœ์œ„ ์ •๋ณด ์ถ”๊ฐ€
    • ์ž๋™ํ™”๋œ ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ๋ฅผ ํ†ตํ•œ ์ฃผ๊ฐ„/์›”๊ฐ„ ๋žญํ‚น ๊ณ„์‚ฐ ์‹œ์Šคํ…œ ๋„์ž…
  • Infrastructure

    • ์ฃผ๊ฐ„/์›”๊ฐ„ ๋žญํ‚น ๋ฐ์ดํ„ฐ๋ฅผ ์œ„ํ•œ ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ ์ถ”๊ฐ€
    • ๋ฉ”ํŠธ๋ฆญ์Šค ์ถ”์  ๊ธฐ๋Šฅ์˜ ๋‚ ์งœ ๊ธฐ๋ฐ˜ ๋ฒ”์œ„ ์ง€์ • ๊ฐœ์„ 

โœ๏ธ Tip: You can customize this high-level summary in your review settings.

yeonsu00 and others added 13 commits December 30, 2025 19:19
[volume-10] Collect, Stack, Zip
@coderabbitai
Copy link

coderabbitai bot commented Jan 1, 2026

Walkthrough

์ด PR์€ ์ƒํ’ˆ ๋žญํ‚น ๊ธฐ๋Šฅ์„ ์ผ์ผ(DAILY)์—์„œ ์ฃผ๊ฐ„(WEEKLY), ์›”๊ฐ„(MONTHLY)์œผ๋กœ ํ™•์žฅํ•ฉ๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šด commerce-batch ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ถ”๊ฐ€ํ•˜๊ณ , ๋ฐฐ์น˜ ์ž‘์—…์œผ๋กœ ์ฃผ๊ฐ„/์›”๊ฐ„ ๋žญํ‚น์„ ๊ณ„์‚ฐ ๋ฐ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. API, ๋„๋ฉ”์ธ ๋ชจ๋ธ, ์ €์žฅ์†Œ, ์„œ๋น„์Šค ๊ณ„์ธต์ด ๋‹ค์ค‘ ์‹œ๊ฐ„ ๋ฒ”์œ„๋ฅผ ์ง€์›ํ•˜๋„๋ก ๊ฐœ์„ ๋ฉ๋‹ˆ๋‹ค.

Changes

์ฝ”ํ˜ธํŠธ / ํŒŒ์ผ ๋ณ€๊ฒฝ ์‚ฌํ•ญ
๋žญํ‚น ์ปค๋งจ๋“œ ๋ฐ ํƒ€์ž… ์ •์˜
apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingCommand.java
GetDailyRankingCommand โ†’ GetRankingCommand (date, type, page, size ํ•„๋“œ๋กœ ๋ณ€๊ฒฝ), ์ƒˆ๋กœ์šด RankingType enum (DAILY, WEEKLY, MONTHLY) ์ถ”๊ฐ€
๋žญํ‚น Facade ๋ฐ ์„œ๋น„์Šค
apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingFacade.java, apps/commerce-api/src/main/java/com/loopers/domain/ranking/RankingService.java
getDailyRanking โ†’ getRanking ๋ฉ”์„œ๋“œ ๋ณ€๊ฒฝ, getWeeklyRanking, getMonthlyRanking ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€, RankingType์— ๋”ฐ๋ฅธ ๋ถ„๊ธฐ ์ฒ˜๋ฆฌ ๋ฐ ์—๋Ÿฌ ํ•ธ๋“ค๋ง ์ถ”๊ฐ€
์ฃผ๊ฐ„ ๋žญํ‚น ์—”ํ‹ฐํ‹ฐ ๋ฐ ์ €์žฅ์†Œ
apps/commerce-api/src/main/java/com/loopers/domain/ranking/MvProductRankWeekly.java, apps/commerce-api/src/main/java/com/loopers/domain/ranking/MvProductRankWeeklyRepository.java, apps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankWeeklyJpaRepository.java, apps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankWeeklyRepositoryImpl.java
์ฃผ๊ฐ„ ๋žญํ‚น ๋ฐ์ดํ„ฐ ์ €์žฅ ๋ฐ ์กฐํšŒ๋ฅผ ์œ„ํ•œ ์ƒˆ๋กœ์šด ์—”ํ‹ฐํ‹ฐ, ๋„๋ฉ”์ธ ์ €์žฅ์†Œ ์ธํ„ฐํŽ˜์ด์Šค, JPA ์ €์žฅ์†Œ, ๊ตฌํ˜„์ฒด ์ถ”๊ฐ€
์›”๊ฐ„ ๋žญํ‚น ์—”ํ‹ฐํ‹ฐ ๋ฐ ์ €์žฅ์†Œ
apps/commerce-api/src/main/java/com/loopers/domain/ranking/MvProductRankMonthly.java, apps/commerce-api/src/main/java/com/loopers/domain/ranking/MvProductRankMonthlyRepository.java, apps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyJpaRepository.java, apps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyRepositoryImpl.java
์›”๊ฐ„ ๋žญํ‚น ๋ฐ์ดํ„ฐ ์ €์žฅ ๋ฐ ์กฐํšŒ๋ฅผ ์œ„ํ•œ ์ƒˆ๋กœ์šด ์—”ํ‹ฐํ‹ฐ, ๋„๋ฉ”์ธ ์ €์žฅ์†Œ ์ธํ„ฐํŽ˜์ด์Šค, JPA ์ €์žฅ์†Œ, ๊ตฌํ˜„์ฒด ์ถ”๊ฐ€
๋žญํ‚น ๊ธฐ๊ฐ„ ์œ ํ‹ธ๋ฆฌํ‹ฐ
apps/commerce-api/src/main/java/com/loopers/domain/ranking/RankingPeriod.java
์ฃผ๊ฐ„/์›”๊ฐ„ ๊ธฐ๊ฐ„ ๋ฒ”์œ„๋ฅผ ๊ณ„์‚ฐํ•˜๋Š” ofWeek, ofMonth ์ •์  ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€
API ์ŠคํŽ™ ๋ฐ ์ปจํŠธ๋กค๋Ÿฌ
apps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1ApiSpec.java, apps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1Controller.java
getDailyRanking โ†’ getRanking ๋ฉ”์„œ๋“œ๋ช… ๋ณ€๊ฒฝ, type ํŒŒ๋ผ๋ฏธํ„ฐ ์ถ”๊ฐ€, ๋ฐ˜ํ™˜ ํƒ€์ž…์„ DailyRankingListResponse โ†’ RankingListResponse๋กœ ๋ณ€๊ฒฝ, RankingType ํŒŒ์‹ฑ ๋ฐ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ์ถ”๊ฐ€
API DTO ๋ชจ๋ธ
apps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1Dto.java
DailyRankingListResponse โ†’ RankingListResponse ์ด๋ฆ„ ๋ณ€๊ฒฝ, DailyRankingItem โ†’ RankingItem ์ด๋ฆ„ ๋ณ€๊ฒฝ, price, likeCount, rank ํ•„๋“œ ์ถ”๊ฐ€
API ์„ค์ • ๋ฐ ํ…Œ์ŠคํŠธ
apps/commerce-api/src/main/resources/application.yml, apps/commerce-api/src/test/java/com/loopers/application/ranking/RankingFacadeIntegrationTest.java, apps/commerce-api/src/test/java/com/loopers/domain/ranking/RankingServiceIntegrationTest.java, apps/commerce-api/src/test/java/com/loopers/interfaces/api/ranking/RankingV1ApiE2ETest.java
kafka.yml ์„ค์ • import ์ถ”๊ฐ€, ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ƒˆ๋กœ์šด command/DTO ํƒ€์ž…์œผ๋กœ ์—…๋ฐ์ดํŠธ, ์ฃผ๊ฐ„/์›”๊ฐ„ ๋žญํ‚น ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ์ถ”๊ฐ€
๋ฐฐ์น˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ•ต์‹ฌ
apps/commerce-batch/build.gradle.kts, apps/commerce-batch/src/main/java/com/loopers/CommerceBatchApplication.java, apps/commerce-batch/src/main/resources/application.yml, apps/commerce-batch/src/test/java/com/loopers/CommerceBatchApplicationTests.java
์ƒˆ๋กœ์šด commerce-batch Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒ์„ฑ, ์˜์กด์„ฑ ์„ค์ •, ๋ฉ€ํ‹ฐ ํ”„๋กœํ•„ ์„ค์ • ์ถ”๊ฐ€, ๊ธฐ๋ณธ ์ปจํ…์ŠคํŠธ ๋กœ๋“œ ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€
๋ฐฐ์น˜ ๋žญํ‚น ๊ณ„์‚ฐ ํŒŒ์ดํ”„๋ผ์ธ
apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingJobConfig.java, apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingProcessor.java, apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingWriter.java, apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingCalculationTasklet.java, apps/commerce-batch/src/main/java/com/loopers/application/ranking/JobParameterUtils.java
Spring Batch ์ž‘์—… ์„ค์ •(2๋‹จ๊ณ„: ๋ฉ”ํŠธ๋ฆญ ์ฒ˜๋ฆฌ, ๋žญํ‚น ๊ณ„์‚ฐ), ํ”„๋กœ์„ธ์„œ(๋ฉ”ํŠธ๋ฆญโ†’์ง‘๊ณ„), ๋ผ์ดํ„ฐ(์ฃผ๊ฐ„/์›”๊ฐ„ ์ €์žฅ), Tasklet(์ˆœ์œ„ ๊ณ„์‚ฐ), ์ž‘์—… ํŒŒ๋ผ๋ฏธํ„ฐ ์œ ํ‹ธ ์ถ”๊ฐ€
๋ฐฐ์น˜ ๋ฉ”ํŠธ๋ฆญ ์ฒ˜๋ฆฌ
apps/commerce-batch/src/main/java/com/loopers/application/ranking/ProductMetricsItemReader.java, apps/commerce-batch/src/main/java/com/loopers/domain/metrics/ProductMetrics.java, apps/commerce-batch/src/main/java/com/loopers/domain/metrics/ProductMetricsRepository.java, apps/commerce-batch/src/main/java/com/loopers/domain/metrics/ProductMetricsService.java, apps/commerce-batch/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsJpaRepository.java, apps/commerce-batch/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsRepositoryImpl.java
๋ฐฐ์น˜ ItemReader, ๋ฉ”ํŠธ๋ฆญ ์—”ํ‹ฐํ‹ฐ(๋ฉ”ํŠธ๋ฆญ ๋‚ ์งœ ์ถ”๊ฐ€), ๋„๋ฉ”์ธ/์ธํ”„๋ผ ์ €์žฅ์†Œ, ์„œ๋น„์Šค ๊ณ„์ธต ์ถ”๊ฐ€
๋ฐฐ์น˜ ๋žญํ‚น ๋„๋ฉ”์ธ ์„œ๋น„์Šค
apps/commerce-batch/src/main/java/com/loopers/domain/ranking/WeeklyRankingService.java, apps/commerce-batch/src/main/java/com/loopers/domain/ranking/MonthlyRankingService.java, apps/commerce-batch/src/main/java/com/loopers/domain/ranking/RankingCalculator.java, apps/commerce-batch/src/main/java/com/loopers/domain/ranking/RankingWeight.java, apps/commerce-batch/src/main/java/com/loopers/domain/ranking/RankingType.java, apps/commerce-batch/src/main/java/com/loopers/domain/ranking/RankingPeriod.java
์ฃผ๊ฐ„/์›”๊ฐ„ ๋žญํ‚น upsert ๋ฐ ๊ณ„์‚ฐ ๋กœ์ง, ์ ์ˆ˜ ๊ณ„์‚ฐ ์ „๋žต(๊ฐ€์ค‘์น˜ ๊ธฐ๋ฐ˜), ๋žญํ‚น ํƒ€์ž…/๊ธฐ๊ฐ„ ๊ฐ’ ๊ฐ์ฒด ์ถ”๊ฐ€
๋ฐฐ์น˜ ๋žญํ‚น ์ธํ”„๋ผ
apps/commerce-batch/src/main/java/com/loopers/infrastructure/ranking/MvProductRankWeeklyJpaRepository.java, apps/commerce-batch/src/main/java/com/loopers/infrastructure/ranking/MvProductRankWeeklyRepositoryImpl.java, apps/commerce-batch/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyJpaRepository.java, apps/commerce-batch/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyRepositoryImpl.java
์ฃผ๊ฐ„/์›”๊ฐ„ ๋žญํ‚น JPA ์ €์žฅ์†Œ ๋ฐ ๊ตฌํ˜„์ฒด(๊ธฐ๊ฐ„ ๋ฒ”์œ„๋กœ ์กฐํšŒ, ์ •๋ ฌ) ์ถ”๊ฐ€
๋ฐฐ์น˜ ์Šค์ผ€์ค„๋Ÿฌ
apps/commerce-batch/src/main/java/com/loopers/interfaces/scheduler/RankingBatchScheduler.java, apps/commerce-batch/src/main/java/com/loopers/batch/job/BatchConfig.java
์ฃผ๊ฐ„/์›”๊ฐ„ ๋žญํ‚น ๋ฐฐ์น˜ ์ž‘์—…์„ ๋งค์ผ ์ •ํ•ด์ง„ ์‹œ๊ฐ„์— ์‹คํ–‰ํ•˜๋Š” ์Šค์ผ€์ค„๋Ÿฌ, TaskExecutor ๋นˆ ์ถ”๊ฐ€
์ŠคํŠธ๋ฆฌ๋จธ ๋ฉ”ํŠธ๋ฆญ ๋™๊ธฐํ™”
apps/commerce-streamer/src/main/java/com/loopers/domain/metrics/ProductMetrics.java, apps/commerce-streamer/src/main/java/com/loopers/domain/metrics/ProductMetricsRepository.java, apps/commerce-streamer/src/main/java/com/loopers/domain/metrics/ProductMetricsService.java, apps/commerce-streamer/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsJpaRepository.java, apps/commerce-streamer/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsRepositoryImpl.java, apps/commerce-streamer/src/test/java/com/loopers/interfaces/consumer/MetricsConsumerIdempotencyTest.java
ProductMetrics์— metricsDate ํ•„๋“œ ์ถ”๊ฐ€, ๋ณตํ•ฉ ์œ ๋‹ˆํฌ ์ œ์•ฝ ์กฐ๊ฑด(productId, metricsDate), ์กฐํšŒ/์—…๋ฐ์ดํŠธ ๋ฉ”์„œ๋“œ ๋‚ ์งœ ํŒŒ๋ผ๋ฏธํ„ฐ ์ถ”๊ฐ€
Kafka ์„ค์ •
modules/kafka/src/main/java/com/loopers/config/kafka/KafkaConfig.java
String ํƒ€์ž… ์ „์šฉ KafkaTemplate ๋ฐ ProducerFactory ๋นˆ ์ถ”๊ฐ€
ํ”„๋กœ์ ํŠธ ์„ค์ •
settings.gradle.kts
commerce-batch ๋ชจ๋“ˆ์„ ๋ฉ€ํ‹ฐ ํ”„๋กœ์ ํŠธ ๋นŒ๋“œ์— ํฌํ•จ

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant API as RankingV1Controller
    participant Facade as RankingFacade
    participant Service as RankingService
    participant Repo as MvProductRank*Repository
    participant DB as Database

    Client->>API: GET /rankings?date=2025-01-15&type=WEEKLY&page=1&size=10
    API->>API: ํŒŒ๋ผ๋ฏธํ„ฐ ํŒŒ์‹ฑ (typeโ†’RankingType.WEEKLY)
    API->>API: GetRankingCommand ์ƒ์„ฑ
    API->>Facade: getRanking(command)
    
    alt RankingType.WEEKLY
        Facade->>Service: getWeeklyRanking(date, page, size)
        Service->>Repo: findByPeriodOrderByRankingAsc(startDate, endDate, page, size)
        Repo->>DB: ์ฟผ๋ฆฌ ์‹คํ–‰
        DB-->>Repo: List<MvProductRankWeekly>
        Repo-->>Service: ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜
    else RankingType.MONTHLY
        Facade->>Service: getMonthlyRanking(date, page, size)
        Service->>Repo: findByPeriodOrderByRankingAsc(startDate, endDate, page, size)
        Repo->>DB: ์ฟผ๋ฆฌ ์‹คํ–‰
        DB-->>Repo: List<MvProductRankMonthly>
        Repo-->>Service: ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜
    else Unsupported Type
        Facade->>API: CoreException (BAD_REQUEST)
    end
    
    Service-->>Facade: List<Ranking>
    Facade->>Facade: ์ƒํ’ˆ/๋ธŒ๋žœ๋“œ ์ •๋ณด ์กฐํšŒ ๋ฐ ๊ตฌ์„ฑ
    Facade-->>API: RankingInfo
    API->>API: RankingListResponse๋กœ ๋ณ€ํ™˜
    API-->>Client: ApiResponse<RankingListResponse>
Loading

Estimated code review effort

๐ŸŽฏ 5 (Critical) | โฑ๏ธ ~120 minutes

Possibly related PRs

  • [volume-9] Product Ranking with Redisย #216: ์ดˆ๊ธฐ RankingCommand, RankingFacade, RankingService๋ฅผ ๋„์ž…ํ•œ PR๋กœ, ํ˜„์žฌ PR์—์„œ ์ด๋“ค ํด๋ž˜์Šค๋ฅผ WEEKLY/MONTHLY ์ง€์›์œผ๋กœ ํ™•์žฅํ•ฉ๋‹ˆ๋‹ค.
  • [volume - 8] Decoupling with Kafka ย #192: commerce-api์˜ application.yml์— kafka.yml ์„ค์ • import๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๋ณ€๊ฒฝ๊ณผ ๋™์ผํ•ฉ๋‹ˆ๋‹ค.
  • [volume - 8] Decoupling with Kafka ย #205: ProductMetrics ์—”ํ‹ฐํ‹ฐ์™€ ์ €์žฅ์†Œ๋ฅผ ์ˆ˜์ •ํ•˜์—ฌ metricsDate ํ•„๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๋„๋ฉ”์ธ ๊ณ„์ธต ๋ณ€๊ฒฝ์ด ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค.

Suggested labels

enhancement, batch-processing, ranking-system

Poem

๐Ÿฐ ๋ฐฐ์น˜ ์ž‘์—…์ด ๋ฐค์ƒˆ ๋‹ฌ๋ ค๊ฐ€๊ณ ,
์ฃผ๊ฐ„๊ณผ ์›”๊ฐ„ ์ˆœ์œ„๋ฅผ ์„ธ์–ด์˜ฌ๋ ค,
๋ฉ”ํŠธ๋ฆญ๋“ค์ด ์ถค์„ ์ถ”๋ฉฐ ์ ์ˆ˜๋กœ ๋ณ€์‹ ํ•ด,
์ƒํ’ˆ ๋žญํ‚น์€ ์ด์ œ ์‹œ๊ฐ„์˜ ๊ฒฐ์ •์ž๊ฐ€ ๋˜์—ˆ๋„ค.
์„ธ ๊ฐ€์ง€ ์‹œ๊ฐ„, ํ•˜๋‚˜์˜ ์˜๊ด‘! ๐Ÿ†

Pre-merge checks and finishing touches

โŒ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage โš ๏ธ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check โ“ Inconclusive PR ์ œ๋ชฉ์ด ๋ณ€๊ฒฝ์‚ฌํ•ญ์˜ ํ•ต์‹ฌ์„ ๋ช…ํ™•ํ•˜๊ฒŒ ๋‚˜ํƒ€๋‚ด์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. 'Collect, Stack, Zip'์€ ์ถ”์ƒ์ ์ด๊ณ  ๊ตฌ์ฒด์ ์ธ ๊ธฐ๋Šฅ์„ ์„ค๋ช…ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ œ๋ชฉ์„ 'Spring Batch ๊ธฐ๋ฐ˜ ์ฃผ๊ฐ„/์›”๊ฐ„ ๋žญํ‚น ์ง‘๊ณ„ ๋ฐ ํ†ตํ•ฉ API ๊ตฌํ˜„' ๋˜๋Š” 'Implement weekly/monthly ranking batch job and unified ranking API'๋กœ ๋ณ€๊ฒฝํ•˜์„ธ์š”.
โœ… Passed checks (1 passed)
Check name Status Explanation
Description check โœ… Passed PR ์„ค๋ช…์ด ์š”๊ตฌ๋˜๋Š” ๋ชจ๋“  ํ•„์ˆ˜ ์„น์…˜์„ ํฌํ•จํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ์š”์•ฝ, ๋ฆฌ๋ทฐ ํฌ์ธํŠธ, ์ฒดํฌ๋ฆฌ์ŠคํŠธ๊ฐ€ ์ƒ์„ธํ•˜๊ฒŒ ์ž‘์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
โœจ Finishing touches
  • ๐Ÿ“ Generate docstrings

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

Comment @coderabbitai help to get the list of available commands and usage tips.

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: 13

Caution

Some comments are outside the diff and canโ€™t be posted inline due to platform limitations.

โš ๏ธ Outside diff range comments (3)
apps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1Dto.java (1)

20-20: ์ž ์žฌ์  NullPointerException

item.rank()๊ฐ€ null์„ ๋ฐ˜ํ™˜ํ•  ๊ฒฝ์šฐ intValue() ํ˜ธ์ถœ ์‹œ NPE๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. RankingInfo.Item์˜ rank ํ•„๋“œ๊ฐ€ ํ•ญ์ƒ non-null์ž„์ด ๋ณด์žฅ๋˜๋Š”์ง€ ํ™•์ธํ•˜๊ฑฐ๋‚˜, ๋ฐฉ์–ด์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜์„ธ์š”.

๐Ÿ”Ž ๋ฐฉ์–ด์  ์ฒ˜๋ฆฌ ์ œ์•ˆ
-                            item.rank().intValue()
+                            item.rank() != null ? item.rank().intValue() : 0
apps/commerce-streamer/src/main/java/com/loopers/domain/metrics/ProductMetricsService.java (2)

46-57: ๋™์‹œ์„ฑ ๋ฌธ์ œ: incrementLikeCount์™€ ๋™์ผํ•œ ํŒจํ„ด ์ ์šฉ ํ•„์š”

๋‘ ์Šค๋ ˆ๋“œ๊ฐ€ ๋™์‹œ์— ๊ฐ™์€ productId์— ๋Œ€ํ•ด ํ˜ธ์ถœํ•˜๋ฉด, ๋‘˜ ๋‹ค findByProductIdAndMetricsDate์—์„œ ๋นˆ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›์•„ ์ƒˆ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ €์žฅํ•˜๋ ค ์‹œ๋„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ unique constraint ์œ„๋ฐ˜์œผ๋กœ DataIntegrityViolationException์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

incrementLikeCount์ฒ˜๋Ÿผ ์›์ž์  ์—…๋ฐ์ดํŠธ ํ›„ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ํŒจํ„ด์„ ์ ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

๐Ÿ”Ž ๊ถŒ์žฅ ์ˆ˜์ •์•ˆ
 @Transactional
 public void incrementViewCount(Long productId) {
     LocalDate today = LocalDate.now();
-    ProductMetrics metrics = productMetricsRepository.findByProductIdAndMetricsDate(productId, today)
-            .orElseGet(() -> {
-                ProductMetrics newMetrics = ProductMetrics.create(productId, today);
-                productMetricsRepository.saveProductMetrics(newMetrics);
-                return newMetrics;
-            });
-    metrics.incrementViewCount();
-    productMetricsRepository.saveProductMetrics(metrics);
+    int updatedRows = productMetricsRepository.incrementViewCount(productId, today);
+
+    if (updatedRows == 0) {
+        try {
+            ProductMetrics newMetrics = ProductMetrics.create(productId, today);
+            newMetrics.incrementViewCount();
+            productMetricsRepository.saveProductMetrics(newMetrics);
+        } catch (DataIntegrityViolationException e) {
+            productMetricsRepository.incrementViewCount(productId, today);
+        }
+    }
 }

59-70: ๋™์‹œ์„ฑ ๋ฌธ์ œ: incrementViewCount์™€ ๋™์ผํ•œ ๋ฌธ์ œ

incrementViewCount์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋™์‹œ ํ˜ธ์ถœ ์‹œ unique constraint ์œ„๋ฐ˜์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋™์ผํ•œ ์›์ž์  ์—…๋ฐ์ดํŠธ ํŒจํ„ด์„ ์ ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿงน Nitpick comments (25)
apps/commerce-batch/src/main/java/com/loopers/CommerceBatchApplication.java (1)

14-18: ํƒ€์ž„์กด ์„ค์ • ๋ฐฉ์‹์— ๋Œ€ํ•œ ๊ณ ๋ ค์‚ฌํ•ญ

TimeZone.setDefault()๋Š” JVM ์ „์—ญ์— ์˜ํ–ฅ์„ ๋ฏธ์นฉ๋‹ˆ๋‹ค. ํ˜„์žฌ commerce-batch๊ฐ€ ๋…๋ฆฝ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ์‹คํ–‰๋˜๋ฏ€๋กœ ๋ฌธ์ œ์—†์ง€๋งŒ, ํ–ฅํ›„ ๋‹ค๋ฅธ ๋ชจ๋“ˆ๊ณผ ๋™์ผ JVM์—์„œ ์‹คํ–‰๋  ๊ฒฝ์šฐ ๋ถ€์ž‘์šฉ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋Œ€์•ˆ์œผ๋กœ application.yml์—์„œ spring.jackson.time-zone ์„ค์ •์ด๋‚˜, ํ•„์š”ํ•œ ๊ณณ์—์„œ ๋ช…์‹œ์ ์œผ๋กœ ZoneId.of("Asia/Seoul")์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹๋„ ๊ณ ๋ คํ•ด ๋ณด์„ธ์š”.

apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingCalculationTasklet.java (1)

35-43: ๋ช…์‹œ์ ์ธ ํƒ€์ž… ์ฒ˜๋ฆฌ ๊ถŒ์žฅ

ํ˜„์žฌ else ๋ถ„๊ธฐ๊ฐ€ WEEKLY๊ฐ€ ์•„๋‹Œ ๋ชจ๋“  ๊ฒฝ์šฐ๋ฅผ MONTHLY๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. RankingType ์—ด๊ฑฐํ˜•์ด ํ™•์žฅ๋  ๊ฒฝ์šฐ ์˜๋„์น˜ ์•Š์€ ๋™์ž‘์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

RankingWriter์™€ ๋™์ผํ•œ ํŒจํ„ด์ด์ง€๋งŒ, ํ–ฅํ›„ ์œ ์ง€๋ณด์ˆ˜๋ฅผ ์œ„ํ•ด switch ๋ฌธ์ด๋‚˜ ๋ช…์‹œ์  ์กฐ๊ฑด ์ฒ˜๋ฆฌ๋ฅผ ๊ณ ๋ คํ•ด ๋ณด์„ธ์š”.

๐Ÿ”Ž switch ๋ฌธ์„ ์‚ฌ์šฉํ•œ ์ œ์•ˆ
-        if (rankingType == RankingType.WEEKLY) {
-            period = RankingPeriod.ofWeek(targetDate);
-            weeklyRankingService.calculateAndUpdateRanking(period);
-            log.info("์ฃผ๊ฐ„ ๋žญํ‚น ๊ณ„์‚ฐ ์™„๋ฃŒ: targetDate={}, period={} ~ {}", targetDate, period.startDate(), period.endDate());
-        } else {
-            period = RankingPeriod.ofMonth(targetDate);
-            monthlyRankingService.calculateAndUpdateRanking(period);
-            log.info("์›”๊ฐ„ ๋žญํ‚น ๊ณ„์‚ฐ ์™„๋ฃŒ: targetDate={}, period={} ~ {}", targetDate, period.startDate(), period.endDate());
-        }
+        switch (rankingType) {
+            case WEEKLY -> {
+                period = RankingPeriod.ofWeek(targetDate);
+                weeklyRankingService.calculateAndUpdateRanking(period);
+                log.info("์ฃผ๊ฐ„ ๋žญํ‚น ๊ณ„์‚ฐ ์™„๋ฃŒ: targetDate={}, period={} ~ {}", targetDate, period.startDate(), period.endDate());
+            }
+            case MONTHLY -> {
+                period = RankingPeriod.ofMonth(targetDate);
+                monthlyRankingService.calculateAndUpdateRanking(period);
+                log.info("์›”๊ฐ„ ๋žญํ‚น ๊ณ„์‚ฐ ์™„๋ฃŒ: targetDate={}, period={} ~ {}", targetDate, period.startDate(), period.endDate());
+            }
+            default -> throw new IllegalArgumentException("์ง€์›ํ•˜์ง€ ์•Š๋Š” ๋žญํ‚น ํƒ€์ž…: " + rankingType);
+        }
apps/commerce-api/src/main/java/com/loopers/domain/ranking/MvProductRankWeeklyRepository.java (1)

6-8: ํŽ˜์ด์ง€๋„ค์ด์…˜ ๋ฐฉ์‹์„ Spring Data์˜ Pageable๋กœ ํ†ต์ผํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

ํ˜„์žฌ int page, int size ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ˆ˜๋™ ํŽ˜์ด์ง€๋„ค์ด์…˜์„ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํ•™์Šต ์ปจํ…์ŠคํŠธ์— ๋”ฐ๋ฅด๋ฉด ์ด ์ฝ”๋“œ๋ฒ ์ด์Šค์—์„œ๋Š” ๋„๋ฉ”์ธ ์ €์žฅ์†Œ์—์„œ Spring Data์˜ Pageable ํƒ€์ž… ์‚ฌ์šฉ์ด ํ—ˆ์šฉ๋˜๋ฉฐ, ์ด๋Š” ์ผ๊ด€์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.

Based on learnings, domain repositories in this codebase are allowed to use Spring Data's Pageable types.

๐Ÿ”Ž Pageable์„ ์‚ฌ์šฉํ•˜๋„๋ก ๋ฆฌํŒฉํ† ๋ง ์ œ์•ˆ
+import org.springframework.data.domain.Pageable;
+
 public interface MvProductRankWeeklyRepository {
-    List<MvProductRankWeekly> findByPeriodOrderByRankingAsc(LocalDate periodStartDate, LocalDate periodEndDate, int page, int size);
+    List<MvProductRankWeekly> findByPeriodOrderByRankingAsc(LocalDate periodStartDate, LocalDate periodEndDate, Pageable pageable);
 }
apps/commerce-api/src/main/java/com/loopers/domain/ranking/MvProductRankMonthlyRepository.java (1)

6-8: ํŽ˜์ด์ง€๋„ค์ด์…˜ ๋ฐฉ์‹์„ Spring Data์˜ Pageable๋กœ ํ†ต์ผํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

MvProductRankWeeklyRepository์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ์ˆ˜๋™ ํŽ˜์ด์ง€๋„ค์ด์…˜(int page, int size) ๋Œ€์‹  Spring Data์˜ Pageable์„ ์‚ฌ์šฉํ•˜์—ฌ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•˜์„ธ์š”.

Based on learnings, domain repositories in this codebase are allowed to use Spring Data's Pageable types.

๐Ÿ”Ž Pageable์„ ์‚ฌ์šฉํ•˜๋„๋ก ๋ฆฌํŒฉํ† ๋ง ์ œ์•ˆ
+import org.springframework.data.domain.Pageable;
+
 public interface MvProductRankMonthlyRepository {
-    List<MvProductRankMonthly> findByPeriodOrderByRankingAsc(LocalDate periodStartDate, LocalDate periodEndDate);
+    List<MvProductRankMonthly> findByPeriodOrderByRankingAsc(LocalDate periodStartDate, LocalDate periodEndDate, Pageable pageable);
 }
apps/commerce-batch/src/main/java/com/loopers/domain/metrics/ProductMetricsService.java (1)

15-17: ์ฝ๊ธฐ ์ „์šฉ ํŠธ๋žœ์žญ์…˜ ์ถ”๊ฐ€๋ฅผ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฝ๊ธฐ ์ž‘์—…์— @Transactional(readOnly = true) ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•˜๋ฉด ์ปค๋„ฅ์…˜ ํ’€ ์ตœ์ ํ™” ๋ฐ JDBC ๋“œ๋ผ์ด๋ฒ„์— ๋Œ€ํ•œ ์ฝ๊ธฐ ์ „์šฉ ํžŒํŠธ๋ฅผ ์ œ๊ณตํ•˜์—ฌ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ”Ž ์ œ์•ˆํ•˜๋Š” ์ˆ˜์ • ์‚ฌํ•ญ
+import org.springframework.transaction.annotation.Transactional;
+
 @Service
 @RequiredArgsConstructor
 public class ProductMetricsService {
 
     private final ProductMetricsRepository productMetricsRepository;
 
+    @Transactional(readOnly = true)
     public List<ProductMetrics> findByMetricsDate(LocalDate metricsDate) {
         return productMetricsRepository.findByMetricsDate(metricsDate);
     }
apps/commerce-batch/src/main/java/com/loopers/domain/ranking/RankingPeriod.java (1)

12-23: ์ž…๋ ฅ ํŒŒ๋ผ๋ฏธํ„ฐ null ๊ฒ€์ฆ์„ ๊ณ ๋ คํ•ด๋ณด์„ธ์š”.

ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ๋“ค์ด null date๋ฅผ ๋ฐ›์œผ๋ฉด NullPointerException์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ๋ฐฉ์–ด์  ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ์œ„ํ•ด null ๊ฒ€์ฆ ์ถ”๊ฐ€๋ฅผ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ”Ž ์ œ์•ˆํ•˜๋Š” ์ˆ˜์ •์•ˆ
+import java.util.Objects;
+
 public record RankingPeriod(
         LocalDate startDate,
         LocalDate endDate
 ) {
     public static RankingPeriod ofWeek(LocalDate date) {
+        Objects.requireNonNull(date, "date must not be null");
         LocalDate weekStart = date.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
         LocalDate weekEnd = date.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY));
         return new RankingPeriod(weekStart, weekEnd);
     }
 
     public static RankingPeriod ofMonth(LocalDate date) {
+        Objects.requireNonNull(date, "date must not be null");
         YearMonth yearMonth = YearMonth.from(date);
         LocalDate monthStart = yearMonth.atDay(1);
         LocalDate monthEnd = yearMonth.atEndOfMonth();
         return new RankingPeriod(monthStart, monthEnd);
     }
 }
apps/commerce-batch/src/main/java/com/loopers/application/ranking/ProductMetricsItemReader.java (1)

49-52: ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ๋ฉ”๋ชจ๋ฆฌ ๋กœ๋”ฉ ๊ณ ๋ ค

findByMetricsDate()๊ฐ€ ํ•ด๋‹น ๋‚ ์งœ์˜ ๋ชจ๋“  ProductMetrics๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์— ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๊ฐ€ ๋งŽ์•„์ง€๋ฉด OOM ์œ„ํ—˜์ด ์žˆ์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ ๋ฐ์ดํ„ฐ ๊ทœ๋ชจ๊ฐ€ ์ž‘๋‹ค๋ฉด ๋ฌธ์ œ์—†์ง€๋งŒ, ํ–ฅํ›„ JdbcPagingItemReader ๋˜๋Š” ์ปค์„œ ๊ธฐ๋ฐ˜ ๋ฆฌ๋”๋กœ ์ „ํ™˜์„ ๊ณ ๋ คํ•ด ์ฃผ์„ธ์š”.

apps/commerce-api/src/test/java/com/loopers/domain/ranking/RankingServiceIntegrationTest.java (1)

403-405: ํ—ฌํผ ๋ฉ”์„œ๋“œ ์ด๋ฆ„ ์ผ๊ด€์„ฑ ๊ฐœ์„  ๊ณ ๋ ค

getDailyRankingKey๋กœ ์ด๋ฆ„์ด ๋ณ€๊ฒฝ๋˜์—ˆ์œผ๋‚˜, ๋‹ค๋ฅธ ํ…Œ์ŠคํŠธ ํŒŒ์ผ๋“ค(RankingCacheServiceIntegrationTest, RankingFacadeIntegrationTest, RankingV1ApiE2ETest)์—์„œ๋Š” ์—ฌ์ „ํžˆ getRankingKey๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ „์ฒด์˜ ์ผ๊ด€์„ฑ์„ ์œ„ํ•ด ๋‹ค๋ฅธ ํŒŒ์ผ๋“ค๋„ ํ•จ๊ป˜ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด ์ฃผ์„ธ์š”.

apps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyRepositoryImpl.java (1)

19-23: ์ž…๋ ฅ ํŒŒ๋ผ๋ฏธํ„ฐ ์œ ํšจ์„ฑ ๊ฒ€์ฆ ์ถ”๊ฐ€ ๊ถŒ์žฅ

์ปจํŠธ๋กค๋Ÿฌ ๋ ˆ์ด์–ด์—์„œ page์™€ size ํŒŒ๋ผ๋ฏธํ„ฐ์— ๋Œ€ํ•œ ๋ช…์‹œ์  ๊ฒ€์ฆ์ด ์—†์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ Spring Data์˜ PageRequest.of()๊ฐ€ ์•”๋ฌต์ ์œผ๋กœ page < 0 ๋˜๋Š” size < 1์ผ ๋•Œ IllegalArgumentException์„ ๋ฐœ์ƒ์‹œํ‚ค์ง€๋งŒ, ์ปจํŠธ๋กค๋Ÿฌ์—์„œ @Min(1)๊ณผ @Positive ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•˜๋ฉด ์œ ํšจ์„ฑ ๊ฒ€์ฆ ๊ณ„์•ฝ์„ ๋ช…ํ™•ํžˆ ํ•˜๊ณ  ๋” ๋ช…ํ™•ํ•œ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingFacade.java (1)

28-39: if-else ์ฒด์ธ ๋Œ€์‹  switch ํ‘œํ˜„์‹ ์‚ฌ์šฉ ๊ถŒ์žฅ

ํ˜„์žฌ if-else ์ฒด์ธ์€ ๋™์ž‘ํ•˜์ง€๋งŒ, ์ƒˆ๋กœ์šด ๋žญํ‚น ํƒ€์ž…์ด ์ถ”๊ฐ€๋  ๋•Œ๋งˆ๋‹ค ์ด ๋ฉ”์„œ๋“œ๋ฅผ ์ˆ˜์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Java 14+ switch ํ‘œํ˜„์‹์„ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ€๋…์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์ด ํ–ฅ์ƒ๋ฉ๋‹ˆ๋‹ค.

๐Ÿ”Ž Switch ํ‘œํ˜„์‹์„ ์‚ฌ์šฉํ•œ ๋ฆฌํŒฉํ„ฐ๋ง ์ œ์•ˆ
     public RankingInfo getRanking(RankingCommand.GetRankingCommand command) {
-        List<Ranking> rankings;
-
-        if (command.type() == RankingCommand.RankingType.DAILY) {
-            rankings = rankingService.getDailyRanking(command.date(), command.page(), command.size());
-        } else if (command.type() == RankingCommand.RankingType.WEEKLY) {
-            rankings = rankingService.getWeeklyRanking(command.date(), command.page(), command.size());
-        } else if (command.type() == RankingCommand.RankingType.MONTHLY) {
-            rankings = rankingService.getMonthlyRanking(command.date(), command.page(), command.size());
-        } else {
-            throw new CoreException(ErrorType.BAD_REQUEST, "์ง€์›ํ•˜์ง€ ์•Š๋Š” ๋žญํ‚น ํƒ€์ž…์ž…๋‹ˆ๋‹ค: " + command.type());
-        }
+        List<Ranking> rankings = switch (command.type()) {
+            case DAILY -> rankingService.getDailyRanking(command.date(), command.page(), command.size());
+            case WEEKLY -> rankingService.getWeeklyRanking(command.date(), command.page(), command.size());
+            case MONTHLY -> rankingService.getMonthlyRanking(command.date(), command.page(), command.size());
+        };

์ฐธ๊ณ : enum์ด ๋ชจ๋“  ์ผ€์ด์Šค๋ฅผ ์ปค๋ฒ„ํ•˜๋ฏ€๋กœ default ์ ˆ์ด ๋ถˆํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šด RankingType์ด ์ถ”๊ฐ€๋˜๋ฉด ์ปดํŒŒ์ผ ํƒ€์ž„์— ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์—ฌ ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.

apps/commerce-batch/src/main/java/com/loopers/domain/ranking/MvProductRankMonthlyRepository.java (1)

7-15: ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„์€ ์˜ฌ๋ฐ”๋ฅด๋‚˜, ์—”ํ‹ฐํ‹ฐ ์ค‘๋ณต ๋ฌธ์ œ ๊ฒ€ํ†  ๊ถŒ์žฅ

๋ฆฌํฌ์ง€ํ† ๋ฆฌ ์ธํ„ฐํŽ˜์ด์Šค ์ž์ฒด๋Š” ์ž˜ ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ PR ๋ชฉ์ ์—์„œ ์–ธ๊ธ‰ํ•˜์‹  ๊ฒƒ์ฒ˜๋Ÿผ, commerce-batch์™€ commerce-api๊ฐ€ ๋™์ผํ•œ MV ํ…Œ์ด๋ธ”์— ๋Œ€ํ•ด ๊ฐ๊ฐ ๋ณ„๋„์˜ ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค๋ฅผ ์ •์˜ํ•˜๋Š” ๊ฒƒ์€ ์ฝ”๋“œ ์ค‘๋ณต ๋ฐ ์œ ์ง€๋ณด์ˆ˜ ๋ฌธ์ œ๋ฅผ ์•ผ๊ธฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ถŒ์žฅ์‚ฌํ•ญ:

  • ๊ณต์œ  MV ์—”ํ‹ฐํ‹ฐ(MvProductRankWeekly, MvProductRankMonthly)๋ฅผ modules/jpa์— ์ •์˜ํ•˜๊ณ  ๋‘ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๊ณตํ†ต์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•˜์„ธ์š”.
  • ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ ์‹œ ํ•œ ๊ณณ๋งŒ ์ˆ˜์ •ํ•˜๋ฉด ๋˜๊ณ , ํƒ€์ž… ์•ˆ์ •์„ฑ๋„ ๋ณด์žฅ๋ฉ๋‹ˆ๋‹ค.
  • ๊ฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋„๋ฉ”์ธ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋Š” ํ˜„์žฌ์ฒ˜๋Ÿผ ๋…๋ฆฝ์ ์œผ๋กœ ์œ ์ง€ํ•˜๋˜, ์ธํ”„๋ผ ๊ณ„์ธต์—์„œ ๊ณต์œ  ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ฐธ์กฐํ•˜๋„๋ก ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
apps/commerce-streamer/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsJpaRepository.java (1)

16-18: @Modifying ์ฟผ๋ฆฌ์— clearAutomatically ํ”Œ๋ž˜๊ทธ ์ถ”๊ฐ€ ๊ถŒ์žฅ

@Modifying ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•œ ๋ฒŒํฌ ์—…๋ฐ์ดํŠธ ์ฟผ๋ฆฌ๋Š” ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋ฅผ ์šฐํšŒํ•˜๋ฏ€๋กœ, ์บ์‹œ๋œ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์˜ค๋ž˜๋œ ์ƒํƒœ๋กœ ๋‚จ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋™์ผ ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ํ•ด๋‹น ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋‹ค์‹œ ์กฐํšŒํ•  ๋•Œ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋™์ž‘์„ ์œ ๋ฐœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ”Ž clearAutomatically ํ”Œ๋ž˜๊ทธ ์ถ”๊ฐ€ ์ œ์•ˆ
-    @Modifying
+    @Modifying(clearAutomatically = true)
     @Query("UPDATE ProductMetrics m SET m.likeCount = m.likeCount + 1 WHERE m.productId = :productId AND m.metricsDate = :metricsDate")
     int incrementLikeCount(@Param("productId") Long productId, @Param("metricsDate") LocalDate metricsDate);

clearAutomatically = true๋ฅผ ์„ค์ •ํ•˜๋ฉด ์ฟผ๋ฆฌ ์‹คํ–‰ ํ›„ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๊ฐ€ ์ž๋™์œผ๋กœ ํด๋ฆฌ์–ด๋˜์–ด ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ์ด ๋ณด์žฅ๋ฉ๋‹ˆ๋‹ค.

apps/commerce-batch/src/main/java/com/loopers/interfaces/scheduler/RankingBatchScheduler.java (1)

31-73: LocalDateTime.now() ์ค‘๋ณต ํ˜ธ์ถœ ์ œ๊ฑฐ ๊ถŒ์žฅ

๊ฐ ๋ฉ”์„œ๋“œ์—์„œ LocalDateTime.now()๊ฐ€ ์—ฌ๋Ÿฌ ๋ฒˆ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค(๋ผ์ธ 34, 42, 47 ๋ฐ 56, 64, 69). ๋ฉ”์„œ๋“œ ์‹œ์ž‘ ์‹œ ํ•œ ๋ฒˆ๋งŒ ํ˜ธ์ถœํ•˜์—ฌ ์žฌ์‚ฌ์šฉํ•˜๋ฉด ์ฝ”๋“œ๊ฐ€ ๋” ๋ช…ํ™•ํ•ด์ง€๊ณ , ๋กœ๊ทธ์˜ ์‹œ๊ฐ„ ์ผ๊ด€์„ฑ๋„ ๋ณด์žฅ๋ฉ๋‹ˆ๋‹ค.

๐Ÿ”Ž ๋ฆฌํŒฉํ„ฐ๋ง ์ œ์•ˆ
     @Scheduled(cron = "0 0 3 * * *")
     public void runWeeklyRankingJob() {
         try {
-            LocalDate yesterday = LocalDate.now().minusDays(1);
+            LocalDateTime executionTime = LocalDateTime.now();
+            LocalDate yesterday = executionTime.toLocalDate().minusDays(1);
             String targetDate = yesterday.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
             
-            log.info("์ฃผ๊ฐ„ ๋žญํ‚น ๋ฐฐ์น˜ ์ž‘์—… ์‹œ์ž‘: targetDate={}, time={}", targetDate, LocalDateTime.now());
+            log.info("์ฃผ๊ฐ„ ๋žญํ‚น ๋ฐฐ์น˜ ์ž‘์—… ์‹œ์ž‘: targetDate={}, time={}", targetDate, executionTime);
             
             JobParameters jobParameters = new JobParametersBuilder()
                     .addString("rankingType", "WEEKLY")
                     .addString("targetDate", targetDate)
-                    .addString("executionTime", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME))
+                    .addString("executionTime", executionTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME))
                     .toJobParameters();
             
             jobLauncher.run(rankingJob, jobParameters);
             
-            log.info("์ฃผ๊ฐ„ ๋žญํ‚น ๋ฐฐ์น˜ ์ž‘์—… ์™„๋ฃŒ: targetDate={}, time={}", targetDate, LocalDateTime.now());
+            log.info("์ฃผ๊ฐ„ ๋žญํ‚น ๋ฐฐ์น˜ ์ž‘์—… ์™„๋ฃŒ: targetDate={}, time={}", targetDate, executionTime);
         } catch (Exception e) {
             log.error("์ฃผ๊ฐ„ ๋žญํ‚น ๋ฐฐ์น˜ ์ž‘์—… ์‹คํŒจ", e);
         }
     }

์›”๊ฐ„ ๋žญํ‚น ์ž‘์—…์—๋„ ๋™์ผํ•œ ํŒจํ„ด์„ ์ ์šฉํ•˜์„ธ์š”.

apps/commerce-api/src/test/java/com/loopers/interfaces/api/ranking/RankingV1ApiE2ETest.java (1)

75-214: ์ผ์ผ ๋žญํ‚น ํ…Œ์ŠคํŠธ๋Š” ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ฃผ๊ฐ„/์›”๊ฐ„ ๋žญํ‚น ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€๋ฅผ ๊ณ ๋ คํ•ด์ฃผ์„ธ์š”.

DTO ํƒ€์ž… ๋ณ€๊ฒฝ๊ณผ type=DAILY ํŒŒ๋ผ๋ฏธํ„ฐ ์ถ”๊ฐ€๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋ฐ˜์˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ, ์ด PR์—์„œ ์ฃผ๊ฐ„(WEEKLY) ๋ฐ ์›”๊ฐ„(MONTHLY) ๋žญํ‚น API๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ์œผ๋ฏ€๋กœ ํ•ด๋‹น ์ผ€์ด์Šค์— ๋Œ€ํ•œ E2E ํ…Œ์ŠคํŠธ๋„ ์ถ”๊ฐ€ํ•˜๋ฉด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค.

apps/commerce-api/src/main/java/com/loopers/domain/ranking/RankingService.java (1)

39-61: ์ฃผ๊ฐ„/์›”๊ฐ„ ๋žญํ‚น ์กฐํšŒ ๋กœ์ง์ด ์ค‘๋ณต๋ฉ๋‹ˆ๋‹ค.

getWeeklyRanking๊ณผ getMonthlyRanking ๋ฉ”์„œ๋“œ๋Š” ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์™€ period ์ƒ์„ฑ ๋ฐฉ์‹๋งŒ ๋‹ค๋ฅด๊ณ  ๋‚˜๋จธ์ง€ ๋กœ์ง์ด ๋™์ผํ•ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ ์ƒํƒœ์—์„œ๋„ ๋™์ž‘์—๋Š” ๋ฌธ์ œ์—†์ง€๋งŒ, ์ถ”ํ›„ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ์œ„ํ•ด ๊ณตํ†ต ๋กœ์ง์„ ์ถ”์ถœํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ”Ž ๋ฆฌํŒฉํ† ๋ง ์˜ˆ์‹œ
private <T> List<Ranking> getRankingFromRepository(
        RankingPeriod period,
        int page,
        int size,
        TriFunction<LocalDate, LocalDate, Integer, Integer, List<T>> finder,
        Function<T, Long> productIdExtractor,
        Function<T, Integer> rankingExtractor,
        Function<T, Double> scoreExtractor
) {
    List<T> ranks = finder.apply(period.startDate(), period.endDate(), page, size);
    
    if (ranks.isEmpty()) {
        return new ArrayList<>();
    }
    
    return ranks.stream()
            .filter(rank -> rankingExtractor.apply(rank) != null)
            .map(rank -> new Ranking(
                    productIdExtractor.apply(rank),
                    rankingExtractor.apply(rank).longValue(),
                    scoreExtractor.apply(rank)
            ))
            .toList();
}

Also applies to: 63-85

apps/commerce-batch/src/main/java/com/loopers/domain/ranking/MvProductRankWeeklyRepository.java (1)

7-15: commerce-batch์™€ commerce-api ๋ชจ๋“ˆ์— ๋™์ผํ•œ ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ์ค‘๋ณต ์ •์˜๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

๋‘ ๋ชจ๋“ˆ ๋ชจ๋‘์— MvProductRankWeeklyRepository๊ฐ€ ์กด์žฌํ•˜์ง€๋งŒ ๋ฉ”์„œ๋“œ ์‹œ๊ทธ๋‹ˆ์ฒ˜๊ฐ€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค:

  • commerce-batch: findByPeriodOrderByRankingAsc(LocalDate periodStartDate, LocalDate periodEndDate) - ํŽ˜์ด์ง€๋„ค์ด์…˜ ์—†์Œ
  • commerce-api: findByPeriodOrderByRankingAsc(LocalDate periodStartDate, LocalDate periodEndDate, int page, int size) - ํŽ˜์ด์ง€๋„ค์ด์…˜ ํฌํ•จ

๊ฐ™์€ ์ด๋ฆ„์˜ ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ๋‹ค๋ฅธ ์‹œ๊ทธ๋‹ˆ์ฒ˜๋กœ ์ •์˜๋˜์–ด ์žˆ์–ด ํ–ฅํ›„ ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์–ด๋ ค์›Œ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. PR ์„ค๋ช…์— ์–ธ๊ธ‰๋œ ๊ณตํ†ต ๋ชจ๋“ˆ(modules/jpa ๋“ฑ)๋กœ ์ด ์ธํ„ฐํŽ˜์ด์Šค์™€ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋ถ„๋ฆฌํ•˜๊ฑฐ๋‚˜, ๋ชจ๋“ˆ๋ณ„ ์š”๊ตฌ์‚ฌํ•ญ์— ๋งž๊ฒŒ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋ช…ํ™•ํžˆ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

apps/commerce-batch/src/main/java/com/loopers/domain/ranking/WeeklyRankingService.java (3)

62-68: ๋ฐฐ์น˜ ์ €์žฅ์„ ์‚ฌ์šฉํ•˜์—ฌ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ˜„์žฌ ๊ฐ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ฐœ๋ณ„์ ์œผ๋กœ ์ €์žฅํ•˜๊ณ  ์žˆ์–ด ์ตœ๋Œ€ 100๋ฒˆ์˜ save ํ˜ธ์ถœ์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. saveAll()์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐฐ์น˜๋กœ ์ฒ˜๋ฆฌ๋˜์–ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์™•๋ณต ํšŸ์ˆ˜๊ฐ€ ์ค„์–ด๋“ญ๋‹ˆ๋‹ค.

๐Ÿ”Ž ์ œ์•ˆํ•˜๋Š” ์ˆ˜์ •
 // TOP 100 ์ƒํ’ˆ์˜ ๋žญํ‚น๋งŒ ์—…๋ฐ์ดํŠธ
-int updatedCount = 0;
 for (int i = 0; i < top100.size(); i++) {
     MvProductRankWeekly rank = top100.get(i);
     rank.updateRanking(i + 1);
-    mvProductRankWeeklyRepository.save(rank);
-    updatedCount++;
 }
+mvProductRankWeeklyRepository.saveAll(top100);
+int updatedCount = top100.size();

Note: saveAll ๋ฉ”์„œ๋“œ๋ฅผ MvProductRankWeeklyRepository ์ธํ„ฐํŽ˜์ด์Šค์— ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.


75-80: ๋ฐฐ์น˜ ์‚ญ์ œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐœ๋ณ„ ์‚ญ์ œ ๋Œ€์‹  ์‚ญ์ œํ•  ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ˆ˜์ง‘ํ•œ ํ›„ ํ•œ ๋ฒˆ์— ์‚ญ์ œํ•˜๋ฉด ์„ฑ๋Šฅ์ด ํ–ฅ์ƒ๋ฉ๋‹ˆ๋‹ค.

๐Ÿ”Ž ์ œ์•ˆํ•˜๋Š” ์ˆ˜์ •
-int deletedCount = 0;
+List<MvProductRankWeekly> toDelete = new ArrayList<>();
 for (MvProductRankWeekly existing : allRanks) {
     if (!top100ProductIds.contains(existing.getProductId())) {
-        mvProductRankWeeklyRepository.delete(existing);
-        deletedCount++;
+        toDelete.add(existing);
     }
 }
+mvProductRankWeeklyRepository.deleteAll(toDelete);
+int deletedCount = toDelete.size();

Note: deleteAll ๋ฉ”์„œ๋“œ๋ฅผ MvProductRankWeeklyRepository ์ธํ„ฐํŽ˜์ด์Šค์— ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.


14-86: MonthlyRankingService์™€ ์ฝ”๋“œ ์ค‘๋ณต์„ ๊ณ ๋ คํ•ด๋ณด์„ธ์š”.

WeeklyRankingService์™€ MonthlyRankingService๊ฐ€ ๊ฑฐ์˜ ๋™์ผํ•œ ๋กœ์ง์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ œ๋„ค๋ฆญ ํƒ€์ž…์ด๋‚˜ ์ „๋žต ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ณตํ†ต ๋กœ์ง์„ ์ถ”์ƒํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ ํ˜„์žฌ ๊ตฌ์กฐ๋„ ๋ช…ํ™•ํ•˜๊ณ  ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์šฐ๋ฏ€๋กœ, ํ–ฅํ›„ ๋ฆฌํŒฉํ† ๋ง ์‹œ ๊ณ ๋ คํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

apps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1ApiSpec.java (1)

19-20: type ํŒŒ๋ผ๋ฏธํ„ฐ์˜ ํƒ€์ž… ์•ˆ์ •์„ฑ์„ ๊ณ ๋ คํ•ด๋ณด์„ธ์š”.

ํ˜„์žฌ type ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ String์œผ๋กœ ์ •์˜๋˜์–ด ์žˆ์–ด ๋Ÿฐํƒ€์ž„์— ๊ฒ€์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด Swagger ๋ฌธ์„œํ™”๋ฅผ ์œ„ํ•ด @Schema(allowableValues = {"DAILY", "WEEKLY", "MONTHLY"})๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜, ์ปจํŠธ๋กค๋Ÿฌ์—์„œ enum์œผ๋กœ ๋ฐ›์•„ ํƒ€์ž… ์•ˆ์ •์„ฑ์„ ๋†’์ด๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณด์„ธ์š”.

apps/commerce-batch/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyRepositoryImpl.java (1)

12-37: MvProductRankWeeklyRepositoryImpl๊ณผ ์ฝ”๋“œ ์ค‘๋ณต์„ ๊ณ ๋ คํ•ด๋ณด์„ธ์š”.

๋‘ repository ๊ตฌํ˜„์ฒด๊ฐ€ ๊ฑฐ์˜ ๋™์ผํ•œ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ œ๋„ค๋ฆญ ํƒ€์ž…์„ ์‚ฌ์šฉํ•œ ์ถ”์ƒ ํด๋ž˜์Šค๋กœ ๊ณตํ†ต ๋กœ์ง์„ ์ถ”์ถœํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ํ˜„์žฌ ๊ตฌ์กฐ๋„ ์ถฉ๋ถ„ํžˆ ๋ช…ํ™•ํ•˜๋ฏ€๋กœ ์„ ํƒ ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค.

apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingJobConfig.java (1)

63-76: ๋™์‹œ ์‹คํ–‰ ์‹œ ์ƒํƒœ ์ถฉ๋Œ ๊ฐ€๋Šฅ: @StepScope ์ ์šฉ ๊ถŒ์žฅ

ProductMetricsItemReader์™€ RankingWriter๋Š” ๋‚ด๋ถ€์— ์ƒํƒœ(stepExecution, productMetricsList, currentIndex, period, rankingType)๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ ์‹ฑ๊ธ€ํ†ค์œผ๋กœ ์ƒ์„ฑ๋˜๋ฏ€๋กœ, ์ฃผ๊ฐ„/์›”๊ฐ„ ๋ฐฐ์น˜๊ฐ€ ๋™์‹œ์— ์‹คํ–‰๋˜๋ฉด ์ƒํƒœ๊ฐ€ ๋ฎ์–ด์”Œ์›Œ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ”Ž ๊ถŒ์žฅ ์ˆ˜์ •์•ˆ
+import org.springframework.batch.core.configuration.annotation.StepScope;

 @Bean
+@StepScope
 public ItemReader<ProductMetrics> rankingReader() {
     return new ProductMetricsItemReader(productMetricsService);
 }

 @Bean
+@StepScope
 public ItemProcessor<ProductMetrics, ProductRankingAggregate> rankingProcessor() {
     return new RankingProcessor(rankingCalculator);
 }

 @Bean
+@StepScope
 public ItemWriter<ProductRankingAggregate> rankingWriter() {
     return new RankingWriter(weeklyRankingService, monthlyRankingService);
 }
apps/commerce-batch/src/main/java/com/loopers/domain/ranking/MonthlyRankingService.java (2)

61-80: ์„ฑ๋Šฅ ์ตœ์ ํ™” ๊ณ ๋ ค: ๋ฐฐ์น˜ ์ €์žฅ/์‚ญ์ œ

๋ฃจํ”„ ๋‚ด ๊ฐœ๋ณ„ save()/delete() ํ˜ธ์ถœ์€ ๋ฐฐ์น˜ ์ž‘์—…์—์„œ ๋ณ‘๋ชฉ์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. saveAll() ๋ฐ deleteAll()๋กœ ๋ณ€๊ฒฝํ•˜๋ฉด ์„ฑ๋Šฅ์ด ํ–ฅ์ƒ๋ฉ๋‹ˆ๋‹ค.

๐Ÿ”Ž ๊ถŒ์žฅ ์ˆ˜์ •์•ˆ
-        int updatedCount = 0;
-        for (int i = 0; i < top100.size(); i++) {
-            MvProductRankMonthly rank = top100.get(i);
-            rank.updateRanking(i + 1);
-            mvProductRankMonthlyRepository.save(rank);
-            updatedCount++;
-        }
+        for (int i = 0; i < top100.size(); i++) {
+            top100.get(i).updateRanking(i + 1);
+        }
+        mvProductRankMonthlyRepository.saveAll(top100);

         // TOP 100์—์„œ ๋ฐ€๋ ค๋‚œ ๊ธฐ์กด ๋ฐ์ดํ„ฐ ์‚ญ์ œ
         Set<Long> top100ProductIds = top100.stream()
                 .map(MvProductRankMonthly::getProductId)
                 .collect(Collectors.toSet());

-        int deletedCount = 0;
-        for (MvProductRankMonthly existing : allRanks) {
-            if (!top100ProductIds.contains(existing.getProductId())) {
-                mvProductRankMonthlyRepository.delete(existing);
-                deletedCount++;
-            }
-        }
+        List<MvProductRankMonthly> toDelete = allRanks.stream()
+                .filter(r -> !top100ProductIds.contains(r.getProductId()))
+                .toList();
+        mvProductRankMonthlyRepository.deleteAll(toDelete);

-        log.info("์›”๊ฐ„ ๋žญํ‚น ๊ณ„์‚ฐ ์™„๋ฃŒ: period={} ~ {}, updated={}, deleted={}",
-                period.startDate(), period.endDate(), updatedCount, deletedCount);
+        log.info("์›”๊ฐ„ ๋žญํ‚น ๊ณ„์‚ฐ ์™„๋ฃŒ: period={} ~ {}, updated={}, deleted={}",
+                period.startDate(), period.endDate(), top100.size(), toDelete.size());

11-85: ์ฝ”๋“œ ์ค‘๋ณต: WeeklyRankingService์™€ ๋™์ผํ•œ ๋กœ์ง

WeeklyRankingService์™€ MonthlyRankingService๊ฐ€ ๊ฑฐ์˜ ๋™์ผํ•œ ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค. ์ œ๋„ค๋ฆญ ์ถ”์ƒ ํด๋ž˜์Šค๋‚˜ ํ…œํ”Œ๋ฆฟ ๋ฉ”์„œ๋“œ ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜๋ฉด ์ค‘๋ณต์„ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค๋งŒ, ํ˜„์žฌ ๊ทœ๋ชจ์—์„œ๋Š” ๊ณผํ•œ ์ถ”์ƒํ™”์ผ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ํ–ฅํ›„ ํ™•์žฅ ์‹œ ๊ณ ๋ คํ•˜์…”๋„ ๋ฉ๋‹ˆ๋‹ค.

apps/commerce-batch/src/main/java/com/loopers/domain/ranking/MvProductRankMonthly.java (1)

1-95: ์—”ํ‹ฐํ‹ฐ ๊ตฌ์กฐ๊ฐ€ MvProductRankWeekly์™€ ์ผ๊ด€๋˜๊ฒŒ ์ž˜ ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

MvProductRankWeekly์™€ ๋™์ผํ•œ ๊ตฌ์กฐ๋ฅผ ๋”ฐ๋ฅด๊ณ  ์žˆ์œผ๋ฉฐ, JPA ๋งคํ•‘, ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ, ์—…๋ฐ์ดํŠธ ๋ฉ”์„œ๋“œ๊ฐ€ ์ ์ ˆํ•˜๊ฒŒ ๊ตฌํ˜„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

PR ๋ชฉํ‘œ์—์„œ ์–ธ๊ธ‰๋œ ๊ฒƒ์ฒ˜๋Ÿผ, commerce-batch์™€ commerce-api์—์„œ ๋™์ผํ•œ ํ…Œ์ด๋ธ”์— ๋Œ€ํ•ด ๋ณ„๋„์˜ ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค๋ฅผ ์ •์˜ํ•˜๊ณ  ์žˆ๋Š” ์ ์— ๋Œ€ํ•ด์„œ๋Š” ๊ณต์œ  ๋ชจ๋“ˆ(modules/jpa)๋กœ ํ†ตํ•ฉํ•˜๋Š” ๊ฒƒ์ด ์œ ์ง€๋ณด์ˆ˜์„ฑ ์ธก๋ฉด์—์„œ ๋” ์ข‹์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ ์ด๋Š” ๋ณ„๋„์˜ ๋ฆฌํŒฉํ† ๋ง์œผ๋กœ ์ง„ํ–‰ํ•ด๋„ ๋ฌด๋ฐฉํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“œ Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

๐Ÿ“ฅ Commits

Reviewing files that changed from the base of the PR and between 50eba17 and de4d9cb.

๐Ÿ“’ Files selected for processing (59)
  • apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingCommand.java
  • apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingFacade.java
  • apps/commerce-api/src/main/java/com/loopers/domain/ranking/MvProductRankMonthly.java
  • apps/commerce-api/src/main/java/com/loopers/domain/ranking/MvProductRankMonthlyRepository.java
  • apps/commerce-api/src/main/java/com/loopers/domain/ranking/MvProductRankWeekly.java
  • apps/commerce-api/src/main/java/com/loopers/domain/ranking/MvProductRankWeeklyRepository.java
  • apps/commerce-api/src/main/java/com/loopers/domain/ranking/RankingPeriod.java
  • apps/commerce-api/src/main/java/com/loopers/domain/ranking/RankingService.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyJpaRepository.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyRepositoryImpl.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankWeeklyJpaRepository.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankWeeklyRepositoryImpl.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1ApiSpec.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1Controller.java
  • apps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1Dto.java
  • apps/commerce-api/src/main/resources/application.yml
  • apps/commerce-api/src/test/java/com/loopers/application/ranking/RankingFacadeIntegrationTest.java
  • apps/commerce-api/src/test/java/com/loopers/domain/ranking/RankingServiceIntegrationTest.java
  • apps/commerce-api/src/test/java/com/loopers/interfaces/api/ranking/RankingV1ApiE2ETest.java
  • apps/commerce-batch/build.gradle.kts
  • apps/commerce-batch/src/main/java/com/loopers/CommerceBatchApplication.java
  • apps/commerce-batch/src/main/java/com/loopers/application/ranking/JobParameterUtils.java
  • apps/commerce-batch/src/main/java/com/loopers/application/ranking/ProductMetricsItemReader.java
  • apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingCalculationTasklet.java
  • apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingJobConfig.java
  • apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingProcessor.java
  • apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingWriter.java
  • apps/commerce-batch/src/main/java/com/loopers/batch/job/BatchConfig.java
  • apps/commerce-batch/src/main/java/com/loopers/domain/metrics/ProductMetrics.java
  • apps/commerce-batch/src/main/java/com/loopers/domain/metrics/ProductMetricsRepository.java
  • apps/commerce-batch/src/main/java/com/loopers/domain/metrics/ProductMetricsService.java
  • apps/commerce-batch/src/main/java/com/loopers/domain/ranking/MonthlyRankingService.java
  • apps/commerce-batch/src/main/java/com/loopers/domain/ranking/MvProductRankMonthly.java
  • apps/commerce-batch/src/main/java/com/loopers/domain/ranking/MvProductRankMonthlyRepository.java
  • apps/commerce-batch/src/main/java/com/loopers/domain/ranking/MvProductRankWeekly.java
  • apps/commerce-batch/src/main/java/com/loopers/domain/ranking/MvProductRankWeeklyRepository.java
  • apps/commerce-batch/src/main/java/com/loopers/domain/ranking/ProductRankingAggregate.java
  • apps/commerce-batch/src/main/java/com/loopers/domain/ranking/RankingCalculator.java
  • apps/commerce-batch/src/main/java/com/loopers/domain/ranking/RankingPeriod.java
  • apps/commerce-batch/src/main/java/com/loopers/domain/ranking/RankingType.java
  • apps/commerce-batch/src/main/java/com/loopers/domain/ranking/RankingWeight.java
  • apps/commerce-batch/src/main/java/com/loopers/domain/ranking/WeeklyRankingService.java
  • apps/commerce-batch/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsJpaRepository.java
  • apps/commerce-batch/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsRepositoryImpl.java
  • apps/commerce-batch/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyJpaRepository.java
  • apps/commerce-batch/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyRepositoryImpl.java
  • apps/commerce-batch/src/main/java/com/loopers/infrastructure/ranking/MvProductRankWeeklyJpaRepository.java
  • apps/commerce-batch/src/main/java/com/loopers/infrastructure/ranking/MvProductRankWeeklyRepositoryImpl.java
  • apps/commerce-batch/src/main/java/com/loopers/interfaces/scheduler/RankingBatchScheduler.java
  • apps/commerce-batch/src/main/resources/application.yml
  • apps/commerce-batch/src/test/java/com/loopers/CommerceBatchApplicationTests.java
  • apps/commerce-streamer/src/main/java/com/loopers/domain/metrics/ProductMetrics.java
  • apps/commerce-streamer/src/main/java/com/loopers/domain/metrics/ProductMetricsRepository.java
  • apps/commerce-streamer/src/main/java/com/loopers/domain/metrics/ProductMetricsService.java
  • apps/commerce-streamer/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsJpaRepository.java
  • apps/commerce-streamer/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsRepositoryImpl.java
  • apps/commerce-streamer/src/test/java/com/loopers/interfaces/consumer/MetricsConsumerIdempotencyTest.java
  • modules/kafka/src/main/java/com/loopers/config/kafka/KafkaConfig.java
  • settings.gradle.kts
๐Ÿงฐ Additional context used
๐Ÿง  Learnings (5)
๐Ÿ“š Learning: 2025-11-09T10:41:39.297Z
Learnt from: ghojeong
Repo: Loopers-dev-lab/loopers-spring-kotlin-template PR: 25
File: apps/commerce-api/src/main/kotlin/com/loopers/domain/product/ProductRepository.kt:1-12
Timestamp: 2025-11-09T10:41:39.297Z
Learning: In this codebase, domain repository interfaces are allowed to use Spring Data's org.springframework.data.domain.Page and org.springframework.data.domain.Pageable types. This is an accepted architectural decision and should not be flagged as a DIP violation.

Applied to files:

  • apps/commerce-batch/src/main/java/com/loopers/infrastructure/ranking/MvProductRankWeeklyJpaRepository.java
  • apps/commerce-api/src/main/java/com/loopers/domain/ranking/MvProductRankMonthlyRepository.java
  • apps/commerce-batch/src/main/java/com/loopers/domain/ranking/MvProductRankWeeklyRepository.java
  • apps/commerce-batch/src/main/java/com/loopers/infrastructure/ranking/MvProductRankWeeklyRepositoryImpl.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankWeeklyJpaRepository.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyJpaRepository.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankWeeklyRepositoryImpl.java
  • apps/commerce-api/src/main/java/com/loopers/domain/ranking/MvProductRankWeeklyRepository.java
  • apps/commerce-batch/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyJpaRepository.java
  • apps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyRepositoryImpl.java
  • apps/commerce-batch/src/main/java/com/loopers/domain/ranking/MvProductRankMonthlyRepository.java
๐Ÿ“š Learning: 2025-11-27T09:09:24.961Z
Learnt from: sky980221
Repo: Loopers-dev-lab/loopers-spring-java-template PR: 121
File: apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductJpaRepository.java:22-24
Timestamp: 2025-11-27T09:09:24.961Z
Learning: Product ์—”ํ‹ฐํ‹ฐ (apps/commerce-api/src/main/java/com/loopers/domain/product/Product.java)๋Š” ์œ ์ฆˆ์ผ€์ด์Šค๋ณ„๋กœ ์˜๋„์ ์œผ๋กœ ๋‹ค๋ฅธ ๋ฝ ์ „๋žต์„ ์‚ฌ์šฉํ•œ๋‹ค: ์ข‹์•„์š” ๊ธฐ๋Šฅ์—๋Š” ๋น„๊ด€์  ๋ฝ(findByIdForUpdate)์„, ์žฌ๊ณ  ์ฐจ๊ฐ์—๋Š” ๋‚™๊ด€์  ๋ฝ(Version + ์žฌ์‹œ๋„)์„ ์‚ฌ์šฉํ•œ๋‹ค.

Applied to files:

  • apps/commerce-streamer/src/main/java/com/loopers/domain/metrics/ProductMetricsRepository.java
  • apps/commerce-streamer/src/main/java/com/loopers/domain/metrics/ProductMetricsService.java
  • apps/commerce-batch/src/main/java/com/loopers/domain/metrics/ProductMetrics.java
  • apps/commerce-streamer/src/test/java/com/loopers/interfaces/consumer/MetricsConsumerIdempotencyTest.java
  • apps/commerce-streamer/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsJpaRepository.java
  • apps/commerce-streamer/src/main/java/com/loopers/domain/metrics/ProductMetrics.java
  • apps/commerce-streamer/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsRepositoryImpl.java
๐Ÿ“š Learning: 2025-12-19T21:30:16.024Z
Learnt from: toongri
Repo: Loopers-dev-lab/loopers-spring-kotlin-template PR: 68
File: apps/commerce-api/src/main/kotlin/com/loopers/infrastructure/outbox/OutboxEventListener.kt:0-0
Timestamp: 2025-12-19T21:30:16.024Z
Learning: In the Loopers-dev-lab/loopers-spring-kotlin-template Kafka event pipeline, Like events (LikeCreatedEventV1, LikeCanceledEventV1) intentionally use aggregateType="Like" with aggregateId=productId. The aggregateId serves as a partitioning/grouping key (not a unique Like entity identifier), ensuring all like events for the same product go to the same partition for ordering guarantees and aligning with ProductStatisticService's product-based aggregation logic. Using individual like_id would scatter events across partitions and break the statistics aggregation pattern.

Applied to files:

  • apps/commerce-batch/src/main/java/com/loopers/domain/ranking/ProductRankingAggregate.java
  • apps/commerce-streamer/src/main/java/com/loopers/domain/metrics/ProductMetricsService.java
๐Ÿ“š Learning: 2025-11-21T03:38:07.494Z
Learnt from: minor7295
Repo: Loopers-dev-lab/loopers-spring-java-template PR: 99
File: apps/commerce-api/src/main/resources/application.yml:26-30
Timestamp: 2025-11-21T03:38:07.494Z
Learning: The batch job implementation for likeCount synchronization in apps/commerce-api is temporary and intended for development environment only. It will be replaced with Event-Driven Architecture (EDA) before production deployment, so production-level configuration concerns (like profile-based initialize-schema settings) are not required.

Applied to files:

  • apps/commerce-batch/src/main/resources/application.yml
  • apps/commerce-batch/src/main/java/com/loopers/CommerceBatchApplication.java
  • apps/commerce-batch/src/main/java/com/loopers/batch/job/BatchConfig.java
๐Ÿ“š Learning: 2025-12-19T09:30:12.459Z
Learnt from: HongChangMo
Repo: Loopers-dev-lab/loopers-spring-java-template PR: 198
File: apps/commerce-api/src/main/java/com/loopers/application/payment/PaymentEventListener.java:0-0
Timestamp: 2025-12-19T09:30:12.459Z
Learning: In the loopers-spring-java-template repository's commerce-api module, when entities are managed within a transactional context (e.g., Transactional methods), prefer relying on JPA dirty checking for automatic persistence rather than explicit save() calls. Both Payment and Order entities in PaymentEventListener use this pattern, with state changes automatically flushed on transaction commit.

Applied to files:

  • apps/commerce-batch/src/test/java/com/loopers/CommerceBatchApplicationTests.java
๐Ÿงฌ Code graph analysis (17)
apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingCalculationTasklet.java (5)
apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingProcessor.java (1)
  • RequiredArgsConstructor (9-21)
apps/commerce-batch/src/main/java/com/loopers/application/ranking/JobParameterUtils.java (1)
  • Slf4j (10-46)
apps/commerce-batch/src/main/java/com/loopers/application/ranking/ProductMetricsItemReader.java (1)
  • Slf4j (17-65)
apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingJobConfig.java (1)
  • Slf4j (23-77)
apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingWriter.java (1)
  • Slf4j (17-65)
apps/commerce-api/src/main/java/com/loopers/domain/ranking/MvProductRankMonthly.java (1)
apps/commerce-api/src/main/java/com/loopers/domain/ranking/MvProductRankWeekly.java (1)
  • Entity (13-76)
apps/commerce-batch/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyRepositoryImpl.java (1)
apps/commerce-batch/src/main/java/com/loopers/infrastructure/ranking/MvProductRankWeeklyRepositoryImpl.java (1)
  • Repository (12-37)
apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingWriter.java (4)
apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingProcessor.java (1)
  • RequiredArgsConstructor (9-21)
apps/commerce-batch/src/main/java/com/loopers/application/ranking/JobParameterUtils.java (1)
  • Slf4j (10-46)
apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingCalculationTasklet.java (1)
  • Slf4j (17-47)
apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingJobConfig.java (1)
  • Slf4j (23-77)
apps/commerce-batch/src/main/java/com/loopers/domain/ranking/MvProductRankMonthly.java (1)
apps/commerce-batch/src/main/java/com/loopers/domain/ranking/MvProductRankWeekly.java (1)
  • Entity (13-95)
apps/commerce-batch/src/main/java/com/loopers/domain/ranking/MvProductRankWeekly.java (1)
apps/commerce-batch/src/main/java/com/loopers/domain/ranking/MvProductRankMonthly.java (1)
  • Entity (13-95)
apps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankWeeklyRepositoryImpl.java (2)
apps/commerce-api/src/main/java/com/loopers/domain/ranking/RankingService.java (1)
  • RequiredArgsConstructor (12-86)
apps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyRepositoryImpl.java (1)
  • Repository (13-24)
apps/commerce-api/src/main/java/com/loopers/domain/ranking/MvProductRankWeekly.java (1)
apps/commerce-api/src/main/java/com/loopers/domain/ranking/MvProductRankMonthly.java (1)
  • Entity (13-76)
apps/commerce-batch/src/main/java/com/loopers/domain/ranking/WeeklyRankingService.java (1)
apps/commerce-batch/src/main/java/com/loopers/domain/ranking/MonthlyRankingService.java (1)
  • Slf4j (11-85)
apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingFacade.java (1)
apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingCommand.java (1)
  • RankingCommand (5-21)
apps/commerce-batch/src/main/java/com/loopers/domain/ranking/MonthlyRankingService.java (1)
apps/commerce-batch/src/main/java/com/loopers/domain/ranking/WeeklyRankingService.java (1)
  • Slf4j (11-86)
apps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1Controller.java (2)
apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingCommand.java (1)
  • RankingCommand (5-21)
apps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1Dto.java (1)
  • RankingV1Dto (7-38)
apps/commerce-batch/src/main/java/com/loopers/application/ranking/ProductMetricsItemReader.java (5)
apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingProcessor.java (1)
  • RequiredArgsConstructor (9-21)
apps/commerce-batch/src/main/java/com/loopers/application/ranking/JobParameterUtils.java (1)
  • Slf4j (10-46)
apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingCalculationTasklet.java (1)
  • Slf4j (17-47)
apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingJobConfig.java (1)
  • Slf4j (23-77)
apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingWriter.java (1)
  • Slf4j (17-65)
apps/commerce-streamer/src/test/java/com/loopers/interfaces/consumer/MetricsConsumerIdempotencyTest.java (1)
k6_product_list.js (1)
  • productId (356-356)
apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingJobConfig.java (6)
apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingProcessor.java (1)
  • RequiredArgsConstructor (9-21)
apps/commerce-batch/src/main/java/com/loopers/application/ranking/JobParameterUtils.java (1)
  • Slf4j (10-46)
apps/commerce-batch/src/main/java/com/loopers/application/ranking/ProductMetricsItemReader.java (1)
  • Slf4j (17-65)
apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingCalculationTasklet.java (1)
  • Slf4j (17-47)
apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingWriter.java (1)
  • Slf4j (17-65)
apps/commerce-batch/src/main/java/com/loopers/batch/job/BatchConfig.java (1)
  • Configuration (9-19)
apps/commerce-api/src/test/java/com/loopers/domain/ranking/RankingServiceIntegrationTest.java (3)
apps/commerce-api/src/test/java/com/loopers/interfaces/api/ranking/RankingV1ApiE2ETest.java (1)
  • DisplayName (75-214)
apps/commerce-api/src/test/java/com/loopers/application/ranking/RankingFacadeIntegrationTest.java (1)
  • DisplayName (61-196)
apps/commerce-api/src/test/java/com/loopers/infrastructure/cache/RankingCacheServiceIntegrationTest.java (2)
  • DisplayName (43-115)
  • DisplayName (117-160)
apps/commerce-api/src/test/java/com/loopers/application/ranking/RankingFacadeIntegrationTest.java (1)
apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingCommand.java (1)
  • RankingCommand (5-21)
๐Ÿ”‡ Additional comments (51)
apps/commerce-api/src/main/resources/application.yml (1)

24-24: Kafka ์„ค์ •์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

kafka.yml ํŒŒ์ผ์ด modules/kafka/src/main/resources/ ์œ„์น˜์— ์กด์žฌํ•˜๋ฉฐ, ํ•„์š”ํ•œ ๋ชจ๋“  Kafka ์—ฐ๊ฒฐ ์ •๋ณด์™€ ์ง๋ ฌํ™” ์„ค์ •(StringSerializer, JsonSerializer, ByteArrayDeserializer)์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. commerce-api์˜ build.gradle.kts์—์„œ kafka ๋ชจ๋“ˆ์„ ์˜์กด์„ฑ์œผ๋กœ ์„ ์–ธํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, KafkaConfig์—์„œ String ํƒ€์ž…์˜ KafkaTemplate ๋นˆ๊ณผ ๊ธฐํƒ€ ํ•„์š”ํ•œ Kafka ๋นˆ๋“ค์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ •์˜๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. application.yml์˜ import ์ˆœ์„œ(jpa.yml โ†’ redis.yml โ†’ kafka.yml โ†’ logging.yml โ†’ monitoring.yml)๋„ ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค.

modules/kafka/src/main/java/com/loopers/config/kafka/KafkaConfig.java (1)

49-58: String ํƒ€์ž… Kafka ๋นˆ์˜ ์ง๋ ฌํ™”(Serializer) ์„ค์ •์„ ํ™•์ธํ•ด ์ฃผ์„ธ์š”.

stringProducerFactory์™€ stringKafkaTemplate (49-58์ค„)์ด ์ถ”๊ฐ€๋˜์—ˆ๋Š”๋ฐ, ๊ธฐ์กด Object, Object ํƒ€์ž… ๋นˆ๊ณผ ๋™์ผํ•œ kafkaProperties.buildProducerProperties() ์„ค์ •์„ ์žฌ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

KafkaProperties์—์„œ ๋ช…์‹œ์ ์œผ๋กœ StringSerializer๊ฐ€ ์„ค์ •๋˜์–ด ์žˆ์ง€ ์•Š์œผ๋ฏ€๋กœ, buildProducerProperties()์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋ณธ ์„ค์ •์ด String ํƒ€์ž…์— ์ ํ•ฉํ•œ์ง€ ํ™•์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ Object/JSON ์ง๋ ฌํ™” ๋ฐฉ์‹์œผ๋กœ ์„ค์ •๋œ ๊ฒฝ์šฐ String ํƒ€์ž… ๋นˆ์ด ์˜ˆ์ƒ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ถ”๊ฐ€๋กœ, String ํƒ€์ž…์˜ producer ๋นˆ๋งŒ ์กด์žฌํ•˜๊ณ  consumer ๋นˆ(stringConsumerFactory, stringConsumerTemplate)์ด ์—†๋‹ค๋ฉด, ๋ฉ”์‹œ์ง€๋ฅผ ์ •์ƒ์ ์œผ๋กœ ์ˆ˜์‹ ํ•˜๊ธฐ ์œ„ํ•ด ๋ณ„๋„์˜ consumer ์„ค์ •๋„ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

settings.gradle.kts (1)

7-7: LGTM!

์ƒˆ๋กœ์šด commerce-batch ๋ชจ๋“ˆ์ด ์ •์ƒ์ ์œผ๋กœ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

apps/commerce-batch/build.gradle.kts (3)

11-11: LGTM!

๋ฐฐ์น˜ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ํ•„์ˆ˜ ์˜์กด์„ฑ์ž…๋‹ˆ๋‹ค.


22-23: LGTM!

ํ…Œ์ŠคํŠธ ํ”ฝ์Šค์ฒ˜ ์˜์กด์„ฑ์ด ์ ์ ˆํ•˜๊ฒŒ ๊ตฌ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.


17-17: ์ด ๊ตฌ๋ฌธ์€ ์˜ฌ๋ฐ”๋ฆ…๋‹ˆ๋‹ค.

com.querydsl:querydsl-apt::jakarta์˜ ์ด์ค‘ ์ฝœ๋ก (::) ๊ตฌ๋ฌธ์€ ์œ ํšจํ•œ Gradle ์˜์กด์„ฑ ๊ตฌ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด๋Š” Spring Boot์˜ ์˜์กด์„ฑ ๊ด€๋ฆฌ(dependency-management)๋‚˜ BOM์„ ์‚ฌ์šฉํ•  ๋•Œ ๋ฒ„์ „์„ ์ƒ๋žตํ•˜๋Š” ์ •์ƒ์ ์ธ ํŒจํ„ด์ด๋ฉฐ, ํ”„๋กœ์ ํŠธ ์ „์—ญ์—์„œ ์ผ๊ด€๋˜๊ฒŒ ์ ์šฉ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ˆ˜์ •์ด ํ•„์š” ์—†์Šต๋‹ˆ๋‹ค.

apps/commerce-batch/src/test/java/com/loopers/CommerceBatchApplicationTests.java (1)

6-11: LGTM!

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ ๋กœ๋”ฉ์„ ๊ฒ€์ฆํ•˜๋Š” ํ‘œ์ค€ ์Šค๋ชจํฌ ํ…Œ์ŠคํŠธ์ž…๋‹ˆ๋‹ค. ๋ฐฐ์น˜ ์„ค์ • ์˜ค๋ฅ˜๋ฅผ ์กฐ๊ธฐ์— ๊ฐ์ง€ํ•˜๋Š” ๋ฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

apps/commerce-api/src/main/java/com/loopers/domain/ranking/RankingPeriod.java (1)

8-24: LGTM!

RankingPeriod ๋ ˆ์ฝ”๋“œ๊ฐ€ ๊น”๋”ํ•˜๊ฒŒ ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ofWeek์€ ISO ํ‘œ์ค€(์›”์š”์ผ ์‹œ์ž‘)์„ ๋”ฐ๋ฅด๋ฉฐ, ofMonth๋Š” YearMonth๋ฅผ ์ ์ ˆํžˆ ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค.

apps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1Dto.java (1)

9-26: LGTM - API DTO ๋ฆฌํŒฉํ† ๋ง

DailyRankingListResponse์—์„œ RankingListResponse๋กœ์˜ ์ด๋ฆ„ ๋ณ€๊ฒฝ๊ณผ ํ™•์žฅ๋œ ํ•„๋“œ ์ถ”๊ฐ€๊ฐ€ ๋‹ค์ค‘ ๋žญํ‚น ํƒ€์ž…(DAILY/WEEKLY/MONTHLY) ์ง€์›์— ์ ์ ˆํžˆ ๋ฐ˜์˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

apps/commerce-streamer/src/main/java/com/loopers/domain/metrics/ProductMetrics.java (2)

14-29: LGTM!

metricsDate ํ•„๋“œ ์ถ”๊ฐ€์™€ (product_id, metrics_date) ๋ณตํ•ฉ ์œ ๋‹ˆํฌ ์ œ์•ฝ์กฐ๊ฑด์ด ์ผ๋ณ„ ๋ฉ”ํŠธ๋ฆญ์Šค ์ง‘๊ณ„ ์š”๊ตฌ์‚ฌํ•ญ์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค. @Version์„ ํ†ตํ•œ ๋‚™๊ด€์  ๋ฝํ‚น๋„ ๊ธฐ์กด ํŒจํ„ด๊ณผ ์ผ๊ด€๋ฉ๋‹ˆ๋‹ค.


55-63: LGTM!

create() ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ๊ฐ€ metricsDate ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ํฌํ•จํ•˜๋„๋ก ์ ์ ˆํžˆ ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

apps/commerce-batch/src/main/java/com/loopers/domain/ranking/RankingType.java (1)

3-6: LGTM!

RankingType ์—ด๊ฑฐํ˜•์ด ๋ฐฐ์น˜ ์ž‘์—…์˜ ์ฃผ๊ฐ„/์›”๊ฐ„ ๋žญํ‚น ์œ ํ˜•์„ ๋ช…ํ™•ํžˆ ๊ตฌ๋ถ„ํ•ฉ๋‹ˆ๋‹ค. DAILY ๋žญํ‚น์€ Redis์—์„œ ์ฒ˜๋ฆฌ๋˜๋ฏ€๋กœ ์ด ์—ด๊ฑฐํ˜•์—์„œ ์ œ์™ธ๋œ ๊ฒƒ์ด ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค.

apps/commerce-batch/src/main/java/com/loopers/domain/metrics/ProductMetricsRepository.java (1)

6-8: LGTM!

๊ฐ„๊ฒฐํ•˜๊ณ  ๋ช…ํ™•ํ•œ ์ €์žฅ์†Œ ์ธํ„ฐํŽ˜์ด์Šค์ž…๋‹ˆ๋‹ค.

apps/commerce-batch/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsRepositoryImpl.java (1)

11-21: LGTM!

์ €์žฅ์†Œ ํŒจํ„ด์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ตฌํ˜„ํ–ˆ์œผ๋ฉฐ, JPA ์ €์žฅ์†Œ๋กœ์˜ ์œ„์ž„์ด ๊น”๋”ํ•ฉ๋‹ˆ๋‹ค.

apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingCommand.java (1)

7-19: LGTM!

RankingType enum๊ณผ ํ†ตํ•ฉ๋œ GetRankingCommand ๊ตฌ์กฐ๊ฐ€ ๊น”๋”ํ•˜๊ฒŒ ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. DAILY, WEEKLY, MONTHLY ํƒ€์ž…์„ ๋ช…ํ™•ํ•˜๊ฒŒ ๊ตฌ๋ถ„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

apps/commerce-streamer/src/test/java/com/loopers/interfaces/consumer/MetricsConsumerIdempotencyTest.java (1)

78-80: ๋‚ ์งœ ๊ธฐ๋ฐ˜ ์ฟผ๋ฆฌ๋กœ์˜ ์ „ํ™˜์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ ์šฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

findByProductIdAndMetricsDate๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‚ ์งœ๋ณ„ ๋ฉ”ํŠธ๋ฆญ ์กฐํšŒ๋กœ ์ •ํ™•ํ•˜๊ฒŒ ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. LocalDate.now() ์‚ฌ์šฉ์€ ๋™์ผ ๋‚ ์งœ ๋‚ด ๋ฉฑ๋“ฑ์„ฑ ํ…Œ์ŠคํŠธ์— ์ ํ•ฉํ•˜๋ฉฐ, ํ…Œ์ŠคํŠธ ๋กœ์ง์ด ์ •ํ™•ํ•ฉ๋‹ˆ๋‹ค.

Also applies to: 90-91, 100-101, 141-143, 152-153, 200-202, 213-215, 260-262, 270-272

apps/commerce-batch/src/main/java/com/loopers/domain/ranking/RankingWeight.java (1)

6-17: ๊ฐ€์ค‘์น˜ ์„ค์ •์ด ํ•ฉ๋ฆฌ์ ์ด์ง€๋งŒ ๋น„์ฆˆ๋‹ˆ์Šค ์š”๊ตฌ์‚ฌํ•ญ ๊ฒ€์ฆ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

๋žญํ‚น ์ ์ˆ˜ ๊ณ„์‚ฐ์„ ์œ„ํ•œ ๊ฐ€์ค‘์น˜๊ฐ€ ๋ช…ํ™•ํ•˜๊ฒŒ ์ •์˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค:

  • ์กฐํšŒ์ˆ˜: 10%
  • ์ข‹์•„์š”: 20%
  • ์ฃผ๋ฌธ ์ƒ์„ฑ: 70%

๊ฐ€์ค‘์น˜ ํ•ฉ์ด 1.0์œผ๋กœ ์ •๊ทœํ™”๋˜์–ด ์žˆ์–ด ์ ์ˆ˜ ๊ณ„์‚ฐ์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค. ์ฃผ๋ฌธ ์ƒ์„ฑ์— ๊ฐ€์žฅ ๋†’์€ ๊ฐ€์ค‘์น˜๋ฅผ ๋ถ€์—ฌํ•œ ๊ฒƒ์€ ์ „์ž์ƒ๊ฑฐ๋ž˜ ๋งฅ๋ฝ์—์„œ ํƒ€๋‹นํ•ด ๋ณด์ž…๋‹ˆ๋‹ค.

๋น„์ฆˆ๋‹ˆ์Šค ์ดํ•ด๊ด€๊ณ„์ž์™€ ์ด ๊ฐ€์ค‘์น˜ ๋น„์œจ์ด ์ œํ’ˆ ๋žญํ‚น ๋ชฉํ‘œ์™€ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

apps/commerce-batch/src/main/java/com/loopers/domain/ranking/RankingPeriod.java (2)

12-16: ์ฃผ๊ฐ„ ๊ธฐ๊ฐ„ ๊ณ„์‚ฐ ๋กœ์ง์ด ์ •ํ™•ํ•ฉ๋‹ˆ๋‹ค.

ISO-8601 ํ‘œ์ค€(์›”์š”์ผ ์‹œ์ž‘, ์ผ์š”์ผ ์ข…๋ฃŒ)์— ๋”ฐ๋ผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. TemporalAdjusters ํ™œ์šฉ๋„ ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค.


18-23: ์›”๊ฐ„ ๊ธฐ๊ฐ„ ๊ณ„์‚ฐ ๋กœ์ง์ด ์ •ํ™•ํ•ฉ๋‹ˆ๋‹ค.

YearMonth๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์›”์˜ ์ฒซ๋‚ ๊ณผ ๋งˆ์ง€๋ง‰ ๋‚ ์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.

apps/commerce-streamer/src/main/java/com/loopers/domain/metrics/ProductMetricsRepository.java (2)

11-11: ๋‚ ์งœ๋ณ„ ๋ฉ”ํŠธ๋ฆญ ์กฐํšŒ ๋ฉ”์„œ๋“œ๊ฐ€ ์ ์ ˆํ•˜๊ฒŒ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์ œํ’ˆ ID์™€ ๋ฉ”ํŠธ๋ฆญ ๋‚ ์งœ๋กœ ์กฐํšŒํ•˜๋Š” ๋ฉ”์„œ๋“œ๊ฐ€ ๋ช…ํ™•ํ•˜๊ฒŒ ์ •์˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค.


13-13: ๋ฉ”์„œ๋“œ ์‹œ๊ทธ๋‹ˆ์ฒ˜ ๋ณ€๊ฒฝ์ด ์ผ๊ด€๋˜๊ฒŒ ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

incrementLikeCount์˜ LocalDate metricsDate ํŒŒ๋ผ๋ฏธํ„ฐ ์ถ”๊ฐ€๊ฐ€ ๋ชจ๋“  ๊ณ„์ธต์—์„œ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ ์šฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ProductMetricsRepositoryImpl๊ณผ ProductMetricsJpaRepository์˜ ๊ตฌํ˜„์ด ์ธํ„ฐํŽ˜์ด์Šค ์‹œ๊ทธ๋‹ˆ์ฒ˜์™€ ์ผ์น˜ํ•˜๋ฉฐ, ProductMetricsService์˜ ๋ชจ๋“  ํ˜ธ์ถœ๋ถ€(21๋ฒˆ ์ค„, 29๋ฒˆ ์ค„)์—์„œ LocalDate ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ •ํ™•ํ•˜๊ฒŒ ์ „๋‹ฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

apps/commerce-batch/src/main/java/com/loopers/infrastructure/ranking/MvProductRankWeeklyJpaRepository.java (1)

12-16: ๋ณตํ•ฉ ํ‚ค ๊ธฐ๋ฐ˜ ์—”ํ‹ฐํ‹ฐ ์กฐํšŒ ๋ฉ”์„œ๋“œ๊ฐ€ ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค.

์ œํ’ˆ ID์™€ ๊ธฐ๊ฐ„์œผ๋กœ ์ฃผ๊ฐ„ ๋žญํ‚น์„ ์กฐํšŒํ•˜๋Š” ๋ฉ”์„œ๋“œ ์‹œ๊ทธ๋‹ˆ์ฒ˜๊ฐ€ ์˜ฌ๋ฐ”๋ฆ…๋‹ˆ๋‹ค.

apps/commerce-batch/src/main/java/com/loopers/domain/ranking/RankingCalculator.java (1)

8-24: ์ ์ˆ˜ ๊ณ„์‚ฐ ๋กœ์ง์ด ์ •ํ™•ํ•˜๊ณ  ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.

null ์ฒดํฌ์™€ ๊ฐ€์ค‘์น˜ ์ ์šฉ์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๊ฐ ๋ฉ”ํŠธ๋ฆญ์— ๋Œ€ํ•œ ๋ฐฉ์–ด์  ๊ฒ€์ฆ์ด ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค.

apps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyJpaRepository.java (1)

12-16: Pageable์„ ํ™œ์šฉํ•œ ํŽ˜์ด์ง• ์ฟผ๋ฆฌ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ๊ฐ„๋ณ„ ๋žญํ‚น ์กฐํšŒ ๋ฉ”์„œ๋“œ์— Pageable์ด ํฌํ•จ๋˜์–ด ์„ฑ๋Šฅ๊ณผ ํ™•์žฅ์„ฑ์„ ๊ณ ๋ คํ•œ ์„ค๊ณ„์ž…๋‹ˆ๋‹ค. (๋‹จ, Weekly ์ €์žฅ์†Œ์—๋Š” ์ด ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๋ˆ„๋ฝ๋˜์–ด ์žˆ์œผ๋‹ˆ Weekly ์ €์žฅ์†Œ ๋ฆฌ๋ทฐ ์ฝ”๋ฉ˜ํŠธ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”)

apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingProcessor.java (1)

9-21: LGTM!

Spring Batch ItemProcessor ๊ตฌํ˜„์ด ๊น”๋”ํ•ฉ๋‹ˆ๋‹ค. ProductMetrics๋ฅผ ๋ฐ›์•„ ProductRankingAggregate๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋กœ์ง์ด ๋ช…ํ™•ํ•˜๊ณ , RankingCalculator๋ฅผ ํ†ตํ•œ ์ ์ˆ˜ ๊ณ„์‚ฐ ์œ„์ž„์ด ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค.

apps/commerce-batch/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsJpaRepository.java (1)

11-14: LGTM!

๋‚ ์งœ ๊ธฐ๋ฐ˜ ์กฐํšŒ ์ฟผ๋ฆฌ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ORDER BY productId๋กœ ์ผ๊ด€๋œ ์ˆœ์„œ๋ฅผ ๋ณด์žฅํ•˜์—ฌ ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ ์‹œ ์žฌ์‹œ์ž‘ ๊ฐ€๋Šฅ์„ฑ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

apps/commerce-api/src/test/java/com/loopers/domain/ranking/RankingServiceIntegrationTest.java (2)

169-284: ์ฃผ๊ฐ„ ๋žญํ‚น ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€๊ฐ€ ์–‘ํ˜ธํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ์กฐํšŒ, ํŽ˜์ด์ง•, ๋นˆ ๋ฆฌ์ŠคํŠธ, null ๋žญํ‚น ํ•„ํ„ฐ๋ง ์ผ€์ด์Šค๋ฅผ ํฌํ•จํ•˜์—ฌ ํ•„์š”ํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค๊ฐ€ ์ž˜ ํ…Œ์ŠคํŠธ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.


286-401: ์›”๊ฐ„ ๋žญํ‚น ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€๊ฐ€ ์–‘ํ˜ธํ•ฉ๋‹ˆ๋‹ค.

์ฃผ๊ฐ„ ๋žญํ‚น ํ…Œ์ŠคํŠธ์™€ ๋™์ผํ•œ ๊ตฌ์กฐ๋กœ ์ผ๊ด€์„ฑ ์žˆ๊ฒŒ ์ž‘์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

apps/commerce-streamer/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsRepositoryImpl.java (1)

26-34: LGTM!

๋‚ ์งœ ๊ธฐ๋ฐ˜ ๋ฉ”ํŠธ๋ฆญ ์กฐํšŒ ๋ฐ ์—…๋ฐ์ดํŠธ ๋ฉ”์„œ๋“œ๊ฐ€ ์ ์ ˆํ•˜๊ฒŒ ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. incrementLikeCount๊ฐ€ int๋ฅผ ๋ฐ˜ํ™˜ํ•˜์—ฌ ์˜ํ–ฅ๋ฐ›์€ ํ–‰ ์ˆ˜๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด ์ข‹์Šต๋‹ˆ๋‹ค.

apps/commerce-batch/src/main/resources/application.yml (1)

13-25: ๋ฐฐ์น˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— web-application-type: servlet ์„ค์ • ํ™•์ธ

๋ฐฐ์น˜ ์ „์šฉ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ servlet ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋‹ˆํ„ฐ๋ง ์—”๋“œํฌ์ธํŠธ๋‚˜ Actuator๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์ ์ ˆํ•˜์ง€๋งŒ, ์ˆœ์ˆ˜ ๋ฐฐ์น˜ ์‹คํ–‰๋งŒ ํ•„์š”ํ•˜๋‹ค๋ฉด none์œผ๋กœ ์„ค์ •ํ•˜์—ฌ ๋ฆฌ์†Œ์Šค๋ฅผ ์ ˆ์•ฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

apps/commerce-batch/src/main/java/com/loopers/application/ranking/ProductMetricsItemReader.java (1)

28-31: @BeforeStep๊ณผ open() ํ˜ธ์ถœ ์ˆœ์„œ ์ฃผ์˜

ItemStream.open()์ด @BeforeStep๋ณด๋‹ค ๋จผ์ € ํ˜ธ์ถœ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ Line 49์—์„œ stepExecution์ด null์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. JobParameterUtils.getTargetDate()๊ฐ€ null์„ ์ฒ˜๋ฆฌํ•˜์—ฌ ์ „๋‚  ๋‚ ์งœ๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€๋งŒ, ์ด๊ฒƒ์ด ์˜๋„๋œ ๋™์ž‘์ธ์ง€ ํ™•์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ”Ž ์•ˆ์ „ํ•œ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ๋Œ€์•ˆ

StepExecutionListener๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ beforeStep์—์„œ ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ์„ ์ˆ˜ํ–‰ํ•˜๊ฑฐ๋‚˜, open()์—์„œ StepSynchronizationManager๋ฅผ ํ†ตํ•ด StepExecution์„ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ•์„ ๊ณ ๋ คํ•ด ์ฃผ์„ธ์š”.

@Override
public void open(ExecutionContext executionContext) throws ItemStreamException {
    // StepExecution์ด null์ธ ๊ฒฝ์šฐ ๋Œ€๋น„
    StepExecution stepExec = this.stepExecution;
    if (stepExec == null) {
        stepExec = StepSynchronizationManager.getContext().getStepExecution();
    }
    LocalDate targetDate = JobParameterUtils.getTargetDate(stepExec);
    // ...
}

Also applies to: 41-53

apps/commerce-streamer/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsJpaRepository.java (1)

14-14: ๋‚ ์งœ ๊ธฐ๋ฐ˜ ์กฐํšŒ ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€ ํ™•์ธ

findByProductIdAndMetricsDate ๋ฉ”์„œ๋“œ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. Spring Data JPA ๋„ค์ด๋ฐ ์ปจ๋ฒค์…˜์„ ์ค€์ˆ˜ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ๋‚ ์งœ๋ณ„ ๋ฉ”ํŠธ๋ฆญ ์กฐํšŒ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

apps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankWeeklyJpaRepository.java (1)

10-17: ํ˜„์žฌ ์ธํ„ฐํŽ˜์ด์Šค ์„ค๊ณ„๋Š” ์˜ฌ๋ฐ”๋ฆ„

MvProductRankWeeklyJpaRepository (commerce-api ๋ชจ๋“ˆ)๋Š” ์‹ค์ œ ๊ตฌํ˜„์ฒด(MvProductRankWeeklyRepositoryImpl)์—์„œ findByPeriodStartDateAndPeriodEndDateOrderByRankingAsc ๋ฉ”์„œ๋“œ๋งŒ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ ํ˜„์žฌ ์ฝ”๋“œ๋Š” ์ •ํ™•ํ•ฉ๋‹ˆ๋‹ค.

findByProductIdAndPeriodStartDateAndPeriodEndDate ๋ฉ”์„œ๋“œ๋Š” commerce-batch ๋ชจ๋“ˆ์˜ ๋ณ„๋„ MvProductRankWeeklyJpaRepository์—๋งŒ ์กด์žฌํ•˜๋ฉฐ, ๋‘ ๋ชจ๋“ˆ์€ ์„œ๋กœ ๋‹ค๋ฅธ ์š”๊ตฌ์‚ฌํ•ญ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ AI ์š”์•ฝ๊ณผ์˜ ๋ถˆ์ผ์น˜๋Š” ์‹ค์ œ ๋ฌธ์ œ๊ฐ€ ์•„๋‹ˆ๋ฉฐ, ํ˜„์žฌ ์ฝ”๋“œ๋Š” ๋ชจ๋“ˆ์˜ ์‚ฌ์šฉ ํŒจํ„ด๊ณผ ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค.

Likely an incorrect or invalid review comment.

apps/commerce-batch/src/main/java/com/loopers/interfaces/scheduler/RankingBatchScheduler.java (2)

53-73: ์›”๊ฐ„ ๋žญํ‚น ๋ฐฐ์น˜ ์ž‘์—…์ด ๋งค์ผ ์‹คํ–‰๋˜๋Š” ์Šค์ผ€์ค„ ๊ฒ€์ฆ ํ•„์š”

ํ˜„์žฌ cron ํ‘œํ˜„์‹ "0 30 3 * * *"๋Š” ๋งค์ผ 03:30์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ์›”๊ฐ„(MONTHLY) ๋žญํ‚น์ด ์ •๋ง ๋งค์ผ ์‹คํ–‰๋˜์–ด์•ผ ํ•˜๋Š”์ง€ ํ™•์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

์ฃผ๊ฐ„(WEEKLY) ๋ฐฐ์น˜๋„ ๋™์ผํ•œ ํŒจํ„ด์œผ๋กœ ๋งค์ผ ์‹คํ–‰๋˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ, ์ด๋Š” ์ผ๊ด€๋œ ์„ค๊ณ„ ์„ ํƒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค:

  • ์ฃผ๊ฐ„ ๋ฐฐ์น˜: ๋งค์ผ 03:00 ์‹คํ–‰
  • ์›”๊ฐ„ ๋ฐฐ์น˜: ๋งค์ผ 03:30 ์‹คํ–‰

๊ณ ๋ ค์‚ฌํ•ญ:

  • ๋งŒ์•ฝ rolling window ๋ฐฉ์‹(7์ผ/30์ผ ์œˆ๋„์šฐ๋ฅผ ๋งค์ผ ๊ฐฑ์‹ )์ด ์˜๋„๋ผ๋ฉด ํ˜„์žฌ ๊ตฌํ˜„์ด ๋งž์Šต๋‹ˆ๋‹ค.
  • ํ•˜์ง€๋งŒ ์ผ๋ฐ˜์ ์œผ๋กœ ์›”๊ฐ„ ๋žญํ‚น์€ ์›” 1ํšŒ๋งŒ ์‹คํ–‰๋˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ž…๋‹ˆ๋‹ค.
  • ๋งค์ผ ์‹คํ–‰ ์‹œ ์ƒ๋‹นํ•œ ๊ณ„์‚ฐ ๋น„์šฉ๊ณผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ถ€ํ•˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋น„์ฆˆ๋‹ˆ์Šค ์š”๊ตฌ์‚ฌํ•ญ๊ณผ ๊ตฌํ˜„ ์˜๋„(rolling window vs ์›”๋ณ„/์ฃผ๋ณ„ ์ •์‚ฐ)๋ฅผ ๋ช…ํ™•ํžˆ ํ™•์ธํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.


31-51: ํ˜„์žฌ ์Šค์ผ€์ค„๋ง ์„ค๊ณ„๋Š” ์˜๋„์ ์ธ ๋กค๋ง ์œˆ๋„์šฐ ํŒจํ„ด์ด๋ฏ€๋กœ ์ˆ˜์ • ๋ถˆํ•„์š”

์ฝ”๋“œ๋ฅผ ๊ฒ€ํ† ํ•œ ๊ฒฐ๊ณผ, ์ฃผ๊ฐ„ ๋žญํ‚น ๋ฐฐ์น˜๊ฐ€ ๋งค์ผ ์‹คํ–‰๋˜๋Š” ๊ฒƒ์€ ์˜๋„์ ์ธ ์„ค๊ณ„์ž…๋‹ˆ๋‹ค:

  • ProductMetricsItemReader: ์–ด์ œ์˜ ์ผ์ผ ๋ฉ”ํŠธ๋ฆญ์„ ์กฐํšŒ
  • RankingPeriod.ofWeek(): ํ•ด๋‹น ๋‚ ์งœ๊ฐ€ ์†ํ•œ ์ฃผ(์›”์š”์ผ~์ผ์š”์ผ)๋ฅผ ์ž๋™ ๊ณ„์‚ฐ
  • WeeklyRankingService.upsertMetrics(): ํ•ด๋‹น ์ฃผ์˜ ๊ธฐ๊ฐ„์— ๋ฉ”ํŠธ๋ฆญ ์—…๋ฐ์ดํŠธ
  • calculateAndUpdateRanking(): ๊ทธ ์ฃผ์˜ ์ƒ์œ„ 100๊ฐœ ์ƒํ’ˆ ์žฌ๊ณ„์‚ฐ

๋งค์ผ ์ƒˆ๋กœ์šด ์ผ์ผ ๋ฉ”ํŠธ๋ฆญ์ด ํ•ด๋‹น ์ฃผ์— ๋ฐ˜์˜๋˜์–ด ์ตœ์‹  ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ฃผ๊ฐ„ ๋žญํ‚น์ด ์œ ์ง€๋˜๋Š” ๋กค๋ง ์œˆ๋„์šฐ ํŒจํ„ด์ž…๋‹ˆ๋‹ค. ์›”๊ฐ„ ๋žญํ‚น๋„ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์ •์ƒ์ ์ด๊ณ  ์˜๋„๋œ ๋™์ž‘์ด๋ฏ€๋กœ ํ˜„์žฌ ๊ตฌํ˜„์ด ์˜ฌ๋ฐ”๋ฆ…๋‹ˆ๋‹ค.

apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingWriter.java (1)

40-63: LGTM - ๊ตฌํ˜„์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

๊ฐœ๋ณ„ ์•„์ดํ…œ์„ ์ˆœํšŒํ•˜๋ฉฐ rankingType์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ์„œ๋น„์Šค๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋กœ์ง์ด ๋ช…ํ™•ํ•ฉ๋‹ˆ๋‹ค. ์ฒญํฌ ์‚ฌ์ด์ฆˆ(100)์— ๋Œ€ํ•ด ๊ฐœ๋ณ„ upsert ํ˜ธ์ถœ์ด ๋ฐœ์ƒํ•˜์ง€๋งŒ, ํ˜„์žฌ ์š”๊ตฌ์‚ฌํ•ญ์—์„œ๋Š” ์ˆ˜์šฉ ๊ฐ€๋Šฅํ•œ ์ˆ˜์ค€์ž…๋‹ˆ๋‹ค.

apps/commerce-batch/src/main/java/com/loopers/domain/ranking/ProductRankingAggregate.java (1)

5-30: LGTM - ๊น”๋”ํ•œ aggregate ๊ตฌํ˜„์ž…๋‹ˆ๋‹ค.

addMetrics์—์„œ null ์ฒ˜๋ฆฌ๊ฐ€ ์ ์ ˆํ•˜๊ฒŒ ๋˜์–ด ์žˆ๊ณ , RankingCalculator์— ์ ์ˆ˜ ๊ณ„์‚ฐ์„ ์œ„์ž„ํ•˜์—ฌ ๋‹จ์ผ ์ฑ…์ž„ ์›์น™์„ ์ž˜ ๋”ฐ๋ฅด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

apps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankWeeklyRepositoryImpl.java (1)

19-23: ์ธํ„ฐํŽ˜์ด์Šค์™€ ๊ตฌํ˜„ ์‹œ๊ทธ๋‹ˆ์ฒ˜๊ฐ€ ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค.

MvProductRankWeeklyRepository ์ธํ„ฐํŽ˜์ด์Šค(apps/commerce-api/src/main/java/com/loopers/domain/ranking/MvProductRankWeeklyRepository.java, Line 7)์—์„œ ๋ฉ”์„œ๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •์˜๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค:

List<MvProductRankWeekly> findByPeriodOrderByRankingAsc(LocalDate periodStartDate, LocalDate periodEndDate, int page, int size);

์ด๋Š” ๊ตฌํ˜„์ฒด์˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜์™€ ์ •ํ™•ํžˆ ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค. ์ปดํŒŒ์ผ ์—๋Ÿฌ๋Š” ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

apps/commerce-batch/src/main/java/com/loopers/application/ranking/JobParameterUtils.java (1)

16-32: LGTM!

getTargetDate ๋ฉ”์„œ๋“œ๋Š” null ์ฒดํฌ, ํŒŒ์‹ฑ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ, ์ ์ ˆํ•œ ๊ธฐ๋ณธ๊ฐ’ ๋ฐ˜ํ™˜ ๋“ฑ ๋ฐฉ์–ด์ ์œผ๋กœ ์ž˜ ๊ตฌํ˜„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

apps/commerce-batch/src/main/java/com/loopers/domain/ranking/WeeklyRankingService.java (1)

20-42: LGTM!

upsert ๋กœ์ง์ด ๋ช…ํ™•ํ•˜๊ณ  ์ •ํ™•ํ•˜๊ฒŒ ๊ตฌํ˜„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ifPresentOrElse๋ฅผ ์‚ฌ์šฉํ•œ ๊ตฌ์กฐ๋„ ์ฝ๊ธฐ ์ข‹์Šต๋‹ˆ๋‹ค.

apps/commerce-api/src/main/java/com/loopers/domain/ranking/MvProductRankMonthly.java (1)

13-76: commerce-api์™€ commerce-batch ๊ฐ„ ์—”ํ‹ฐํ‹ฐ ์ค‘๋ณต์„ ๊ฒ€ํ† ํ•˜์„ธ์š”.

PR ์„ค๋ช…์— ์–ธ๊ธ‰๋œ ๋Œ€๋กœ, ๋™์ผํ•œ MV ํ…Œ์ด๋ธ”์„ ๊ฐ€๋ฆฌํ‚ค๋Š” ์—”ํ‹ฐํ‹ฐ๊ฐ€ commerce-api์™€ commerce-batch์— ๊ฐ๊ฐ ์ •์˜๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ฝ”๋“œ ์ค‘๋ณต๊ณผ ๋ถˆ์ผ์น˜ ์œ„ํ—˜์„ ์ดˆ๋ž˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. modules/jpa์™€ ๊ฐ™์€ ๊ณตํ†ต ๋ชจ๋“ˆ๋กœ ์ด๋™ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณด์„ธ์š”.

์ด ๋ฌธ์ œ์— ๋Œ€ํ•œ ํŒ€์˜ ๊ฒฐ์ • ์‚ฌํ•ญ์„ ํ™•์ธํ•˜๊ณ , ๊ณต์œ  ์—”ํ‹ฐํ‹ฐ๋กœ ํ†ตํ•ฉํ•  ๊ณ„ํš์ด ์žˆ๋Š”์ง€ ๊ฒ€ํ† ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

apps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1ApiSpec.java (1)

16-25: LGTM!

API ์‹œ๊ทธ๋‹ˆ์ฒ˜๊ฐ€ ์ผ๊ฐ„/์ฃผ๊ฐ„/์›”๊ฐ„ ๋žญํ‚น์„ ์ง€์›ํ•˜๋„๋ก ๋ช…ํ™•ํ•˜๊ฒŒ ํ™•์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ํŒŒ๋ผ๋ฏธํ„ฐ ์ˆœ์„œ๋„ ์ง๊ด€์ ์ž…๋‹ˆ๋‹ค.

apps/commerce-batch/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyRepositoryImpl.java (1)

12-37: LGTM!

Repository ๊ตฌํ˜„์ด ๋ช…ํ™•ํ•˜๊ณ  JPA repository๋กœ ์ ์ ˆํžˆ ์œ„์ž„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

apps/commerce-streamer/src/main/java/com/loopers/domain/metrics/ProductMetricsService.java (1)

18-32: ์ข‹์•„์š” ์ฆ๊ฐ€ ์‹œ ๊ฒฝ์Ÿ ์กฐ๊ฑด(race condition) ์ฒ˜๋ฆฌ๊ฐ€ ์ž˜ ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

์›์ž์  ์—…๋ฐ์ดํŠธ๋ฅผ ๋จผ์ € ์‹œ๋„ํ•˜๊ณ , ํ–‰์ด ์—†์œผ๋ฉด ์ƒˆ๋กœ ์ƒ์„ฑํ•˜๋ฉฐ, ๋™์‹œ์— ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ๊ฐ€ ์ƒ์„ฑํ–ˆ์„ ๊ฒฝ์šฐ DataIntegrityViolationException์„ catchํ•˜์—ฌ ๋‹ค์‹œ ์—…๋ฐ์ดํŠธํ•˜๋Š” ํŒจํ„ด์ด ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค.

apps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingJobConfig.java (1)

38-61: Job ๊ตฌ์„ฑ LGTM

๋‘ ๋‹จ๊ณ„ ๊ตฌ์„ฑ์ด ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค:

  1. rankingStep: ์ฒญํฌ ์ฒ˜๋ฆฌ๋กœ ProductMetrics๋ฅผ ์ฝ์–ด ์ ์ˆ˜ ๊ณ„์‚ฐ ํ›„ MV ํ…Œ์ด๋ธ”์— upsert
  2. rankingCalculationStep: TOP 100 ์ถ”์ถœ ๋ฐ ๋žญํ‚น ์—…๋ฐ์ดํŠธ
apps/commerce-batch/src/main/java/com/loopers/domain/metrics/ProductMetrics.java (1)

14-64: ์—”ํ‹ฐํ‹ฐ ๊ตฌ์กฐ LGTM

๋ฐฐ์น˜ ๋ชจ๋“ˆ์—์„œ ์ฝ๊ธฐ ์ „์šฉ์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ์—”ํ‹ฐํ‹ฐ๋กœ ์ ์ ˆํ•˜๊ฒŒ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. @Version ํ•„๋“œ๋Š” ๋‚™๊ด€์  ๋ฝํ‚น์„ ์œ„ํ•ด ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฉฐ, unique constraint๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์„ค์ •๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

PR objectives์—์„œ ์–ธ๊ธ‰๋œ ๊ฒƒ์ฒ˜๋Ÿผ, commerce-batch์™€ commerce-streamer๊ฐ€ ๋ณ„๋„์˜ ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํ˜„์žฌ ๊ตฌ์กฐ๋Š” ์ž‘๋™ํ•˜์ง€๋งŒ, ๋ชจ๋“ˆ๊ฐ„ ์ผ๊ด€์„ฑ์„ ์œ„ํ•ด ๊ณต์œ  ๋ชจ๋“ˆ(modules/jpa)๋กœ ํ†ตํ•ฉํ•˜๋Š” ๊ฒƒ๋„ ๊ณ ๋ คํ•ด ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

apps/commerce-api/src/test/java/com/loopers/application/ranking/RankingFacadeIntegrationTest.java (1)

85-90: ์ƒˆ ์ปค๋งจ๋“œ ๊ตฌ์กฐ ์ ์šฉ LGTM

GetRankingCommand์™€ RankingType.DAILY ์‚ฌ์šฉ์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

WEEKLY/MONTHLY ํƒ€์ž…์— ๋Œ€ํ•œ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋„ ์ถ”๊ฐ€ํ•˜๋ฉด API ๋ณ€๊ฒฝ์— ๋Œ€ํ•œ ๊ฒ€์ฆ์ด ๋” ์™„๋ฒฝํ•ด์งˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

apps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1Controller.java (1)

32-42: ๋žญํ‚น ํƒ€์ž… ๊ฒ€์ฆ ๋ฐ API ํ†ตํ•ฉ LGTM

์ž˜๋ชป๋œ ๋žญํ‚น ํƒ€์ž…์— ๋Œ€ํ•œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ฐ€ ์ ์ ˆํ•˜๋ฉฐ, ์ƒˆ๋กœ์šด ํ†ตํ•ฉ API ๊ตฌ์กฐ๊ฐ€ ์ž˜ ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

apps/commerce-batch/src/main/java/com/loopers/domain/ranking/MonthlyRankingService.java (1)

24-26: ๋ฉ”์„œ๋“œ๊ฐ€ MvProductRankMonthly ์—”ํ‹ฐํ‹ฐ์— ์ •์˜๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. updateMetrics() (๋ผ์ธ 85-90)๊ณผ updateRanking() (๋ผ์ธ 92-94)์ด ๋ชจ๋‘ ๊ตฌํ˜„๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ์ฝ”๋“œ๋Š” ์ •์ƒ์ ์œผ๋กœ ์ปดํŒŒ์ผ๋˜๊ณ  ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

Likely an incorrect or invalid review comment.

apps/commerce-batch/src/main/java/com/loopers/infrastructure/ranking/MvProductRankWeeklyRepositoryImpl.java (2)

12-37: Repository ๊ตฌํ˜„์ด ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค.

JpaRepository๋กœ์˜ ์œ„์ž„ ํŒจํ„ด์ด ์ž˜ ์ ์šฉ๋˜์–ด ์žˆ๊ณ , ๋„๋ฉ”์ธ ๋ ˆ์ด์–ด์™€ ์ธํ”„๋ผ์ŠคํŠธ๋Ÿญ์ฒ˜ ๋ ˆ์ด์–ด๊ฐ€ ์ ์ ˆํžˆ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์ฝ”๋“œ๋ฒ ์ด์Šค์—์„œ Spring Data์˜ Page/Pageable ํƒ€์ž… ์‚ฌ์šฉ์ด ํ—ˆ์šฉ๋œ ํŒจํ„ด์ž„์„ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.


23-26: ์ด ๋ฆฌ๋ทฐ ์ง€์ ์€ ๋ถ€์ •ํ™•ํ•ฉ๋‹ˆ๋‹ค. ํŽ˜์ด์ง• ์ง€์› ๋ˆ„๋ฝ์ด ์•„๋‹ˆ๋ผ ์˜๋„์  ์„ค๊ณ„์ž…๋‹ˆ๋‹ค.

commerce-batch ๋ชจ๋“ˆ์˜ MvProductRankWeeklyRepository ์ธํ„ฐํŽ˜์ด์Šค๋Š” ํŽ˜์ด์ง• ํŒŒ๋ผ๋ฏธํ„ฐ ์—†์ด ์ •์˜๋˜์–ด ์žˆ์œผ๋ฉฐ(LocalDate periodStartDate, LocalDate periodEndDate), ๊ตฌํ˜„์ฒด๊ฐ€ ์ด๋ฅผ ์ •ํ™•ํ•˜๊ฒŒ ๋”ฐ๋ฅด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฐฐ์น˜ ์ฒ˜๋ฆฌ์šฉ ๋ชจ๋“ˆ์ด๋ฏ€๋กœ ํŽ˜์ด์ง•์ด ํ•„์š” ์—†์œผ๋ฉฐ, ๋ณ„๋„ ๋ชจ๋“ˆ์ธ commerce-api์— ํŽ˜์ด์ง•์„ ์ง€์›ํ•˜๋Š” ๋ณ„๋„์˜ ์ธํ„ฐํŽ˜์ด์Šค์™€ ๊ตฌํ˜„์ฒด๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ ๋ชจ๋“ˆ์˜ ์—ญํ• ์— ๋งž๊ฒŒ ์„ค๊ณ„๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ์ˆ˜์ •์ด ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Likely an incorrect or invalid review comment.

Comment on lines +13 to +76
@Entity
@Table(
name = "mv_product_rank_monthly",
uniqueConstraints = {
@UniqueConstraint(columnNames = {"product_id", "period_start_date", "period_end_date"})
}
)
@Getter
public class MvProductRankMonthly extends BaseEntity {

@Column(name = "product_id", nullable = false)
private Long productId;

@Column(nullable = true)
private Integer ranking;

@Column(nullable = false)
private Double score;

@Column(name = "period_start_date", nullable = false)
private LocalDate periodStartDate;

@Column(name = "period_end_date", nullable = false)
private LocalDate periodEndDate;

@Column(name = "like_count", nullable = false)
private Long likeCount;

@Column(name = "view_count", nullable = false)
private Long viewCount;

@Column(name = "sales_count", nullable = false)
private Long salesCount;

@Builder
private MvProductRankMonthly(Long productId, Integer ranking, Double score, LocalDate periodStartDate,
LocalDate periodEndDate, Long likeCount, Long viewCount, Long salesCount) {
this.productId = productId;
this.ranking = ranking;
this.score = score;
this.periodStartDate = periodStartDate;
this.periodEndDate = periodEndDate;
this.likeCount = likeCount;
this.viewCount = viewCount;
this.salesCount = salesCount;
}

public MvProductRankMonthly() {
}

public static MvProductRankMonthly create(Long productId, Integer ranking, Double score, LocalDate periodStartDate,
LocalDate periodEndDate, Long likeCount, Long viewCount, Long salesCount) {
return MvProductRankMonthly.builder()
.productId(productId)
.ranking(ranking)
.score(score)
.periodStartDate(periodStartDate)
.periodEndDate(periodEndDate)
.likeCount(likeCount)
.viewCount(viewCount)
.salesCount(salesCount)
.build();
}
}
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐Ÿ”ด Critical

์—”ํ‹ฐํ‹ฐ์— ํ•„์š”ํ•œ ์—…๋ฐ์ดํŠธ ๋ฉ”์„œ๋“œ๊ฐ€ ๋ˆ„๋ฝ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

MonthlyRankingService์—์„œ existing.updateMetrics(score, likeCount, viewCount, salesCount)์™€ rank.updateRanking(i + 1)๋ฅผ ํ˜ธ์ถœํ•˜๋Š”๋ฐ, ์ด ์—”ํ‹ฐํ‹ฐ์—๋Š” ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋“ค์ด ์—†์Šต๋‹ˆ๋‹ค. ์ด ๋ฉ”์„œ๋“œ๋“ค์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ”Ž ์ œ์•ˆํ•˜๋Š” ์ˆ˜์ •
     public MvProductRankMonthly() {
     }
 
+    public void updateMetrics(Double score, Long likeCount, Long viewCount, Long salesCount) {
+        this.score = score;
+        this.likeCount = likeCount;
+        this.viewCount = viewCount;
+        this.salesCount = salesCount;
+    }
+
+    public void updateRanking(Integer ranking) {
+        this.ranking = ranking;
+    }
+
     public static MvProductRankMonthly create(Long productId, Integer ranking, Double score, LocalDate periodStartDate,
๐Ÿค– Prompt for AI Agents
In
apps/commerce-api/src/main/java/com/loopers/domain/ranking/MvProductRankMonthly.java
around lines 13 to 76, the entity is missing the updateMetrics(...) and
updateRanking(...) methods used by MonthlyRankingService; add a public method
updateMetrics(Double score, Long likeCount, Long viewCount, Long salesCount)
that updates the score, likeCount, viewCount and salesCount fields (and
optionally validates/null-checks inputs) and a public method
updateRanking(Integer ranking) that sets the ranking field; keep methods simple
mutators (void) and place them alongside the existing constructors/builders so
the service calls compile.

Comment on lines +47 to +58
@Builder
private MvProductRankWeekly(Long productId, Integer ranking, Double score, LocalDate periodStartDate, LocalDate periodEndDate,
Long likeCount, Long viewCount, Long salesCount) {
this.productId = productId;
this.ranking = ranking;
this.score = score;
this.periodStartDate = periodStartDate;
this.periodEndDate = periodEndDate;
this.likeCount = likeCount;
this.viewCount = viewCount;
this.salesCount = salesCount;
}
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐Ÿ”ด Critical

๋ˆ„๋ฝ๋œ ๋ฉ”์„œ๋“œ: updateMetrics ๋ฐ updateRanking

WeeklyRankingService์—์„œ existing.updateMetrics(...) ๋ฐ rank.updateRanking(...)์„ ํ˜ธ์ถœํ•˜์ง€๋งŒ, ์ด ์—”ํ‹ฐํ‹ฐ์— ํ•ด๋‹น ๋ฉ”์„œ๋“œ๊ฐ€ ์ •์˜๋˜์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ปดํŒŒ์ผ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ”Ž ๊ถŒ์žฅ ์ˆ˜์ •์•ˆ
     public MvProductRankWeekly() {
     }

+    public void updateMetrics(Double score, Long likeCount, Long viewCount, Long salesCount) {
+        this.score = score;
+        this.likeCount = likeCount;
+        this.viewCount = viewCount;
+        this.salesCount = salesCount;
+    }
+
+    public void updateRanking(Integer ranking) {
+        this.ranking = ranking;
+    }
+
     public static MvProductRankWeekly create(Long productId, Integer ranking, Double score, LocalDate periodStartDate,

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +10 to +18
public interface MvProductRankMonthlyJpaRepository extends JpaRepository<MvProductRankMonthly, Long> {

List<MvProductRankMonthly> findByPeriodStartDateAndPeriodEndDateOrderByRankingAsc(
LocalDate periodStartDate,
LocalDate periodEndDate,
Pageable pageable
);

}
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐Ÿ”ด Critical

๐Ÿงฉ Analysis chain

๐Ÿ Script executed:

# Find the Weekly repository interface
fd -t f "MvProductRankWeeklyJpaRepository" --type=file

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 299


๐Ÿ Script executed:

# Check the Monthly repository file mentioned in the review
cat "apps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyJpaRepository.java"

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 660


๐Ÿ Script executed:

# Search for all methods in the Weekly repository
rg "findByProductId" --type=java -A 5 | head -50

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 7308


๐Ÿ Script executed:

# Check for usage of Monthly repository's methods
rg "MvProductRankMonthlyJpaRepository" --type=java -A 3

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 3870


๐Ÿ Script executed:

cat "apps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyRepositoryImpl.java"

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 1087


findByProductIdAndPeriodStartDateAndPeriodEndDate ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€ ํ•„์š”

์ด Monthly ์ €์žฅ์†Œ์—์„œ findByProductIdAndPeriodStartDateAndPeriodEndDate ๋ฉ”์„œ๋“œ๊ฐ€ ๋ˆ„๋ฝ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. Weekly ์ €์žฅ์†Œ์—๋Š” ์žˆ์œผ๋ฉฐ, ๋™์ผํ•œ ์—”ํ‹ฐํ‹ฐ์— ๋Œ€ํ•œ batch ๋ชจ๋“ˆ์˜ MvProductRankMonthlyJpaRepository์—๋„ ์ด ๋ฉ”์„œ๋“œ๊ฐ€ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

๋ชจ๋“ˆ ๊ฐ„ ์ธํ„ฐํŽ˜์ด์Šค ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”:

Optional<MvProductRankMonthly> findByProductIdAndPeriodStartDateAndPeriodEndDate(
        Long productId,
        LocalDate periodStartDate,
        LocalDate periodEndDate
);
๐Ÿค– Prompt for AI Agents
In
apps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyJpaRepository.java
around lines 10 to 18, the repository is missing the method
findByProductIdAndPeriodStartDateAndPeriodEndDate required for consistency with
the weekly repository and the batch module; add the following method signature
to the interface: Optional<MvProductRankMonthly>
findByProductIdAndPeriodStartDateAndPeriodEndDate(Long productId, LocalDate
periodStartDate, LocalDate periodEndDate); ensuring imports for Optional and
LocalDate are available and the method follows the existing repository style.

Comment on lines +3 to +8
implementation(project(":modules:jpa"))
implementation(project(":modules:redis"))
implementation(project(":modules:kafka"))
implementation(project(":supports:jackson"))
implementation(project(":supports:logging"))
implementation(project(":supports:monitoring"))
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐ŸŸก Minor

๐Ÿงฉ Analysis chain

๐Ÿ Script executed:

#!/bin/bash
# Check for Redis and Kafka usage in commerce-batch

echo "=== Checking for Redis usage ==="
rg -n --type=java --type=kt -C3 'RedisTemplate|StringRedisTemplate|@EnableRedis|RedisConfig|redis' apps/commerce-batch/ 2>/dev/null || echo "No Redis usage found"

echo -e "\n=== Checking for Kafka usage ==="
rg -n --type=java --type=kt -C3 'KafkaTemplate|@KafkaListener|@EnableKafka|KafkaConfig|kafka' apps/commerce-batch/ 2>/dev/null || echo "No Kafka usage found"

echo -e "\n=== Checking directory structure ==="
find apps/commerce-batch -type f \( -name "*.java" -o -name "*.kt" \) | head -20

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 1972


๐Ÿ Script executed:

#!/bin/bash
# Check the actual build.gradle.kts for commerce-batch to understand all dependencies

echo "=== Full build.gradle.kts content for commerce-batch ==="
cat apps/commerce-batch/build.gradle.kts

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 999


๐Ÿ Script executed:

#!/bin/bash
# List all source files in commerce-batch

echo "=== Source files in commerce-batch ==="
find apps/commerce-batch -type f \( -name "*.java" -o -name "*.kt" \) -exec wc -l {} + | sort -n

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 3035


commerce-batch์—์„œ Redis์™€ Kafka ์˜์กด์„ฑ์„ ์ œ๊ฑฐํ•˜์„ธ์š”.

์ฝ”๋“œ๋ฒ ์ด์Šค ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ, commerce-batch์˜ ๋ชจ๋“  ์†Œ์Šค ํŒŒ์ผ(1,224์ค„)์—์„œ Redis๋‚˜ Kafka ๊ด€๋ จ ํด๋ž˜์Šค(RedisTemplate, KafkaTemplate, @KafkaListener ๋“ฑ)์˜ ์‚ฌ์šฉ์ด ํ™•์ธ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ฐฐ์น˜ ์ž‘์—…์€ product_metrics ํ…Œ์ด๋ธ”์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ณ  MV(Materialized View) ํ…Œ์ด๋ธ”์— ์ˆœ์œ„ ์ •๋ณด๋ฅผ ์“ฐ๋Š” ์ˆœ์ˆ˜ ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ๋งŒ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

๋ถˆํ•„์š”ํ•œ ์˜์กด์„ฑ์„ ์ œ๊ฑฐํ•˜๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ ์‹œ๊ฐ„๊ณผ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์„ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

dependencies {
    implementation(project(":modules:jpa"))
    // implementation(project(":modules:redis"))  // ์‚ฌ์šฉ๋˜์ง€ ์•Š์Œ
    // implementation(project(":modules:kafka"))  // ์‚ฌ์šฉ๋˜์ง€ ์•Š์Œ
    implementation(project(":supports:jackson"))
    implementation(project(":supports:logging"))
    implementation(project(":supports:monitoring"))
    ...
}
๐Ÿค– Prompt for AI Agents
In apps/commerce-batch/build.gradle.kts around lines 3 to 8, the commerce-batch
module declares unused Redis and Kafka project dependencies; remove or comment
out the implementation(project(":modules:redis")) and
implementation(project(":modules:kafka")) lines to eliminate unnecessary startup
and memory overhead, then re-sync/refresh Gradle to ensure the build works
without those dependencies and run the test/build to confirm nothing else
requires them.

implementation("org.springframework.boot:spring-boot-starter-batch")

// web
implementation("org.springframework.boot:spring-boot-starter-web")
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐ŸŸ  Major

๐Ÿงฉ Analysis chain

๐Ÿ Script executed:

#!/bin/bash
# Check for web-related functionality in commerce-batch

echo "=== Checking for Controller/RestController annotations ==="
rg -n --type=java --type=kt -C3 '@Controller|@RestController|@RequestMapping' apps/commerce-batch/ || echo "No controllers found"

echo -e "\n=== Checking for WebMvc configuration ==="
rg -n --type=java --type=kt -C3 '@EnableWebMvc|WebMvcConfigurer' apps/commerce-batch/ || echo "No WebMvc configuration found"

echo -e "\n=== Checking for servlet/web-related imports ==="
rg -n --type=java --type=kt 'org\.springframework\.web|javax\.servlet|jakarta\.servlet' apps/commerce-batch/ || echo "No servlet/web imports found"

echo -e "\n=== Listing all files in commerce-batch source ==="
find apps/commerce-batch -name "*.kt" -o -name "*.java" | head -20

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 2207


๐Ÿ Script executed:

#!/bin/bash
# Check for actuator or monitoring setup

echo "=== Checking for actuator imports/configuration ==="
rg -n 'actuator|health|metrics' apps/commerce-batch/build.gradle.kts

echo -e "\n=== Checking for server configuration ==="
rg -n 'server\.port|server\.servlet' apps/commerce-batch/ || echo "No server configuration found"

echo -e "\n=== Viewing build.gradle.kts to see all dependencies ==="
cat apps/commerce-batch/build.gradle.kts

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 1125


spring-boot-starter-web ์˜์กด์„ฑ์„ ์ œ๊ฑฐํ•˜์„ธ์š”.

์ด ๋ฐฐ์น˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์›น ๊ธฐ๋Šฅ์ด ์ „ํ˜€ ์‚ฌ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ปจํŠธ๋กค๋Ÿฌ, ์›น ์„ค์ •, ์„œ๋ธ”๋ฆฟ ๊ด€๋ จ ์ฝ”๋“œ๊ฐ€ ์—†์œผ๋ฉฐ, ์„œ๋ฒ„ ํฌํŠธ ์„ค์ •๋„ ์—†์Šต๋‹ˆ๋‹ค. ์›น ์˜์กด์„ฑ์„ ์ œ๊ฑฐํ•˜๋ฉด ๋ถˆํ•„์š”ํ•œ ์˜ค๋ฒ„ํ—ค๋“œ(Tomcat, ์›น ์Šคํƒ, ์Šค๋ ˆ๋“œ ํ’€)๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

spring-boot-starter-batch๋งŒ์œผ๋กœ ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ์— ํ•„์š”ํ•œ ๋ชจ๋“  ๊ธฐ๋Šฅ์ด ์ถฉ๋ถ„ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿค– Prompt for AI Agents
In apps/commerce-batch/build.gradle.kts around line 14, remove the unnecessary
web starter dependency
implementation("org.springframework.boot:spring-boot-starter-web") because this
batch app does not use any web functionality; delete that line and keep only
spring-boot-starter-batch (or other required batch/runtime dependencies), then
refresh/resolve Gradle dependencies and verify the build and batch tests pass.

Comment on lines +13 to +18
@Bean
public TaskExecutor taskExecutor() {
SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
taskExecutor.setConcurrencyLimit(10);
return taskExecutor;
}
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐ŸŸ  Major

ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ SimpleAsyncTaskExecutor ์‚ฌ์šฉ์„ ์žฌ๊ณ ํ•˜์„ธ์š”.

SimpleAsyncTaskExecutor๋Š” ๊ฐ ํƒœ์Šคํฌ๋งˆ๋‹ค ์ƒˆ๋กœ์šด ์Šค๋ ˆ๋“œ๋ฅผ ์ƒ์„ฑํ•˜๋ฏ€๋กœ ์Šค๋ ˆ๋“œ ํ’€๋ง์ด ์—†์–ด ๋ฆฌ์†Œ์Šค ๊ด€๋ฆฌ๊ฐ€ ๋น„ํšจ์œจ์ ์ž…๋‹ˆ๋‹ค. ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ThreadPoolTaskExecutor๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค.

๐Ÿ”Ž ThreadPoolTaskExecutor๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ์ œ์•ˆ
-import org.springframework.core.task.SimpleAsyncTaskExecutor;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.core.task.TaskExecutor;
 
 @Configuration
 @EnableBatchProcessing
 public class BatchConfig {
 
     @Bean
     public TaskExecutor taskExecutor() {
-        SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
-        taskExecutor.setConcurrencyLimit(10);
+        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
+        taskExecutor.setCorePoolSize(5);
+        taskExecutor.setMaxPoolSize(10);
+        taskExecutor.setQueueCapacity(25);
+        taskExecutor.setThreadNamePrefix("batch-");
+        taskExecutor.initialize();
         return taskExecutor;
     }
 }

Committable suggestion skipped: line range outside the PR's diff.

๐Ÿค– Prompt for AI Agents
In apps/commerce-batch/src/main/java/com/loopers/batch/job/BatchConfig.java
around lines 13-18, the bean currently creates a SimpleAsyncTaskExecutor which
spawns a new thread per task and is unsuitable for production; replace it with a
ThreadPoolTaskExecutor, configure sensible pool settings (corePoolSize,
maxPoolSize), set a queueCapacity, threadNamePrefix, call initialize(), and
return it as the TaskExecutor bean so tasks are pooled and resource usage is
controlled.

Comment on lines +13 to +95
@Entity
@Table(
name = "mv_product_rank_weekly",
uniqueConstraints = {
@UniqueConstraint(columnNames = {"product_id", "period_start_date", "period_end_date"})
}
)
@Getter
public class MvProductRankWeekly extends BaseEntity {

@Column(name = "product_id", nullable = false)
private Long productId;

@Column(nullable = true)
private Integer ranking;

@Column(nullable = false)
private Double score;

@Column(name = "period_start_date", nullable = false)
private LocalDate periodStartDate;

@Column(name = "period_end_date", nullable = false)
private LocalDate periodEndDate;

@Column(name = "like_count", nullable = false)
private Long likeCount;

@Column(name = "view_count", nullable = false)
private Long viewCount;

@Column(name = "sales_count", nullable = false)
private Long salesCount;

@Builder
private MvProductRankWeekly(Long productId, Integer ranking, Double score, LocalDate periodStartDate, LocalDate periodEndDate,
Long likeCount, Long viewCount, Long salesCount) {
this.productId = productId;
this.ranking = ranking;
this.score = score;
this.periodStartDate = periodStartDate;
this.periodEndDate = periodEndDate;
this.likeCount = likeCount;
this.viewCount = viewCount;
this.salesCount = salesCount;
}

public MvProductRankWeekly() {
}

public static MvProductRankWeekly create(Long productId, Integer ranking, Double score, LocalDate periodStartDate,
LocalDate periodEndDate, Long likeCount, Long viewCount, Long salesCount) {
return MvProductRankWeekly.builder()
.productId(productId)
.ranking(ranking)
.score(score)
.periodStartDate(periodStartDate)
.periodEndDate(periodEndDate)
.likeCount(likeCount)
.viewCount(viewCount)
.salesCount(salesCount)
.build();
}

public void update(Integer ranking, Double score, Long likeCount, Long viewCount, Long salesCount) {
this.ranking = ranking;
this.score = score;
this.likeCount = likeCount;
this.viewCount = viewCount;
this.salesCount = salesCount;
}

public void updateMetrics(Double score, Long likeCount, Long viewCount, Long salesCount) {
this.score = score;
this.likeCount = likeCount;
this.viewCount = viewCount;
this.salesCount = salesCount;
}

public void updateRanking(Integer ranking) {
this.ranking = ranking;
}
}
Copy link

Choose a reason for hiding this comment

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

๐Ÿ› ๏ธ Refactor suggestion | ๐ŸŸ  Major

MvProductRankMonthly์™€์˜ ์ค‘๋ณต ์ฝ”๋“œ๋ฅผ ์ œ๊ฑฐํ•˜๊ณ  ๊ณต์œ  ๋ชจ๋“ˆ ํ™œ์šฉ์„ ๊ณ ๋ คํ•˜์„ธ์š”.

MvProductRankWeekly์™€ MvProductRankMonthly ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๊ฑฐ์˜ ๋™์ผํ•œ ๊ตฌ์กฐ์™€ ๋ฉ”์„œ๋“œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค (ํ…Œ์ด๋ธ”๋ช…๊ณผ ํด๋ž˜์Šค๋ช…๋งŒ ๋‹ค๋ฆ„). PR ๋ชฉํ‘œ์—์„œ ์–ธ๊ธ‰ํ•˜์‹  ๊ฒƒ์ฒ˜๋Ÿผ, commerce-batch์™€ commerce-api๊ฐ€ ๋™์ผํ•œ ํ…Œ์ด๋ธ”์„ ์ฐธ์กฐํ•˜๋Š” ๋ณ„๋„์˜ ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค๋ฅผ ์ •์˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ ์ ‘๊ทผ ๋ฐฉ๋ฒ•์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค:

  1. ๊ณต์œ  ์—”ํ‹ฐํ‹ฐ ๋ชจ๋“ˆ ์ƒ์„ฑ: modules/jpa์— MvProductRankWeekly์™€ MvProductRankMonthly๋ฅผ ๊ณตํ†ต ์—”ํ‹ฐํ‹ฐ๋กœ ์ •์˜ํ•˜์—ฌ commerce-batch์™€ commerce-api๊ฐ€ ๋ชจ๋‘ ์ฐธ์กฐํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  2. ์ถ”์ƒ ๋ฒ ์ด์Šค ํด๋ž˜์Šค ํ™œ์šฉ: ๊ณตํ†ต ํ•„๋“œ์™€ ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”์ถœํ•œ ์ถ”์ƒ ํด๋ž˜์Šค MvProductRankBase๋ฅผ ๋งŒ๋“ค์–ด ์ฃผ๊ฐ„/์›”๊ฐ„ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์ƒ์†๋ฐ›๋„๋ก ํ•˜๋Š” ๋ฐฉ์‹๋„ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ค‘๋ณต์„ ์ œ๊ฑฐํ•˜๊ณ  ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๋ฆฌํŒฉํ† ๋ง์„ ์œ„ํ•œ ๊ณต์œ  ์—”ํ‹ฐํ‹ฐ ๊ตฌ์กฐ๋ฅผ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜, ์ƒˆ๋กœ์šด ์ด์Šˆ๋ฅผ ์—ด์–ด ์ž‘์—…์„ ์ถ”์ ํ•˜๋Š” ๊ฒƒ์„ ๋„์™€๋“œ๋ฆด๊นŒ์š”?

๐Ÿค– Prompt for AI Agents
In
apps/commerce-batch/src/main/java/com/loopers/domain/ranking/MvProductRankWeekly.java
lines 13-95, this entity is nearly identical to MvProductRankMonthly; extract
the common fields and behavior into a shared module (e.g. modules/jpa) as a
MvProductRankBase (use @MappedSuperclass or a shared @Entity base) containing
productId, ranking, score, periodStartDate, periodEndDate, likeCount, viewCount,
salesCount and the common builder/update methods, then make two thin entity
classes MvProductRankWeekly and MvProductRankMonthly that extend the base and
only declare the @Table(name=...) unique constraints; update commerce-batch and
commerce-api to depend on the new shared module and remove the duplicate classes
from each service (or open a tracking issue if you prefer staged migration).

Comment on lines +18 to +21
List<MvProductRankMonthly> findByPeriodStartDateAndPeriodEndDateOrderByRankingAsc(
LocalDate periodStartDate,
LocalDate periodEndDate
);
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐Ÿ”ด Critical

Pageable ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๋ˆ„๋ฝ๋˜์–ด ์ปดํŒŒ์ผ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

MvProductRankMonthlyRepositoryImpl์—์„œ ์ด ๋ฉ”์„œ๋“œ๋ฅผ Pageable ํŒŒ๋ผ๋ฏธํ„ฐ์™€ ํ•จ๊ป˜ ํ˜ธ์ถœํ•˜์ง€๋งŒ, ํ˜„์žฌ ๋ฉ”์„œ๋“œ ์‹œ๊ทธ๋‹ˆ์ฒ˜์—๋Š” ํ•ด๋‹น ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด ์ปดํŒŒ์ผ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฑฐ๋‚˜ ์ž˜๋ชป๋œ ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ”Ž ์ˆ˜์ • ์ œ์•ˆ
+import org.springframework.data.domain.Pageable;

 public interface MvProductRankMonthlyJpaRepository extends JpaRepository<MvProductRankMonthly, Long> {
     
     Optional<MvProductRankMonthly> findByProductIdAndPeriodStartDateAndPeriodEndDate(
             Long productId,
             LocalDate periodStartDate,
             LocalDate periodEndDate
     );
     
     List<MvProductRankMonthly> findByPeriodStartDateAndPeriodEndDateOrderByRankingAsc(
             LocalDate periodStartDate,
-            LocalDate periodEndDate
+            LocalDate periodEndDate,
+            Pageable pageable
     );
 }
๐Ÿ“ 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.

Suggested change
List<MvProductRankMonthly> findByPeriodStartDateAndPeriodEndDateOrderByRankingAsc(
LocalDate periodStartDate,
LocalDate periodEndDate
);
import org.springframework.data.domain.Pageable;
public interface MvProductRankMonthlyJpaRepository extends JpaRepository<MvProductRankMonthly, Long> {
Optional<MvProductRankMonthly> findByProductIdAndPeriodStartDateAndPeriodEndDate(
Long productId,
LocalDate periodStartDate,
LocalDate periodEndDate
);
List<MvProductRankMonthly> findByPeriodStartDateAndPeriodEndDateOrderByRankingAsc(
LocalDate periodStartDate,
LocalDate periodEndDate,
Pageable pageable
);
}
๐Ÿค– Prompt for AI Agents
In
apps/commerce-batch/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyJpaRepository.java
around lines 18-21, the repository method is missing a Pageable parameter
causing compile errors when MvProductRankMonthlyRepositoryImpl calls it with a
Pageable; update the method signature to accept a Pageable as the last parameter
(e.g. add "Pageable pageable") and import
org.springframework.data.domain.Pageable so the implementation can pass
pagination through.

Comment on lines +18 to +21
List<MvProductRankWeekly> findByPeriodStartDateAndPeriodEndDateOrderByRankingAsc(
LocalDate periodStartDate,
LocalDate periodEndDate
);
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐Ÿ”ด Critical

[Critical] Pageable ํŒŒ๋ผ๋ฏธํ„ฐ ๋ˆ„๋ฝ์œผ๋กœ ์ธํ•œ ์„ฑ๋Šฅ ๋ฌธ์ œ๊ฐ€ ์˜ˆ์ƒ๋ฉ๋‹ˆ๋‹ค.

findByPeriodStartDateAndPeriodEndDateOrderByRankingAsc ๋ฉ”์„œ๋“œ์— Pageable ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์—†์–ด ๋ชจ๋“  ๋ ˆ์ฝ”๋“œ๋ฅผ ํ•œ ๋ฒˆ์— ๋กœ๋“œํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋‹ค์Œ ๋ฌธ์ œ๋ฅผ ์•ผ๊ธฐํ•ฉ๋‹ˆ๋‹ค:

  1. ์„ฑ๋Šฅ ์ด์Šˆ: ๋žญํ‚น ๋ฐ์ดํ„ฐ๊ฐ€ ์ฆ๊ฐ€ํ•˜๋ฉด ๋ฉ”๋ชจ๋ฆฌ ๋ถ€์กฑ ๋ฐ ์‘๋‹ต ์ง€์—ฐ ๋ฐœ์ƒ
  2. Monthly ์ €์žฅ์†Œ์™€์˜ ๋ถˆ์ผ์น˜: MvProductRankMonthlyJpaRepository๋Š” ๋™์ผํ•œ ๋ฉ”์„œ๋“œ์— Pageable ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ์Œ
  3. AI ์š”์•ฝ๊ณผ์˜ ๋ถˆ์ผ์น˜: ์š”์•ฝ์—๋Š” Pageable์ด ์žˆ๋‹ค๊ณ  ๋ช…์‹œ๋˜์–ด ์žˆ์œผ๋‚˜ ์‹ค์ œ ์ฝ”๋“œ์—๋Š” ๋ˆ„๋ฝ๋จ
๐Ÿ”Ž ์ œ์•ˆํ•˜๋Š” ์ˆ˜์ •์•ˆ
 public interface MvProductRankWeeklyJpaRepository extends JpaRepository<MvProductRankWeekly, Long> {
     
     Optional<MvProductRankWeekly> findByProductIdAndPeriodStartDateAndPeriodEndDate(
             Long productId,
             LocalDate periodStartDate,
             LocalDate periodEndDate
     );
     
     List<MvProductRankWeekly> findByPeriodStartDateAndPeriodEndDateOrderByRankingAsc(
             LocalDate periodStartDate,
             LocalDate periodEndDate,
+            Pageable pageable
     );
 }
๐Ÿ“ 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.

Suggested change
List<MvProductRankWeekly> findByPeriodStartDateAndPeriodEndDateOrderByRankingAsc(
LocalDate periodStartDate,
LocalDate periodEndDate
);
Page<MvProductRankWeekly> findByPeriodStartDateAndPeriodEndDateOrderByRankingAsc(
LocalDate periodStartDate,
LocalDate periodEndDate,
Pageable pageable
);
๐Ÿค– Prompt for AI Agents
In
apps/commerce-batch/src/main/java/com/loopers/infrastructure/ranking/MvProductRankWeeklyJpaRepository.java
around lines 18-21, the repository method lacks a Pageable parameter causing
full-table loads; add a Pageable parameter to the method signature (and import
org.springframework.data.domain.Pageable), then update all call sites to pass a
Pageable; if you need pagination metadata instead of just a slice, change the
return type to Page<MvProductRankWeekly> and update callers accordingly to
handle Page results.

Comment on lines +4 to +10
threads:
max: 200 # ?? ?? ??? ? (default : 200)
min-spare: 10 # ?? ?? ??? ? (default : 10)
connection-timeout: 1m # ?? ???? (ms) (default : 60000ms = 1m)
max-connections: 8192 # ?? ?? ?? ? (default : 8192)
accept-count: 100 # ?? ? ?? (default : 100)
keep-alive-timeout: 60s # 60s
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐ŸŸก Minor

์ฃผ์„ ์ธ์ฝ”๋”ฉ ๊นจ์ง

์ฃผ์„์ด ??๋กœ ํ‘œ์‹œ๋˜์–ด ์ธ์ฝ”๋”ฉ ๋ฌธ์ œ๊ฐ€ ์žˆ์–ด ๋ณด์ž…๋‹ˆ๋‹ค. ํŒŒ์ผ ์ธ์ฝ”๋”ฉ์ด UTF-8์ธ์ง€ ํ™•์ธํ•˜๊ณ  ์ฃผ์„์„ ๋ณต๊ตฌํ•ด ์ฃผ์„ธ์š”.

๐Ÿ”Ž ์˜ˆ์ƒ๋˜๋Š” ์ˆ˜์ •
 server:
   shutdown: graceful
   tomcat:
     threads:
-      max: 200 # ?? ?? ??? ? (default : 200)
-      min-spare: 10 # ?? ?? ??? ? (default : 10)
-    connection-timeout: 1m # ?? ???? (ms) (default : 60000ms = 1m)
-    max-connections: 8192 # ?? ?? ?? ? (default : 8192)
-    accept-count: 100 # ?? ? ?? (default : 100)
-    keep-alive-timeout: 60s # 60s
+      max: 200 # ์ตœ๋Œ€ ์Šค๋ ˆ๋“œ ์ˆ˜ (default: 200)
+      min-spare: 10 # ์ตœ์†Œ ์—ฌ์œ  ์Šค๋ ˆ๋“œ ์ˆ˜ (default: 10)
+    connection-timeout: 1m # ์—ฐ๊ฒฐ ํƒ€์ž„์•„์›ƒ (default: 60000ms = 1m)
+    max-connections: 8192 # ์ตœ๋Œ€ ์—ฐ๊ฒฐ ์ˆ˜ (default: 8192)
+    accept-count: 100 # ๋Œ€๊ธฐ ํ ํฌ๊ธฐ (default: 100)
+    keep-alive-timeout: 60s # Keep-Alive ํƒ€์ž„์•„์›ƒ
๐Ÿ“ 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.

Suggested change
threads:
max: 200 # ?? ?? ??? ? (default : 200)
min-spare: 10 # ?? ?? ??? ? (default : 10)
connection-timeout: 1m # ?? ???? (ms) (default : 60000ms = 1m)
max-connections: 8192 # ?? ?? ?? ? (default : 8192)
accept-count: 100 # ?? ? ?? (default : 100)
keep-alive-timeout: 60s # 60s
threads:
max: 200 # ์ตœ๋Œ€ ์Šค๋ ˆ๋“œ ์ˆ˜ (default: 200)
min-spare: 10 # ์ตœ์†Œ ์—ฌ์œ  ์Šค๋ ˆ๋“œ ์ˆ˜ (default: 10)
connection-timeout: 1m # ์—ฐ๊ฒฐ ํƒ€์ž„์•„์›ƒ (default: 60000ms = 1m)
max-connections: 8192 # ์ตœ๋Œ€ ์—ฐ๊ฒฐ ์ˆ˜ (default: 8192)
accept-count: 100 # ๋Œ€๊ธฐ ํ ํฌ๊ธฐ (default: 100)
keep-alive-timeout: 60s # Keep-Alive ํƒ€์ž„์•„์›ƒ
๐Ÿค– Prompt for AI Agents
In apps/commerce-batch/src/main/resources/application.yml around lines 4-10 the
inline comments show garbled characters ("??"), indicating the file was saved
with the wrong encoding; re-save the file as UTF-8 and restore the original
Korean comments (or replace with clear English) by retrieving the correct text
from git history (git log/git show for this file or the last good commit) or
from the author, then commit the UTF-8-encoded file; verify by opening the file
in your editor with UTF-8 and confirming the comments read correctly, and run a
quick build to ensure no tooling breaks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant