-
Notifications
You must be signed in to change notification settings - Fork 34
[volume-10] Collect, Stack, Zip #232
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: yeonsu00
Are you sure you want to change the base?
Conversation
Feature/week10 ranking batch
[volume-10] Collect, Stack, Zip
Walkthrough์ด PR์ ์ํ ๋ญํน ๊ธฐ๋ฅ์ ์ผ์ผ(DAILY)์์ ์ฃผ๊ฐ(WEEKLY), ์๊ฐ(MONTHLY)์ผ๋ก ํ์ฅํฉ๋๋ค. ์๋ก์ด commerce-batch ์ ํ๋ฆฌ์ผ์ด์ ์ ์ถ๊ฐํ๊ณ , ๋ฐฐ์น ์์ ์ผ๋ก ์ฃผ๊ฐ/์๊ฐ ๋ญํน์ ๊ณ์ฐ ๋ฐ ์ ๋ฐ์ดํธํฉ๋๋ค. API, ๋๋ฉ์ธ ๋ชจ๋ธ, ์ ์ฅ์, ์๋น์ค ๊ณ์ธต์ด ๋ค์ค ์๊ฐ ๋ฒ์๋ฅผ ์ง์ํ๋๋ก ๊ฐ์ ๋ฉ๋๋ค. Changes
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>
Estimated code review effort๐ฏ 5 (Critical) | โฑ๏ธ ~120 minutes Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touchesโ Failed checks (1 warning, 1 inconclusive)
โ Passed checks (1 passed)
โจ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 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() : 0apps/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
๐ Files selected for processing (59)
apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingCommand.javaapps/commerce-api/src/main/java/com/loopers/application/ranking/RankingFacade.javaapps/commerce-api/src/main/java/com/loopers/domain/ranking/MvProductRankMonthly.javaapps/commerce-api/src/main/java/com/loopers/domain/ranking/MvProductRankMonthlyRepository.javaapps/commerce-api/src/main/java/com/loopers/domain/ranking/MvProductRankWeekly.javaapps/commerce-api/src/main/java/com/loopers/domain/ranking/MvProductRankWeeklyRepository.javaapps/commerce-api/src/main/java/com/loopers/domain/ranking/RankingPeriod.javaapps/commerce-api/src/main/java/com/loopers/domain/ranking/RankingService.javaapps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyJpaRepository.javaapps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyRepositoryImpl.javaapps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankWeeklyJpaRepository.javaapps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankWeeklyRepositoryImpl.javaapps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1ApiSpec.javaapps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1Controller.javaapps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1Dto.javaapps/commerce-api/src/main/resources/application.ymlapps/commerce-api/src/test/java/com/loopers/application/ranking/RankingFacadeIntegrationTest.javaapps/commerce-api/src/test/java/com/loopers/domain/ranking/RankingServiceIntegrationTest.javaapps/commerce-api/src/test/java/com/loopers/interfaces/api/ranking/RankingV1ApiE2ETest.javaapps/commerce-batch/build.gradle.ktsapps/commerce-batch/src/main/java/com/loopers/CommerceBatchApplication.javaapps/commerce-batch/src/main/java/com/loopers/application/ranking/JobParameterUtils.javaapps/commerce-batch/src/main/java/com/loopers/application/ranking/ProductMetricsItemReader.javaapps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingCalculationTasklet.javaapps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingJobConfig.javaapps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingProcessor.javaapps/commerce-batch/src/main/java/com/loopers/application/ranking/RankingWriter.javaapps/commerce-batch/src/main/java/com/loopers/batch/job/BatchConfig.javaapps/commerce-batch/src/main/java/com/loopers/domain/metrics/ProductMetrics.javaapps/commerce-batch/src/main/java/com/loopers/domain/metrics/ProductMetricsRepository.javaapps/commerce-batch/src/main/java/com/loopers/domain/metrics/ProductMetricsService.javaapps/commerce-batch/src/main/java/com/loopers/domain/ranking/MonthlyRankingService.javaapps/commerce-batch/src/main/java/com/loopers/domain/ranking/MvProductRankMonthly.javaapps/commerce-batch/src/main/java/com/loopers/domain/ranking/MvProductRankMonthlyRepository.javaapps/commerce-batch/src/main/java/com/loopers/domain/ranking/MvProductRankWeekly.javaapps/commerce-batch/src/main/java/com/loopers/domain/ranking/MvProductRankWeeklyRepository.javaapps/commerce-batch/src/main/java/com/loopers/domain/ranking/ProductRankingAggregate.javaapps/commerce-batch/src/main/java/com/loopers/domain/ranking/RankingCalculator.javaapps/commerce-batch/src/main/java/com/loopers/domain/ranking/RankingPeriod.javaapps/commerce-batch/src/main/java/com/loopers/domain/ranking/RankingType.javaapps/commerce-batch/src/main/java/com/loopers/domain/ranking/RankingWeight.javaapps/commerce-batch/src/main/java/com/loopers/domain/ranking/WeeklyRankingService.javaapps/commerce-batch/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsJpaRepository.javaapps/commerce-batch/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsRepositoryImpl.javaapps/commerce-batch/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyJpaRepository.javaapps/commerce-batch/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyRepositoryImpl.javaapps/commerce-batch/src/main/java/com/loopers/infrastructure/ranking/MvProductRankWeeklyJpaRepository.javaapps/commerce-batch/src/main/java/com/loopers/infrastructure/ranking/MvProductRankWeeklyRepositoryImpl.javaapps/commerce-batch/src/main/java/com/loopers/interfaces/scheduler/RankingBatchScheduler.javaapps/commerce-batch/src/main/resources/application.ymlapps/commerce-batch/src/test/java/com/loopers/CommerceBatchApplicationTests.javaapps/commerce-streamer/src/main/java/com/loopers/domain/metrics/ProductMetrics.javaapps/commerce-streamer/src/main/java/com/loopers/domain/metrics/ProductMetricsRepository.javaapps/commerce-streamer/src/main/java/com/loopers/domain/metrics/ProductMetricsService.javaapps/commerce-streamer/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsJpaRepository.javaapps/commerce-streamer/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsRepositoryImpl.javaapps/commerce-streamer/src/test/java/com/loopers/interfaces/consumer/MetricsConsumerIdempotencyTest.javamodules/kafka/src/main/java/com/loopers/config/kafka/KafkaConfig.javasettings.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.javaapps/commerce-api/src/main/java/com/loopers/domain/ranking/MvProductRankMonthlyRepository.javaapps/commerce-batch/src/main/java/com/loopers/domain/ranking/MvProductRankWeeklyRepository.javaapps/commerce-batch/src/main/java/com/loopers/infrastructure/ranking/MvProductRankWeeklyRepositoryImpl.javaapps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankWeeklyJpaRepository.javaapps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyJpaRepository.javaapps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankWeeklyRepositoryImpl.javaapps/commerce-api/src/main/java/com/loopers/domain/ranking/MvProductRankWeeklyRepository.javaapps/commerce-batch/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyJpaRepository.javaapps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/MvProductRankMonthlyRepositoryImpl.javaapps/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.javaapps/commerce-streamer/src/main/java/com/loopers/domain/metrics/ProductMetricsService.javaapps/commerce-batch/src/main/java/com/loopers/domain/metrics/ProductMetrics.javaapps/commerce-streamer/src/test/java/com/loopers/interfaces/consumer/MetricsConsumerIdempotencyTest.javaapps/commerce-streamer/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsJpaRepository.javaapps/commerce-streamer/src/main/java/com/loopers/domain/metrics/ProductMetrics.javaapps/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.javaapps/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.ymlapps/commerce-batch/src/main/java/com/loopers/CommerceBatchApplication.javaapps/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!
RankingTypeenum๊ณผ ํตํฉ๋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๋ ๋จ๊ณ ๊ตฌ์ฑ์ด ์ ์ ํฉ๋๋ค:
rankingStep: ์ฒญํฌ ์ฒ๋ฆฌ๋ก ProductMetrics๋ฅผ ์ฝ์ด ์ ์ ๊ณ์ฐ ํ MV ํ ์ด๋ธ์ upsertrankingCalculationStep: 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.
| @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(); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
์ํฐํฐ์ ํ์ํ ์ ๋ฐ์ดํธ ๋ฉ์๋๊ฐ ๋๋ฝ๋์ด ์์ต๋๋ค.
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.
| @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; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๋๋ฝ๋ ๋ฉ์๋: 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.
| public interface MvProductRankMonthlyJpaRepository extends JpaRepository<MvProductRankMonthly, Long> { | ||
|
|
||
| List<MvProductRankMonthly> findByPeriodStartDateAndPeriodEndDateOrderByRankingAsc( | ||
| LocalDate periodStartDate, | ||
| LocalDate periodEndDate, | ||
| Pageable pageable | ||
| ); | ||
|
|
||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๐งฉ Analysis chain
๐ Script executed:
# Find the Weekly repository interface
fd -t f "MvProductRankWeeklyJpaRepository" --type=fileRepository: 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 -50Repository: 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 3Repository: 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.
| implementation(project(":modules:jpa")) | ||
| implementation(project(":modules:redis")) | ||
| implementation(project(":modules:kafka")) | ||
| implementation(project(":supports:jackson")) | ||
| implementation(project(":supports:logging")) | ||
| implementation(project(":supports:monitoring")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๐งฉ Analysis chain
๐ 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 -20Repository: 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.ktsRepository: 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 -nRepository: 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") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๐งฉ Analysis chain
๐ 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 -20Repository: 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.ktsRepository: 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.
| @Bean | ||
| public TaskExecutor taskExecutor() { | ||
| SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(); | ||
| taskExecutor.setConcurrencyLimit(10); | ||
| return taskExecutor; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ํ๋ก๋์
ํ๊ฒฝ์์ 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.
| @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; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๐ ๏ธ Refactor suggestion | ๐ Major
MvProductRankMonthly์์ ์ค๋ณต ์ฝ๋๋ฅผ ์ ๊ฑฐํ๊ณ ๊ณต์ ๋ชจ๋ ํ์ฉ์ ๊ณ ๋ คํ์ธ์.
MvProductRankWeekly์ MvProductRankMonthly ์ํฐํฐ๊ฐ ๊ฑฐ์ ๋์ผํ ๊ตฌ์กฐ์ ๋ฉ์๋๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋ค (ํ
์ด๋ธ๋ช
๊ณผ ํด๋์ค๋ช
๋ง ๋ค๋ฆ). PR ๋ชฉํ์์ ์ธ๊ธํ์ ๊ฒ์ฒ๋ผ, commerce-batch์ commerce-api๊ฐ ๋์ผํ ํ
์ด๋ธ์ ์ฐธ์กฐํ๋ ๋ณ๋์ ์ํฐํฐ ํด๋์ค๋ฅผ ์ ์ํ๊ณ ์์ต๋๋ค.
๋ค์ ์ ๊ทผ ๋ฐฉ๋ฒ์ ๊ถ์ฅํฉ๋๋ค:
- ๊ณต์ ์ํฐํฐ ๋ชจ๋ ์์ฑ:
modules/jpa์MvProductRankWeekly์MvProductRankMonthly๋ฅผ ๊ณตํต ์ํฐํฐ๋ก ์ ์ํ์ฌ commerce-batch์ commerce-api๊ฐ ๋ชจ๋ ์ฐธ์กฐํ๋๋ก ํฉ๋๋ค. - ์ถ์ ๋ฒ ์ด์ค ํด๋์ค ํ์ฉ: ๊ณตํต ํ๋์ ๋ฉ์๋๋ฅผ ์ถ์ถํ ์ถ์ ํด๋์ค
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).
| List<MvProductRankMonthly> findByPeriodStartDateAndPeriodEndDateOrderByRankingAsc( | ||
| LocalDate periodStartDate, | ||
| LocalDate periodEndDate | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| List<MvProductRankWeekly> findByPeriodStartDateAndPeriodEndDateOrderByRankingAsc( | ||
| LocalDate periodStartDate, | ||
| LocalDate periodEndDate | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Critical] Pageable ํ๋ผ๋ฏธํฐ ๋๋ฝ์ผ๋ก ์ธํ ์ฑ๋ฅ ๋ฌธ์ ๊ฐ ์์๋ฉ๋๋ค.
findByPeriodStartDateAndPeriodEndDateOrderByRankingAsc ๋ฉ์๋์ Pageable ํ๋ผ๋ฏธํฐ๊ฐ ์์ด ๋ชจ๋ ๋ ์ฝ๋๋ฅผ ํ ๋ฒ์ ๋ก๋ํ๊ฒ ๋ฉ๋๋ค. ์ด๋ ๋ค์ ๋ฌธ์ ๋ฅผ ์ผ๊ธฐํฉ๋๋ค:
- ์ฑ๋ฅ ์ด์: ๋ญํน ๋ฐ์ดํฐ๊ฐ ์ฆ๊ฐํ๋ฉด ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ ๋ฐ ์๋ต ์ง์ฐ ๋ฐ์
- Monthly ์ ์ฅ์์์ ๋ถ์ผ์น:
MvProductRankMonthlyJpaRepository๋ ๋์ผํ ๋ฉ์๋์ Pageable ํ๋ผ๋ฏธํฐ๋ฅผ ํฌํจํ๊ณ ์์ - 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.
| 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.
| 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
์ฃผ์ ์ธ์ฝ๋ฉ ๊นจ์ง
์ฃผ์์ด ??๋ก ํ์๋์ด ์ธ์ฝ๋ฉ ๋ฌธ์ ๊ฐ ์์ด ๋ณด์
๋๋ค. ํ์ผ ์ธ์ฝ๋ฉ์ด 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.
| 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.
๐ Summary
mv_product_rank_weekly,mv_product_rank_monthlyํ ์ด๋ธ์ ์ฃผ๊ฐ/์๊ฐ TOP 100 ๋ญํน ์ ์ฅGET/api/v1/rankings?date=yyyyMMdd&type=DAILY|WEEKLY|MONTHLY&page=1&size=20๐ฌ Review Points
1. ๋ฐฐ์น ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ API ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ณต์ ๋ฐฉ์
๋ฐฐ์น ์ ํ๋ฆฌ์ผ์ด์ ์ commerce-batch๋ก ๋ฐ๋ก ๋ง๋ค์์ต๋๋ค.
commerce-batch์์ MV ํ ์ด๋ธ์ ๋ญํน์ ๊ณ์ฐํด์ ์ ์ฅํ๊ณ , API ์ ํ๋ฆฌ์ผ์ด์ (commerce-api)์ MV ํ ์ด๋ธ์์ ์กฐํํฉ๋๋ค.
์ด๋ ๊ฒ commerce-batch์ commerce-api์์ ๊ฐ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๊ณต์ ํ๊ณ ์๋๋ฐ ์ด๊ฑธ ์ด๋ป๊ฒ ์ฒ๋ฆฌํด์ผ ํ ์ง ๊ถ๊ธํฉ๋๋ค.
ํ์ฌ๋ ๊ฐ ์ ํ๋ฆฌ์ผ์ด์ ์ (๊ฐ์ ํ ์ด๋ธ์ ๊ฐ๋ฆฌํค์ง๋ง) ๋ณ๋ ์ํฐํฐ ํด๋์ค๋ฅผ ๊ตฌํํ์ต๋๋ค.
modules/jpa ๋ชจ๋์ ๊ณตํต ์ค์ ์ผ๋ก ํ๋์ ํด๋์ค๋ง ๊ตฌํํด์ผ ํ ๊น์?
โ Checklist
๐งฑ Spring Batch
๐งฉ Ranking API
Summary by CodeRabbit
Release Notes
New Features
Infrastructure
โ๏ธ Tip: You can customize this high-level summary in your review settings.