-
Notifications
You must be signed in to change notification settings - Fork 34
[volume-9] Product Ranking with Redis #223
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
[volume-9] Product Ranking with Redis #223
Conversation
- ProductMetricsService๋ฅผ ProductMetricsFacade๋ก ๋ณ๊ฒฝ - EventHandledService๋ฅผ EventHandledFacade๋ก ๋ณ๊ฒฝ
- ์ฒ๋ฆฌ ๋ฐฉ์ ๋ณ๊ฒฝ: Kafka ๋ฉ์์ง ๋จ๊ฑด ์ฒ๋ฆฌ -> ๋ฐฐ์น(Batch) ์ฒ๋ฆฌ๋ก ๋ณ๊ฒฝํ์ฌ ์ฒ๋ฆฌ๋ ๊ฐ์ - ์ผ ๋จ์ ๋๊ณ ์ ์ฅ์ ์ํ ProductMetricsDaily ์ํฐํฐ ์ ์ฉ
- ๊ธฐ์กด ๋จ๊ฑด์ผ๋ก ์ฒ๋ฆฌ๋๋ ์ฃผ๋ฌธ๋ณ ์ํ ๋ชฉ๋ก, List ๋ณ๊ฒฝ
- ์ํ ๋จ๊ฑด ์ง๊ณ ์ฒ๋ฆฌ์์ ์ํ๋ณ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ์์
- ์ฒ๋ฆฌ ๋ฐฉ์ ๋ณ๊ฒฝ: Kafka ๋ฉ์์ง ๋จ๊ฑด ์ฒ๋ฆฌ -> ๋ฐฐ์น(Batch) ์ฒ๋ฆฌ๋ก ๋ณ๊ฒฝํ์ฌ ์ฒ๋ฆฌ๋ ๊ฐ์ - ์ผ์ผ ์ํ ์ฃผ๋ฌธ ๋๊ณ ๋ฐ์ดํฐ ์ ์ฌ
- ์ฒ๋ฆฌ ๋ฐฉ์ ๋ณ๊ฒฝ: Kafka ๋ฉ์์ง ๋จ๊ฑด ์ฒ๋ฆฌ -> ๋ฐฐ์น(Batch) ์ฒ๋ฆฌ๋ก ๋ณ๊ฒฝํ์ฌ ์ฒ๋ฆฌ๋ ๊ฐ์ - ์ผ์ผ ์ํ ์กฐํ ๋๊ณ ๋ฐ์ดํฐ ์ ์ฌ
- ์ํ ๋ฐฐ์น ๋ฆฌ์ค๋๋ก ์ ์ฌ๋(ProductMetricsDaily) ๊ธฐ๋ฐ ๋ญํน ๊ฐฑ์ ์ค์ผ์ค๋ฌ ๊ตฌํ
- ๋ญํน ์กฐํ API(TOP-N, PAGE, ์ ์ฒด) ๊ตฌํ - Redis ZSET ๊ธฐ๋ฐ ์กฐํ
- ์ํ ์์ธ ์กฐํ ์ ๋ญํน ์ ๋ณด ๋ฐ์ดํฐ ์ถ๊ฐ
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Walkthrough์ด ํ ์์ฒญ์ ์์(ranking) ์์คํ ๊ตฌํ, ์ฃผ๋ฌธ ์ด๋ฒคํธ ํ์ฅ, ๋ฐฐ์น ์ด๋ฒคํธ ์ฒ๋ฆฌ ์ถ๊ฐ๋ฅผ ํตํด ์๊ฑฐ๋ ํ๋ซํผ์ ์ด๋ฒคํธ ๊ธฐ๋ฐ ์ํคํ ์ฒ๋ฅผ ์ฌ๊ตฌ์กฐํํฉ๋๋ค. ๋๋ฉ์ธ ์๋น์ค ์ค์ฌ ์ค๊ณ์์ ์ ํ๋ฆฌ์ผ์ด์ ํ์ฌ๋ ์ค์ฌ์ผ๋ก ์ ํํ๋ฉฐ, Redis ๊ธฐ๋ฐ ์์ ์ ์ฅ์์ ์ผ์ผ ๋ฉํธ๋ฆญ ์ถ์ ์ ๋์ ํฉ๋๋ค. Changes
Estimated code review effort๐ฏ 4 (Complex) | โฑ๏ธ ~60 minutes Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touchesโ Failed checks (1 warning, 1 inconclusive)
โ Passed checks (1 passed)
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 (1)
apps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/like/ProductLikeEventConsumer.java (1)
17-89: ๋นํ์ฑํ๋ ์ปจ์๋จธ ์ฝ๋ ์ ๋ฆฌ ํ์
@Component์ด๋ ธํ ์ด์ ์ด ์ฃผ์ ์ฒ๋ฆฌ๋์ด ์ด ์ปจ์๋จธ๊ฐ ๋นํ์ฑํ๋์์ง๋ง, ์ ์ฒด ๊ตฌํ ์ฝ๋(89์ค)๊ฐ ๊ทธ๋๋ก ๋จ์์์ต๋๋ค. ๋ฐฐ์น ์ปจ์๋จธ(ProductLikeBatchEventConsumer)๋ก ๋์ฒด๋ ๊ฒ์ผ๋ก ๋ณด์ด๋ฏ๋ก, ๋ค์ ์ค ํ๋๋ฅผ ์ ํํด์ฃผ์ธ์:
- ๊ถ์ฅ: ์ด ํ์ผ์ ์์ ํ ์ญ์
- ์ฐธ๊ณ ์ฉ์ผ๋ก ๋ณด์กดํ๋ ค๋ ๊ฒฝ์ฐ, ํ์ผ ์๋จ์ ๋ช ํํ ์ฃผ์ ์ถ๊ฐ (์: "์ด ํด๋์ค๋ ๋ฐฐ์น ์ฒ๋ฆฌ๋ก ๋์ฒด๋์์ผ๋ฉฐ ์ฐธ๊ณ ์ฉ์ผ๋ก๋ง ๋ณด์กด๋จ")
ํ์ฌ ์ํ๋ ์ฝ๋๋ฒ ์ด์ค์ ํผ๋์ ์ฃผ๊ณ ์ ์ง๋ณด์์ฑ์ ์ ํดํฉ๋๋ค.
๐ ์ฝ๋ ์ ๋ฆฌ๋ฅผ ์ํ ์ ์
Option 1: ํ์ผ ์ญ์ (๊ถ์ฅ)
# ์ด ํ์ผ์ git์์ ์ ๊ฑฐ git rm apps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/like/ProductLikeEventConsumer.javaOption 2: ๋ช ํํ ๋ฌธ์ํ
ํ์ผ์ ์ ์งํ๋ ค๋ฉด ์๋จ์ ๋ค์๊ณผ ๊ฐ์ ์ฃผ์์ ์ถ๊ฐ:package com.loopers.interfaces.consumer.like; +/** + * @deprecated ์ด ํด๋์ค๋ ๋ฐฐ์น ์ฒ๋ฆฌ ๋ฐฉ์(ProductLikeBatchEventConsumer)์ผ๋ก ๋์ฒด๋์์ต๋๋ค. + * ์ฐธ๊ณ ๋ชฉ์ ์ผ๋ก๋ง ๋ณด์กด๋๋ฉฐ, ํ๋ก๋์ ์์ ์ฌ์ฉ๋์ง ์์ต๋๋ค. + * TODO: ๋ฐฐ์น ์ฒ๋ฆฌ๊ฐ ์์ ํ๋๋ฉด ์ด ํ์ผ์ ์ญ์ ํ ์์ ์ ๋๋ค. + */ // ๋ฉ์์ง ๋จ๊ฑด ์ฒ๋ฆฌ ๋นํ์ฑํ //@Component
๐งน Nitpick comments (23)
apps/commerce-api/src/main/java/com/loopers/domain/ranking/Ranking.java (1)
8-10:rankํ๋์ ํ์ ์ผ๊ด์ฑ ๊ฒํ ํ์
rank๊ฐ primitiveint๋ก ์ ์ธ๋์ด ์์ง๋ง,SimpleRankingInfo์์๋Integer(nullable)๋ก ์ ์๋์ด ์์ต๋๋ค. ์์๊ฐ ์๋ ๊ฒฝ์ฐ๋ฅผ ํํํด์ผ ํ๋ค๋ฉดInteger๋ก ๋ณ๊ฒฝํ๋ ๊ฒ์ด ์ผ๊ด์ฑ ์์ต๋๋ค.๐ ์ ์๋ ์์
@Getter public class Ranking { - private final int rank; + private final Integer rank; private final Long productId; private final Double score; @Builder - public Ranking(int rank, Long productId, Double score) { + public Ranking(Integer rank, Long productId, Double score) { this.rank = rank; this.productId = productId; this.score = score; } }apps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/product/dto/ProductEvent.java (1)
3-8: ์ด๋ฒคํธ DTO ์ค๊ณ ์น์ธ๊น๋ํ record ๊ธฐ๋ฐ ์ด๋ฒคํธ DTO์ ๋๋ค.
ProductLikeEvent์ ๋์ผํ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ง๊ณ ์์ด ์ฝ๋ ์ค๋ณต์ด ์์ต๋๋ค. ํ์ํ๋ค๋ฉด ๊ณตํต ์ธํฐํ์ด์ค๋ ์ ๋ค๋ฆญ ํ์ ์ ๊ณ ๋ คํ ์ ์์ง๋ง, ๋๋ฉ์ธ ๋ถ๋ฆฌ๋ฅผ ์ํด ๊ฐ๋ณ ํ์ ์ ์ ์งํ๋ ๊ฒ๋ ํฉ๋ฆฌ์ ์ธ ์ ํ์ ๋๋ค.apps/commerce-collector/src/main/java/com/loopers/application/eventhandled/EventHandledInfo.java (1)
9-12: ์ ์ ํฉํ ๋ฆฌ ๋ฉ์๋ ๋จ์ํ ๊ณ ๋ ค
of()๋ฉ์๋๊ฐ ๋จ์ํ ์์ฑ์๋ฅผ ํธ์ถ๋ง ํ๊ณ ์์ต๋๋ค. Record๋ ์ด๋ฏธ public ์ ๊ท ์์ฑ์๋ฅผ ์ ๊ณตํ๋ฏ๋ก, ์ด ํฉํ ๋ฆฌ ๋ฉ์๋๋ ์ถ๊ฐ์ ์ธ ๊ฐ์น๋ฅผ ์ ๊ณตํ์ง ์์ต๋๋ค.ํ์ฌ ๊ตฌํ๋ ๋์ํ์ง๋ง, ํธ์ถ ์ธก์์
new EventHandledInfo(...)๋ฅผ ์ง์ ์ฌ์ฉํ๊ฑฐ๋, ํฉํ ๋ฆฌ ๋ฉ์๋๊ฐ ์ค์ ๋ก ๊ฒ์ฆ์ด๋ ๋ณํ ๋ก์ง์ ์ถ๊ฐํ๋ ๊ฒฝ์ฐ์๋ง ์ ์งํ๋ ๊ฒ์ ๊ณ ๋ คํด๋ณด์ธ์.apps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1ApiSpec.java (1)
29-37: ์๋ํฌ์ธํธ ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ ๋ฐฉ์์ ๋ถ์ผ์น ๋ฐ date ํ๋ผ๋ฏธํฐ ๊ฒ์ฆ
getTopRanking๊ณผgetRankingWithPaging์๋ํฌ์ธํธ๋ ์์ฒญ DTO๋ฅผ ์ฌ์ฉํ๋ ๋ฐ๋ฉด,getProductRanking์ ๊ฐ๋ณ@PathVariable๊ณผ@RequestParam์ ์ฌ์ฉํ๊ณ ์์ด ์ผ๊ด์ฑ์ด ๋ถ์กฑํฉ๋๋ค. ๋ํ ๋ชจ๋ ์๋ํฌ์ธํธ์dateํ๋ผ๋ฏธํฐ๊ฐ ๋ช ์์ ์ธ@Pattern๊ฒ์ฆ ์์ด ๋ฌธ์์ด๋ก ์ ์ธ๋์ด ์์ต๋๋ค.ํ์ฌ๋ ์ปจํธ๋กค๋ฌ์
parseDate()๋ฉ์๋์์yyyyMMddํ์์ผ๋ก ๋ฐํ์์ ํ์ฑ๋๊ณ ์์ผ๋ฉฐ, ํ์์ด@Schema์ด๋ ธํ ์ด์ ์ผ๋ก ๋ฌธ์ํ๋์ด ์์ต๋๋ค. ๋ค๋ง ์๋ชป๋ ํ์์ date๊ฐ ์ ๋ฌ๋๋ฉดDateTimeParseException์ด ๋ฐ์ํ๋ฏ๋ก, ๋ค์ ๊ฐ์ ์ ๊ณ ๋ คํ์ธ์:
- ์ค๊ณ ์ผ๊ด์ฑ:
getProductRanking์๋ํฌ์ธํธ๋ฅผGetProductRankingRequestDTO๋ก ํต์ผํ๊ฑฐ๋, ํ์ฌ ์ค๊ณ๊ฐ ์๋์ ์ธ ๊ฒฝ์ฐ ๊ทธ ์ด์ ๋ฅผ ๋ฌธ์ํํด์ฃผ์ธ์.- date ํ๋ผ๋ฏธํฐ ๊ฒ์ฆ: ๋ชจ๋ date ํ๋ผ๋ฏธํฐ์
@Pattern(regexp = "\\d{8}")๊ฐ์ ๋ช ์์ ๊ฒ์ฆ์ ์ถ๊ฐํ์ฌ ์ ๋ ฅ๊ฐ ๊ฒ์ฆ์ ์ ์ธ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ ๊ฒ์ด ์ข์ต๋๋ค.apps/commerce-collector/src/main/java/com/loopers/application/like/ProductLikeBatchEventHandler.java (2)
59-63: ๋ฐฐ์น ๋ฉฑ๋ฑ์ฑ ์ฒดํฌ ์ต์ ํ ๊ณ ๋ คํ์ฌ ๊ฐ ์ด๋ฒคํธ๋ง๋ค ๊ฐ๋ณ์ ์ผ๋ก
isAlreadyHandled๋ฅผ ํธ์ถํ๊ณ ์์ต๋๋ค. ๋๋์ ์ด๋ฒคํธ ์ฒ๋ฆฌ ์ N๋ฒ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. EventHandledFacade์ ๋ฐฐ์น ์ฒดํฌ ๋ฉ์๋(์:areAlreadyHandled(List<String> eventIds))๋ฅผ ์ถ๊ฐํ๋ฉด ์ฑ๋ฅ์ ๊ฐ์ ํ ์ ์์ต๋๋ค.
85-92: ์ด๋ฒคํธ ํ์ ๋น๊ต์ ์์ ์ฌ์ฉ ๊ถ์ฅ๋ฌธ์์ด ๋ฆฌํฐ๋ด ๋น๊ต ๋์ KafkaTopics.ProductLike ์์๋ฅผ ์ง์ ์ฌ์ฉํ๊ณ ์์ด ์ํธํ์ง๋ง, ์ ์ ์๋ ์ด๋ฒคํธ ํ์ ์ ๋ํ ์ฒ๋ฆฌ๊ฐ 0์ ๋ฐํํฉ๋๋ค. ์์์น ๋ชปํ ์ด๋ฒคํธ ํ์ ์ด ๋ค์ด์ฌ ๊ฒฝ์ฐ ๋ก๊ทธ๋ฅผ ๋จ๊ธฐ๊ฑฐ๋ ์์ธ๋ฅผ ๋ฐ์์ํค๋ ๊ฒ์ ๊ณ ๋ คํด๋ณด์ธ์.
๐ ์ ์ํ๋ ๊ฐ์ ์
private int calculateDelta(String eventType) { if (KafkaTopics.ProductLike.LIKE_ADDED.equals(eventType)) { return 1; } else if (KafkaTopics.ProductLike.LIKE_REMOVED.equals(eventType)) { return -1; } + log.warn("์ ์ ์๋ ์ด๋ฒคํธ ํ์ : {}", eventType); return 0; }apps/commerce-api/src/main/java/com/loopers/application/product/ProductEventPublisher.java (1)
32-55: ์ด๋ฒคํธ ๋ฐํ ํจํด์ด ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํ๋์์ต๋๋ค.
REQUIRES_NEW์ ํ ์ ๋ต์ผ๋ก ๋ถ๋ชจ ํธ๋์ญ์ ๊ณผ ๋ ๋ฆฝ์ ์ธ ์ด๋ฒคํธ ๋ฐํ์ ๋ณด์ฅํ๋ฉฐ, ์คํจ ์ ์กฐํ ์์ ์ ์ํฅ์ ์ฃผ์ง ์๋ fire-and-forget ํจํด์ด ์ ์ ํ ์ ์ฉ๋์์ต๋๋ค.์ ํ์ ๊ฐ์ ์ฌํญ: ๋ก๊ทธ ๋ ๋ฒจ ์กฐ์
Line 45์ ์ฑ๊ณต ๋ก๊ทธ๊ฐ
debug๋ ๋ฒจ๋ก ๋์ด ์์ต๋๋ค. ์ด๋ฒคํธ ๋ฐํ์ ์ค์ํ ๋น์ฆ๋์ค ๋์์ด๋ฏ๋ก ๊ฐ์ฌ ์ถ์ (audit trail)์ ์ํดinfo๋ ๋ฒจ์ ๊ณ ๋ คํด๋ณด์ธ์.- log.debug("ProductViewedEvent ๋ฐํ ์ฑ๊ณต - productId: {}", productId); + log.info("ProductViewedEvent ๋ฐํ ์ฑ๊ณต - productId: {}", productId);apps/commerce-collector/src/main/java/com/loopers/domain/metrics/ProductMetricsDaily.java (1)
71-74: ํ์์กด ๋ช ์ ๊ถ์ฅLine 73์
ZonedDateTime.now()๋ ์์คํ ๊ธฐ๋ณธ ํ์์กด์ ์ฌ์ฉํฉ๋๋ค. ๋ช ์์ ์ผ๋กZonedDateTime.now(ZoneId.of("Asia/Seoul"))์ ์ฌ์ฉํ์ฌ ํ์์กด ๋ถ์ผ์น๋ฅผ ๋ฐฉ์งํ๋ ๊ฒ์ด ์ข์ต๋๋ค.๐ ์ ์ํ๋ ์์
public void markAsProcessed() { this.isProcessed = true; - this.processedAt = ZonedDateTime.now(); + this.processedAt = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); }ํ์ ์ ์์๋ก ์ถ์ถ:
private static final ZoneId KST = ZoneId.of("Asia/Seoul");apps/commerce-api/src/main/java/com/loopers/application/product/ProductDetailInfo.java (1)
20-45:from(Product)๊ณผof(Product, RankingInfo.ProductRankings)๊ฐ ์ค๋ณต ์ฝ๋ ์ ๊ฑฐ ๊ถ์ฅ๋ ํฉํ ๋ฆฌ ๋ฉ์๋๊ฐ ๋์ผํ ํ๋ ๋งคํ ๋ก์ง์ ๋ฐ๋ณตํ๊ณ ์์ต๋๋ค.
from(Product)์ดof(Product, null)์ ์์ํ๋๋ก ๋ณ๊ฒฝํ๋ฉด ์ ์ง๋ณด์์ฑ์ด ํฅ์๋ฉ๋๋ค.๐ ์ค๋ณต ์ ๊ฑฐ ์ ์
public static ProductDetailInfo from(Product product) { - return new ProductDetailInfo( - product.getId(), - product.getProductCode(), - product.getProductName(), - product.getPrice().getAmount(), - product.getStockQuantity(), - product.getLikeCount(), - product.getBrand() != null ? BrandInfo.from(product.getBrand()) : null, - null - ); + return of(product, null); }apps/commerce-api/src/main/java/com/loopers/domain/ranking/RankingService.java (1)
1-14: ๋๋ฉ์ธ ๊ณ์ธต์์ ์ธํ๋ผ์คํธ๋ญ์ฒ ๊ณ์ธต ์ง์ ์์กด ๋ฌธ์
RankingService๊ฐ ๋๋ฉ์ธ ํจํค์ง(com.loopers.domain.ranking)์ ์์นํ์ง๋ง, ์ธํ๋ผ์คํธ๋ญ์ฒ ๊ณ์ธต์RedisRankingRepository๋ฅผ ์ง์ ์์กดํ๊ณ ์์ต๋๋ค. ์ด๋ ์์กด์ฑ ์ญ์ ์์น(DIP)์ ์๋ฐํฉ๋๋ค.๋๋ฉ์ธ ๊ณ์ธต์
RankingRepository์ธํฐํ์ด์ค๋ฅผ ์ ์ํ๊ณ ,RedisRankingRepository๊ฐ ์ด๋ฅผ ๊ตฌํํ๋๋ก ํ๊ฑฐ๋, ์ด ์๋น์ค๋ฅผ application ๊ณ์ธต์ผ๋ก ์ด๋ํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค.apps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/order/dto/OrderEvent.java (1)
31-35:OrderItem๋ ์ฝ๋์๋@JsonIgnoreProperties์ถ๊ฐ ๊ถ์ฅ
OrderEvent์OrderCreatedPayload์๋@JsonIgnoreProperties(ignoreUnknown = true)๊ฐ ์ ์ฉ๋์ด ์์ผ๋,OrderItem์๋ ๋๋ฝ๋์ด ์์ต๋๋ค. ์คํค๋ง ์งํ ์ ์ผ๊ด๋ ์ญํธํ์ฑ์ ์ํด ์ถ๊ฐ๋ฅผ ๊ถ์ฅํฉ๋๋ค.๐ ์์ ์ ์
+ @JsonIgnoreProperties(ignoreUnknown = true) public record OrderItem( Long productId, Integer quantity, BigDecimal price ) {}apps/commerce-collector/src/main/java/com/loopers/application/product/ProductBatchEventHandler.java (1)
57-61:filterUnprocessedEvents์์ N+1 ์ฟผ๋ฆฌ ๋ฐ์ ๊ฐ๋ฅ์ฑ๊ฐ ์ด๋ฒคํธ๋ง๋ค
eventHandledFacade.isAlreadyHandled(event.eventId())๋ฅผ ๊ฐ๋ณ ํธ์ถํ๊ณ ์์ด, ์ด๋ฒคํธ ์๊ฐ ๋ง์ ๊ฒฝ์ฐ N+1 ์ฟผ๋ฆฌ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
EventHandledFacade์isAlreadyHandledBatch(List<String> eventIds)๋ฉ์๋๋ฅผ ์ถ๊ฐํ์ฌ ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก ์ฒ๋ฆฌํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค.๐ ์ต์ ํ ์ ์
private List<ProductEvent> filterUnprocessedEvents(List<ProductEvent> events) { + List<String> eventIds = events.stream() + .map(ProductEvent::eventId) + .collect(Collectors.toList()); + Set<String> handledEventIds = eventHandledFacade.findHandledEventIds(eventIds); + return events.stream() - .filter(event -> !eventHandledFacade.isAlreadyHandled(event.eventId())) + .filter(event -> !handledEventIds.contains(event.eventId())) .collect(Collectors.toList()); }apps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/product/ProductBatchEventConsumer.java (1)
37-41: ๋ฏธ์ฌ์ฉ ํ๋ผ๋ฏธํฐkeysํ์ธ
@Header(KafkaHeaders.RECEIVED_KEY) List<String> keysํ๋ผ๋ฏธํฐ๊ฐ ์ ์ธ๋์ด ์์ง๋ง ๋ฉ์๋ ๋ด์์ ์ฌ์ฉ๋์ง ์์ต๋๋ค. ์๋์ ์ธ ๊ฒ์ด ์๋๋ผ๋ฉด ์ ๊ฑฐํ๊ฑฐ๋, ๋ก๊น /์ถ์ ๋ชฉ์ ์ผ๋ก ์ฌ์ฉํ๋ ๊ฒ์ ๊ณ ๋ คํด ์ฃผ์ธ์.apps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/order/OrderBatchEventConsumer.java (1)
66-80:ArrayListimport ์ฌ์ฉ ๊ถ์ฅ
java.util.ArrayList๋ฅผ ์ง์ ์ฐธ์กฐํ๋ ๋์ import ๋ฌธ์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ผ๊ด์ฑ ์๋ ์ฝ๋ ์คํ์ผ์ ๋๋ค.๐ ์ ์๋ ์์
import java.util.List; +import java.util.ArrayList; ... private List<OrderEvent> parseMessages(List<String> messages, List<String> keys) { - List<OrderEvent> events = new java.util.ArrayList<>(); + List<OrderEvent> events = new ArrayList<>();apps/commerce-collector/src/main/java/com/loopers/application/ranking/RankingScheduler.java (1)
39-39: ํ์์กด ๊ณ ๋ ค ํ์
LocalDate.now()๋ ์์คํ ๊ธฐ๋ณธ ํ์์กด์ ์ฌ์ฉํฉ๋๋ค. ์๋ฒ ๋ฐฐํฌ ํ๊ฒฝ์ด ๋ค๋ฅธ ํ์์กด์ผ ๊ฒฝ์ฐ ์์์น ๋ชปํ ๋์์ด ๋ฐ์ํ ์ ์์ต๋๋ค.๐ ๊ถ์ฅ ์์
-LocalDate today = LocalDate.now(); +LocalDate today = LocalDate.now(ZoneId.of("Asia/Seoul"));๋๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ญ์์ ์ฌ์ฉํ
Clock๋น์ ์ฃผ์ ๋ฐ์ ์ฌ์ฉํ๋ ๊ฒ๋ ํ ์คํธ ์ฉ์ด์ฑ์ ๋์ด๋ ๋ฐฉ๋ฒ์ ๋๋ค.apps/commerce-collector/src/main/java/com/loopers/application/ranking/RankingFacade.java (2)
88-88: ํ์์กด ์ผ๊ด์ฑ ํ์ธ ํ์
LocalDate.now()๊ฐRankingScheduler์ ์ด ํด๋์ค์์ ๋ชจ๋ ์ฌ์ฉ๋ฉ๋๋ค. ๋ ์ปดํฌ๋ํธ๊ฐ ๋ค๋ฅธ ์์ ์ ์คํ๋ ๋ ํ์์กด ๊ฒฝ๊ณ์์ ๋ ์ง ๋ถ์ผ์น๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. ๋ช ์์ ํ์์กด ์ฌ์ฉ์ ๊ถ์ฅํฉ๋๋ค.๐ ๊ถ์ฅ ์์
+private static final ZoneId ZONE_ID = ZoneId.of("Asia/Seoul"); + private void incrementRanking(String prefix, Map<Long, Integer> deltas, double weight) { // ... - String todayKey = prefix + ":" + LocalDate.now().format(DATE_FORMATTER); + String todayKey = prefix + ":" + LocalDate.now(ZONE_ID).format(DATE_FORMATTER);Also applies to: 129-129
137-138: ๋ถ๋์์์ ๋น๊ต ์ ์ฃผ์
entry.getValue() == 0.0๋น๊ต๋ ๋ถ๋์์์ ์ ๋ฐ๋ ๋ฌธ์ ๋ก ์์์น ๋ชปํ ๊ฒฐ๊ณผ๋ฅผ ๋ผ ์ ์์ต๋๋ค. ํ์ง๋ง ์ด ๊ฒฝ์ฐ ์ ์๊ฐ ์ ์ ์ฐ์ฐ์ ๊ฒฐ๊ณผ์ด๋ฏ๋ก ์ค์ ๋ก๋ ๋ฌธ์ ๊ฐ ์์ ๊ฐ๋ฅ์ฑ์ด ๋์ต๋๋ค.๐ ๋ ์์ ํ ๋น๊ต ๋ฐฉ์
-if (entry.getValue() == 0.0) continue; +if (Math.abs(entry.getValue()) < 1e-9) continue;apps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/RedisRankingRepository.java (1)
129-142: NumberFormatException ์ฒ๋ฆฌ ํ์
Long.parseLong(tuple.getValue().toString())์ Redis์ ์๋ชป๋ ๋ฐ์ดํฐ๊ฐ ์ ์ฅ๋ ๊ฒฝ์ฐNumberFormatException์ ๋ฐ์์ํฌ ์ ์์ต๋๋ค. ๋ฐฉ์ด์ ์ฒ๋ฆฌ๋ฅผ ๊ถ์ฅํฉ๋๋ค.๐ ๊ถ์ฅ ์์
for (ZSetOperations.TypedTuple<Object> tuple : result) { if (tuple.getValue() == null) { continue; } + Long productId; + try { + productId = Long.parseLong(tuple.getValue().toString()); + } catch (NumberFormatException e) { + log.warn("์๋ชป๋ productId ํ์ - value: {}", tuple.getValue()); + continue; + } - Long productId = Long.parseLong(tuple.getValue().toString()); Double score = tuple.getScore(); entries.add(Ranking.builder() .rank(rank++) .productId(productId) .score(score) .build()); }apps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1Controller.java (3)
91-96: ์๋ชป๋ ๋ ์ง ํ์ ์ฒ๋ฆฌ ํ์
LocalDate.parse()๋ ์๋ชป๋ ํ์์ ๋ ์ง ๋ฌธ์์ด์ ๋ํดDateTimeParseException์ ๋ฐ์์ํต๋๋ค. ํ์ฌ ์ ์ญ ์์ธ ํธ๋ค๋ฌ๊ฐ ์๋ค๋ฉด ๋ฌธ์ ์์ง๋ง, ๋ช ํํ ์๋ฌ ๋ฉ์์ง๋ฅผ ์ํด ๋ช ์์ ์ฒ๋ฆฌ๋ฅผ ๊ถ์ฅํฉ๋๋ค.๐ ๊ถ์ฅ ์์
private LocalDate parseDate(String dateString) { if (dateString == null || dateString.isBlank()) { return LocalDate.now(); } + try { return LocalDate.parse(dateString, DATE_FORMATTER); + } catch (DateTimeParseException e) { + throw new IllegalArgumentException("์๋ชป๋ ๋ ์ง ํ์์ ๋๋ค. yyyyMMdd ํ์์ ์ฌ์ฉํด์ฃผ์ธ์: " + dateString); + } }๊ด๋ จ ์ฝ๋ ์ค๋ํซ
ApiControllerAdvice.kt์์ ์ผ๋ฐThrowable์INTERNAL_ERROR๋ก ์ฒ๋ฆฌํ๋ฏ๋ก,IllegalArgumentException์ด๋ ์ปค์คํ ์์ธ๋ฅผ ์ฌ์ฉํ์ฌ ์ ์ ํ 400 ์๋ต์ ๋ฐํํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
82-84: null ์๋ต ๋์ 404 ๋ฐํ ๊ณ ๋ ค
ApiResponse.success(null)๋์ ๋ช ํํ 404 ์๋ต์ด๋ ๋น ์๋ต ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ ๊ฒ์ด API ์ฌ์ฉ์์๊ฒ ๋ ๋ช ํํฉ๋๋ค. ์ฃผ์์์๋ "์ ์ ํ ์๋ฌ ์๋ต"์ ์ธ๊ธํ๊ณ ์์ต๋๋ค.๐ ๊ถ์ฅ ์์ ์ต์
์ต์ 1: 404 ์๋ต ๋ฐํ
if (ranking == null) { - return ApiResponse.success(null); // ๋๋ ์ ์ ํ ์๋ฌ ์๋ต + throw new CoreException(ErrorType.NOT_FOUND, "ํด๋น ์ํ์ ๋ญํน ์ ๋ณด๊ฐ ์์ต๋๋ค."); }์ต์ 2: Optional ํจํด ์ฌ์ฉ
-if (ranking == null) { - return ApiResponse.success(null); // ๋๋ ์ ์ ํ ์๋ฌ ์๋ต -} - -return ApiResponse.success( - RankingV1Dto.ProductRankingResponse.from(ranking) -); +return ApiResponse.success( + ranking != null ? RankingV1Dto.ProductRankingResponse.from(ranking) : null +);
22-46: ๋ญํน ์กฐํ์ ์นด์ดํธ ์กฐํ ๋ถ๋ฆฌ
getRankingWithPaging๊ณผgetTopRanking๋ชจ๋ ๋ญํน ๋ฐ์ดํฐ์ ์ด ๊ฐ์๋ฅผ ๋ณ๋๋ก ์กฐํํฉ๋๋ค. ์ฑ๋ฅ์ด ์ค์ํ๋ค๋ฉดRankingFacade์์ ํ ๋ฒ์ ๋ฐํํ๋ ๋ฐฉ์์ ๊ณ ๋ คํด๋ณผ ์ ์์ต๋๋ค.Also applies to: 48-69
apps/commerce-collector/src/main/java/com/loopers/application/order/OrderBatchEventHandler.java (1)
125-138: NPE ๋ฐฉ์ง ๋ฐ ์คํ์ผ ๊ฐ์
- Line 126:
new java.util.ArrayList<>()๋์ import ์ฌ์ฉ ๊ถ์ฅ- Line 133:
event.payload().orderId()๊ฐ null์ธ ๊ฒฝ์ฐ NPE ๋ฐ์ ๊ฐ๋ฅ๐ ๊ถ์ฅ ์์
+import java.util.ArrayList; private List<EventHandledInfo> createEventHandledInfos(Iterable<OrderEvent> events) { - List<EventHandledInfo> infos = new java.util.ArrayList<>(); + List<EventHandledInfo> infos = new ArrayList<>(); for (OrderEvent event : events) { if (KafkaTopics.Order.ORDER_CREATED.equals(event.eventType())) { + OrderEvent.OrderCreatedPayload payload = event.payload(); + if (payload == null || payload.orderId() == null) { + log.warn("์ ํจํ์ง ์์ payload - eventId: {}", event.eventId()); + continue; + } infos.add(EventHandledInfo.of( event.eventId(), event.eventType(), AggregateTypes.ORDER, - event.payload().orderId().toString() + payload.orderId().toString() )); } } return infos; }apps/commerce-collector/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsDailyRepositoryImpl.java (1)
48-151: ๋ฐฐ์น upsert ๊ตฌํ์ด ์ ๋์ด ์์ต๋๋ค.
BatchPreparedStatementSetter๋ฅผ ์ฌ์ฉํ ๋ฐฐ์น ์ฒ๋ฆฌ์ON DUPLICATE KEY UPDATEํจํด์ด ์ ์ ํฉ๋๋ค. ๋น ๋งต์ ๋ํ early return๋ ์ข์ ๋ฐฉ์ด์ ์ฝ๋ฉ์ ๋๋ค.์ ํ์ ์ผ๋ก, ์ธ upsert ๋ฉ์๋์ ๊ตฌ์กฐ๊ฐ ๊ฑฐ์ ๋์ผํ๋ฏ๋ก ๊ณตํต private ํฌํผ ๋ฉ์๋๋ก ์ถ์ถํ๋ฉด ์ค๋ณต์ ์ค์ผ ์ ์์ต๋๋ค:
private void upsertDeltas(Map<Long, Integer> deltas, LocalDate metricDate, String sql, String metricName) { if (deltas.isEmpty()) return; // ... ๊ณตํต ๋ก์ง }
๐ Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
๐ Files selected for processing (54)
apps/commerce-api/src/main/java/com/loopers/application/order/OrderFacade.javaapps/commerce-api/src/main/java/com/loopers/application/outbox/OutboxEventPublisher.javaapps/commerce-api/src/main/java/com/loopers/application/product/ProductDetailInfo.javaapps/commerce-api/src/main/java/com/loopers/application/product/ProductEventPublisher.javaapps/commerce-api/src/main/java/com/loopers/application/product/ProductFacade.javaapps/commerce-api/src/main/java/com/loopers/application/product/ProductInfo.javaapps/commerce-api/src/main/java/com/loopers/application/ranking/RankingFacade.javaapps/commerce-api/src/main/java/com/loopers/application/ranking/RankingInfo.javaapps/commerce-api/src/main/java/com/loopers/application/ranking/SimpleRankingInfo.javaapps/commerce-api/src/main/java/com/loopers/domain/order/event/OrderCreatedEvent.javaapps/commerce-api/src/main/java/com/loopers/domain/product/ProductRepository.javaapps/commerce-api/src/main/java/com/loopers/domain/product/ProductService.javaapps/commerce-api/src/main/java/com/loopers/domain/ranking/Ranking.javaapps/commerce-api/src/main/java/com/loopers/domain/ranking/RankingService.javaapps/commerce-api/src/main/java/com/loopers/domain/ranking/RankingType.javaapps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductJpaRepository.javaapps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductRepositoryImpl.javaapps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/RedisRankingRepository.javaapps/commerce-api/src/main/java/com/loopers/interfaces/api/product/ProductV1DTO.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-collector/src/main/java/com/loopers/CommerceCollectorApplication.javaapps/commerce-collector/src/main/java/com/loopers/application/eventhandled/EventHandledFacade.javaapps/commerce-collector/src/main/java/com/loopers/application/eventhandled/EventHandledInfo.javaapps/commerce-collector/src/main/java/com/loopers/application/like/ProductLikeBatchEventHandler.javaapps/commerce-collector/src/main/java/com/loopers/application/like/ProductLikeEventHandler.javaapps/commerce-collector/src/main/java/com/loopers/application/metrics/ProductMetricsDailyFacade.javaapps/commerce-collector/src/main/java/com/loopers/application/metrics/ProductMetricsFacade.javaapps/commerce-collector/src/main/java/com/loopers/application/order/OrderBatchEventHandler.javaapps/commerce-collector/src/main/java/com/loopers/application/order/OrderEventHandler.javaapps/commerce-collector/src/main/java/com/loopers/application/product/ProductBatchEventHandler.javaapps/commerce-collector/src/main/java/com/loopers/application/product/ProductEventHandler.javaapps/commerce-collector/src/main/java/com/loopers/application/ranking/RankingFacade.javaapps/commerce-collector/src/main/java/com/loopers/application/ranking/RankingScheduler.javaapps/commerce-collector/src/main/java/com/loopers/domain/eventhandled/EventHandledRepository.javaapps/commerce-collector/src/main/java/com/loopers/domain/eventhandled/EventHandledService.javaapps/commerce-collector/src/main/java/com/loopers/domain/metrics/ProductMetricsDaily.javaapps/commerce-collector/src/main/java/com/loopers/domain/metrics/ProductMetricsDailyRepository.javaapps/commerce-collector/src/main/java/com/loopers/domain/metrics/ProductMetricsRepository.javaapps/commerce-collector/src/main/java/com/loopers/infrastructure/eventhandled/EventHandledRepositoryImpl.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/ProductMetricsRepositoryImpl.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/like/ProductLikeBatchEventConsumer.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/like/ProductLikeEventConsumer.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/like/dto/ProductLikeEvent.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/order/OrderBatchEventConsumer.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/order/OrderEventConsumer.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/order/dto/OrderEvent.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/product/ProductBatchEventConsumer.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/product/ProductEventConsumer.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/product/dto/ProductEvent.javaapps/commerce-collector/src/main/resources/application.yml
๐ค Files with no reviewable changes (1)
- apps/commerce-collector/src/main/java/com/loopers/domain/eventhandled/EventHandledService.java
๐งฐ Additional context used
๐ง Learnings (9)
๐ Learning: 2025-12-19T20:59:57.713Z
Learnt from: toongri
Repo: Loopers-dev-lab/loopers-spring-kotlin-template PR: 68
File: docs/week8/round8-detailed-design.md:151-178
Timestamp: 2025-12-19T20:59:57.713Z
Learning: In the Loopers-dev-lab/loopers-spring-kotlin-template repository's Kafka event pipeline, only 5 domain events are intentionally published to Kafka via CloudEventEnvelopeFactory: OrderPaidEventV1, LikeCreatedEventV1, LikeCanceledEventV1, ProductViewedEventV1, and StockDepletedEventV1. Other domain events (OrderCreatedEventV1, OrderCanceledEventV1, PaymentCreatedEventV1, PaymentPaidEventV1, PaymentFailedEventV1) are internal-only and intentionally not mapped in resolveMetadata(), which correctly returns null for them to exclude them from Outbox publication.
Applied to files:
apps/commerce-api/src/main/java/com/loopers/application/outbox/OutboxEventPublisher.javaapps/commerce-api/src/main/java/com/loopers/application/product/ProductEventPublisher.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/like/ProductLikeBatchEventConsumer.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/order/OrderBatchEventConsumer.javaapps/commerce-collector/src/main/java/com/loopers/application/order/OrderBatchEventHandler.javaapps/commerce-api/src/main/java/com/loopers/domain/order/event/OrderCreatedEvent.javaapps/commerce-collector/src/main/java/com/loopers/application/order/OrderEventHandler.javaapps/commerce-collector/src/main/java/com/loopers/application/like/ProductLikeEventHandler.javaapps/commerce-api/src/main/java/com/loopers/application/order/OrderFacade.javaapps/commerce-collector/src/main/java/com/loopers/application/eventhandled/EventHandledFacade.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/like/dto/ProductLikeEvent.javaapps/commerce-api/src/main/java/com/loopers/application/product/ProductFacade.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/order/dto/OrderEvent.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/product/ProductBatchEventConsumer.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/product/ProductEventConsumer.javaapps/commerce-collector/src/main/java/com/loopers/application/product/ProductBatchEventHandler.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/order/OrderEventConsumer.javaapps/commerce-collector/src/main/java/com/loopers/application/like/ProductLikeBatchEventHandler.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/like/ProductLikeEventConsumer.javaapps/commerce-collector/src/main/java/com/loopers/application/product/ProductEventHandler.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-api/src/main/java/com/loopers/application/outbox/OutboxEventPublisher.javaapps/commerce-api/src/main/java/com/loopers/application/product/ProductEventPublisher.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/like/ProductLikeBatchEventConsumer.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/product/dto/ProductEvent.javaapps/commerce-collector/src/main/java/com/loopers/application/eventhandled/EventHandledInfo.javaapps/commerce-collector/src/main/java/com/loopers/application/like/ProductLikeEventHandler.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/like/dto/ProductLikeEvent.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/product/ProductBatchEventConsumer.javaapps/commerce-collector/src/main/java/com/loopers/application/like/ProductLikeBatchEventHandler.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/like/ProductLikeEventConsumer.javaapps/commerce-collector/src/main/java/com/loopers/application/product/ProductEventHandler.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/domain/eventhandled/EventHandledRepository.javaapps/commerce-api/src/main/java/com/loopers/application/product/ProductEventPublisher.javaapps/commerce-collector/src/main/java/com/loopers/application/order/OrderBatchEventHandler.javaapps/commerce-collector/src/main/java/com/loopers/application/order/OrderEventHandler.javaapps/commerce-api/src/main/java/com/loopers/application/order/OrderFacade.javaapps/commerce-collector/src/main/java/com/loopers/infrastructure/eventhandled/EventHandledRepositoryImpl.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/product/ProductEventConsumer.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/order/OrderEventConsumer.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-api/src/main/java/com/loopers/infrastructure/product/ProductJpaRepository.javaapps/commerce-collector/src/main/java/com/loopers/domain/metrics/ProductMetricsDaily.javaapps/commerce-collector/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsRepositoryImpl.javaapps/commerce-collector/src/main/java/com/loopers/domain/metrics/ProductMetricsRepository.javaapps/commerce-api/src/main/java/com/loopers/domain/product/ProductRepository.javaapps/commerce-collector/src/main/java/com/loopers/application/like/ProductLikeEventHandler.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/like/dto/ProductLikeEvent.javaapps/commerce-api/src/main/java/com/loopers/application/product/ProductFacade.javaapps/commerce-collector/src/main/java/com/loopers/application/metrics/ProductMetricsDailyFacade.javaapps/commerce-api/src/main/java/com/loopers/application/product/ProductInfo.javaapps/commerce-collector/src/main/java/com/loopers/application/metrics/ProductMetricsFacade.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-collector/src/main/java/com/loopers/interfaces/consumer/like/ProductLikeBatchEventConsumer.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/product/ProductBatchEventConsumer.javaapps/commerce-collector/src/main/java/com/loopers/application/like/ProductLikeBatchEventHandler.java
๐ Learning: 2025-12-18T13:24:54.339Z
Learnt from: kilian-develop
Repo: Loopers-dev-lab/loopers-spring-java-template PR: 190
File: apps/commerce-streamer/src/main/java/com/loopers/applications/streamer/consumer/product/IncreaseProductViewKafkaConsumer.java:25-35
Timestamp: 2025-12-18T13:24:54.339Z
Learning: In this codebase, Kafka consumers delegate error handling and event tracking to the service layer via EventInboxAspect. Service methods annotated with InboxEvent are intercepted by the aspect, which handles failures by saving failed EventInbox entries and logging errors. This centralized approach avoids duplicating error handling logic across multiple consumers.
Applied to files:
apps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/like/ProductLikeBatchEventConsumer.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/order/OrderBatchEventConsumer.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/product/ProductBatchEventConsumer.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/product/ProductEventConsumer.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/order/OrderEventConsumer.javaapps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/like/ProductLikeEventConsumer.javaapps/commerce-collector/src/main/java/com/loopers/application/product/ProductEventHandler.java
๐ Learning: 2025-12-02T08:12:06.383Z
Learnt from: junoade
Repo: Loopers-dev-lab/loopers-spring-java-template PR: 0
File: :0-0
Timestamp: 2025-12-02T08:12:06.383Z
Learning: ProductQueryService์์ ์ํ ๋ชฉ๋ก ์กฐํ ์ Redis ์บ์๋ฅผ ์ ์ฉํ์ผ๋ฉฐ, ์บ์ ํค๋ brandId, sortType, pageNumber, pageSize์ ์กฐํฉ์ผ๋ก ๊ตฌ์ฑ๋๊ณ TTL์ 5๋ถ์ผ๋ก ์ค์ ๋์ด ์๋ค.
Applied to files:
apps/commerce-api/src/main/java/com/loopers/application/product/ProductFacade.java
๐ Learning: 2025-12-07T15:47:18.277Z
Learnt from: toongri
Repo: Loopers-dev-lab/loopers-spring-kotlin-template PR: 52
File: apps/commerce-api/src/main/kotlin/com/loopers/infrastructure/payment/PgFeignConfig.kt:0-0
Timestamp: 2025-12-07T15:47:18.277Z
Learning: In Spring Cloud OpenFeign, configuration classes specified in FeignClient(configuration = ...) do NOT need the Configuration annotation. Spring Cloud OpenFeign creates a separate ApplicationContext for each named client and directly instantiates Bean methods from the configuration class. Adding Configuration would cause the class to be registered globally via ComponentScan, making the configuration apply to ALL Feign clients instead of just the specific client, which is usually undesirable.
Applied to files:
apps/commerce-collector/src/main/java/com/loopers/CommerceCollectorApplication.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/infrastructure/metrics/ProductMetricsDailyJpaRepository.java
๐งฌ Code graph analysis (14)
apps/commerce-api/src/main/java/com/loopers/application/outbox/OutboxEventPublisher.java (1)
modules/kafka/src/main/java/com/loopers/kafka/KafkaTopics.java (1)
KafkaTopics(3-58)
apps/commerce-api/src/main/java/com/loopers/application/product/ProductEventPublisher.java (3)
modules/kafka/src/main/java/com/loopers/kafka/AggregateTypes.java (1)
AggregateTypes(3-14)modules/kafka/src/main/java/com/loopers/kafka/KafkaTopics.java (2)
KafkaTopics(3-58)ProductDetail(20-26)apps/commerce-api/src/main/java/com/loopers/application/product/ProductFacade.java (1)
Slf4j(17-132)
apps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/like/ProductLikeBatchEventConsumer.java (4)
modules/kafka/src/main/java/com/loopers/kafka/KafkaTopics.java (1)
KafkaTopics(3-58)apps/commerce-collector/src/main/java/com/loopers/application/metrics/ProductMetricsDailyFacade.java (1)
Component(12-42)apps/commerce-collector/src/main/java/com/loopers/application/eventhandled/EventHandledFacade.java (1)
Component(12-58)apps/commerce-collector/src/main/java/com/loopers/infrastructure/eventhandled/EventHandledRepositoryImpl.java (1)
Component(10-34)
apps/commerce-collector/src/main/java/com/loopers/application/ranking/RankingFacade.java (2)
apps/commerce-collector/src/main/java/com/loopers/application/ranking/RankingScheduler.java (1)
Slf4j(19-139)apps/pg-simulator/src/main/kotlin/com/loopers/interfaces/api/ApiControllerAdvice.kt (1)
log(22-119)
apps/commerce-collector/src/main/java/com/loopers/application/order/OrderBatchEventHandler.java (4)
modules/kafka/src/main/java/com/loopers/kafka/AggregateTypes.java (1)
AggregateTypes(3-14)modules/kafka/src/main/java/com/loopers/kafka/KafkaTopics.java (2)
KafkaTopics(3-58)Order(28-36)apps/commerce-collector/src/main/java/com/loopers/application/metrics/ProductMetricsFacade.java (1)
Slf4j(13-70)apps/commerce-collector/src/main/java/com/loopers/application/metrics/ProductMetricsDailyFacade.java (1)
Component(12-42)
apps/commerce-api/src/main/java/com/loopers/application/order/OrderFacade.java (1)
modules/kafka/src/main/java/com/loopers/kafka/KafkaTopics.java (2)
KafkaTopics(3-58)Coupon(38-45)
apps/commerce-collector/src/main/java/com/loopers/application/eventhandled/EventHandledFacade.java (1)
apps/commerce-collector/src/main/java/com/loopers/infrastructure/eventhandled/EventHandledRepositoryImpl.java (1)
Component(10-34)
apps/commerce-api/src/main/java/com/loopers/interfaces/api/product/ProductV1DTO.java (1)
apps/commerce-api/src/main/java/com/loopers/interfaces/api/brand/BrandV1Dto.java (1)
BrandV1Dto(5-19)
apps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/product/ProductBatchEventConsumer.java (1)
modules/kafka/src/main/java/com/loopers/kafka/KafkaTopics.java (1)
KafkaTopics(3-58)
apps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1Controller.java (1)
apps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1Dto.java (1)
RankingV1Dto(13-151)
apps/commerce-api/src/main/java/com/loopers/domain/ranking/RankingService.java (1)
apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingFacade.java (1)
Component(18-127)
apps/commerce-collector/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsDailyRepositoryImpl.java (2)
apps/commerce-collector/src/main/java/com/loopers/application/metrics/ProductMetricsDailyFacade.java (1)
Component(12-42)apps/commerce-collector/src/main/java/com/loopers/application/eventhandled/EventHandledFacade.java (1)
Component(12-58)
apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingFacade.java (2)
apps/commerce-api/src/main/java/com/loopers/domain/product/ProductService.java (1)
RequiredArgsConstructor(15-80)apps/commerce-api/src/main/java/com/loopers/domain/ranking/RankingService.java (1)
Component(10-56)
apps/commerce-collector/src/main/java/com/loopers/application/metrics/ProductMetricsFacade.java (3)
apps/commerce-collector/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsRepositoryImpl.java (1)
Slf4j(18-144)apps/commerce-collector/src/main/java/com/loopers/application/metrics/ProductMetricsDailyFacade.java (1)
Component(12-42)apps/commerce-collector/src/main/java/com/loopers/application/eventhandled/EventHandledFacade.java (1)
Component(12-58)
๐ Additional comments (50)
apps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/product/ProductEventConsumer.java (2)
1-1: ํจํค์ง ์ฌ๊ตฌ์ฑ์ด ์ ์ ํฉ๋๋ค.์ ํ(product) ๋๋ฉ์ธ๋ณ ์ปจ์๋จธ ๋ค์์คํ์ด์ค๋ก์ ์ฌ๊ตฌ์ฑ์ด PR์ ์ ๋ฐ์ ์ธ ๊ตฌ์กฐ ๊ฐ์ ๊ณผ ์ผ์นํฉ๋๋ค.
20-79: ๋ฐฐ์น ์ปจ์๋จธ๊ฐ ์กด์ฌํ๊ณ PRODUCT_VIEWED ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ์ง๋ง, ์๋ฌ ํธ๋ค๋ง ์ ๋ต์ด ๋จ๊ฑด ์ปจ์๋จธ์ ๋ค๋ฆ ๋๋ค.ProductBatchEventConsumer๋ ์กด์ฌํ๋ฉฐ PRODUCT_VIEWED ์ด๋ฒคํธ๋ฅผ ์ ์์ ์ผ๋ก ์ฒ๋ฆฌํฉ๋๋ค. ๊ทธ๋ฌ๋ ์๋ฌ ํธ๋ค๋ง ํจํด์ ๋ถ์ผ์น๊ฐ ์์ต๋๋ค:
- ProductEventConsumer: JSON ํ์ฑ ์๋ฌ ์ ๋ช ์์ ์ผ๋ก acknowledgment.acknowledge()๋ฅผ ํธ์ถํ์ฌ ์ฌ์๋๋ฅผ ๋ฐฉ์งํ๊ณ , ๋น์ฆ๋์ค ๋ก์ง ์๋ฌ๋ ์์ธ๋ฅผ ๋์ ธ ์ฌ์๋๋ฅผ ํ์ฉํฉ๋๋ค.
- ProductBatchEventConsumer: JSON ํ์ฑ ์๋ฌ๋ฅผ parseEvent() ๋ฉ์๋์์ catchํ์ฌ null์ ๋ฐํํ๊ณ Objects::nonNull๋ก ํํฐ๋งํฉ๋๋ค. ์ด๋ ์๋ฌ๋ฅผ ์๋ฌต์ ์ผ๋ก ์ฒ๋ฆฌํ๋ฉฐ, ๋ฐฐ์น ๋ ๋ฒจ ์์ธ๋ง ๋ฐ์์ํต๋๋ค.
๋ํ ์ถ๊ฐ ๊ฒ์ฆ์ด ํ์ํฉ๋๋ค:
- ์ด ์ฝ๋๋ฒ ์ด์ค์์๋ EventInboxAspect ํจํด์ด ์กด์ฌํ์ง ์์ต๋๋ค. ์๋ฌ ์ฒ๋ฆฌ๊ฐ ์๋ try-catch๋ก ๊ตฌํ๋์ด ์์ต๋๋ค.
- JSON ํ์ฑ ์คํจ์ ๋ํ ์ฌ์๋ ์ ์ฑ ์ด ๋ ์ปจ์๋จธ ๊ฐ์ ๋ถ์ผ์นํฉ๋๋ค. ๋ฐฐ์น ๋ฒ์ ์์ ํ์ฑ ์คํจ ๋ฉ์์ง๊ฐ ์๋์ผ๋ก ๋ฒ๋ ค์ง๋๋ฐ, ์ด๊ฒ์ด ์๋๋ ๋์์ธ์ง ํ์ธํ์ธ์.
apps/commerce-collector/src/main/resources/application.yml (1)
31-33: application.yml์ Kafka ์ค์ ์ด ํจ๊ณผ ์์์ ํ๋ ์ค์ ๊ฐ๋ค์ด
modules/kafka/src/main/java/com/loopers/confg/kafka/KafkaConfig.java์ ํ๋์ฝ๋ฉ๋ ์์๋ก ์ธํด ๋ฌด์๋ฉ๋๋ค. ์ค์ ์ ์ฉ๋๋ ์ค์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
max.poll.records: 100 โ 3000 (ํ๋์ฝ๋ฉ๋จ)fetch-min-size: 1024 โ 1MB (ํ๋์ฝ๋ฉ๋จ)fetch-max-wait: 500ms โ 5000ms (ํ๋์ฝ๋ฉ๋จ)๋ฐ๋ผ์
max.poll.interval.ms(๊ธฐ๋ณธ 5๋ถ) ๋์ MAX_POLL_INTERVAL_MS = 2๋ถ(120์ด)์ด ์ค์ ์ ํ๊ฐ์ ๋๋ค. 3000๊ฐ ๋ฉ์์ง๋ฅผ 3๊ฐ ๋์์ฑ์ผ๋ก ์ฒ๋ฆฌํ ๋ 2๋ถ ๋ด์ ๋ฐฐ์น ์ฒ๋ฆฌ๊ฐ ์๋ฃ๋์ง ์์ผ๋ฉด ์ปจ์๋จธ ๋ฆฌ๋ฐธ๋ฐ์ฑ ์ํ์ด ๋์ต๋๋ค. KafkaConfig์ ํ๋์ฝ๋ฉ๋ ๊ฐ๋ค์ด ์ค๊ณ ์๋๋ฅผ ๋ฐ์ํ๋์ง, ๊ทธ๋ฆฌ๊ณ ๋ฐฐ์น ์ฒ๋ฆฌ ์๊ฐ์ด ์ค์ ๋ก 2๋ถ ์ด๋ด๋ก ์๋ฃ๋๋์ง ๊ฒ์ฆํ์ญ์์ค.Likely an incorrect or invalid review comment.
apps/commerce-api/src/main/java/com/loopers/domain/product/ProductRepository.java (1)
19-20: ๋๋ฉ์ธ ์ธํฐํ์ด์ค ์ถ๊ฐ๊ฐ ์ ์ ํฉ๋๋ค.์๋ก์ด ๋ฉ์๋ ์๊ทธ๋์ฒ๊ฐ ๋ฆฌํฌ์งํ ๋ฆฌ ๊ณ์ธต ์ ์ฒด์ ์ผ๊ด์ฑ ์๊ฒ ์ถ๊ฐ๋์์ต๋๋ค.
apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductRepositoryImpl.java (1)
58-61: ๊ตฌํ์ด ์ฌ๋ฐ๋ฆ ๋๋ค.JPA ๋ฆฌํฌ์งํ ๋ฆฌ๋ก์ ๋จ์ ์์ ํจํด์ด ์ ํํ๊ฒ ๊ตฌํ๋์์ผ๋ฉฐ,
@Override์ด๋ ธํ ์ด์ ์ผ๋ก ์ธํฐํ์ด์ค ๊ณ์ฝ์ ๋ช ์์ ์ผ๋ก ์ค์ํ๊ณ ์์ต๋๋ค.apps/commerce-api/src/main/java/com/loopers/domain/ranking/RankingType.java (1)
6-16: LGTM!Redis ํค ์ ๋์ฌ์ ์ค๋ช ์ ์บก์ํํ ๊น๋ํ enum ์ค๊ณ์ ๋๋ค. Lombok ์ด๋ ธํ ์ด์ ์ฌ์ฉ์ด ์ ์ ํ๋ฉฐ, ์ผ๊ด๋ ํค ๋ค์ด๋ฐ ์ปจ๋ฒค์ (
ranking:{type})์ด Redis ZSET ๊ธฐ๋ฐ ๋ญํน ์์คํ ์ ์ ๋ถํฉํฉ๋๋ค.apps/commerce-collector/src/main/java/com/loopers/domain/metrics/ProductMetricsRepository.java (1)
11-14: LGTM!๋ฐฐ์น ์ ๋ฐ์ดํธ ๋ฉ์๋ ์ถ๊ฐ๊ฐ ์ ์ ํฉ๋๋ค. Kafka ๋ฐฐ์น ๋ฆฌ์ค๋์์ ์ง๊ณ๋ ๋ธํ๊ฐ์ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ๊ธฐ ์ํ ์ค๊ณ๊ฐ ์ ๋ฐ์๋์ด ์์ต๋๋ค. ๋ฉ์๋ ๋ค์ด๋ฐ์ด ๋ช ํํ๊ณ ์ผ๊ด์ฑ ์์ต๋๋ค.
apps/commerce-api/src/main/java/com/loopers/domain/order/event/OrderCreatedEvent.java (1)
14-21:items๋ฆฌ์คํธ์ null ์์ ์ฑ ํ์ธ ํ์
itemsํ๋๊ฐ null๋ก ์ ๋ฌ๋ ๊ฒฝ์ฐ downstream ์ปจ์๋จธ์์ NPE๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. ํฉํ ๋ฆฌ ๋ฉ์๋์์ null ์ฒดํฌ ๋๋ ๋น ๋ฆฌ์คํธ ๊ธฐ๋ณธ๊ฐ ์ฒ๋ฆฌ๋ฅผ ๊ณ ๋ คํด ์ฃผ์ธ์.๐ ์ ์๋ ์์
public static OrderCreatedEvent of( Long orderId, Long userId, BigDecimal totalPrice, PaymentType paymentType, List<OrderItem> items ) { return new OrderCreatedEvent( orderId, userId, totalPrice, paymentType, - items, + items != null ? items : List.of(), LocalDateTime.now() ); }apps/commerce-api/src/main/java/com/loopers/application/order/OrderFacade.java (1)
161-168: LGTM - ์ฃผ๋ฌธ ์์ดํ ๋งคํ ๋ก์ง
savedOrder.getOrderItems()์์OrderCreatedEvent.OrderItem์ผ๋ก์ ๋ณํ์ด ๊น๋ํฉ๋๋ค. ๋จ,item.getPrice()๊ฐ null์ผ ๊ฒฝ์ฐ NPE ๊ฐ๋ฅ์ฑ์ด ์์ผ๋ ํ์ธํด ์ฃผ์ธ์.apps/commerce-api/src/main/java/com/loopers/application/outbox/OutboxEventPublisher.java (1)
94-94: LGTM!
PRODUCT_VIEWaggregate type์ ๋ํ ํ ํฝ ๋งคํ์ด ์ ์ ํ๊ฒ ์ถ๊ฐ๋์์ต๋๋ค.ProductEventPublisher์ outbox ๊ธฐ๋ฐ ์ด๋ฒคํธ ๋ฐํ๊ณผ ์ผ๊ด์ฑ ์๊ฒ ์ฐ๋๋ฉ๋๋ค.apps/commerce-collector/src/main/java/com/loopers/CommerceCollectorApplication.java (1)
6-9: LGTM!
@EnableScheduling์ถ๊ฐ๋กRankingScheduler๋ฑ ์ค์ผ์ค๋ง ๊ธฐ๋ฐ ์ปดํฌ๋ํธ๊ฐ ์ ์ ๋์ํ ์ ์์ต๋๋ค. ์ ์ ํ ๋ณ๊ฒฝ์ ๋๋ค.apps/commerce-api/src/main/java/com/loopers/application/ranking/SimpleRankingInfo.java (1)
9-17: LGTM - ํฉํ ๋ฆฌ ๋ฉ์๋ ์ค๊ณ ํ์ธ
from()๋ฉ์๋์์ null ์ ๋ ฅ ์ null์ ๋ฐํํ๋ ์ค๊ณ๊ฐ JSON ์ง๋ ฌํ ์ ํ๋ ์๋ต ๋ชฉ์ ์ด๋ผ๋ฉด ์ ์ ํฉ๋๋ค. ๋ค๋ง, ํธ์ถ๋ถ์์ null ์ฒดํฌ๊ฐ ํ์ํ ์ ์์ผ๋ ์ฌ์ฉ ํจํด์ ํ์ธํด ์ฃผ์ธ์.apps/commerce-collector/src/main/java/com/loopers/domain/eventhandled/EventHandledRepository.java (1)
8-8: ๋ฐฐ์น ์ ์ฅ ๋ฉ์๋ ์ถ๊ฐ ์น์ธ๋ฐฐ์น ์ด๋ฒคํธ ์ฒ๋ฆฌ๋ฅผ ์ํ
saveAll๋ฉ์๋๊ฐ ์ ์ ํ๊ฒ ์ถ๊ฐ๋์์ต๋๋ค. ์ด๋ Spring Data Repository ํจํด๊ณผ ์ผ๊ด์ฑ์ด ์์ผ๋ฉฐ, PR์์ ๋์ ๋ ๋ฐฐ์น ์ฒ๋ฆฌ ์ํฌํ๋ก์ฐ๋ฅผ ์ ์ง์ํฉ๋๋ค.apps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/like/dto/ProductLikeEvent.java (1)
3-8: ๊ฐ๊ฒฐํ ์ด๋ฒคํธ DTO ์ค๊ณ ์น์ธ๋ถ๋ณ์ฑ์ ์ ๊ณตํ๋ record๋ฅผ ์ฌ์ฉํ ์ด๋ฒคํธ DTO ์ค๊ณ๊ฐ ์ ์ ํฉ๋๋ค. ๋ฐฐ์น ์ด๋ฒคํธ ์ฒ๋ฆฌ ํจํด์ ์ ๋ถํฉํ๋ฉฐ, ํ๋ ๊ตฌ์ฑ๋ ํฉ๋ฆฌ์ ์ ๋๋ค.
apps/commerce-collector/src/main/java/com/loopers/infrastructure/eventhandled/EventHandledRepositoryImpl.java (1)
25-28: ๋ฐฐ์น ์ ์ฅ ๊ตฌํ ์น์ธJPA Repository์ ์์ํ๋ ๊ฐ๊ฒฐํ ๊ตฌํ์ด ์ ์ ํฉ๋๋ค. void ๋ฐํ ํ์ ์ ํธ์ถ์๊ฐ ์ ์ฅ๋ ์ํฐํฐ๋ฅผ ํ์๋ก ํ์ง ์๋ ๊ฒฝ์ฐ ํจ์จ์ ์ ๋๋ค.
apps/commerce-api/src/main/java/com/loopers/application/product/ProductInfo.java (1)
16-25: ๊ฐ๊ฒฉ ํ๋๋ ์ํฐํฐ ๋ ๋ฒจ์์ ํ์ ๊ฒ์ฆ์ด ์ ์ฉ๋์ด ์์ต๋๋คProduct ์ํฐํฐ์ ์์ฑ์์์
validationPrice()๋ฉ์๋๋ก price๊ฐ null์ด๋ฉด CoreException์ ๋ฐ์์ํต๋๋ค(lines 99-102). ๋ฐ๋ผ์product.getPrice().getAmount()๋ NPE ์ํ์ด ์์ผ๋ฉฐ, ํ์ฌ ์ฝ๋๋ ์์ ํฉ๋๋ค.Likely an incorrect or invalid review comment.
apps/commerce-collector/src/main/java/com/loopers/application/product/ProductEventHandler.java (1)
1-40: ํ์ฌ๋ ํจํด ๋ง์ด๊ทธ๋ ์ด์ ์ด ์ฌ๋ฐ๋ฅด๊ฒ ์ ์ฉ๋์์ต๋๋ค.๋๋ฉ์ธ ์๋น์ค์์ ์ ํ๋ฆฌ์ผ์ด์ ํ์ฌ๋๋ก์ ์ ํ์ด ์ผ๊ด๋๊ฒ ์ ์ฉ๋์์ผ๋ฉฐ, ๋ฉฑ๋ฑ์ฑ ์ฒดํฌ โ ๋ฉํธ๋ฆญ ์ฆ๊ฐ โ ์ด๋ฒคํธ ์ฒ๋ฆฌ ์๋ฃ ๊ธฐ๋ก์ ํ๋ฆ์ด ์ ์ง๋๊ณ ์์ต๋๋ค.
apps/commerce-collector/src/main/java/com/loopers/application/order/OrderEventHandler.java (1)
1-65: ํ์ฌ๋ ๋ง์ด๊ทธ๋ ์ด์ ๋ฐ ์ฃผ๋ฌธ ์๋ ์ฒ๋ฆฌ๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ๋ฐ์๋์์ต๋๋ค.๋๋ฉ์ธ ์๋น์ค์์ ์ ํ๋ฆฌ์ผ์ด์ ํ์ฌ๋๋ก์ ์ ํ์ด ์ผ๊ด๋๊ฒ ์ ์ฉ๋์์ผ๋ฉฐ,
incrementOrderCount์ ์๋ ํ๋ผ๋ฏธํฐ๊ฐ ์ถ๊ฐ๋์ด ์ฃผ๋ฌธ ์ง๊ณ๊ฐ ๋ ์ ํํด์ก์ต๋๋ค.apps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/like/ProductLikeBatchEventConsumer.java (1)
44-48: ์๋ชป๋ ๋ฉ์์ง ์ฒ๋ฆฌ ์ ๋ต ํ์ธ ํ์ํ์ฑ์ ์คํจํ ๋ฉ์์ง๋ null๋ก ๋ณํ๋์ด ํํฐ๋ง๋ฉ๋๋ค. ์ด๋ ํด๋น ๋ฉ์์ง๊ฐ ์๊ตฌ์ ์ผ๋ก ์์ค๋จ์ ์๋ฏธํฉ๋๋ค. ๋ง์ฝ ์ผ์์ ์ธ ํ์ฑ ์ค๋ฅ(์: ObjectMapper ์ค์ ๋ถ์ผ์น)๋ผ๋ฉด DLQ(Dead Letter Queue)๋ก ๋ผ์ฐํ ํ๊ฑฐ๋ ๋ณ๋๋ก ๊ธฐ๋กํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
ํ์ฌ ๋ฐฐ์น ๋ด ์ผ๋ถ ๋ฉ์์ง๋ง ์ฑ๊ณตํ๊ณ ๋๋จธ์ง๋ ์คํต๋๋ ๋ถ๋ถ ์ฑ๊ณต ์๋๋ฆฌ์ค๊ฐ ์๋๋ ๋์์ธ์ง ํ์ธํด์ฃผ์ธ์.
apps/commerce-collector/src/main/java/com/loopers/application/eventhandled/EventHandledFacade.java (1)
1-58: ์ด๋ฒคํธ ์ฒ๋ฆฌ ์ํ ๊ด๋ฆฌ ํ์ฌ๋๊ฐ ์ ์ค๊ณ๋์์ต๋๋ค.๋ฉฑ๋ฑ์ฑ ์ฒดํฌ์ ๋ฐฐ์น ์ฒ๋ฆฌ๋ฅผ ๋ชจ๋ ์ง์ํ๋ฉฐ, ์ ์ ํ ํธ๋์ญ์ ๊ฒฝ๊ณ์ null ์์ ์ฑ ๊ฒ์ฌ๊ฐ ํฌํจ๋์ด ์์ต๋๋ค. ๋ฐฐ์น ์ฒ๋ฆฌ ๋ฉ์๋๋ ๋๋ ์ด๋ฒคํธ ์ฒ๋ฆฌ ์ ์ฑ๋ฅ ํฅ์์ ๊ธฐ์ฌํฉ๋๋ค.
apps/commerce-collector/src/main/java/com/loopers/application/metrics/ProductMetricsDailyFacade.java (1)
1-42: ์ผ์ผ ๋ฉํธ๋ฆญ ๋ฐฐ์น ์ ๋ฐ์ดํธ ํ์ฌ๋๊ฐ ์ ์ ํ ๊ตฌํ๋์์ต๋๋ค.
Propagation.REQUIRED๋ฅผ ์ฌ์ฉํ์ฌ ์์ ํธ๋์ญ์ ์ ์ฐธ์ฌํจ์ผ๋ก์จ ์ด๋ฒคํธ ์ฒ๋ฆฌ์ ๋ฉํธ๋ฆญ ์ ๋ฐ์ดํธ ๊ฐ์ ์์์ฑ์ ๋ณด์ฅํฉ๋๋ค. ์ธ ๊ฐ์ง ๋ฉํธ๋ฆญ ํ์ (์ข์์, ์กฐํ, ์ฃผ๋ฌธ)์ ๋ํ ์ผ๊ด๋ ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํฉ๋๋ค.apps/commerce-collector/src/main/java/com/loopers/domain/metrics/ProductMetricsDaily.java (2)
14-19: ์ผ์ผ ๋ฉํธ๋ฆญ ์ค๋ณต ๋ฐฉ์ง๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํ๋์์ต๋๋ค.
(product_id, metric_date)์ ๋ํ ์ ๋ํฌ ์ ์ฝ ์กฐ๊ฑด์ด ์ผ๋ณ ๋ฉํธ๋ฆญ ๋ ์ฝ๋์ ์ค๋ณต์ ๋ฐฉ์งํฉ๋๋ค. Upsert ์ฐ์ฐ๊ณผ ํจ๊ป ์ฌ์ฉํ๋ฉด ๋ฉฑ๋ฑ์ฑ์ ๋ณด์ฅํ ์ ์์ต๋๋ค.
59-69: ์ด ๋ฉ์๋๋ค์ ์ฝ๋์์ ์ฌ์ฉ๋์ง ์์ผ๋ฏ๋ก ๋์์ฑ ์ ์ด ๊ฒํ ๊ฐ ๋ถํ์ํฉ๋๋ค.
addLikeDelta,addViewDelta,addOrderDelta๋ฉ์๋๋ ProductMetricsDaily ์ํฐํฐ์ ์ ์๋์ด ์์ง๋ง ์ค์ ๋ก๋ ์ด๋์๋ ํธ์ถ๋์ง ์์ต๋๋ค. ์ค์ ๋ฐฐ์น ์ ๋ฐ์ดํธ๋ ProductMetricsDailyFacade๋ฅผ ํตํด ProductMetricsDailyRepository์ upsert ๋ฉ์๋๋ฅผ ์ง์ ํธ์ถํ๋ฉฐ, ์ด ๋ฉ์๋๋ค์ MySQL์ON DUPLICATE KEY UPDATE์์์ ์ฐ์ฐ์ ์ฌ์ฉํฉ๋๋ค.(product_id, metric_date)๊ณ ์ ์ ์ฝ์ผ๋ก ์ธํด ๋์ ์คํ ์์๋ ์์์ฑ์ด ๋ณด์ฅ๋๋ฏ๋ก lost update ๋ฌธ์ ๋ ๋ฐ์ํ์ง ์์ต๋๋ค.Likely an incorrect or invalid review comment.
apps/commerce-collector/src/main/java/com/loopers/application/like/ProductLikeBatchEventHandler.java (1)
98-99: ์ ํ๋ฆฌ์ผ์ด์ ์ด ์์ ์TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul"))๋ก ๊ธฐ๋ณธ ํ์์กด์ ๋ช ์์ ์ผ๋ก ์ค์ ํ๋ฏ๋ก, ๋ช ์์ ์ธ ZoneId ์ง์ ์ ๋ถํ์ํฉ๋๋ค. ํ์ฌ ์ฝ๋๋ RankingScheduler, RankingFacade, OrderBatchEventHandler ๋ฑ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฒด์์ ์ผ๊ด๋๊ฒ ์ฌ์ฉ๋๋ ํจํด์ ๋ฐ๋ฅด๊ณ ์์ต๋๋ค.Likely an incorrect or invalid review comment.
apps/commerce-api/src/main/java/com/loopers/domain/ranking/RankingService.java (1)
19-55: ์ ๋ ฅ ์ ํจ์ฑ ๊ฒ์ฆ ๋ฐ ํ์ด์ง๋ค์ด์ ๋ก์ง ํ์ธ
limit,page,sizeํ๋ผ๋ฏธํฐ์ ๋ํ ์ ํจ์ฑ ๊ฒ์ฆ๊ณผoffset๊ณ์ฐ ๋ก์ง์ด ์ ์ ํ๊ฒ ๊ตฌํ๋์ด ์์ต๋๋ค.apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingInfo.java (1)
6-47: ๋ ์ฝ๋ ๊ตฌ์กฐ ๋ฐ ํฉํ ๋ฆฌ ๋ฉ์๋ ๊ตฌํ ์ ์
RankingInfo์ ์ค์ฒฉ ๋ ์ฝ๋ProductRankings์ ๊ตฌ์กฐ๊ฐ ๋ช ํํ๊ณ , null ์ฒ๋ฆฌ ๋ก์ง(๋ชจ๋ ๋ญํน์ด null์ด๋ฉด ์ ์ฒด๋ฅผ null๋ก ๋ฐํ)์ด API ์๋ต์ ์ผ๊ด์ฑ์ ์ํด ์ ์ ํ๊ฒ ๊ตฌํ๋์ด ์์ต๋๋ค.apps/commerce-collector/src/main/java/com/loopers/application/product/ProductBatchEventHandler.java (1)
31-55: ์ ์ฒด์ ์ธ ๋ฐฐ์น ์ฒ๋ฆฌ ๋ก์ง ์ ์ ํธ๋์ญ์ ๊ฒฝ๊ณ ๋ด์์ ํํฐ๋ง, ์ค๋ณต ์ ๊ฑฐ, ๋ธํ ๊ณ์ฐ, ๋ฉํธ๋ฆญ ์ ๋ฐ์ดํธ, ์ด๋ฒคํธ ์ฒ๋ฆฌ ์๋ฃ ๋งํน๊น์ง์ ํ๋ฆ์ด ๋ช ํํ๊ฒ ๊ตฌํ๋์ด ์์ต๋๋ค. ์์ธ ๋ฐ์ ์ ๋กค๋ฐฑ์ ์ํ
RuntimeException๋ํ๋ ์ ์ ํฉ๋๋ค.apps/commerce-collector/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsRepositoryImpl.java (2)
47-54:like_count์์ ๋ฐฉ์ง ๋ก์ง ํ์ธINSERT ์
GREATEST(?, 0)๋ก ์์๋ฅผ ๋ฐฉ์งํ๊ณ ์์ผ๋, UPDATE ์์๋like_count = like_count + VALUES(like_count)๋ก ๋ธํ๋ฅผ ์ง์ ๋ํฉ๋๋ค. ์ทจ์ ์ด๋ฒคํธ๋ก ์ธํด ์์ ๋ธํ๊ฐ ์ ๋ฌ๋๊ณlike_count๊ฐ ํด๋น ๊ฐ๋ณด๋ค ์์ ๊ฒฝ์ฐ, ๊ฒฐ๊ณผ๊ฐ ์์๊ฐ ๋ ์ ์์ต๋๋ค.๐ ์์ ๋ฐฉ์ง ์์ ์ ์
ON DUPLICATE KEY UPDATE - like_count = like_count + VALUES(like_count), + like_count = GREATEST(like_count + VALUES(like_count), 0), updated_at = NOW()
41-73: ๋ฐฐ์น ์ ์ํธ ํจํด ๊ตฌํ ์ ์ ๋น ๋งต ์ฒดํฌ, BatchPreparedStatementSetter ํ์ฉ, ๋ก๊น ๋ฑ ๋ฐฐ์น ์ ์ํธ ํจํด์ด ์ผ๊ด๋๊ฒ ์ ๊ตฌํ๋์ด ์์ต๋๋ค. ๋ค๋ง,
ON DUPLICATE KEY UPDATE๊ตฌ๋ฌธ์ MySQL ์ ์ฉ์ด๋ฏ๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ด์์ฑ์ด ํ์ํ ๊ฒฝ์ฐ ๊ณ ๋ ค๊ฐ ํ์ํฉ๋๋ค.apps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/product/ProductBatchEventConsumer.java (2)
45-53: ์ด๋ฒคํธ ํ์ ํํฐ๋ง ์์น ๊ฒํ๊ธฐ์กด
ProductEventConsumer๋PRODUCT_VIEWED์ด๋ฒคํธ๋ง ์ฒ๋ฆฌํ๋๋ก ํํฐ๋งํ์ง๋ง, ์ด ๋ฐฐ์น ์ปจ์๋จธ๋ ๋ชจ๋ ์ด๋ฒคํธ๋ฅผ ํธ๋ค๋ฌ๋ก ์ ๋ฌํฉ๋๋ค. ํธ๋ค๋ฌ์calculateDelta์์PRODUCT_VIEWED๊ฐ ์๋ ๊ฒฝ์ฐ 0์ ๋ฐํํ์ง๋ง, ํด๋น ์ด๋ฒคํธ๋ "์ฒ๋ฆฌ ์๋ฃ"๋ก ๋งํน๋ฉ๋๋ค.์ด๊ฒ์ด ์๋๋ ๋์์ด๋ผ๋ฉด ๋ฌธ์ ์์ผ๋,
PRODUCT_VIEWED์ธ์ ์ด๋ฒคํธ๊ฐ product ํ ํฝ์ ๋ฐํ๋ ๊ฒฝ์ฐ๋ฅผ ๋๋นํ์ฌ ์ปจ์๋จธ ๋ ๋ฒจ์์ ํํฐ๋ง์ ์ถ๊ฐํ๋ ๊ฒ๋ ๊ณ ๋ คํด ๋ณผ ์ ์์ต๋๋ค.
65-96: ๋ฉ์์ง ํ์ฑ ๋ฐ ์ ํจ์ฑ ๊ฒ์ฆ ๋ก์ง ์ ์ ํ์ ํ๋ ๊ฒ์ฆ, ํ์ฑ ์คํจ ์ null ๋ฐํ ๋ฐ ๊ฒฝ๊ณ ๋ก๊น ๋ฑ ๋ฐฉ์ด์ ํ๋ก๊ทธ๋๋ฐ์ด ์ ์ ์ฉ๋์ด ์์ต๋๋ค.
ProductLikeBatchEventConsumer์ ๋์ผํ ํจํด์ ๋ฐ๋ฅด๊ณ ์์ด ์ผ๊ด์ฑ์ด ์ ์ง๋ฉ๋๋ค.apps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/order/OrderBatchEventConsumer.java (2)
86-96: JSON ํ์ฑ ์คํจ ์ฒ๋ฆฌ - LGTMํ์ฑ ์คํจ ์ null์ ๋ฐํํ๊ณ ๋ก๊ทธ๋ง ๋จ๊ธฐ๋ ๋ฐฉ์์ poison pill ๋ฉ์์ง๋ฅผ ์ ์ ํ ์ฒ๋ฆฌํฉ๋๋ค. ์ฌ์๋๊ฐ ๋ถํ์ํ ๋ฉ์์ง๋ฅผ ์คํตํ์ฌ ๋ฐฐ์น ์ ์ฒด๊ฐ ์คํจํ๋ ๊ฒ์ ๋ฐฉ์งํฉ๋๋ค.
28-32: ๋ฐฐ์น ๋ฆฌ์ค๋ ์ค์ ์ด ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌ์ฑ๋์ด ์์ต๋๋ค.
KafkaConfig.BATCH_LISTENER๋ MAX_POLL_RECORDS_CONFIG๊ฐ 3000์ผ๋ก ์ค์ ๋์ด ์๊ณ , ๋์์ฑ์ด 3์ผ๋ก ๊ตฌ์ฑ๋์ด ์์ผ๋ฉฐ, ์๋ ์น์ธ(MANUAL) ๋ชจ๋๊ฐ ์ ์ ํ๊ฒ ์ ์ฉ๋์ด ์์ต๋๋ค. BatchMessagingMessageConverter๋ ์ฌ๋ฐ๋ฅด๊ฒ ์ค์ ๋์ด ๋ฐฐ์น ๋ฉ์์ง ์ฒ๋ฆฌ์ ๋ฌธ์ ๊ฐ ์์ต๋๋ค.apps/commerce-collector/src/main/java/com/loopers/application/ranking/RankingScheduler.java (1)
127-138: ์ผ์๋ณ ๋ฐ์ดํฐ ์ ๋ฆฌ ๋ก์ง - LGTM10์ผ ์ด์ ๋ฐ์ดํฐ ์ญ์ ๋ก์ง์ด ์ ์ ํฉ๋๋ค. ์๋ฌ ํธ๋ค๋ง๊ณผ ๋ก๊น ๋ ์ ๊ตฌํ๋์ด ์์ต๋๋ค.
apps/commerce-api/src/main/java/com/loopers/interfaces/api/product/ProductV1DTO.java (2)
64-102: ๋ญํน DTO ์ถ๊ฐ - LGTM
RankingResponse์SimpleRankingResponse๋ ์ฝ๋๊ฐ null ์์ ํ๊ฒ ๊ตฌํ๋์์ผ๋ฉฐ, ๊ธฐ์กด DTO ํจํด(BrandV1Dto.BrandResponse)๊ณผ ์ผ๊ด์ฑ ์๋ ๊ตฌ์กฐ์ ๋๋ค.
36-51: ProductDetailResponse ๋ญํน ํ๋ ์ถ๊ฐ - LGTM
rankingsํ๋ ์ถ๊ฐ์ ๋งคํ ๋ก์ง์ด ์ ์ ํฉ๋๋ค. null ์ฒดํฌ๋ฅผ ํตํด ๋ญํน ์ ๋ณด๊ฐ ์๋ ๊ฒฝ์ฐ๋ ์์ ํ๊ฒ ์ฒ๋ฆฌ๋ฉ๋๋ค.apps/commerce-collector/src/main/java/com/loopers/application/ranking/RankingFacade.java (1)
91-107: Redis ํ์ดํ๋ผ์ธ ๊ตฌํ - LGTM
SessionCallback์ ์ฌ์ฉํ ํ์ดํ๋ผ์ธ ์ฒ๋ฆฌ๋ก Redis ํธ์ถ ํ์๋ฅผ ์ต์ํํ ์ข์ ๊ตฌํ์ ๋๋ค. TTL ์ค์ ๋ ํ์ดํ๋ผ์ธ ๋ด์์ ํจ๊ป ์ฒ๋ฆฌ๋ฉ๋๋ค.apps/commerce-collector/src/main/java/com/loopers/application/like/ProductLikeEventHandler.java (2)
3-16: Facade ํจํด์ผ๋ก์ ๋ฆฌํฉํ ๋ง - LGTM
EventHandledServiceโEventHandledFacade,ProductMetricsServiceโProductMetricsFacade๋ก์ ๋ณ๊ฒฝ์ด ์ผ๊ด๋๊ฒ ์ ์ฉ๋์์ต๋๋ค. ๋ฉฑ๋ฑ์ฑ ์ฒดํฌ์ ์ด๋ฒคํธ ์ฒ๋ฆฌ ํ๋ฆ์ด ์ ์ง๋ฉ๋๋ค.
21-36: ์ข์์ ์ด๋ฒคํธ ์ฒ๋ฆฌ ๋ก์ง - LGTM
handleLikeAdded์handleLikeRemoved๋ฉ์๋๊ฐ ๋ฉฑ๋ฑ์ฑ ์ฒดํฌ, ๋ฉํธ๋ฆญ ์ ๋ฐ์ดํธ, ์ด๋ฒคํธ ์ฒ๋ฆฌ ์๋ฃ ๊ธฐ๋ก์ ์์๋ก ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํ๋์ด ์์ต๋๋ค. ํ์ต๋ ๋ด์ฉ์ ๋ฐ๋ฅด๋ฉด, Like ์ด๋ฒคํธ๋ productId๋ฅผ aggregateId๋ก ์ฌ์ฉํ์ฌ ํํฐ์ ์์๋ฅผ ๋ณด์ฅํฉ๋๋ค.Also applies to: 41-56
apps/commerce-api/src/main/java/com/loopers/infrastructure/ranking/RedisRankingRepository.java (2)
32-45: TOP N ๋ญํน ์กฐํ - LGTM
ZREVRANGE๋ฅผ ์ฌ์ฉํ ์ ์ ๋ด๋ฆผ์ฐจ์ ์กฐํ๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํ๋์์ต๋๋ค. ๋น ๊ฒฐ๊ณผ์ ๋ํ ์ฒ๋ฆฌ๋ ์ ์ ํฉ๋๋ค.
81-103: ๋จ์ผ ์ํ ๋ญํน ์กฐํ - LGTM์ ์์ ์์๋ฅผ ๊ฐ๊ฐ ์กฐํํ์ฌ
Ranking๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ๋ก์ง์ด ์ ์ ํฉ๋๋ค. null ์ฒดํฌ๋ ์ ๋์ด ์์ต๋๋ค.apps/commerce-collector/src/main/java/com/loopers/application/order/OrderBatchEventHandler.java (2)
78-92: ORDER_CREATED ์ธ ์ด๋ฒคํธ ํ์ ์ฒ๋ฆฌ
aggregateOrderQuantities์์ORDER_CREATED๊ฐ ์๋ ์ด๋ฒคํธ๋ ๊ฒฝ๊ณ ๋ก๊ทธ๋ง ๋จ๊ธฐ๊ณ ์คํตํฉ๋๋ค. ์ด ๋์์ด ์๋๋ ๊ฒ์ธ์ง ํ์ธ์ด ํ์ํฉ๋๋ค. ๋ง์ฝ ๋ค๋ฅธ ์ฃผ๋ฌธ ์ด๋ฒคํธ(ORDER_COMPLETED, ORDER_CANCELLED ๋ฑ)๋ ์ฒ๋ฆฌํด์ผ ํ๋ค๋ฉด ๋ณ๋ ๋ก์ง์ด ํ์ํฉ๋๋ค.
30-61: ๋ฐฐ์น ์ฒ๋ฆฌ ํ๋ฆ - LGTM์ ์ฒด์ ์ธ ๋ฐฐ์น ์ฒ๋ฆฌ ํ๋ฆ์ด ์ ์ ํฉ๋๋ค:
- ๋ฏธ์ฒ๋ฆฌ ์ด๋ฒคํธ ํํฐ๋ง
- ์ค๋ณต ์ ๊ฑฐ
- ์ฃผ๋ฌธ ์๋ ์ง๊ณ
- ๋ฉํธ๋ฆญ ์ ๋ฐ์ดํธ
- ์ด๋ฒคํธ ์ฒ๋ฆฌ ์๋ฃ ๊ธฐ๋ก
ํธ๋์ญ์ ๋ด์์ ๋ชจ๋ ์์ ์ด ์์์ ์ผ๋ก ์ฒ๋ฆฌ๋๋ฉฐ, ์คํจ ์ ์ ์ฒด ๋กค๋ฐฑ๋ฉ๋๋ค. ํ์ต๋ ๋ด์ฉ์ ๋ฐ๋ฅด๋ฉด JPA dirty checking์ ํ์ฉํ ํจํด๊ณผ ์ผ๊ด๋ฉ๋๋ค.
apps/commerce-api/src/main/java/com/loopers/application/product/ProductFacade.java (1)
40-84: LGTM! Cache-Aside ํจํด๊ณผ ๋ญํน ํตํฉ์ด ์ ๊ตฌํ๋์์ต๋๋ค.์บ์ ํํธ/๋ฏธ์ค ์์ชฝ ๊ฒฝ๋ก์์ ์ด๋ฒคํธ ๋ฐํ์ด ์ฌ๋ฐ๋ฅด๊ฒ ์ฒ๋ฆฌ๋๊ณ ์์ผ๋ฉฐ, ๋ญํน ์ ๋ณด ์กฐํ ๋ฐ ProductDetailInfo ๊ตฌ์ฑ ๋ก์ง์ด ๋ช ํํฉ๋๋ค.
ํ ๊ฐ์ง ์ฐธ๊ณ ์ฌํญ: ์บ์๋ ProductDetailInfo์ ๋ญํน ์ ๋ณด๊ฐ ํฌํจ๋์ด ์์ด TTL(5๋ถ) ๋์ ๋ญํน์ด ๋ณ๊ฒฝ๋์ด๋ ๋ฐ์๋์ง ์์ต๋๋ค. ์ค์๊ฐ ๋ญํน์ด ์ค์ํ๋ค๋ฉด ๋ญํน ์ ๋ณด๋ฅผ ๋ณ๋๋ก ์กฐํํ๋ ๊ฒ์ ๊ณ ๋ คํด ๋ณผ ์ ์์ต๋๋ค.
apps/commerce-collector/src/main/java/com/loopers/interfaces/consumer/order/OrderEventConsumer.java (1)
17-18: ๋จ๊ฑด ์ฒ๋ฆฌ ์ปจ์๋จธ ๋นํ์ฑํ ํ์ธ.๋ฐฐ์น ๋ฆฌ์ค๋๋ก ์ ํํ๋ฉด์
@Component๋ฅผ ์ฃผ์ ์ฒ๋ฆฌํ ๊ฒ์ผ๋ก ๋ณด์ ๋๋ค. ํฅํ ์ ์ง๋ณด์๋ฅผ ์ํด ์ด ํด๋์ค๋ฅผ ์์ ํ ์ ๊ฑฐํ๊ฑฐ๋, ํ์ ์ ์ฌํ์ฑํํ ์ ์๋๋ก ๋ช ํํ ์ฃผ์์ ์ถ๊ฐํ๋ ๊ฒ์ ๊ณ ๋ คํด ์ฃผ์ธ์.apps/commerce-collector/src/main/java/com/loopers/application/metrics/ProductMetricsFacade.java (1)
43-56: ๋ฐฐ์น ์ ๋ฐ์ดํธ API ์ถ๊ฐ๊ฐ ์ ๋์ด ์์ต๋๋ค.
Propagation.REQUIRED๋ฅผ ์ฌ์ฉํ์ฌ ์์ ํธ๋์ญ์ ์ ์ฐธ์ฌํ๋ ์ค๊ณ๊ฐProductMetricsDailyFacade์ ์ผ๊ด๋ฉ๋๋ค. Repository์ upsert ๋ฉ์๋๋ก ์์ํ๋ ๋ฐฉ์๋ ๊น๋ํฉ๋๋ค.apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingFacade.java (1)
28-52: ๋ญํน ์กฐํ ๋ฉ์๋๋ค์ ๊ตฌ์กฐ๊ฐ ๋ช ํํฉ๋๋ค.
getTopRanking๊ณผgetRankingWithPaging๋ฉ์๋๊ฐ ๋น ๊ฒฐ๊ณผ์ ๋ํด early returnํ๊ณ ,enrichWithProductInfo๋ก ์ํ ์ ๋ณด๋ฅผ ๋ณด๊ฐํ๋ ํจํด์ด ์ผ๊ด๋ฉ๋๋ค.๋จ, ์์์ ์ง์ ํ
enrichWithProductInfo์ NPE ๋ฒ๊ทธ ์์ ์ด ํ์ํฉ๋๋ค.apps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1Dto.java (2)
15-58: ์์ฒญ DTO์ ๊ธฐ๋ณธ๊ฐ ์ฒ๋ฆฌ๊ฐ ์ ๋์ด ์์ต๋๋ค.Record์ compact constructor๋ฅผ ํ์ฉํ์ฌ
limit,page,size์ ๊ธฐ๋ณธ๊ฐ์ ์ค์ ํ๋ ํจํด์ด ์ ์ ํฉ๋๋ค.@Min/@Max๊ฒ์ฆ ์ด๋ ธํ ์ด์ ๋ ์ฌ๋ฐ๋ฅด๊ฒ ์ ์ฉ๋์ด ์์ต๋๋ค.
83-127: ์๋ต DTO ๊ตฌ์กฐ๊ฐ ๊น๋ํฉ๋๋ค.
TopRankingResponse์PagingRankingResponse์ ์ ์ ํฉํ ๋ฆฌ ๋ฉ์๋๊ฐ ๋๋ฉ์ธ ๊ฐ์ฒด์์ DTO๋ก์ ๋ณํ์ ๋ช ํํ๊ฒ ์ฒ๋ฆฌํ๊ณ ์์ต๋๋ค.totalPages๊ณ์ฐ ๋ก์ง๋@Min(1)๊ฒ์ฆ์ผ๋ก ์ธํด 0 ๋๋์ ๋ฌธ์ ๊ฐ ์์ต๋๋ค.apps/commerce-collector/src/main/java/com/loopers/domain/metrics/ProductMetricsDailyRepository.java (1)
8-21: ๋ฆฌํฌ์งํ ๋ฆฌ ์ธํฐํ์ด์ค ์ค๊ณ๊ฐ ์ ์ ํฉ๋๋ค.๋๋ฉ์ธ ๊ณ์ธต์์ ์ธํ๋ผ ๊ตฌํ์ ๋ถ๋ฆฌํ๋ ์ข์ ์ค๊ณ์ ๋๋ค. CRUD, ๋ฐฐ์น upsert, ์ ๋ฆฌ(cleanup) ๋ฉ์๋๊ฐ ๋ช ํํ๊ฒ ์ ์๋์ด ์์ผ๋ฉฐ, ๋ฐํ ํ์ ๋ ์ ์ ํฉ๋๋ค (
Optionalfor single find,intfor delete count).
apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingFacade.java
Show resolved
Hide resolved
apps/commerce-api/src/main/java/com/loopers/domain/product/ProductService.java
Show resolved
Hide resolved
apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductJpaRepository.java
Outdated
Show resolved
Hide resolved
apps/commerce-collector/src/main/java/com/loopers/application/order/OrderBatchEventHandler.java
Show resolved
Hide resolved
...mmerce-collector/src/main/java/com/loopers/application/product/ProductBatchEventHandler.java
Outdated
Show resolved
Hide resolved
...collector/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsRepositoryImpl.java
Show resolved
Hide resolved
...lector/src/main/java/com/loopers/interfaces/consumer/like/ProductLikeBatchEventConsumer.java
Show resolved
Hide resolved
...mmerce-collector/src/main/java/com/loopers/interfaces/consumer/order/OrderEventConsumer.java
Show resolved
Hide resolved
...ce-collector/src/main/java/com/loopers/interfaces/consumer/product/ProductEventConsumer.java
Show resolved
Hide resolved
- ProductMetrics order_count์ total_order_quantity ๊ตฌ๋ถ - ์ค์ผ์ค๋ฌ ์์ ๊ฒฝ๊ณ ๋ฌธ์ ์์
|
@coderabbitai ์๋์ ์ฒดํฌ๋ฆฌ์คํธ๋ฅผ ๋ง์กฑํ๋์ง ํ์ธํด์ค ๐ Ranking Consumer
โพ Ranking API
|
|
Tip For best results, initiate chat on the files or code changes. ๐งฉ Analysis chain๐ Script executed: Length of output: 12352 ๐ Script executed: Length of output: 6341 @HongChangMo ์ฒดํฌ๋ฆฌ์คํธ๋ฅผ ๊ฒ์ฆํ ๊ฒฐ๊ณผ๋ฅผ ๊ณต์ ๋๋ฆฝ๋๋ค: ๐ Ranking Consumer
โพ Ranking API
๋ชจ๋ ์ฒดํฌ๋ฆฌ์คํธ ํญ๋ชฉ์ด ์ถฉ์กฑ๋์์ต๋๋ค! ๐ |
- Kafka DLQ ์ ์ฉ - OrderMetrics DTO ๋ถ๋ฆฌ
๐ Summary
๐ฌ Review Points
1. ์ผ์๋ณ ์ ์ฒด ์บ์ ์ด๊ธฐํ ๊ธฐ๋ฅ
๋ฉํ ๋ง๋ ๋ฉํ ๋๊ป์ ์กฐ์ธํด์ฃผ์ ๋๋ก Scheduler๋ฅผ ํตํด Score๋ฅผ ๊ณ์ฐํ๋ ๋ก์ง์ ๋ถ๋ฆฌํ์์ต๋๋ค.
๊ถ๊ธํ ์ ์, ์ ์ฒด score์ ๋ํ sync๋ฅผ ๋ง์ถ๊ธฐ ์ํ "์ผ์๋ณ ์ ์ฒด ์บ์ ์ด๊ธฐํ" ๊ธฐ๋ฅ ๊ฐ์๊ฒ ์์ผ๋ฉด ์ข์์ง ๊ถ๊ธํ์ฌ ์ง๋ฌธ๋๋ฆฝ๋๋ค.
ํ์ฌ๋ ProductMetricsDaily(์ผ์๋ณ ์ํ ์ง๊ณ ์ ๋ณด) ํ ์ด๋ธ์์ ์ผ์๋ณ ์ฆ๋ถ ๋ฐ์ดํฐ์ ๋ํ ๋ญํน ์ฒ๋ฆฌ๋ฅผ ํ๋๋ก ํ์๋๋ฐ,
๋ง์ฝ์ "์ผ์๋ณ ์ ์ฒด ์บ์ ์ด๊ธฐํ" ๊ฐ์ ๊ธฐ๋ฅ์ด ์๋ค๋ฉด, ProductMetrics(์ํ ์ง๊ณ ์ ๋ณด) ํ ์ด๋ธ์์ ํด๋น ์ํ์ ๋ํ ์ง๊ณ(์ค์ผ์ค๋ฌ๊ฐ ๋๋ ๋ ์ง ์ง๊ณ)๋ฅผ ๊ฐ์ง๊ณ ์์
์บ์๋ฅผ ์ ์ฒด ์ด๊ธฐํํ ์ ์๋ ๊ธฐ๋ฅ์ด ์์ผ๋ฉด ์ด๋จ๊น ๊ถ๊ธํ์ฌ ์ง๋ฌธ๋๋ฆฝ๋๋ค.
2. ๋ญํน ์์คํ ํ๋ฆ
๋ฉํ ๋ง๋ ๋ง์ ์ฃผ์ ๋ด์ฉ์ ์ฐธ๊ณ ํด์ ๋ญํน ์์คํ ์ ํ๋ฆ์ ์๋์ ๊ฐ์ด ๊ตฌํํ์์ต๋๋ค.
๊ถ๊ธํ์ ์ ๋ฉํ ๋ง ๋ ๋ง์ํด์ฃผ์ ๋ด์ฉ์ฒ๋ผ
๋๊ณ์๋ญํน ๊ฐฑ์์ Scheduler๋ฅผ ํตํด ๋ถ๋ฆฌํ๋๋ฐ, ์ด ๊ตฌ์กฐ์ฒ๋ผ ๋ถ๋ฆฌํ๋๊ฒ ๋ง๋์ง ๊ถ๊ธํฉ๋๋ค.โ Checklist
๐ Ranking Consumer
โพ Ranking API
๐ References
Summary by CodeRabbit
๋ฆด๋ฆฌ์ค ๋ ธํธ
์๋ก์ด ๊ธฐ๋ฅ
๊ฐ์ ์ฌํญ
โ๏ธ Tip: You can customize this high-level summary in your review settings.