-
Notifications
You must be signed in to change notification settings - Fork 34
[volume-10] Collect, Stack, Zip #236
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: HongChangMo
Are you sure you want to change the base?
Conversation
- Chunk Oriented Processing ๊ธฐ๋ฐ ์ฃผ๊ฐ ๋ญํน ๋ฐฐ์น ์ฒ๋ฆฌ ๊ตฌํ - ์ํ ์ง๊ณ ๊ฒฐ๊ณผ MV ํ์ฉ
- Chunk Oriented Processing ๊ธฐ๋ฐ ์๊ฐ ๋ญํน ๋ฐฐ์น ์ฒ๋ฆฌ ๊ตฌํ - ์ํ ์ง๊ณ ๊ฒฐ๊ณผ MV ํ์ฉ
- ์ค๋๋ ์ผ์๋ณ ๋ฐ์ดํฐ ์ ๋ฆฌ 10์ผ ๊ธฐ์ค -> 30์ผ ์์
- PeriodType๋ณ ์ผ๊ฐ, ์ฃผ๊ฐ, ์๊ฐ ๋ญํน ์ ๊ณต - ์ผ๊ฐ์ Redis ZSET ์ผ๋ก ์ ๊ณต - ์ฃผ๊ฐ, ์๊ฐ MV๋ฅผ ํ์ฉํ ๋ญํน ์ ๊ณต
์ํฌ์ค๋ฃจ์ฃผ๊ฐ ๋ฐ ์๊ฐ ์ํ ์์ ์กฐํ ๊ธฐ๋ฅ์ ์ถ๊ฐํ๊ธฐ ์ํด ๋ณ๊ฒฝ ์ฌํญ
์ํ์ค ๋ค์ด์ด๊ทธ๋จsequenceDiagram
participant Client
participant RankingController as RankingV1<br/>Controller
participant RankingFacade as RankingFacade
participant PeriodService as PeriodRanking<br/>Service
participant WeeklyRepo as Weekly<br/>Repository
participant MonthlyRepo as Monthly<br/>Repository
participant Enrichment as Enrichment<br/>Logic
Client->>RankingController: GET /ranking<br/>(rankingType, periodType,<br/>date, limit)
RankingController->>RankingFacade: getTopRanking(type,<br/>periodType, date, limit)
alt periodType == WEEKLY
RankingFacade->>PeriodService: getTopWeeklyRanking(type,<br/>date, limit)
PeriodService->>WeeklyRepo: findByYearAndWeek<br/>OrderBy[Type]Desc(year,<br/>week, limit)
WeeklyRepo-->>PeriodService: List<ProductMetricsWeekly>
PeriodService->>PeriodService: convertWeeklyToRanking()<br/>calculateScore()
PeriodService-->>RankingFacade: List<Ranking>
else periodType == MONTHLY
RankingFacade->>PeriodService: getTopMonthlyRanking(type,<br/>date, limit)
PeriodService->>MonthlyRepo: findByYearAndMonth<br/>OrderBy[Type]Desc(year,<br/>month, limit)
MonthlyRepo-->>PeriodService: List<ProductMetricsMonthly>
PeriodService->>PeriodService: convertMonthlyToRanking()<br/>calculateScore()
PeriodService-->>RankingFacade: List<Ranking>
else periodType == DAILY (default)
RankingFacade->>RankingFacade: getTopRanking(type,<br/>date, limit)<br/>๊ธฐ์กด ๋ก์ง
end
RankingFacade->>Enrichment: enrichWithProductInfo(rankings)
Enrichment-->>RankingFacade: List<RankingInfo>
RankingFacade-->>RankingController: List<RankingInfo>
RankingController-->>Client: JSON Response
์์ ์ฝ๋ ๋ฆฌ๋ทฐ ๋ ธ๋ ฅ๐ฏ 4 (๋ณต์กํจ) | โฑ๏ธ ~60๋ถ ๊ด๋ จ ๊ฐ๋ฅ์ฑ ์๋ PR
์ ์๋ ๋ ์ด๋ธ
์ (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: 9
Caution
Some comments are outside the diff and canโt be posted inline due to platform limitations.
โ ๏ธ Outside diff range comments (2)
apps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1Controller.java (2)
37-37:getTotalRankingCount์ periodType ์ ๋ฌ ๋๋ฝ
getRankingWithPaging์๋periodType์ ์ ๋ฌํ์ง๋ง,getTotalRankingCount์๋ ์ ๋ฌํ์ง ์์ต๋๋ค. ์ฃผ๊ฐ/์๊ฐ ๋ญํน์ ๊ฒฝ์ฐ ๋ฐ์ดํฐ ์์ค๊ฐ ๋ค๋ฅด๋ฏ๋ก(์ผ๊ฐ: Redis, ์ฃผ๊ฐ/์๊ฐ: MV), ์ด ๊ฐ์ ๊ณ์ฐ๋periodType์ ๊ณ ๋ คํด์ผ ํฉ๋๋ค.์ด๋ก ์ธํด ํ์ด์ง๋ค์ด์ ์ ์ด ํ์ด์ง ์๊ฐ ์๋ชป ๊ณ์ฐ๋ ์ ์์ต๋๋ค.
๐ ์ ์ํ๋ ์์ ์ฌํญ
- long totalCount = rankingFacade.getTotalRankingCount(request.type(), date); + long totalCount = rankingFacade.getTotalRankingCount(request.type(), request.periodType(), date);
63-63: ๋์ผ ์ด์:getTopRanking์์๋ periodType ๋๋ฝ
getTopRanking์๋ํฌ์ธํธ์์๋getTotalRankingCountํธ์ถ ์periodType์ ์ ๋ฌํด์ผ ํฉ๋๋ค.๐ ์ ์ํ๋ ์์ ์ฌํญ
- long totalCount = rankingFacade.getTotalRankingCount(request.type(), date); + long totalCount = rankingFacade.getTotalRankingCount(request.type(), request.periodType(), date);
โป๏ธ Duplicate comments (1)
apps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1Dto.java (1)
38-65: ๋์ผํ null ์ฒ๋ฆฌ ๊ฒ์ฆ์ด ํ์ํฉ๋๋ค.
GetTopRankingRequest์ ๋์ผํ๊ฒperiodTypeํ๋์ null ์ฒ๋ฆฌ๋ฅผ ํ์ธํ์ธ์.
๐งน Nitpick comments (16)
apps/commerce-api/src/main/java/com/loopers/domain/ranking/Ranking.java (1)
32-47: ํฉํ ๋ฆฌ ๋ฉ์๋ ๊ฐ์ ์ ์
- ํ๋ผ๋ฏธํฐ๋ช
i๋ฅผrank๋ก ๋ณ๊ฒฝํ์ฌ ๊ฐ๋ ์ฑ์ ๋์ด์ธ์.- Line 40์
new Ranking์ฌ์ด ์ด์ค ๊ณต๋ฐฑ์ ์์ ํ์ธ์.๐ ์ ์ํ๋ ์์ ์ฌํญ
public static Ranking of( - int i, + int rank, Long productId, double score, Long totalLikeCount, Long totalViewCount, Long totalOrderCount ) { - return new Ranking( - i, + return new Ranking( + rank, productId, score, totalLikeCount, totalViewCount, totalOrderCount ); }apps/commerce-collector/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsDailyRepositoryImpl.java (1)
164-188: FQN ๋์ import ๋ฌธ ์ฌ์ฉ ๊ถ์ฅ
org.springframework.data.domain.Page,org.springframework.data.domain.Pageable, ๊ทธ๋ฆฌ๊ณ DTO ํด๋์ค๋ค์ ๋ํด ์์ ํ ํจํค์ง๋ช (FQN)์ ์ฌ์ฉํ๊ณ ์์ต๋๋ค. ๊ฐ๋ ์ฑ์ ์ํด ํ์ผ ์๋จ์ import ๋ฌธ์ ์ถ๊ฐํ๋ ๊ฒ์ด ์ข์ต๋๋ค.๐ ์ ์ํ๋ ์์ ์ฌํญ
ํ์ผ ์๋จ์ import ์ถ๊ฐ:
import com.loopers.domain.metrics.dto.MonthlyAggregationDto; import com.loopers.domain.metrics.dto.WeeklyAggregationDto; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable;๊ทธ ํ ๋ฉ์๋ ์๊ทธ๋์ฒ ๊ฐ์ํ:
@Override - public org.springframework.data.domain.Page<com.loopers.domain.metrics.dto.WeeklyAggregationDto> findWeeklyAggregation( + public Page<WeeklyAggregationDto> findWeeklyAggregation( Integer year, Integer week, LocalDate startDate, LocalDate endDate, - org.springframework.data.domain.Pageable pageable + Pageable pageable ) {apps/commerce-api/src/main/java/com/loopers/domain/metrics/ProductMetricsMonthly.java (1)
63-64: ๋ณตํฉ ์ ์ ๊ณ์ฐ ๋ก์ง์ด ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํ๋์์ต๋๋ค.๊ฐ์ค์น ๊ณ์ฐ ๊ณต์(์ข์์ 0.2 + ์กฐํ์ 0.1 + ์ฃผ๋ฌธ์ 0.6)์ด ProductMetricsWeekly์ ์ผ๊ด๋๊ฒ ์ ์ฉ๋์์ต๋๋ค.
๐ ๋ฐฉ์ด์ null ์ฒ๋ฆฌ ์ถ๊ฐ ๊ณ ๋ ค
์คํค๋ง์์
nullable = false๋ก ์ค์ ๋์ด ์์ด ์์ ํ์ง๋ง, ๋ฐฉ์ด์ ์ฝ๋ฉ์ ์ํด null ์ฒดํฌ๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค:public double calculateCompositeScore() { - return (totalLikeCount * 0.2) + (totalViewCount * 0.1) + (totalOrderCount * 0.6); + long likeCount = totalLikeCount != null ? totalLikeCount : 0L; + long viewCount = totalViewCount != null ? totalViewCount : 0L; + long orderCount = totalOrderCount != null ? totalOrderCount : 0L; + return (likeCount * 0.2) + (viewCount * 0.1) + (orderCount * 0.6); }apps/commerce-collector/src/main/java/com/loopers/batch/metrics/MonthlyMetricsWriter.java (1)
22-26: ํ์ ์์ ์ฑ์ ๊ฐ์ ํ ์ ์์ต๋๋ค.Line 26์์
List<? extends ProductMetricsMonthly>๋ฅผList<ProductMetricsMonthly>๋ก unchecked castํ๊ณ ์์ต๋๋ค. Spring Batch ์ปจํ ์คํธ์์๋ ์ผ๋ฐ์ ์ผ๋ก ์์ ํ์ง๋ง, ์ปดํ์ผ๋ฌ ๊ฒฝ๊ณ ๊ฐ ๋ฐ์ํ๋ฉฐ ์ด๋ก ์ ์ผ๋ก๋ ํ์ ์์ ์ฑ ๋ฌธ์ ๊ฐ ์์ ์ ์์ต๋๋ค.๐ ํ์ ์์ ํ ๋์
@Override public void write(Chunk<? extends ProductMetricsMonthly> chunk) { - List<? extends ProductMetricsMonthly> items = chunk.getItems(); - - // Bulk Insert/Update (UPSERT) - monthlyRepository.saveAll((List<ProductMetricsMonthly>) items); + List<ProductMetricsMonthly> items = new ArrayList<>(); + for (ProductMetricsMonthly item : chunk.getItems()) { + items.add(item); + } + + // Bulk Insert/Update (UPSERT) + monthlyRepository.saveAll(items); log.info("์๊ฐ ์ง๊ณ ์ ์ฅ ์๋ฃ: {} ๊ฑด", items.size()); }๋๋ Java 10+ ์คํธ๋ฆผ ์ฌ์ฉ:
@Override public void write(Chunk<? extends ProductMetricsMonthly> chunk) { - List<? extends ProductMetricsMonthly> items = chunk.getItems(); - - // Bulk Insert/Update (UPSERT) - monthlyRepository.saveAll((List<ProductMetricsMonthly>) items); + List<ProductMetricsMonthly> items = chunk.getItems().stream() + .map(ProductMetricsMonthly.class::cast) + .toList(); + + monthlyRepository.saveAll(items); log.info("์๊ฐ ์ง๊ณ ์ ์ฅ ์๋ฃ: {} ๊ฑด", items.size()); }apps/commerce-collector/src/main/java/com/loopers/batch/metrics/MonthlyMetricsProcessor.java (1)
12-30: ์ํฐํฐ ์์ฑ ํจํด ๊ฒํ ์ ์.ํ์ฌ
ProductMetricsMonthly.create()๋ก ์ํฐํฐ๋ฅผ ์์ฑํ ํ ์ฆ์updateMetrics()๋ฅผ ํธ์ถํ๋ ํจํด์ ์ฌ์ฉํ๊ณ ์์ต๋๋ค. ์ด ํจํด์ ๋์ํ์ง๋ง, ๋ ๋จ๊ณ๋ก ๋๋์ด ์์ด ๋ค์ ๋น์ง๊ด์ ์ผ ์ ์์ต๋๋ค.๋ค์ ์ค ํ๋๋ฅผ ๊ณ ๋ คํด๋ณด์ธ์:
create()๋ฉ์๋๊ฐ ๋ชจ๋ ๋งค๊ฐ๋ณ์(metrics ํฌํจ)๋ฅผ ๋ฐ๋๋ก ํ์ฅ- ๋น๋ ํจํด ์ฌ์ฉ
- ํ์ฌ ํจํด์ด ์๋์ ์ธ ์ค๊ณ๋ผ๋ฉด ๊ทธ๋๋ก ์ ์ง
ํ์ฌ ๊ตฌํ๋ ์ ์ ๋์ํ๋ฏ๋ก ํ์ ์ค๊ณ ์ฒ ํ์ ๋ฐ๋ผ ์ ํํ์๋ฉด ๋ฉ๋๋ค.
apps/commerce-collector/src/main/java/com/loopers/domain/metrics/ProductMetricsWeeklyRepository.java (1)
8-10: ํ์ด์ง๋ค์ด์ ์ง์ ๋ฐ saveAll ๋ฐํ ํ์ ๊ฐ์ ๊ถ์ฅํ์ฌ ๊ตฌํ์์ ๋ ๊ฐ์ง ๊ฐ์ ์ฌํญ์ ๊ณ ๋ คํด๋ณด์ธ์:
findAll()๋ฉ์๋๋ ํ์ด์ง๋ค์ด์ ์์ด ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ๋ฉ๋ชจ๋ฆฌ๋ก ๋ก๋ํ๋ฏ๋ก ๋ฐ์ดํฐ๊ฐ ๋ง์ ๊ฒฝ์ฐ ์ฑ๋ฅ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.saveAll()์ด void๋ฅผ ๋ฐํํ๋๋ฐ, ์ผ๋ฐ์ ์ธ Spring Data ํจํด์์๋ ์ ์ฅ๋ ์ํฐํฐ ๋ฆฌ์คํธ๋ฅผ ๋ฐํํ์ฌ ์์ฑ๋ ID ๋ฑ์ ํ์ธํ ์ ์๋๋ก ํฉ๋๋ค.๐ ๊ฐ์ ๋ ๋ฉ์๋ ์๊ทธ๋์ฒ ์ ์
public interface ProductMetricsWeeklyRepository { ProductMetricsWeekly save(ProductMetricsWeekly metrics); - void saveAll(List<ProductMetricsWeekly> metricsList); + List<ProductMetricsWeekly> saveAll(List<ProductMetricsWeekly> metricsList); int deleteByYearAndWeekBefore(Integer year, Integer week); - List<ProductMetricsWeekly> findAll(); + List<ProductMetricsWeekly> findAll(Pageable pageable); }apps/commerce-collector/src/main/java/com/loopers/batch/metrics/WeeklyMetricsProcessor.java (1)
12-30: DTO ์ ๋ ฅ๊ฐ์ ๋ํ null ๊ฒ์ฆ ์ถ๊ฐ ๊ถ์ฅํ์ฌ ํ๋ก์ธ์๋ DTO์ ํ๋๊ฐ์ ๋ํ null ์ฒดํฌ ์์ด ๋ฐ๋ก ์ฌ์ฉํ๊ณ ์์ต๋๋ค. Spring Batch์์ DTO ์์ฑ ์ null ๊ฐ์ด ํฌํจ๋ ์ ์์ผ๋ฏ๋ก, ๋ฐฉ์ด์ ํ๋ก๊ทธ๋๋ฐ์ ์ํด ์ ๋ ฅ๊ฐ ๊ฒ์ฆ์ ์ถ๊ฐํ๋ ๊ฒ์ ๊ณ ๋ คํด๋ณด์ธ์.
๐ null ๊ฒ์ฆ ์ถ๊ฐ ์ ์
public class WeeklyMetricsProcessor implements ItemProcessor<WeeklyAggregationDto, ProductMetricsWeekly> { @Override public ProductMetricsWeekly process(WeeklyAggregationDto dto) { + // ํ์ ํ๋ ๊ฒ์ฆ + if (dto.getProductId() == null || dto.getYear() == null || dto.getWeek() == null) { + throw new IllegalArgumentException("Required fields cannot be null"); + } + // DTO๋ฅผ ๋๋ฉ์ธ ์ํฐํฐ๋ก ๋ณํ ProductMetricsWeekly metrics = ProductMetricsWeekly.create( dto.getProductId(), dto.getYear(), dto.getWeek(), dto.getPeriodStartDate(), dto.getPeriodEndDate() ); // ์ง๊ณ ๋ฉํธ๋ฆญ ์ ๋ฐ์ดํธ metrics.updateMetrics( - dto.getTotalLikeCount(), - dto.getTotalViewCount(), - dto.getTotalOrderCount() + dto.getTotalLikeCount() != null ? dto.getTotalLikeCount() : 0L, + dto.getTotalViewCount() != null ? dto.getTotalViewCount() : 0L, + dto.getTotalOrderCount() != null ? dto.getTotalOrderCount() : 0L ); return metrics; } }apps/commerce-collector/src/main/java/com/loopers/domain/metrics/ProductMetricsMonthlyRepository.java (1)
7-9: ํ์ด์ง๋ค์ด์ ์ง์ ๋ฐ saveAll ๋ฐํ ํ์ ๊ฐ์ ๊ถ์ฅProductMetricsWeeklyRepository์ ๋์ผํ ๊ฐ์ ์ฌํญ์ ๊ณ ๋ คํด๋ณด์ธ์:
findAll()๋ฉ์๋์ ํ์ด์ง๋ค์ด์ ์ง์ ์ถ๊ฐ๋ก ๋์ฉ๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ์ ๋ฉ๋ชจ๋ฆฌ ๋ฌธ์ ๋ฐฉ์งsaveAll()์ ๋ฐํ ํ์ ์List<ProductMetricsMonthly>๋ก ๋ณ๊ฒฝํ์ฌ ์ ์ฅ๋ ์ํฐํฐ์ ์ ๊ทผ ๊ฐ๋ฅํ๋๋ก ๊ฐ์๐ ๊ฐ์ ๋ ๋ฉ์๋ ์๊ทธ๋์ฒ ์ ์
public interface ProductMetricsMonthlyRepository { ProductMetricsMonthly save(ProductMetricsMonthly metrics); - void saveAll(List<ProductMetricsMonthly> metricsList); + List<ProductMetricsMonthly> saveAll(List<ProductMetricsMonthly> metricsList); int deleteByYearAndMonthBefore(Integer year, Integer month); - List<ProductMetricsMonthly> findAll(); + List<ProductMetricsMonthly> findAll(Pageable pageable); }apps/commerce-collector/src/main/java/com/loopers/domain/metrics/ProductMetricsMonthly.java (1)
91-100: ํ ์คํธ ์ฉ์ด์ฑ์ ์ํด Clock ์ฃผ์ ๊ณ ๋ คLine 99์์
ZonedDateTime.now()๋ฅผ ์ง์ ํธ์ถํ๋ฉด ๋จ์ ํ ์คํธ ์ ์๊ฐ์ ์ ์ดํ๊ธฐ ์ด๋ ต์ต๋๋ค. ํ๋ก๋์ ํ๊ฒฝ์์๋ ๋ฌธ์ ์์ง๋ง, ํ ์คํธ ์ ํน์ ์๊ฐ์ ๊ฒ์ฆํด์ผ ํ ๊ฒฝ์ฐ ์ด๋ ค์์ด ์์ ์ ์์ต๋๋ค.๐ Clock ์ฃผ์ ์ ํตํ ๊ฐ์ ์ ์
ํ๋์ Clock ์ถ๊ฐ:
+import java.time.Clock; + @Entity @Table(...) @Getter @NoArgsConstructor public class ProductMetricsMonthly extends BaseEntity { + + private Clock clock = Clock.systemDefaultZone(); + + // For testing + public void setClock(Clock clock) { + this.clock = clock; + }updateMetrics ๋ฉ์๋ ์์ :
public void updateMetrics( Long likeCount, Long viewCount, Long orderCount ) { this.totalLikeCount = likeCount; this.totalViewCount = viewCount; this.totalOrderCount = orderCount; - this.aggregatedAt = ZonedDateTime.now(); + this.aggregatedAt = ZonedDateTime.now(clock); }apps/commerce-collector/src/main/java/com/loopers/domain/metrics/ProductMetricsWeekly.java (1)
91-100: ํ ์คํธ ์ฉ์ด์ฑ์ ์ํด Clock ์ฃผ์ ๊ณ ๋ คLine 99์
ZonedDateTime.now()์ง์ ํธ์ถ์ ๋จ์ ํ ์คํธ์์ ์๊ฐ ์ ์ด๋ฅผ ์ด๋ ต๊ฒ ๋ง๋ญ๋๋ค. ProductMetricsMonthly์ ๋์ผํ๊ฒ Clock ์ฃผ์ ์ ๊ณ ๋ คํด๋ณด์ธ์.๐ Clock ์ฃผ์ ์ ํตํ ๊ฐ์ ์ ์
+import java.time.Clock; + @Entity @Table(...) @Getter @NoArgsConstructor public class ProductMetricsWeekly extends BaseEntity { + + private Clock clock = Clock.systemDefaultZone(); + + // For testing + public void setClock(Clock clock) { + this.clock = clock; + } // ... existing fields ... public void updateMetrics( Long likeCount, Long viewCount, Long orderCount ) { this.totalLikeCount = likeCount; this.totalViewCount = viewCount; this.totalOrderCount = orderCount; - this.aggregatedAt = ZonedDateTime.now(); + this.aggregatedAt = ZonedDateTime.now(clock); } }apps/commerce-api/src/main/java/com/loopers/domain/ranking/PeriodRankingService.java (2)
61-78: ์ธ๋ฉ๋ชจ๋ฆฌ ํ์ด์ง์ ๋์ฉ๋ ๋ฐ์ดํฐ์์ ๋นํจ์จ์ ์ ๋๋ค.ํ์ฌ ๊ตฌํ์
(page + 1) * size๋งํผ์ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ ํ ์ธ๋ฉ๋ชจ๋ฆฌ์์skip/limit์ ์ ์ฉํฉ๋๋ค. ํ์ด์ง ๋ฒํธ๊ฐ ์ปค์ง์๋ก ๋ถํ์ํ ๋ฐ์ดํฐ๋ฅผ ๋ง์ด ์กฐํํ๊ฒ ๋ฉ๋๋ค.Repository์์ ์ง์
OFFSET/LIMIT์ ์ฌ์ฉํ๋ ํ์ด์ง ์ฟผ๋ฆฌ ๋ฉ์๋๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ ๊ณ ๋ คํด์ฃผ์ธ์.๐ ๊ฐ์ ์ ์
// Repository์ ํ์ด์ง ๋ฉ์๋ ์ถ๊ฐ List<ProductMetricsWeekly> findByYearAndWeekOrderByLikeCountDesc( int year, int week, int offset, int limit); // Service์์ ์ง์ ์ฌ์ฉ public List<Ranking> getWeeklyRankingWithPaging(RankingType type, LocalDate date, int page, int size) { LocalDate targetDate = date != null ? date : LocalDate.now(); int year = targetDate.getYear(); int week = targetDate.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR); List<ProductMetricsWeekly> weeklyMetrics = switch (type) { case LIKE -> weeklyRepository.findByYearAndWeekOrderByLikeCountDescWithPaging( year, week, page * size, size); // ... other cases }; return convertWeeklyToRanking(weeklyMetrics, type, page * size); }
81-130: ์ค๋ณต ์ฝ๋ ์ถ์ถ์ ๊ณ ๋ คํด๋ณผ ์ ์์ต๋๋ค.
convertWeeklyToRanking๊ณผconvertMonthlyToRanking๋ฉ์๋๊ฐ ๊ฑฐ์ ๋์ผํ ๋ก์ง์ ๊ฐ์ง๊ณ ์์ต๋๋ค. ๊ณตํต ์ธํฐํ์ด์ค๋ ํจ์ํ ์ ๊ทผ๋ฒ์ผ๋ก ์ถ์ถํ๋ฉด ์ ์ง๋ณด์์ฑ์ด ํฅ์๋ ์ ์์ต๋๋ค.๐ ๋ฆฌํฉํ ๋ง ์์
// ๊ณตํต ๋ณํ ๋ฉ์๋ ์ถ์ถ private <T> List<Ranking> convertToRanking( List<T> metrics, RankingType type, Function<T, Long> productIdGetter, Function<T, Long> likeCountGetter, Function<T, Long> viewCountGetter, Function<T, Long> orderCountGetter ) { int rank = 1; List<Ranking> rankings = new ArrayList<>(); for (T metric : metrics) { double score = calculateScore(type, likeCountGetter.apply(metric), viewCountGetter.apply(metric), orderCountGetter.apply(metric)); rankings.add(Ranking.of( rank++, productIdGetter.apply(metric), score, likeCountGetter.apply(metric), viewCountGetter.apply(metric), orderCountGetter.apply(metric) )); } return rankings; }apps/commerce-collector/src/main/java/com/loopers/batch/metrics/job/ProductMetricsWeeklyJobConfig.java (2)
73-91: ISO ์ฃผ์ฐจ ๋ ์ง ๊ณ์ฐ ์ ์ฐ๋ ๊ฒฝ๊ณ ์ฃ์ง ์ผ์ด์ค ์ฃผ์ISO 8601 ์ฃผ์ฐจ ๊ธฐ์ค์ผ๋ก, 1์ 1์ผ์ด ํญ์ ํด๋น ์ฐ๋์ ์ฒซ ์ฃผ์ ์ํ์ง ์์ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, 2026๋ 1์ 1์ผ์ ISO ๊ธฐ์ค 2025๋ ์ 53๋ฒ์งธ ์ฃผ์ ํด๋นํฉ๋๋ค.
ํ์ฌ ๊ตฌํ์์
LocalDate.of(year, 1, 1)์ ์์์ ์ผ๋ก ์ฌ์ฉํ๋ฉด, ํน์ ์ฐ๋/์ฃผ์ฐจ ์กฐํฉ์์ ์์์น ๋ชปํ ๋ ์ง๊ฐ ๊ณ์ฐ๋ ์ ์์ต๋๋ค.๐ ๋ ์์ ์ ์ธ ISO ์ฃผ์ฐจ ๋ ์ง ๊ณ์ฐ ๋ฐฉ์ ์ ์
@Bean @StepScope public RepositoryItemReader<WeeklyAggregationDto> weeklyMetricsReader( @Value("#{jobParameters['year']}") Integer year, @Value("#{jobParameters['week']}") Integer week ) { // ์ฃผ๊ฐ ์์์ผ/์ข ๋ฃ์ผ ๊ณ์ฐ - LocalDate startDate = LocalDate.of(year, 1, 1) - .with(IsoFields.WEEK_OF_WEEK_BASED_YEAR, week) - .with(DayOfWeek.MONDAY); + LocalDate startDate = LocalDate.ofYearDay(year, 1) + .with(IsoFields.WEEK_BASED_YEAR, year) + .with(IsoFields.WEEK_OF_WEEK_BASED_YEAR, week) + .with(DayOfWeek.MONDAY); LocalDate endDate = startDate.plusDays(6);
93-103: Processor์ Writer์ ๋ถํ์ํ @StepScope ์ ๊ฑฐ ๊ณ ๋ ค
weeklyMetricsProcessor()์weeklyMetricsWriter()๋ job parameter๋ฅผ ์ฌ์ฉํ์ง ์์ผ๋ฏ๋ก@StepScope๊ฐ ํ์ํ์ง ์์ต๋๋ค.@StepScope๋ ๋งค Step ์คํ๋ง๋ค ์ ์ธ์คํด์ค๋ฅผ ์์ฑํ์ฌ ์ฝ๊ฐ์ ์ค๋ฒํค๋๊ฐ ๋ฐ์ํฉ๋๋ค.๐ @StepScope ์ ๊ฑฐ ์ ์
@Bean - @StepScope public ItemProcessor<WeeklyAggregationDto, ProductMetricsWeekly> weeklyMetricsProcessor() { return new WeeklyMetricsProcessor(); } @Bean - @StepScope public ItemWriter<ProductMetricsWeekly> weeklyMetricsWriter() { return new WeeklyMetricsWriter(weeklyRepository); }apps/commerce-collector/src/main/java/com/loopers/batch/metrics/job/ProductMetricsMonthlyJobConfig.java (1)
89-99: Processor์ Writer์ ๋ถํ์ํ @StepScope ์ ๊ฑฐ ๊ณ ๋ ค์ฃผ๊ฐ ์ค์ ๊ณผ ๋์ผํ๊ฒ,
monthlyMetricsProcessor()์monthlyMetricsWriter()๋ job parameter๋ฅผ ์ฌ์ฉํ์ง ์์ผ๋ฏ๋ก@StepScope๊ฐ ๋ถํ์ํฉ๋๋ค.๐ @StepScope ์ ๊ฑฐ ์ ์
@Bean - @StepScope public ItemProcessor<MonthlyAggregationDto, ProductMetricsMonthly> monthlyMetricsProcessor() { return new MonthlyMetricsProcessor(); } @Bean - @StepScope public ItemWriter<ProductMetricsMonthly> monthlyMetricsWriter() { return new MonthlyMetricsWriter(monthlyRepository); }apps/commerce-api/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsWeeklyRepositoryImpl.java (1)
16-18: @component ๋์ @repository ์ฌ์ฉ ๊ณ ๋ ค์ด ํด๋์ค๋ ๋ฐ์ดํฐ ์ ๊ทผ ๊ณ์ธต์ ๋ด๋นํ๋ฏ๋ก,
@Component๋์@Repository๋ฅผ ์ฌ์ฉํ๋ฉด ์๋ฏธ์ ์ผ๋ก ๋ ๋ช ํํ๊ณ , Spring์ ์์ธ ๋ณํ(exception translation) ๊ธฐ๋ฅ๋ ํ์ฉํ ์ ์์ต๋๋ค.๐ @repository ์ฌ์ฉ ์ ์
-import org.springframework.stereotype.Component; +import org.springframework.stereotype.Repository; /** * ์ฃผ๊ฐ ์ํ ์ง๊ณ Repository ๊ตฌํ (์ฝ๊ธฐ ์ ์ฉ) * commerce-collector์์ ์์ฑํ ์ง๊ณ ๋ฐ์ดํฐ ์กฐํ */ -@Component +@Repository @RequiredArgsConstructor public class ProductMetricsWeeklyRepositoryImpl implements ProductMetricsWeeklyRepository {
๐ Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
๐ Files selected for processing (40)
apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingFacade.javaapps/commerce-api/src/main/java/com/loopers/domain/metrics/ProductMetricsMonthly.javaapps/commerce-api/src/main/java/com/loopers/domain/metrics/ProductMetricsMonthlyRepository.javaapps/commerce-api/src/main/java/com/loopers/domain/metrics/ProductMetricsWeekly.javaapps/commerce-api/src/main/java/com/loopers/domain/metrics/ProductMetricsWeeklyRepository.javaapps/commerce-api/src/main/java/com/loopers/domain/ranking/PeriodRankingService.javaapps/commerce-api/src/main/java/com/loopers/domain/ranking/PeriodType.javaapps/commerce-api/src/main/java/com/loopers/domain/ranking/Ranking.javaapps/commerce-api/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsMonthlyJpaRepository.javaapps/commerce-api/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsMonthlyRepositoryImpl.javaapps/commerce-api/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsWeeklyJpaRepository.javaapps/commerce-api/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsWeeklyRepositoryImpl.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-collector/build.gradle.ktsapps/commerce-collector/src/main/java/com/loopers/application/ranking/RankingScheduler.javaapps/commerce-collector/src/main/java/com/loopers/batch/listener/ChunkListener.javaapps/commerce-collector/src/main/java/com/loopers/batch/listener/JobListener.javaapps/commerce-collector/src/main/java/com/loopers/batch/listener/StepMonitorListener.javaapps/commerce-collector/src/main/java/com/loopers/batch/metrics/MonthlyMetricsProcessor.javaapps/commerce-collector/src/main/java/com/loopers/batch/metrics/MonthlyMetricsWriter.javaapps/commerce-collector/src/main/java/com/loopers/batch/metrics/WeeklyMetricsProcessor.javaapps/commerce-collector/src/main/java/com/loopers/batch/metrics/WeeklyMetricsWriter.javaapps/commerce-collector/src/main/java/com/loopers/batch/metrics/job/ProductMetricsMonthlyJobConfig.javaapps/commerce-collector/src/main/java/com/loopers/batch/metrics/job/ProductMetricsWeeklyJobConfig.javaapps/commerce-collector/src/main/java/com/loopers/domain/metrics/ProductMetricsDailyRepository.javaapps/commerce-collector/src/main/java/com/loopers/domain/metrics/ProductMetricsMonthly.javaapps/commerce-collector/src/main/java/com/loopers/domain/metrics/ProductMetricsMonthlyRepository.javaapps/commerce-collector/src/main/java/com/loopers/domain/metrics/ProductMetricsWeekly.javaapps/commerce-collector/src/main/java/com/loopers/domain/metrics/ProductMetricsWeeklyRepository.javaapps/commerce-collector/src/main/java/com/loopers/domain/metrics/dto/MonthlyAggregationDto.javaapps/commerce-collector/src/main/java/com/loopers/domain/metrics/dto/WeeklyAggregationDto.javaapps/commerce-collector/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsDailyJpaRepository.javaapps/commerce-collector/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsDailyRepositoryImpl.javaapps/commerce-collector/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsMonthlyJpaRepository.javaapps/commerce-collector/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsMonthlyRepositoryImpl.javaapps/commerce-collector/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsWeeklyJpaRepository.javaapps/commerce-collector/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsWeeklyRepositoryImpl.javaapps/commerce-collector/src/test/java/com/loopers/batch/metrics/job/ProductMetricsMonthlyJobTest.javaapps/commerce-collector/src/test/java/com/loopers/batch/metrics/job/ProductMetricsWeeklyJobTest.java
๐งฐ Additional context used
๐ง Learnings (5)
๐ Learning: 2026-01-01T14:12:04.385Z
Learnt from: ghojeong
Repo: Loopers-dev-lab/loopers-spring-kotlin-template PR: 76
File: apps/commerce-batch/src/main/kotlin/com/loopers/batch/listener/ChunkListener.kt:4-5
Timestamp: 2026-01-01T14:12:04.385Z
Learning: In Spring Batch 6.x, ChunkListener API changed from using ChunkContext to Chunk<T> parameters. The correct method signatures are: beforeChunk(Chunk<T> chunk), afterChunk(Chunk<T> chunk), and onChunkError(Exception exception, Chunk<T> chunk). The import path is org.springframework.batch.core.listener.ChunkListener.
Applied to files:
apps/commerce-collector/src/main/java/com/loopers/batch/listener/ChunkListener.javaapps/commerce-collector/src/main/java/com/loopers/batch/listener/JobListener.javaapps/commerce-collector/src/main/java/com/loopers/batch/listener/StepMonitorListener.java
๐ Learning: 2026-01-01T14:12:07.943Z
Learnt from: ghojeong
Repo: Loopers-dev-lab/loopers-spring-kotlin-template PR: 76
File: apps/commerce-batch/src/main/kotlin/com/loopers/batch/listener/JobListener.kt:4-5
Timestamp: 2026-01-01T14:12:07.943Z
Learning: Spring Batch 6.x์์ JobExecution์ org.springframework.batch.core.job ํจํค์ง์, JobExecutionListener๋ org.springframework.batch.core.listener ํจํค์ง์ ์์นํฉ๋๋ค. Spring Batch 5.x์ ๋ฌ๋ฆฌ ํ์ ํจํค์ง๋ฅผ ์ฌ์ฉํฉ๋๋ค.
Applied to files:
apps/commerce-collector/src/main/java/com/loopers/batch/listener/JobListener.javaapps/commerce-collector/src/main/java/com/loopers/batch/listener/StepMonitorListener.java
๐ 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-collector/src/main/java/com/loopers/domain/metrics/ProductMetricsDailyRepository.javaapps/commerce-collector/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsDailyRepositoryImpl.javaapps/commerce-api/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsMonthlyJpaRepository.javaapps/commerce-collector/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsDailyJpaRepository.javaapps/commerce-api/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsWeeklyJpaRepository.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-collector/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsMonthlyRepositoryImpl.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-collector/src/main/java/com/loopers/domain/metrics/ProductMetricsWeekly.javaapps/commerce-api/src/main/java/com/loopers/domain/metrics/ProductMetricsMonthly.javaapps/commerce-api/src/main/java/com/loopers/domain/metrics/ProductMetricsWeekly.javaapps/commerce-collector/src/main/java/com/loopers/domain/metrics/ProductMetricsMonthly.java
๐งฌ Code graph analysis (10)
apps/commerce-collector/src/main/java/com/loopers/batch/listener/ChunkListener.java (2)
apps/commerce-collector/src/main/java/com/loopers/batch/listener/JobListener.java (1)
Slf4j(14-53)apps/commerce-collector/src/main/java/com/loopers/batch/listener/StepMonitorListener.java (1)
Slf4j(14-45)
apps/commerce-collector/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsWeeklyRepositoryImpl.java (2)
apps/commerce-collector/src/main/java/com/loopers/batch/metrics/WeeklyMetricsWriter.java (1)
Slf4j(12-25)apps/commerce-collector/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsDailyRepositoryImpl.java (1)
Slf4j(20-189)
apps/commerce-collector/src/main/java/com/loopers/batch/metrics/MonthlyMetricsWriter.java (1)
apps/commerce-collector/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsMonthlyRepositoryImpl.java (1)
Slf4j(17-84)
apps/commerce-api/src/main/java/com/loopers/domain/ranking/PeriodRankingService.java (3)
apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingFacade.java (1)
Component(16-154)apps/commerce-api/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsMonthlyRepositoryImpl.java (1)
Component(16-67)apps/commerce-api/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsWeeklyRepositoryImpl.java (1)
Component(16-67)
apps/commerce-collector/src/main/java/com/loopers/batch/listener/StepMonitorListener.java (6)
apps/commerce-collector/src/main/java/com/loopers/batch/listener/ChunkListener.java (1)
Slf4j(9-21)apps/commerce-collector/src/main/java/com/loopers/batch/listener/JobListener.java (1)
Slf4j(14-53)apps/commerce-collector/src/main/java/com/loopers/batch/metrics/WeeklyMetricsWriter.java (1)
Slf4j(12-25)apps/commerce-collector/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsDailyRepositoryImpl.java (1)
Slf4j(20-189)apps/commerce-collector/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsWeeklyRepositoryImpl.java (1)
Slf4j(17-84)apps/pg-simulator/src/main/kotlin/com/loopers/interfaces/api/ApiControllerAdvice.kt (1)
log(22-119)
apps/commerce-collector/src/main/java/com/loopers/domain/metrics/ProductMetricsWeekly.java (1)
apps/commerce-collector/src/main/java/com/loopers/domain/metrics/dto/WeeklyAggregationDto.java (1)
Getter(13-26)
apps/commerce-api/src/main/java/com/loopers/domain/metrics/ProductMetricsMonthly.java (3)
apps/commerce-api/src/main/java/com/loopers/domain/ranking/Ranking.java (1)
Getter(6-49)apps/commerce-api/src/main/java/com/loopers/domain/metrics/ProductMetricsWeekly.java (1)
Entity(15-66)apps/pg-simulator/src/main/kotlin/com/loopers/domain/payment/Payment.kt (1)
name(14-87)
apps/commerce-api/src/main/java/com/loopers/domain/metrics/ProductMetricsWeekly.java (2)
apps/commerce-api/src/main/java/com/loopers/domain/ranking/Ranking.java (1)
Getter(6-49)apps/commerce-api/src/main/java/com/loopers/domain/metrics/ProductMetricsMonthly.java (1)
Entity(15-66)
apps/commerce-collector/src/main/java/com/loopers/batch/metrics/job/ProductMetricsWeeklyJobConfig.java (1)
apps/commerce-collector/src/main/java/com/loopers/batch/metrics/WeeklyMetricsProcessor.java (1)
WeeklyMetricsProcessor(10-31)
apps/commerce-collector/src/main/java/com/loopers/domain/metrics/ProductMetricsMonthly.java (1)
apps/commerce-collector/src/main/java/com/loopers/domain/metrics/dto/MonthlyAggregationDto.java (1)
Getter(13-26)
๐ Additional comments (46)
apps/commerce-collector/build.gradle.kts (2)
36-36: Spring Batch ํ ์คํธ ์ข ์์ฑ์ด ์ฌ๋ฐ๋ฅด๊ฒ ์ถ๊ฐ๋์์ต๋๋ค.
spring-batch-test๊ฐtestImplementation์ค์ฝํ๋ก ์ ์ ํ๊ฒ ์ถ๊ฐ๋์ด ๋ฐฐ์น ์์ ํ ์คํธ(JobLauncherTestUtils, StepScopeTestExecutionListener ๋ฑ)๋ฅผ ์ง์ํ ์ ์์ต๋๋ค.
14-14: Spring Batch ์ข ์์ฑ์ด ์ ์ ํ ์ถ๊ฐ๋์์ต๋๋ค.
spring-boot-starter-batch์spring-batch-test์ข ์์ฑ์ด ์ฌ๋ฐ๋ฅด๊ฒ ์ถ๊ฐ๋์์ต๋๋ค. ์ข ์์ฑ์ ์ ์ ํ ์ค์ฝํ ์ฒ๋ฆฌ๋์์ผ๋ฉฐ(implementation vs testImplementation), Spring Boot 3.4.4์ ์์กด์ฑ ๊ด๋ฆฌ๋ฅผ ํตํด ๋ฒ์ ์ด ๊ด๋ฆฌ๋ฉ๋๋ค.2025~2026๋ ํ์ฌ๊น์ง Spring Batch ๊ด๋ จ ๋ณด์ ์ทจ์ฝ์ (CVE)์ด ๋ฐ๊ฒฌ๋์ง ์์์ผ๋ฏ๋ก ๋ณด์์ ๋ฌธ์ ๊ฐ ์์ต๋๋ค.
apps/commerce-collector/src/main/java/com/loopers/application/ranking/RankingScheduler.java (1)
135-141: LGTM! ๋ณด๊ด ๊ธฐ๊ฐ ๋ณ๊ฒฝ์ด ์ ์ ํฉ๋๋ค.์ฃผ๊ฐ/์๊ฐ ์ง๊ณ ๋ฐฐ์น ์์ ์ ์ง์ํ๊ธฐ ์ํด 10์ผ์์ 30์ผ๋ก ๋ณด๊ด ๊ธฐ๊ฐ์ ํ์ฅํ ๊ฒ์ ํฉ๋ฆฌ์ ์ ๋๋ค. ์๊ฐ ์ง๊ณ์๋ ์ต์ 30์ผ์ ๋ฐ์ดํฐ๊ฐ ํ์ํฉ๋๋ค.
apps/commerce-collector/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsDailyJpaRepository.java (2)
31-54: GROUP BY์ Page ์กฐํฉ ์ count ์ฟผ๋ฆฌ ๊ฒ์ฆ ํ์Spring Data JPA์์
GROUP BY๊ฐ ํฌํจ๋ ์ฟผ๋ฆฌ์Page๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ, ์๋ ์์ฑ๋๋ count ์ฟผ๋ฆฌ๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ๋์ํ์ง ์์ ์ ์์ต๋๋ค.GROUP BY๊ฒฐ๊ณผ์ ํ ์๊ฐ ์๋ ์ ์ฒด ๋ ์ฝ๋ ์๋ฅผ ๋ฐํํ ์ ์์ต๋๋ค.count ์ฟผ๋ฆฌ๋ฅผ ๋ช ์์ ์ผ๋ก ์ง์ ํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค:
๐ ์ ์ํ๋ ์์ ์ฌํญ
@Query(value = """ SELECT new com.loopers.domain.metrics.dto.WeeklyAggregationDto( p.productId, :year, :week, :startDate, :endDate, SUM(p.likeDelta), SUM(p.viewDelta), SUM(p.orderDelta), 0L ) FROM ProductMetricsDaily p WHERE p.metricDate BETWEEN :startDate AND :endDate GROUP BY p.productId ORDER BY p.productId - """) + """, + countQuery = """ + SELECT COUNT(DISTINCT p.productId) + FROM ProductMetricsDaily p + WHERE p.metricDate BETWEEN :startDate AND :endDate + """) Page<WeeklyAggregationDto> findWeeklyAggregation(
56-82: ์๊ฐ ์ง๊ณ ์ฟผ๋ฆฌ๋ ๋์ผํ๊ฒ count ์ฟผ๋ฆฌ ๋ช ์ ํ์์ฃผ๊ฐ ์ง๊ณ์ ๋์ผํ ์ด์ ๋ก, ์๊ฐ ์ง๊ณ ์ฟผ๋ฆฌ์๋ ๋ช ์์ ์ธ count ์ฟผ๋ฆฌ๋ฅผ ์ถ๊ฐํ์ธ์.
๐ ์ ์ํ๋ ์์ ์ฌํญ
@Query(value = """ SELECT new com.loopers.domain.metrics.dto.MonthlyAggregationDto( p.productId, :year, :month, :startDate, :endDate, SUM(p.likeDelta), SUM(p.viewDelta), SUM(p.orderDelta), 0L ) FROM ProductMetricsDaily p WHERE p.metricDate BETWEEN :startDate AND :endDate GROUP BY p.productId ORDER BY p.productId - """) + """, + countQuery = """ + SELECT COUNT(DISTINCT p.productId) + FROM ProductMetricsDaily p + WHERE p.metricDate BETWEEN :startDate AND :endDate + """) Page<MonthlyAggregationDto> findMonthlyAggregation(apps/commerce-collector/src/main/java/com/loopers/domain/metrics/ProductMetricsDailyRepository.java (1)
27-66: LGTM! ์ธํฐํ์ด์ค ์ค๊ณ๊ฐ ์ ๋์ด ์์ต๋๋ค.์ฃผ๊ฐ/์๊ฐ ์ง๊ณ ๋ฉ์๋์ ๋ํ Javadoc์ด ๋ช ํํ๊ณ , ํ์ด์ง ์ง์์ด Spring Batch Job๊ณผ์ ํตํฉ์ ์ ํฉํฉ๋๋ค. ๊ธฐ์กด ์ฝ๋๋ฒ ์ด์ค์ Page/Pageable ์ฌ์ฉ ํจํด๊ณผ ์ผ๊ด์ฑ์ด ์์ต๋๋ค.
apps/commerce-api/src/main/java/com/loopers/domain/ranking/PeriodType.java (1)
1-15: LGTM! ๊น๋ํ enum ์ค๊ณ์ ๋๋ค.
PeriodTypeenum์ด ๋ช ํํ๊ฒ ์ ์๋์ด ์์ผ๋ฉฐ, ํ๊ตญ์ด ๋ ์ด๋ธ์ด ์ฌ์ฉ์ ์นํ์ ์ ๋๋ค. Lombok์ ํ์ฉํ์ฌ ๋ณด์ผ๋ฌํ๋ ์ดํธ๋ฅผ ์ต์ํํ ์ ๋ ์ข์ต๋๋ค.apps/commerce-api/src/main/java/com/loopers/domain/metrics/ProductMetricsMonthly.java (2)
15-57: ์ฝ๊ธฐ ์ ์ฉ ์ํฐํฐ ์ค๊ณ๊ฐ ์ ์ ํฉ๋๋ค.Materialized View์ ๋งคํ๋๋ ์ฝ๊ธฐ ์ ์ฉ ์ํฐํฐ๋ก
@Immutable์ด๋ ธํ ์ด์ ์ด ์ ์ ํ๊ฒ ์ฌ์ฉ๋์์ต๋๋ค. JPA๋ฅผ ํตํด ์กฐํ๋ง ๊ฐ๋ฅํ๋ฉฐ, ์ค์ ๋ฐ์ดํฐ๋ commerce-collector์ ๋ฐฐ์น ์์ ์์ ์์ฑ๋ฉ๋๋ค.
15-65: AI ์์ฝ๊ณผ ์ค์ ์ฝ๋ ๊ฐ์ ๋ถ์ผ์น๊ฐ ์์ต๋๋ค.AI ์์ฝ์์ ์ธ๊ธ๋ ๋ค์ ๋ฉ์๋๋ค์ด ์ค์ ์ฝ๋์๋ ์กด์ฌํ์ง ์์ต๋๋ค:
static factory create(productId, year, month, periodStartDate, periodEndDate)updateMetrics(likeCount, viewCount, orderCount)reset()์ฝ๊ธฐ ์ ์ฉ ์ํฐํฐ์ด๋ฏ๋ก ์ด๋ฌํ ๋ฉ์๋๊ฐ ์๋ ๊ฒ์ด ์ฌ๋ฐ๋ฅธ ์ค๊ณ์ด๋ฉฐ, ์ค์ ์ฝ๋๊ฐ ์ ํํฉ๋๋ค.
apps/commerce-collector/src/main/java/com/loopers/domain/metrics/dto/WeeklyAggregationDto.java (1)
13-26: DTO ์ค๊ณ๊ฐ ๊น๋ํ๊ณ ์ ์ ํฉ๋๋ค.Spring Batch
ItemReader์์ ์ฌ์ฉ๋ ์ฃผ๊ฐ ์ง๊ณ ๋ฐ์ดํฐ DTO๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์ ์๋์์ต๋๋ค. Lombok ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ์ฌ ๋ณด์ผ๋ฌํ๋ ์ดํธ ์ฝ๋๋ฅผ ํจ๊ณผ์ ์ผ๋ก ์ ๊ฑฐํ์ต๋๋ค.apps/commerce-api/src/main/java/com/loopers/domain/metrics/ProductMetricsWeeklyRepository.java (1)
6-36: ์ ์ฅ์ ์ธํฐํ์ด์ค๊ฐ ์ ์ค๊ณ๋์์ต๋๋ค.์ฃผ๊ฐ ๋ฉํธ๋ฆญ์ค ์กฐํ๋ฅผ ์ํ ๋ค์ํ ์ ๋ ฌ ๊ธฐ์ค(์ข์์, ์กฐํ์, ์ฃผ๋ฌธ์, ๋ณตํฉ ์ ์)์ ์ ๊ณตํ๋ฉฐ, ๋ฉ์๋ ๋ช ๋ช ์ด ๋ช ํํ๊ณ Javadoc์ด ์ ์ ํ๊ฒ ์์ฑ๋์์ต๋๋ค.
apps/commerce-api/src/main/java/com/loopers/domain/metrics/ProductMetricsMonthlyRepository.java (1)
6-36: ์ ์ฅ์ ์ธํฐํ์ด์ค๊ฐ ์ ์ค๊ณ๋์์ต๋๋ค.์๊ฐ ๋ฉํธ๋ฆญ์ค ์กฐํ๋ฅผ ์ํ ๋ค์ํ ์ ๋ ฌ ๊ธฐ์ค์ ์ ๊ณตํ๋ฉฐ, ProductMetricsWeeklyRepository์ ์ผ๊ด๋ ์ค๊ณ ํจํด์ ๋ฐ๋ฅด๊ณ ์์ต๋๋ค. ๋ฉ์๋ ๋ช ๋ช ๊ณผ ๋ฌธ์ํ๊ฐ ์ ์ ํฉ๋๋ค.
apps/commerce-collector/src/main/java/com/loopers/batch/listener/ChunkListener.java (1)
14-15: ์ฝ๋๋ ํ์ฌ ํ๋ก์ ํธ์ Spring Batch ๋ฒ์ ๊ณผ ์ผ์นํฉ๋๋ค.ํ๋ก์ ํธ๋ Spring Boot 3.4.4๋ฅผ ์ฌ์ฉํ๋ฉฐ, ์ด๋ Spring Batch 5.2.2๋ฅผ ํฌํจํฉ๋๋ค. Spring Batch 5.2.2์์
ChunkListener์ ์ฌ๋ฐ๋ฅธ ๋ฉ์๋ ์๊ทธ๋์ฒ๋ChunkContextํ๋ผ๋ฏธํฐ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ๋๋ค:@AfterChunk void afterChunk(ChunkContext chunkContext)ํ์ฌ ์ฝ๋๋ Spring Batch 5.2.2 API ์ฌ์์ ์ ํํ ๋ฐ๋ฅด๊ณ ์์ผ๋ฏ๋ก ๋ฌธ์ ๊ฐ ์์ต๋๋ค.
Chunk<T>ํ๋ผ๋ฏธํฐ๋ก์ ๋ณ๊ฒฝ์ Spring Batch 6.x ์ด์์ผ๋ก ์ ๊ทธ๋ ์ด๋ํ ๋๋ง ํ์ํฉ๋๋ค.apps/commerce-collector/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsWeeklyJpaRepository.java (1)
11-20: ์ญ์ ์ฟผ๋ฆฌ ๋ก์ง ํ์ธ ์๋ฃ.๋ ๋์ ์ฃผ์ฐจ๋ฅผ ๊ธฐ์ค์ผ๋ก ์ด์ ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํ๋ ๋ก์ง์ด ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํ๋์์ต๋๋ค.
(m.year < :year) OR (m.year = :year AND m.week < :week)์กฐ๊ฑด์ ์ ํํ ์๋ํ ๋๋ก ๋์ํฉ๋๋ค.apps/commerce-api/src/main/java/com/loopers/domain/metrics/ProductMetricsWeekly.java (2)
15-57: ์ฝ๊ธฐ ์ ์ฉ ์ํฐํฐ ๊ตฌ์กฐ๊ฐ ์ ์ค๊ณ๋์์ต๋๋ค.
@Immutable์ด๋ ธํ ์ด์ ๊ณผ ํจ๊ป ์ฝ๊ธฐ ์ ์ฉ ์ํฐํฐ๋ก ๋ช ํํ ์ ์๋์ด ์์ด, Materialized View์ ํน์ฑ์ ์ฝ๋ ๋ ๋ฒจ์์ ์ ํํํ๊ณ ์์ต๋๋ค. ์ฃผ๊ฐ ์ง๊ณ ๋ฐ์ดํฐ๋ฅผ ์ํ ํ๋ ๊ตฌ์ฑ๋ ์ ์ ํฉ๋๋ค.
59-65: ์ ์ ๊ณ์ฐ ๋ก์ง์ ์ผ๊ด์ฑ์ด ํ๋ณด๋์์ต๋๋ค.
calculateCompositeScore()๋ฉ์๋๊ฐ ProductMetricsMonthly(apps/commerce-api/src/main/java/com/loopers/domain/metrics/ProductMetricsMonthly.java Line 57-61)์ ๋์ผํ ๊ฐ์ค์น(์ข์์ 0.2, ์กฐํ 0.1, ์ฃผ๋ฌธ 0.6)๋ฅผ ์ฌ์ฉํ์ฌ ์ผ๊ด์ฑ์ ์ ์งํ๊ณ ์์ต๋๋ค.apps/commerce-collector/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsMonthlyJpaRepository.java (1)
11-20: ์ญ์ ์ฟผ๋ฆฌ ๋ก์ง ํ์ธ ์๋ฃ.๋ ๋์ ์์ ๊ธฐ์ค์ผ๋ก ์ด์ ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํ๋ ๋ก์ง์ด ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํ๋์์ต๋๋ค.
(m.year < :year) OR (m.year = :year AND m.month < :month)์กฐ๊ฑด์ ์ ํํ ์๋ํ ๋๋ก ๋์ํฉ๋๋ค.apps/commerce-collector/src/test/java/com/loopers/batch/metrics/job/ProductMetricsMonthlyJobTest.java (5)
26-61: ํ ์คํธ ์ค์ ์ด ์ ์ ํฉ๋๋ค.Spring Batch ํ ์คํธ ์ค์ ๊ณผ ์๋ช ์ฃผ๊ธฐ ๊ด๋ฆฌ๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌ์ฑ๋์ด ์์ต๋๋ค.
DatabaseCleanUp์ ์ฌ์ฉํ ํ ์ด๋ธ ์ ๋ฆฌ ๋ฐฉ์๋ ์ ์ ํฉ๋๋ค.
63-123: ์๊ฐ ์ง๊ณ ์ฑ๊ณต ์ผ์ด์ค๊ฐ ์ ๊ฒ์ฆ๋์์ต๋๋ค.3๊ฐ ์ํ์ ๋ํ 31์ผ๊ฐ ์ง๊ณ ๋ฐ์ดํฐ ์์ฑ ๋ฐ ๊ฒ์ฆ ๋ก์ง์ด ์ ํํฉ๋๋ค. Job/Step ์ํ์ ์ง๊ณ ๊ฒฐ๊ณผ(310 likes, 3100 views, 155 orders)๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ๊ฒ์ฆ๋๊ณ ์์ต๋๋ค.
125-151: ๋ฐ์ดํฐ ๋ถ์ฌ ์๋๋ฆฌ์ค๊ฐ ์ ์ ํ ๊ฒ์ฆ๋์์ต๋๋ค.Daily ๋ฐ์ดํฐ๊ฐ ์์ ๋ Job์ด ์ฑ๊ณตํ์ง๋ง ์ฒ๋ฆฌ ๊ฑด์๊ฐ 0์ด๊ณ Monthly ๋ฐ์ดํฐ๊ฐ ์์ฑ๋์ง ์๋ ๊ฒ์ ์ฌ๋ฐ๋ฅด๊ฒ ๊ฒ์ฆํ๊ณ ์์ต๋๋ค.
153-206: UPSERT ๋์์ด ์ ํํ ๊ฒ์ฆ๋์์ต๋๋ค.๋์ผ ์์ ๋ํด ์ฌ์คํ ์ ์ ๋ ์ฝ๋๊ฐ ์์ฑ๋์ง ์๊ณ ๊ธฐ์กด ๋ ์ฝ๋๊ฐ ์ ๋ฐ์ดํธ๋๋ ๊ฒ(10 โ 30)์ ์ฌ๋ฐ๋ฅด๊ฒ ํ ์คํธํ๊ณ ์์ต๋๋ค.
208-272: Step ์คํ๊ณผ ์๋ณ ์ผ์ ์ฒ๋ฆฌ๊ฐ ์ ๊ฒ์ฆ๋์์ต๋๋ค.ํน์ Step๋ง ์คํํ๋ ํ ์คํธ์ 2์(28์ผ)๊ณผ 12์(31์ผ)์ ์ผ์ ์ฐจ์ด๋ฅผ ์ ํํ ๊ฒ์ฆํ๋ ํ ์คํธ๊ฐ ๋ชจ๋ ์ ์ ํฉ๋๋ค. 2025๋ ์ด ํ๋ ์์ ๊ณ ๋ คํ 28์ผ ๊ฒ์ฆ๋ ์ ํํฉ๋๋ค.
apps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1Dto.java (2)
67-88: ์ํ ์ ๋ณด ํ๋๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ๋งคํ๋์์ต๋๋ค.
RankingItem์productํ๋๊ฐ ์ถ๊ฐ๋์๊ณfrom()๋ฉ์๋์์info.product()๋ฅผ ์ฌ์ฉํ์ฌ ์ ํํ ๋งคํํ๊ณ ์์ต๋๋ค.
16-36: ์ด ๊ฒํ ์๊ฒฌ์ ๊ทผ๊ฑฐ๊ฐ ์์ต๋๋ค.periodType์ด null์ผ ๋ NPE๊ฐ ๋ฐ์ํ ์ ์๋ค๋ ์ฐ๋ ค๋ ํ๋นํ์ง ์์ต๋๋ค.
RankingFacadeํด๋์ค์getTopRanking()๋ฐgetRankingWithPaging()๋ฉ์๋์์ ๋ค์๊ณผ ๊ฐ์ด null ์ฒ๋ฆฌ๊ฐ ๋ช ์์ ์ผ๋ก ๊ตฌํ๋์ด ์์ต๋๋ค:PeriodType period = periodType != null ? periodType : PeriodType.DAILY;๋ฐ๋ผ์ null๋ก ์ ๋ฌ๋๋๋ผ๋ ๊ธฐ๋ณธ๊ฐ(DAILY)์ผ๋ก ์๋ ์ค์ ๋๋ฏ๋ก ์๋น์ค ๋ ์ด์ด์์ NPE ์ํ์ ์์ต๋๋ค. DTO์
@NotNull์ถ๊ฐ๋ ๊ธฐ๋ณธ๊ฐ ์ค์ ์ ๋ถํ์ํฉ๋๋ค.Likely an incorrect or invalid review comment.
apps/commerce-collector/src/main/java/com/loopers/batch/metrics/WeeklyMetricsWriter.java (2)
18-19: unchecked cast๋ Spring Batch ํจํด์์ ๋ถ๊ฐํผํฉ๋๋ค.Line 19์ unchecked cast๋ Spring Batch์ ์ ๋ค๋ฆญ ์ ์ฝ์ผ๋ก ์ธํด ๋ถ๊ฐํผํ๋ฉฐ, ๋ฐํ์์ ์์ ํฉ๋๋ค.
12-25: @component ์ด๋ ธํ ์ด์ ์ด ํ์ํ์ง ์์ต๋๋ค.
WeeklyMetricsWriter๋ProductMetricsWeeklyJobConfig์์@Bean๋ฉ์๋๋ก ๋ช ์์ ์ผ๋ก ์ธ์คํด์คํ๋๊ณ ์์ต๋๋ค. ์ด๋ Spring Batch์ ํ์ค ํจํด์ผ๋ก, ์๋ ์ค์บ์ด ์๋ ์ค์ ํด๋์ค์์ ์ง์ ๋น์ ์์ฑํฉ๋๋ค.@Component์ด๋ ธํ ์ด์ ์์ด ํ์ฌ ๊ตฌํ์ด ์ ์ ์๋ํ๋ฏ๋ก ์ถ๊ฐ ์กฐ์น๊ฐ ํ์ํ์ง ์์ต๋๋ค.Likely an incorrect or invalid review comment.
apps/commerce-collector/src/main/java/com/loopers/batch/listener/JobListener.java (1)
25-52: Job ์๋ฃ ์ duration ๊ณ์ฐ ๋ฐ ๋ก๊น ์ด ์ ์ ํฉ๋๋ค.์คํ ์๊ฐ ๊ณ์ฐ๊ณผ ํ๊ตญ์ด ์๊ฐ ๋จ์(์๊ฐ/๋ถ/์ด) ํฌ๋งคํ ์ด ์ ํํ๋ฉฐ, ๋ก๊น ๋ก์ง์ ๋ฌธ์ ๊ฐ ์์ต๋๋ค.
apps/commerce-collector/src/main/java/com/loopers/batch/listener/StepMonitorListener.java (1)
19-22: Step ์์ ๋ก๊น ์ด ์ ์ ํฉ๋๋ค.Step ์์ ์ Step ์ด๋ฆ์ ์ฌ๋ฐ๋ฅธ SLF4J ๊ตฌ๋ฌธ์ผ๋ก ๋ก๊น ํ๊ณ ์์ต๋๋ค.
apps/commerce-collector/src/main/java/com/loopers/domain/metrics/ProductMetricsMonthly.java (1)
16-28: ํ ์ด๋ธ ์ค๊ณ๊ฐ ์ ๋์ด ์์ต๋๋คUnique constraint์ ์ธ๋ฑ์ค ์ค์ ์ด ์ ์ ํฉ๋๋ค:
uk_product_year_month๋ ์ค๋ณต ๋ฐ์ดํฐ ๋ฐฉ์งidx_year_month๋ ๊ธฐ๊ฐ๋ณ ์กฐํ ์ต์ ํidx_product_id๋ ์ํ๋ณ ์กฐํ ์ต์ ํ์ด๋ ๋ฐฐ์น upsert ๋ฐ ๋ญํน ์กฐํ ํจํด์ ์ ๋ง์ต๋๋ค.
apps/commerce-collector/src/main/java/com/loopers/domain/metrics/ProductMetricsWeekly.java (2)
16-28: ํ ์ด๋ธ ์ค๊ณ๊ฐ ์ ์ ํฉ๋๋คUnique constraint์ ์ธ๋ฑ์ค ๊ตฌ์ฑ์ด ์ฃผ๊ฐ ๋ญํน ์กฐํ ํจํด์ ์ ๋ง์ต๋๋ค:
uk_product_year_week๋ก ์ค๋ณต ๋ฐฉ์งidx_year_week๋ก ๊ธฐ๊ฐ๋ณ ์กฐํ ์ต์ ํidx_product_id๋ก ์ํ๋ณ ์กฐํ ์ต์ ํ
1-111: AI ์์ฝ์ ๋ชจํธํจ: calculateCompositeScore() ๋ฉ์๋ ์์น ๋ช ํํ ํ์๊ฒํ ๋์์ธ
apps/commerce-collector/src/main/java/com/loopers/domain/metrics/ProductMetricsWeekly.javaํ์ผ์๋ ์ค์ ๋กcalculateCompositeScore()๋ฉ์๋๊ฐ ์์ต๋๋ค. ๋ค๋ง, ์ด๋ ์ฝ๋ ๊ฒฐํจ์ด ์๋๋ผ ์๋์ ์ธ ์ํคํ ์ฒ ์ค๊ณ์ ๋๋ค.์ฝ๋๋ฒ ์ด์ค์๋ ๋์ผํ ์ด๋ฆ์ ProductMetricsWeekly ์ํฐํฐ๊ฐ ๋ ๋ชจ๋์ ์กด์ฌํฉ๋๋ค:
- commerce-collector: ๋ฐฐ์น ์ง๊ณ์ฉ ์ฐ๊ธฐ ์ํฐํฐ (BaseEntity ์์) - ์ ์ ๊ณ์ฐ ๋ฉ์๋ ์์
- commerce-api: ์ฝ๊ธฐ ์ ์ฉ ์ํฐํฐ (@immutable) - calculateCompositeScore() ๋ฉ์๋ ๊ตฌํ๋จ (๊ฐ์ค์น: likeร0.2 + viewร0.1 + orderร0.6)
commerce-collector ๋ชจ๋์์๋ ๋ฐฐ์น ์ฒ๋ฆฌ ์ค ๋ฉํธ๋ฆญ ์ง๊ณ๋ง ๋ด๋นํ๋ฉฐ, ์ ์ ๊ณ์ฐ์ ์ฝ๊ธฐ ์์ ์ commerce-api์ ์ํฐํฐ ๋ฉ์๋๋ PeriodRankingService ๋ฑ์์ ์ํ๋ฉ๋๋ค. ๋ฐ๋ผ์ ํ์ฌ ์ฝ๋๋ ์ ์์ด๋ฉฐ, AI ์์ฝ์ด ์ปจํ ์คํธ ์์ด ๋ฉ์๋ ์ถ๊ฐ๋ฅผ ์ธ๊ธํ ๊ฒ์ผ๋ก ๋ณด์ ๋๋ค.
apps/commerce-collector/src/main/java/com/loopers/domain/metrics/dto/MonthlyAggregationDto.java (1)
1-26: LGTM!DTO ๊ตฌ์กฐ๊ฐ ์๊ฐ ์ง๊ณ ์ฉ๋์ ์ ํฉํ๋ฉฐ, JPA ํ๋ก์ ์ ๋งคํ์ ์ํ
@NoArgsConstructor์ ํธ์์ฑ์ ์ํ@AllArgsConstructor๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์ ์ฉ๋์ด ์์ต๋๋ค.apps/commerce-collector/src/test/java/com/loopers/batch/metrics/job/ProductMetricsWeeklyJobTest.java (2)
125-232: LGTM!๋๋จธ์ง ํ ์คํธ ์ผ์ด์ค๋ค์ด ์ ๊ตฌ์ฑ๋์ด ์์ต๋๋ค:
- ๋น ๋ฐ์ดํฐ ์๋๋ฆฌ์ค ์ฒ๋ฆฌ
- UPSERT ๋ฉฑ๋ฑ์ฑ ๊ฒ์ฆ (10 + 20 = 30)
- Step ๋จ์ ์คํ ๊ฒ์ฆ
ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง๊ฐ ๋ฐฐ์น Job์ ์ฃผ์ ๋์์ ํฌ๊ด์ ์ผ๋ก ๋ค๋ฃจ๊ณ ์์ต๋๋ค.
63-123: ํ ์คํธ ์ผ์ด์ค๊ฐ ์ ๊ตฌ์ฑ๋์ด ์์ต๋๋ค.์ฃผ๊ฐ ์ง๊ณ ๋ฐฐ์น Job์ ์ฑ๊ณต ์๋๋ฆฌ์ค๋ฅผ ์ ์ ํ ๊ฒ์ฆํ๊ณ ์์ผ๋ฉฐ, 7์ผ๊ฐ์ Daily ๋ฐ์ดํฐ๊ฐ Weekly๋ก ์ฌ๋ฐ๋ฅด๊ฒ ์ง๊ณ๋ฉ๋๋ค. ISO ์ฃผ์ฐจ ๊ณ์ฐ๋ ์ ํํฉ๋๋ค: 2025-12-01์ ์์์ผ์ด๋ฉฐ ISO ์ฃผ์ฐจ 49์ ์ฒซ ๋ ์ ๋๋ค(49์ฃผ = 2025-12-01 ์ ~ 2025-12-07 ์ผ).
apps/commerce-api/src/main/java/com/loopers/domain/ranking/PeriodRankingService.java (1)
132-139: ๋ณตํฉ ์ ์ ๊ฐ์ค์น ํฉ๊ณ๊ฐ 1.0์ด ์๋๋๋ค.
ALLํ์ ์ ๊ฐ์ค์น ํฉ๊ณ๊ฐ 0.9์ ๋๋ค (0.2 + 0.1 + 0.6). ์๋๋ ๊ฒ์ธ์ง ํ์ธ์ด ํ์ํฉ๋๋ค. ๋ง์ฝ ์ ๊ทํ๋ ์ ์๊ฐ ํ์ํ๋ค๋ฉด ๊ฐ์ค์น ์กฐ์ ์ ๊ณ ๋ คํด์ฃผ์ธ์.apps/commerce-collector/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsWeeklyRepositoryImpl.java (1)
25-28: LGTM!๋จ์ผ ์ ์ฅ ๋ฐ ์กฐํ/์ญ์ ๋ฉ์๋๊ฐ JPA Repository์ ์ ์ ํ ์์๋์ด ์์ต๋๋ค.
Also applies to: 75-83
apps/commerce-api/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsMonthlyRepositoryImpl.java (1)
1-67: LGTM!์ฝ๊ธฐ ์ ์ฉ Repository ๊ตฌํ์ด JPA Repository์ ์ ์ ํ ์์๋์ด ์์ผ๋ฉฐ, commerce-api ๋ชจ๋์ ProductMetricsWeeklyRepositoryImpl๊ณผ ์ผ๊ด๋ ํจํด์ ๋ฐ๋ฅด๊ณ ์์ต๋๋ค.
apps/commerce-collector/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsMonthlyRepositoryImpl.java (1)
25-28: LGTM!๋จ์ผ ์ ์ฅ ๋ฐ ์กฐํ/์ญ์ ๋ฉ์๋๊ฐ JPA Repository์ ์ ์ ํ ์์๋์ด ์์ต๋๋ค.
Also applies to: 75-83
apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingFacade.java (2)
37-41: DAILY ํ์ ์์dateํ๋ผ๋ฏธํฐ๊ฐ ๋ฌด์๋ฉ๋๋ค.DAILY ์ผ์ด์ค์์ ์ ๋ฌ๋
dateํ๋ผ๋ฏธํฐ ๋์LocalDate.now()๋ฅผ ์ฌ์ฉํ๊ณ ์์ต๋๋ค. ์ด๋ ์๋๋ ๋์์ผ ์ ์์ง๋ง, API ์ฌ์ฉ์๊ฐ ํน์ ๋ ์ง์ ์ผ์ผ ๋ญํน์ ์กฐํํ๋ ค ํ ๋ ํผ๋์ ์ค ์ ์์ต๋๋ค.์๋๋ ๋์์ด๋ผ๋ฉด Javadoc์ ์ด ๋์์ ๋ช ์ํ๊ฑฐ๋, ํ๋ผ๋ฏธํฐ๋ช ์
weeklyOrMonthlyDate๋ก ๋ณ๊ฒฝํ๋ ๊ฒ์ ๊ณ ๋ คํด์ฃผ์ธ์.Also applies to: 64-68
21-21: LGTM!
PeriodRankingServiceํตํฉ์ด ์ ๊ตฌํ๋์์ต๋๋ค. switch ํํ์์ ์ฌ์ฉํ ๊ธฐ๊ฐ๋ณ ๋ผ์ฐํ ๋ก์ง์ด ๋ช ํํ๊ณ , null ์ฒ๋ฆฌ๋ฅผ ํตํ DAILY ๊ธฐ๋ณธ๊ฐ ์ ์ฉ์ด ์ ์ ํฉ๋๋ค.Also applies to: 28-48
apps/commerce-collector/src/main/java/com/loopers/batch/metrics/job/ProductMetricsMonthlyJobConfig.java (1)
46-64: LGTM!์๊ฐ ๋ฐฐ์น Job ๊ตฌ์ฑ์ด ์ฃผ๊ฐ ๊ตฌ์ฑ๊ณผ ์ผ๊ด๋ ํจํด์ผ๋ก ์ ๊ตฌํ๋์์ต๋๋ค. Job/Step/Chunk ๋ฆฌ์ค๋ ์ฐ๊ฒฐ๊ณผ chunk ํฌ๊ธฐ(100)๊ฐ pageSize์ ์ผ์นํ์ฌ ํจ์จ์ ์ธ ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
apps/commerce-api/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsMonthlyJpaRepository.java (2)
33-42: ๋ณตํฉ ์ ์ ๊ฐ์ค์น ํฉ๊ณ ํ์ธ ํ์ํ์ฌ ๋ณตํฉ ์ ์ ๊ณ์ฐ์์ ๊ฐ์ค์น ํฉ์ด 0.9์ ๋๋ค (0.2 + 0.1 + 0.6 = 0.9). ์ด๊ฒ์ด ์๋๋ ์ค๊ณ์ธ์ง, ์๋๋ฉด ๊ฐ์ค์น๊ฐ 1.0์ด ๋์ด์ผ ํ๋์ง ํ์ธ์ด ํ์ํฉ๋๋ค.
๋ํ, ์ด ์ฟผ๋ฆฌ๊ฐ ์์ฃผ ํธ์ถ๋๋ค๋ฉด
(year, month)๋ณตํฉ ์ธ๋ฑ์ค๊ฐ ์ฑ๋ฅ์ ๋์์ด ๋ ์ ์์ต๋๋ค.
12-53: LGTM!Repository ์ธํฐํ์ด์ค๊ฐ Spring Data JPA ๋ช ๋ช ๊ท์น์ ์ ๋ฐ๋ฅด๊ณ ์์ผ๋ฉฐ, ๋ค์ํ ์ ๋ ฌ ๊ธฐ์ค(์ข์์, ์กฐํ์, ์ฃผ๋ฌธ์, ๋ณตํฉ ์ ์)์ ๋ํ ์ฟผ๋ฆฌ ๋ฉ์๋๊ฐ ์ ๊ตฌ์ฑ๋์ด ์์ต๋๋ค. Based on learnings, Pageable ์ฌ์ฉ์ ์ด ์ฝ๋๋ฒ ์ด์ค์ ํ์ฉ๋ ์ํคํ ์ฒ ๊ฒฐ์ ์ ๋๋ค.
apps/commerce-api/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsWeeklyRepositoryImpl.java (1)
22-66: LGTM!JPA Repository๋ก์ ์์ ํจํด์ด ๊น๋ํ๊ฒ ๊ตฌํ๋์์ต๋๋ค.
PageRequest.of(0, limit)๋ฅผ ์ฌ์ฉํ ํ์ด์ง๋ค์ด์ ์ฒ๋ฆฌ๋ ์ ์ ํฉ๋๋ค.apps/commerce-api/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsWeeklyJpaRepository.java (2)
33-42: ๋ณตํฉ ์ ์ ๊ฐ์ค์น ํฉ๊ณ - Monthly Repository์ ๋์ผํ ํ์ธ ํ์Monthly Repository์ ๋์ผํ๊ฒ ๊ฐ์ค์น ํฉ์ด 0.9์ ๋๋ค. ๋ repository ๊ฐ ์ผ๊ด์ฑ์ ์ ์ง๋์ด ์์ผ๋, ์๋๋ ์ค๊ณ์ธ์ง ํ์ธ์ด ํ์ํฉ๋๋ค.
12-53: LGTM!์ฃผ๊ฐ Repository๊ฐ ์๊ฐ Repository์ ์ผ๊ด๋ ํจํด์ผ๋ก ์ ๊ตฌํ๋์์ต๋๋ค.
์ฐธ๊ณ : AI ์์ฝ์์
deleteByYearAndWeekBefore๋ฉ์๋๊ฐ ์ธ๊ธ๋์์ผ๋ ์ค์ ์ฝ๋์๋ ์กด์ฌํ์ง ์์ต๋๋ค. ํด๋น ๋ฉ์๋๊ฐ ๋ค๋ฅธ ํ์ผ์ ์๊ฑฐ๋ ์์ฝ์ด ๋ถ์ ํํ ์ ์์ต๋๋ค.
apps/commerce-api/src/main/java/com/loopers/domain/ranking/Ranking.java
Outdated
Show resolved
Hide resolved
apps/commerce-collector/src/main/java/com/loopers/batch/listener/ChunkListener.java
Outdated
Show resolved
Hide resolved
apps/commerce-collector/src/main/java/com/loopers/batch/listener/JobListener.java
Show resolved
Hide resolved
apps/commerce-collector/src/main/java/com/loopers/batch/listener/StepMonitorListener.java
Show resolved
Hide resolved
...tor/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsMonthlyJpaRepository.java
Outdated
Show resolved
Hide resolved
...or/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsMonthlyRepositoryImpl.java
Show resolved
Hide resolved
...ctor/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsWeeklyJpaRepository.java
Outdated
Show resolved
Hide resolved
...tor/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsWeeklyRepositoryImpl.java
Show resolved
Hide resolved
- ์คํ ์์ - NPE ๋ฐฉ์ง ์ฝ๋ ์ ์ฉ
|
@coderabbitai ์๋์ ์ฒดํฌ๋ฆฌ์คํธ๋ฅผ ๋ง์กฑํ๋์ง ํ์ธํด์ค ๐งฑ Spring Batch
๐งฉ Ranking API
|
|
Tip For best results, initiate chat on the files or code changes. @HongChangMo ์ฒดํฌ๋ฆฌ์คํธ ํญ๋ชฉ๋ค์ ํ์ธํ ๊ฒฐ๊ณผ, ๋ชจ๋ ์๊ตฌ์ฌํญ์ด ์ถฉ์กฑ๋์์ต๋๋ค! โ ๐งฑ Spring Batch
๐งฉ Ranking API
๋ชจ๋ ์๊ตฌ์ฌํญ์ด ์ ๊ตฌํ๋์ด ์์ผ๋ฉฐ, ํ ์คํธ ์ฝ๋๋ก๋ ๊ฒ์ฆ๋์ด ์์ต๋๋ค! ๐ |
๐ Summary
๐ฌ Review Points
์ปค์คํ ItemReader๊ฐ ์๋ RepositoryItemReader๋ฅผ ์ฌ์ฉ
์ฒ์ ๊ตฌํํ ๋, ์ปค์คํ ItemReader๋ฅผ ํตํด์ ๋ง์ฝ ์ข์์ ๋ฐ์ดํฐ๊ฐ 100๋ง ๊ฑด์ด๋ผ๋ฉด,
100๋ง๊ฑด์ ๋ชจ๋ ์ฝ์ด์ ์ฒ๋ฆฌ๋ฅผ ํ๋ค๋ฉด ๋ฐ์ดํฐ ์์ด ๋ ๋ง์์ง๋ฉด OOM ์ด์๊ฐ ๋ฐ์ํ ์ ์์ ๊ฒ ๊ฐ์,
Spring Batch ์์ ์ง์ํ๋ RepositoryItemReader๋ฅผ ์ฌ์ฉํด์ chunk size์ ๋์ผํ
ํ์ด์ง ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ๋๋ก ๊ตฌํํ์์ต๋๋ค.
๊ถ๊ธํ ์ ์ RepositoryItemReader ๋ฅผ ์ฌ์ฉํด์ ์ฒ๋ฆฌํ๋ ๊ฒฝ์ฐ๋ ์ค๋ฌด์์ ๋ง์์ง ๊ถ๊ธํ๊ณ ,
์ ๋ต์ ์๋ค๊ณ ํ์ จ์ง๋ง, ๊ตฌํํ ๋ฐฉ์์ด ์ฌ๋ฐ๋ฅธ ๋ฐฉํฅ์ฑ์ด ๋ง๋์ง ๊ถ๊ธํฉ๋๋ค.
MV๋ฅผ ํ์ฉํ ์ฃผ๊ฐ ์๊ฐ ๋ญํน ๊ฐ์ค์น ์ ์ฉ ์ง๋ฌธ
Redis์ ZSET์ผ๋ก ์ผ๊ฐ ๋ญํน์ ๊ตฌํํ ๋์๋ ZSET score ๊ธฐ๋ฐ top ๋ญํน์ ๋ฝ๋ ๋ฐฉ๋ฒ์ผ๋ก ๊ตฌํํ์๋๋ฐ, ์ด๋ฒ ๊ณผ์ ๋ Spring Batch + MV ๋ฅผ ํ์ฉํด ์ฃผ๊ฐ, ์๊ฐ ์ง๊ณ ๋ฐ์ดํฐ๋ฅผ ์์งํ๊ณ , commerce-api์์ top 100 ๋ญํน์ ์กฐํํ๋ ๊ธฐ๋ฅ์ ๊ตฌํํด์ผ ํ๋๋ฐ,
์ ๊ฐ ๊ถ๊ธํ ์ ์ MV์ ์ ์ฅ๋๋ ๋ฐ์ดํฐ๋ ์ง๊ณ์ ๋ํ ๋ด์ฉ๋ฟ์ด๋ผ, top N ์ ๋ฝ์์ผํ๋ ๊ฒฝ์ฐ ๋ณ๋์ score ๋ฅผ ์ด๋ป๊ฒ ๊ตฌํํ๋๊ฒ ์ข์์ง ๊ถ๊ธํฉ๋๋ค.
์ ๋ ์ด๋ฒ ๊ณผ์ ์์ api๋ฅผ ์กฐํํ๋ commerce-api ์ ์ฝ๊ธฐ ์ ์ฉ ์ํฐํฐ๋ฅผ ๋ง๋ค๊ณ MV์์ ๋ฐ์ดํฐ๋ฅผ db์์ ์กฐํํ ๋ score๋ฅผ ๊ณ์ฐํด์ ์กฐํํ๋๋ก ์ฒ๋ฆฌํ์์ต๋๋ค.
์ ๊ฐ ์๊ฐํ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ผ๋ก๋ ๋ด๋ถ์ score๋ฅผ ๊ณ์ฐํ๋ ๋๋ฉ์ธ ๋ก์ง์ ํตํด ์ข ํฉ ์ ์๋ฅผ ๊ณ์ฐํ๋๋ก ํ๋ ๋ฐฉ๋ฒ๋ ์์ ๊ฒ ๊ฐ์๋ณด์ด๋๋ฐ, commerce-collector ์ง๊ณ ์ฒ๋ฆฌํ๋ ๋ถ๋ถ์์ ๋ฏธ๋ฆฌ score๋ฅผ ๊ณ์ฐํ๋ ๊ฒ์ด ๋ ์ข์ ๋ฐฉ๋ฒ์ธ์ง, ์๋๋ฉด ์ ๊ฐ ๊ตฌํํ ๋ถ๋ถ๋ ๊ด์ฐฎ์ ๋ฐฉ๋ฒ์ธ์ง ๊ถ๊ธํฉ๋๋ค.
์ค์ฝ์ด ๊ณ์ฐ์ repository์์ ์ฒ๋ฆฌ
๋๋ฉ์ธ ๋ก์ง์์ ์ฒ๋ฆฌํ๋ ๊ฒฝ์ฐ
โ Checklist
๐งฑ Spring Batch
๐งฉ Ranking API
Summary by CodeRabbit
๋ฆด๋ฆฌ์ค ๋ ธํธ
์๋ก์ด ๊ธฐ๋ฅ
ํ ์คํธ
โ๏ธ Tip: You can customize this high-level summary in your review settings.