-
Notifications
You must be signed in to change notification settings - Fork 34
[volume - 9] What is Popularity? #210
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] What is Popularity? #210
Conversation
* feat: zset ๋ชจ๋ ์ถ๊ฐ zset * test: ๋ญํน ๊ณ์ฐ์ ๋ํ ํ ์คํธ ์ฝ๋ ์ถ๊ฐ * feat: ๋ญํน ๊ณ์ฐ ์๋น์ค ๊ตฌํ * test: ๋ญํน ์ด๋ฒคํธ ์ปจ์๋จธ ํ ์คํธ ๋ก์ง ์ถ๊ฐ * feat: ๋ญํน ์ปจ์๋จธ ๊ตฌํ * test: ๋ญํน ์กฐํ ํตํฉ ํ ์คํธ ์ฝ๋ ์ถ๊ฐ * feat: ๋ญํน ์กฐํ ์๋น์ค ๋ก์ง ๊ตฌํ * feat: ๋ญํน ์กฐํ ์๋ํฌ์ธํธ ์ถ๊ฐ * test: ๋ญํน ์ ๋ณด ํฌํจํ์ฌ ์ํ ์กฐํํ๋ ํ ์คํธ ์ฝ๋ ์์ฑ * feat: ๋ญํน ํฌํจํ์ฌ ์ํ ์ ๋ณด ์กฐํํ๋๋ก api ์์ --------- Co-authored-by: แแ ตแแ ฅแซแแ งแผ <>
Walkthrough์ํ์ ๋ญํน ๊ธฐ๋ฅ์ ์ถ๊ฐํ๋ ๊ธฐ๋ฅ ๊ฐ๋ฐ๋ก, ์บ์์์ ์ํ ์์ธ ์ ๋ณด๋ฅผ ์กฐํํ ํ ๋ฐํ์์ Redis ZSET์์ ๋ญํน ๋ฐ์ดํฐ๋ฅผ ๋ณ๋๋ก ์กฐํํ์ฌ ๋ฐํํ๋ ๊ตฌ์กฐ๋ฅผ ๊ตฌํํฉ๋๋ค. ์นดํ์นด ์ด๋ฒคํธ(์ข์์, ์ฃผ๋ฌธ, ์กฐํ)๋ฅผ ์๋นํ์ฌ Redis์ ์ ๋ ฌ๋ ์งํฉ์ ๋ญํน ์ ์๋ฅผ ๋์ ํฉ๋๋ค. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant API as RankingV1Controller
participant CatalogFacade
participant Cache as ProductCacheService
participant RankingAPI as RankingService<br/>(API)
participant Redis as RedisZSetTemplate
participant ProductService
Client->>API: GET /api/v1/products/{id}
API->>CatalogFacade: getProduct(productId)
alt Cache Hit
CatalogFacade->>Cache: getCachedProductDetail(productId)
Cache-->>CatalogFacade: ProductDetail (without rank)
CatalogFacade->>RankingAPI: getProductRank(productId, today)
RankingAPI->>Redis: getRank(daily_key, productId)
Redis-->>RankingAPI: Long rank
RankingAPI-->>CatalogFacade: Long rank
CatalogFacade-->>API: ProductInfo(detail, rank)
else Cache Miss
CatalogFacade->>ProductService: getProduct(productId)
ProductService-->>CatalogFacade: Product
CatalogFacade->>Cache: cacheProductDetail(ProductDetail without rank)
CatalogFacade->>RankingAPI: getProductRank(productId, today)
RankingAPI->>Redis: getRank(daily_key, productId)
Redis-->>RankingAPI: Long rank
RankingAPI-->>CatalogFacade: Long rank
CatalogFacade-->>API: ProductInfo(detail, rank)
end
API-->>Client: ApiResponse<ProductResponse>
sequenceDiagram
participant Kafka as Kafka Topics
participant Consumer as RankingConsumer
participant EventService as EventHandledService
participant RankingService
participant Redis as RedisZSetTemplate
Kafka->>Consumer: ConsumerRecord (Like/Order/View Event)
Consumer->>EventService: isAlreadyHandled(eventId)
alt Not Yet Handled
alt Like Event
Consumer->>RankingService: addLikeScore(productId, date, isAdded)
else Order Event
Consumer->>RankingService: addOrderScore(productId, date, amount)
else Product Event
Consumer->>RankingService: addViewScore(productId, date)
end
RankingService->>Redis: incrementScore(key, productId, score)
Redis-->>RankingService: โ
RankingService->>Redis: setTtlIfNotExists(key, 2days)
Redis-->>RankingService: โ
Consumer->>EventService: markAsHandled(eventId)
EventService-->>Consumer: โ
else Already Handled
Note over Consumer: Skip processing
end
Consumer-->>Kafka: acknowledge batch
Estimated code review effort๐ฏ 4 (Complex) | โฑ๏ธ ~45 minutes Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touchesโ Failed checks (1 warning, 1 inconclusive)
โ Passed checks (1 passed)
โจ Finishing touches
๐งช Generate unit tests (beta)
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 |
|
@coderabbitai review |
โ Actions performedReview triggered.
|
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: 6
โป๏ธ Duplicate comments (1)
apps/commerce-api/src/test/java/com/loopers/application/ranking/RankingServiceTest.java (1)
48-69: ๋ฆฌํ๋ ์ ํฌํผ ๋ฉ์๋๊ฐCatalogFacadeTest์ ์ค๋ณต๋ฉ๋๋ค.์์ ์ธ๊ธํ ๋๋ก, ๊ณตํต ํ ์คํธ ์ ํธ๋ฆฌํฐ ํด๋์ค๋ก ์ถ์ถ์ ๊ณ ๋ คํ์ธ์.
๐งน Nitpick comments (15)
apps/commerce-streamer/src/main/java/com/loopers/application/ranking/RankingKeyGenerator.java (1)
34-37: null ์ ๋ ฅ์ ๋ํ ๋ฐฉ์ด ์ฝ๋ ๊ณ ๋ ค
dateํ๋ผ๋ฏธํฐ๊ฐ null์ธ ๊ฒฝ์ฐNullPointerException์ด ๋ฐ์ํฉ๋๋ค. ํธ์ถ์๊ฐ ํญ์ non-null์ ๋ณด์ฅํ๋ค๋ฉด ๊ด์ฐฎ์ง๋ง, ๋ฐฉ์ด์ ํ๋ก๊ทธ๋๋ฐ ๊ด์ ์์ null ์ฒดํฌ๋ฅผ ์ถ๊ฐํ๊ฑฐ๋@NonNull์ด๋ ธํ ์ด์ ์ ๋ช ์ํ๋ ๊ฒ์ ๊ณ ๋ คํด ๋ณด์ธ์.apps/commerce-api/src/main/java/com/loopers/application/catalog/CatalogFacade.java (2)
128-132: ํ ์คํธ ์ฉ์ด์ฑ์ ์ํ ์๊ฐ ์ถ์ํ ๊ณ ๋ ค
LocalDate.now()๋ฅผ ์ง์ ํธ์ถํ๋ฉด ๋จ์ ํ ์คํธ์์ ๋ ์ง๋ฅผ ์ ์ดํ๊ธฐ ์ด๋ ต์ต๋๋ค.Clock์ ์ฃผ์ ๋ฐ๊ฑฐ๋, ๋ณ๋์ ๋ ์ง ์ ๊ณต์๋ฅผ ์ฌ์ฉํ๋ฉด ํ ์คํธ ์ ์๊ฐ์ ๊ณ ์ ํ ์ ์์ต๋๋ค.๐ Clock ์ฃผ์ ์์
// ํ๋ ์ถ๊ฐ private final Clock clock; // ์์ฑ์์์ ์ฃผ์ (๋๋ @Value๋ก ๊ธฐ๋ณธ๊ฐ ์ค์ ) // ์ฌ์ฉ ์ LocalDate today = LocalDate.now(clock);
147-159: ์บ์ ๋ฏธ์ค ์ ํ๋ฆ ๊ฐ์ ๊ฐ๋ฅํ์ฌ ํ๋ฆ์์
ProductInfo.withoutRank(productDetail)์ ๋ ๋ฒ ์์ฑํ๊ณ ์์ต๋๋ค (Line 152, 158). ํ ๋ฒ๋ง ์์ฑํ์ฌ ์ฌ์ฌ์ฉํ๋ฉด ์ฝ๋๊ฐ ๋ ๋ช ํํด์ง๋๋ค.๐ ๊ฐ์ ์ ์
// ๋ญํน ์ ๋ณด ์กฐํ LocalDate today = LocalDate.now(); Long rank = rankingService.getProductRank(productId, today); - // ์บ์์ ์ ์ฅ (๋ญํน ์ ๋ณด๋ ์ ์ธํ๊ณ ์ ์ฅ - ๋ญํน์ ์ค์๊ฐ์ผ๋ก ์กฐํ) - productCacheService.cacheProduct(productId, ProductInfo.withoutRank(productDetail)); + // ์บ์์ ์ ์ฅ (๋ญํน ์ ๋ณด๋ ์ ์ธํ๊ณ ์ ์ฅ - ๋ญํน์ ์ค์๊ฐ์ผ๋ก ์กฐํ) + ProductInfo productInfoWithoutRank = ProductInfo.withoutRank(productDetail); + productCacheService.cacheProduct(productId, productInfoWithoutRank); // โ ์ํ ์กฐํ ์ด๋ฒคํธ ๋ฐํ (๋ฉํธ๋ฆญ ์ง๊ณ์ฉ) productEventPublisher.publish(ProductEvent.ProductViewed.from(productId)); // ๋ก์ปฌ ์บ์์ ์ข์์ ์ ๋ธํ ์ ์ฉ (DB ์กฐํ ๊ฒฐ๊ณผ์๋ ๋ธํ ๋ฐ์) - ProductInfo deltaApplied = productCacheService.applyLikeCountDelta(ProductInfo.withoutRank(productDetail)); + ProductInfo deltaApplied = productCacheService.applyLikeCountDelta(productInfoWithoutRank); return ProductInfo.withRank(deltaApplied.productDetail(), rank);apps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1Controller.java (3)
6-6: ์ฌ์ฉ๋์ง ์๋ import ์ ๊ฑฐ
@DateTimeFormat์ด๋ ธํ ์ด์ ์ด import๋์ด ์์ง๋ง ์ฝ๋์์ ์ฌ์ฉ๋์ง ์์ต๋๋ค.๐ ์ ์
-import org.springframework.format.annotation.DateTimeFormat;
82-87: ์๋ชป๋ ๋ ์ง ํ์์ ๋ํ ํผ๋๋ฐฑ ๋ถ์ฌ๋ ์ง ํ์ฑ ์คํจ ์ ์์ธ๋ฅผ ๋ฌด์ํ๊ณ ์ค๋ ๋ ์ง๋ฅผ ๋ฐํํฉ๋๋ค. ํด๋ผ์ด์ธํธ๊ฐ ์๋ชป๋ ํ์์ ์ ๋ฌํ์ ๋ ์ด๋ฅผ ์ธ์งํ์ง ๋ชปํ ์ ์์ต๋๋ค. ๋ก๊น ์ ์ถ๊ฐํ๊ฑฐ๋, ๋ช ์์ ์ผ๋ก 400 Bad Request๋ฅผ ๋ฐํํ๋ ๊ฒ์ ๊ณ ๋ คํ์ธ์.
๐ ๋ก๊น ์ถ๊ฐ ์์
try { return LocalDate.parse(dateStr, DATE_FORMATTER); } catch (DateTimeParseException e) { - // ํ์ฑ ์คํจ ์ ์ค๋ ๋ ์ง ๋ฐํ + log.warn("๋ ์ง ํ์ฑ ์คํจ, ์ค๋ ๋ ์ง ์ฌ์ฉ: input={}", dateStr); return LocalDate.now(); }
54-62: ๋ฉ์๋ ํ๋ผ๋ฏธํฐ ์ฌํ ๋น ์ง์
page์sizeํ๋ผ๋ฏธํฐ๋ฅผ ๋ฉ์๋ ๋ด์์ ์ฌํ ๋นํ๊ณ ์์ต๋๋ค. ์ด๋ ์ฝ๋ ๊ฐ๋ ์ฑ์ ์ ํ์ํฌ ์ ์์ต๋๋ค. ๋ณ๋์ ์ง์ญ ๋ณ์๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ ๋ช ํํฉ๋๋ค.๐ ๊ฐ์ ์ ์
- // ํ์ด์ง ๊ฒ์ฆ - if (page < 0) { - page = 0; - } - if (size < 1) { - size = 20; - } - if (size > 100) { - size = 100; // ์ต๋ 100๊ฐ๋ก ์ ํ - } + // ํ์ด์ง ๊ฒ์ฆ + int validPage = Math.max(page, 0); + int validSize = Math.max(1, Math.min(size, 100)); - RankingService.RankingsResponse result = rankingService.getRankings(targetDate, page, size); + RankingService.RankingsResponse result = rankingService.getRankings(targetDate, validPage, validSize);apps/commerce-streamer/src/main/java/com/loopers/application/ranking/RankingService.java (1)
133-137: ๊ฐ๋ณ ํธ์ถ๋ง๋ค TTL ์ค์ ํธ์ถ ์ต์ ํ ๊ณ ๋ ค
incrementScore๊ฐ ํธ์ถ๋ ๋๋ง๋คsetTtlIfNotExists๋ฅผ ํธ์ถํฉ๋๋ค.setTtlIfNotExists๋ ๋ด๋ถ์ ์ผ๋ก TTL์ด ์์ ๋๋ง ์ค์ ํ์ง๋ง, ๋งค๋ฒ Redis ํธ์ถ์ด ๋ฐ์ํ ์ ์์ต๋๋ค. ๊ณ ๋น๋ ํธ์ถ ์ ๋ถํ์ํ ๋คํธ์ํฌ ์ค๋ฒํค๋๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
addScoresBatch์ฒ๋ผ ๋ฐฐ์น ๋จ์๋ก TTL์ ์ค์ ํ๊ฑฐ๋, ํค๋ณ๋ก ๋ก์ปฌ์์ TTL ์ค์ ์ฌ๋ถ๋ฅผ ์ถ์ ํ๋ ๋ฐฉ์์ ๊ณ ๋ คํด ๋ณด์ธ์.apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingKeyGenerator.java (1)
18-51: ์ฝ๋ ์ค๋ณต: commerce-streamer ๋ชจ๋๊ณผ ๋์ผํ RankingKeyGenerator ํด๋์ค๊ฐ ์กด์ฌํฉ๋๋ค.
apps/commerce-streamer/src/main/java/com/loopers/application/ranking/RankingKeyGenerator.java์ ๋์ผํ ๋ก์ง์ ํด๋์ค๊ฐ ์์ต๋๋ค. ๋ ๋ชจ๋ ๋ชจ๋์์ ๋์ผํ ํค ์์ฑ ๋ก์ง์ ์ฌ์ฉํ๋ฏ๋ก, ๊ณต์ ๋ชจ๋(์:modules/redis๋๋ ๋ณ๋์modules/ranking-common)๋ก ์ถ์ถํ์ฌ ์ค๋ณต์ ์ ๊ฑฐํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค.ํ์ฌ ๊ตฌํ ์์ฒด๋ ์ฌ๋ฐ๋ฅด๋ฉฐ,
DateTimeFormatter๋ฅผstatic final๋ก ์ฌ์ฌ์ฉํ๋ ๊ฒ์ ์ข์ ํจํด์ ๋๋ค.apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingService.java (1)
82-93:Collectors.toMap์์ ์ค๋ณต ํค ๋ฐ์ ์ ์์ธ ๊ฐ๋ฅ์ฑ
Collectors.toMap์ ์ค๋ณต ํค๊ฐ ์์ ๊ฒฝ์ฐIllegalStateException์ ๋ฐ์์ํต๋๋ค. Redis ZSET์์ ๋ฐํ๋productIds์ ์ค๋ณต์ด ์๋ค๊ณ ๊ฐ์ ํ์ง๋ง,productService.getProducts()๊ฐ ์ค๋ณต๋ ID๋ฅผ ๊ฐ์ง Product๋ฅผ ๋ฐํํ๋ ๊ฒฝ์ฐ ์์ธ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.๐ ์ค๋ณต ํค ์ฒ๋ฆฌ๋ฅผ ์ํ ์์ ์ ์
Map<Long, Product> productMap = products.stream() - .collect(Collectors.toMap(Product::getId, product -> product)); + .collect(Collectors.toMap(Product::getId, product -> product, (existing, replacement) -> existing)); Map<Long, Brand> brandMap = brandService.getBrands(brandIds).stream() - .collect(Collectors.toMap(Brand::getId, brand -> brand)); + .collect(Collectors.toMap(Brand::getId, brand -> brand, (existing, replacement) -> existing));apps/commerce-api/src/test/java/com/loopers/application/catalog/CatalogFacadeTest.java (1)
67-88: ๋ฆฌํ๋ ์ ์ ํธ๋ฆฌํฐ ๋ฉ์๋ ์ค๋ณต
setId(Product, Long)์setId(Brand, Long)๋ฉ์๋๊ฐRankingServiceTest์๋ ๋์ผํ๊ฒ ์กด์ฌํฉ๋๋ค. ํ ์คํธ ์ ํธ๋ฆฌํฐ ํด๋์ค๋ก ์ถ์ถํ๋ฉด ์ฝ๋ ์ค๋ณต์ ์ค์ผ ์ ์์ต๋๋ค.๋ํ lines 157-163๊ณผ 199-206์์
likeCount์ค์ ์ ์ํ ๋ฆฌํ๋ ์ ์ฝ๋๋ ์ค๋ณต๋์ด ์์ต๋๋ค.modules/redis/src/main/java/com/loopers/zset/RedisZSetTemplate.java (2)
59-69: TTL ์กฐ๊ฑด ๊ฒ์ฌ์์ ํค๊ฐ ์กด์ฌํ์ง ์๋ ๊ฒฝ์ฐ ๋๋ฝ
getExpire()๋ ํค๊ฐ ์กด์ฌํ์ง ์์ ๋-2๋ฅผ ๋ฐํํฉ๋๋ค. ํ์ฌ ์กฐ๊ฑดcurrentTtl == null || currentTtl == -1์ ์ด ๊ฒฝ์ฐ๋ฅผ ์ฒ๋ฆฌํ์ง ์์ต๋๋ค. ๋จ, ZSET์incrementScore๊ฐ ๋จผ์ ํธ์ถ๋ ํsetTtlIfNotExists๊ฐ ํธ์ถ๋๋ ์์๋ผ๋ฉด ํค๊ฐ ์ด๋ฏธ ์กด์ฌํ๋ฏ๋ก ๋ฌธ์ ์์ ์ ์์ต๋๋ค.๐ ๋ ๋ช ํํ ์กฐ๊ฑด ์ฒ๋ฆฌ ์ ์
public void setTtlIfNotExists(String key, Duration ttl) { try { Long currentTtl = redisTemplate.getExpire(key); - if (currentTtl == null || currentTtl == -1) { + if (currentTtl == null || currentTtl < 0) { // TTL์ด ์๊ฑฐ๋ -1(๋ง๋ฃ ์๊ฐ ์์)์ธ ๊ฒฝ์ฐ์๋ง ์ค์ + // -2๋ ํค๊ฐ ์กด์ฌํ์ง ์๋ ๊ฒฝ์ฐ redisTemplate.expire(key, ttl); } } catch (Exception e) { log.warn("ZSET TTL ์ค์ ์คํจ: key={}", key, e); } }
41-48: Silent failure ๋์์ ๋ํ ๋ฌธ์ํ ๊ณ ๋ ค
incrementScore์์ ์์ธ ๋ฐ์ ์ ๋ก๊ทธ๋ง ๋จ๊ธฐ๊ณ ๊ณ์ ์งํํ๋ ๊ฒ์ ์๋๋ ์ค๊ณ์ด์ง๋ง, ์ด๋ก ์ธํด ๋ฐ์ดํฐ ์ ์ค์ด ๋ฐ์ํ ์ ์์ต๋๋ค. ํด๋ผ์ด์ธํธ ์ฝ๋์์ ์ด ๋์์ ์ธ์งํ ์ ์๋๋ก Javadoc์ ์ด ๋์์ ๋ช ์ํ๋ ๊ฒ์ด ์ข์ต๋๋ค.apps/commerce-streamer/src/test/java/com/loopers/interfaces/consumer/RankingConsumerTest.java (1)
185-222: ๋ฐฐ์น ์ฒ๋ฆฌ ํ ์คํธ๊ฐ ์ค์ ๋ฐฐ์น ์๋๋ฆฌ์ค๋ฅผ ์์ ํ ๋ฐ์ํ์ง ์์
canConsumeMultipleEventsํ ์คํธ๋consumeLikeEvents์consumeProductEvents๋ฅผ ๋ณ๋๋ก ํธ์ถํฉ๋๋ค. ์ค์ ๋ฐฐ์น ์ฒ๋ฆฌ ์๋๋ฆฌ์ค(๋จ์ผ consumer ํธ์ถ์์ ์ฌ๋ฌ ๋ ์ฝ๋ ์ฒ๋ฆฌ)๋ฅผ ํ ์คํธํ๋ ค๋ฉด ๋์ผํ ์ด๋ฒคํธ ํ์ ์ ์ฌ๋ฌ ๋ ์ฝ๋๋ฅผ ํ๋์ ๋ฆฌ์คํธ๋ก ์ ๋ฌํด์ผ ํฉ๋๋ค.ํ์ฌ ํ ์คํธ๋ ์ฌ์ ํ ์ ํจํ์ง๋ง, ํ ์คํธ ์ด๋ฆ๊ณผ ์๋๊ฐ ์ผ์นํ๋๋ก ์กฐ์ ํ๊ฑฐ๋ ์ค์ ๋ฐฐ์น ์๋๋ฆฌ์ค ํ ์คํธ๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ ๊ณ ๋ คํ์ธ์.
apps/commerce-streamer/src/main/java/com/loopers/interfaces/consumer/RankingConsumer.java (2)
320-344: Like ์ด๋ฒคํธ ํ์ฑ ๋ก์ง์ ๊ฐ์ ํ ์ ์์ต๋๋ค.Lines 323, 339์ ํ์ฑ ๋ก์ง์ด ๋นํจ์จ์ ์ ๋๋ค:
- ์ด๋ฏธ ํ์ ์ด ์ง์ ๋ ๊ฐ์ฒด์ธ ๊ฒฝ์ฐ์๋ JSON์ผ๋ก ์ง๋ ฌํ ํ ๋ค์ ์ญ์ง๋ ฌํํ๋ ๋ถํ์ํ ๊ณผ์ ์ ๊ฑฐ์นฉ๋๋ค.
parseOrderCreatedEvent์parseProductViewedEvent๋instanceof์ฒดํฌ๋ฅผ ๋จผ์ ์ํํ๋ ๋ฐ๋ฉด, ์ด ๋ ๋ฉ์๋๋ ๊ทธ๋ ์ง ์์ ์ผ๊ด์ฑ์ด ๋ถ์กฑํฉ๋๋ค.๐ ๊ฐ์ ๋ ํ์ฑ ๋ก์ง ์ ์
private LikeEvent.LikeAdded parseLikeEvent(Object value) { try { + if (value instanceof LikeEvent.LikeAdded) { + return (LikeEvent.LikeAdded) value; + } + // JSON ๋ฌธ์์ด์ธ ๊ฒฝ์ฐ ํ์ฑ String json = value instanceof String ? (String) value : objectMapper.writeValueAsString(value); return objectMapper.readValue(json, LikeEvent.LikeAdded.class); } catch (Exception e) { throw new RuntimeException("LikeAdded ์ด๋ฒคํธ ํ์ฑ ์คํจ", e); } } private LikeEvent.LikeRemoved parseLikeRemovedEvent(Object value) { try { + if (value instanceof LikeEvent.LikeRemoved) { + return (LikeEvent.LikeRemoved) value; + } + // JSON ๋ฌธ์์ด์ธ ๊ฒฝ์ฐ ํ์ฑ String json = value instanceof String ? (String) value : objectMapper.writeValueAsString(value); return objectMapper.readValue(json, LikeEvent.LikeRemoved.class); } catch (Exception e) { throw new RuntimeException("LikeRemoved ์ด๋ฒคํธ ํ์ฑ ์คํจ", e); } }
420-433: ์ฌ์ฉ๋์ง ์๋ ๋ฉ์๋์ ๋๋ค.
extractVersion๋ฉ์๋๊ฐ ์ ์๋์ด ์์ง๋ง ์ฝ๋ ์ด๋์์๋ ํธ์ถ๋์ง ์์ต๋๋ค. ํฅํ ์ฌ์ฉ ๊ณํ์ด ์๋ค๋ฉด ์ ์งํ๊ณ , ๊ทธ๋ ์ง ์๋ค๋ฉด ์ ๊ฑฐ๋ฅผ ๊ณ ๋ คํ์ธ์.#!/bin/bash # extractVersion ๋ฉ์๋ ์ฌ์ฉ์ฒ ํ์ธ rg -n "extractVersion" --type=java
๐ Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
๐ Files selected for processing (17)
apps/commerce-api/src/main/java/com/loopers/application/catalog/CatalogFacade.javaapps/commerce-api/src/main/java/com/loopers/application/catalog/ProductInfo.javaapps/commerce-api/src/main/java/com/loopers/application/product/ProductCacheService.javaapps/commerce-api/src/main/java/com/loopers/application/ranking/RankingKeyGenerator.javaapps/commerce-api/src/main/java/com/loopers/application/ranking/RankingService.javaapps/commerce-api/src/main/java/com/loopers/interfaces/api/catalog/ProductV1Dto.javaapps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1Controller.javaapps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1Dto.javaapps/commerce-api/src/test/java/com/loopers/application/catalog/CatalogFacadeTest.javaapps/commerce-api/src/test/java/com/loopers/application/ranking/RankingServiceTest.javaapps/commerce-streamer/src/main/java/com/loopers/application/ranking/RankingKeyGenerator.javaapps/commerce-streamer/src/main/java/com/loopers/application/ranking/RankingService.javaapps/commerce-streamer/src/main/java/com/loopers/interfaces/consumer/RankingConsumer.javaapps/commerce-streamer/src/test/java/com/loopers/application/ranking/RankingServiceTest.javaapps/commerce-streamer/src/test/java/com/loopers/interfaces/consumer/RankingConsumerTest.javamodules/redis/src/main/java/com/loopers/zset/RedisZSetTemplate.javamodules/redis/src/main/java/com/loopers/zset/ZSetEntry.java
๐งฐ Additional context used
๐ง Learnings (7)
๐ Common learnings
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๋ถ์ผ๋ก ์ค์ ๋์ด ์๋ค.
๐ Learning: 2025-12-19T21:30:08.018Z
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:08.018Z
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-streamer/src/test/java/com/loopers/interfaces/consumer/RankingConsumerTest.javaapps/commerce-streamer/src/main/java/com/loopers/interfaces/consumer/RankingConsumer.java
๐ Learning: 2025-12-19T20:59:50.009Z
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:50.009Z
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-streamer/src/main/java/com/loopers/interfaces/consumer/RankingConsumer.java
๐ Learning: 2025-12-18T13:24:51.650Z
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:51.650Z
Learning: Adopt centralized error handling for Kafka consumers by using the EventInboxAspect to intercept methods annotated with InboxEvent. Ensure that service methods annotated with InboxEvent save failed EventInbox entries and log errors, avoiding duplicating error handling logic in individual consumers. Apply this pattern broadly to similar consumer/service layers within the commerce-streamer module.
Applied to files:
apps/commerce-streamer/src/main/java/com/loopers/interfaces/consumer/RankingConsumer.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-api/src/main/java/com/loopers/application/catalog/CatalogFacade.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/catalog/CatalogFacade.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/application/catalog/CatalogFacade.javaapps/commerce-api/src/main/java/com/loopers/application/product/ProductCacheService.java
๐งฌ Code graph analysis (3)
apps/commerce-streamer/src/test/java/com/loopers/interfaces/consumer/RankingConsumerTest.java (2)
apps/commerce-api/src/test/java/com/loopers/application/ranking/RankingServiceTest.java (1)
ExtendWith(27-400)apps/commerce-streamer/src/test/java/com/loopers/application/ranking/RankingServiceTest.java (1)
ExtendWith(25-247)
apps/commerce-streamer/src/main/java/com/loopers/interfaces/consumer/RankingConsumer.java (3)
apps/commerce-streamer/src/main/java/com/loopers/application/ranking/RankingService.java (1)
Slf4j(38-138)modules/redis/src/main/java/com/loopers/zset/RedisZSetTemplate.java (1)
Slf4j(24-140)apps/commerce-streamer/src/main/java/com/loopers/application/ranking/RankingKeyGenerator.java (1)
Component(18-52)
modules/redis/src/main/java/com/loopers/zset/RedisZSetTemplate.java (1)
apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingKeyGenerator.java (1)
Component(18-52)
๐ Additional comments (20)
modules/redis/src/main/java/com/loopers/zset/ZSetEntry.java (1)
1-12: LGTM!ZSET ์ํธ๋ฆฌ๋ฅผ ํํํ๋ ๊ฐ๊ฒฐํ record์ ๋๋ค. Javadoc๋ ์ ์ ํ ์์ฑ๋์ด ์์ต๋๋ค.
apps/commerce-api/src/main/java/com/loopers/application/product/ProductCacheService.java (1)
272-272: LGTM!
withoutRankํฉํ ๋ฆฌ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ๋ญํน ์ ๋ณด ์์ดProductInfo๋ฅผ ๋ฐํํ๋ ๊ฒ์ด ์ ์ ํฉ๋๋ค. ๋ญํน ์ ๋ณด๋ ์ค์๊ฐ์ผ๋ก ์กฐํ๋๋ฏ๋ก ์บ์๋ ์ข์์ ๋ธํ ์ ์ฉ ์ ๋ญํน์ ํฌํจํ์ง ์๋ ๊ฒ์ด ์ฌ๋ฐ๋ฅธ ์ค๊ณ์ ๋๋ค.apps/commerce-api/src/main/java/com/loopers/application/catalog/CatalogFacade.java (1)
96-96: ์ํ ๋ชฉ๋ก์ ๋ญํน ์ ๋ณด ๋ฏธํฌํจ ํ์ธ์ํ ๋ชฉ๋ก ์กฐํ ์
withoutRank๋ฅผ ์ฌ์ฉํ์ฌ ๋ญํน ์ ๋ณด๋ฅผ ํฌํจํ์ง ์์ต๋๋ค. ์ด๊ฒ์ด ์๋๋ ๋์์ธ์ง ํ์ธํด ์ฃผ์ธ์. ๋ชฉ๋ก์์๋ ๋ญํน์ ๋ณด์ฌ์ฃผ์ด์ผ ํ๋ค๋ฉด ์์ ์ด ํ์ํฉ๋๋ค.apps/commerce-api/src/main/java/com/loopers/application/catalog/ProductInfo.java (1)
11-31: LGTM!
withoutRank์withRankํฉํ ๋ฆฌ ๋ฉ์๋๋ฅผ ํตํด ๋ญํน ์ ๋ณด ์ ๋ฌด์ ๋ฐ๋ฅธProductInfo์์ฑ์ ๋ช ํํ๊ฒ ๋ถ๋ฆฌํ ์ข์ ์ค๊ณ์ ๋๋ค. record์ ๋ถ๋ณ์ฑ์ ์ ์งํ๋ฉด์ ํ์ฅ์ฑ ์๋ ๊ตฌ์กฐ๋ฅผ ์ ๊ณตํฉ๋๋ค.apps/commerce-api/src/main/java/com/loopers/interfaces/api/catalog/ProductV1Dto.java (1)
26-51: LGTM!
rankํ๋๊ฐProductResponse์ ์ ์ ํ ์ถ๊ฐ๋์์ผ๋ฉฐ,ProductInfo๋ก๋ถํฐ์ ๋งคํ๋ ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํ๋์์ต๋๋ค. Javadoc๋ ๋ช ํํ๊ฒ ์ ๋ฐ์ดํธ๋์์ต๋๋ค.apps/commerce-streamer/src/main/java/com/loopers/application/ranking/RankingService.java (2)
70-76: ์ข์์ ์ทจ์ ์ ์์ ์ ์ ๊ฐ๋ฅ์ฑ์ข์์ ์ทจ์ ์
-LIKE_WEIGHT๋ฅผ ์ ์ฉํฉ๋๋ค. ๋ง์ฝ ์ข์์๊ฐ ์ถ๊ฐ๋๊ธฐ ์ ์ ์ทจ์ ์ด๋ฒคํธ๊ฐ ๋จผ์ ์ฒ๋ฆฌ๋๋ฉด (์ด๋ฒคํธ ์์ ์ญ์ ), ํด๋น ์ํ์ ์ ์๊ฐ ์์๊ฐ ๋ ์ ์์ต๋๋ค. Eventually Consistent ์ค๊ณ ์์น์ ๋ฐ๋ผ ํ์ฉ ๊ฐ๋ฅํ ์ํฉ์ธ์ง ํ์ธํด ์ฃผ์ธ์.
38-48: LGTM!๋ญํน ์ ์ ๊ฐ์ค์น์ TTL ์ค์ ์ด ๋ช ํํ๊ฒ ์ ์๋์ด ์์ต๋๋ค. CQRS Read Model ์ค๊ณ ์์น์ ๋ฐ๋ฅธ ๊ตฌํ์ด ์ ์ ํฉ๋๋ค.
apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingService.java (2)
60-61:@Transactional(readOnly = true)์ด๋ ธํ ์ด์ ์ฌ๊ณ ํ์์ด ๋ฉ์๋๋ ์ฃผ๋ก Redis์์ ๋ฐ์ดํฐ๋ฅผ ์ฝ๊ณ ,
ProductService์BrandService๋ฅผ ํตํด DB ์กฐํ๋ฅผ ์ํํฉ๋๋ค. ๋ง์ฝProductService.getProducts()์BrandService.getBrands()๊ฐ ์ด๋ฏธ ์์ฒด์ ์ผ๋ก ํธ๋์ญ์ ์ ๊ด๋ฆฌํ๊ณ ์๋ค๋ฉด, ์ด ๋ ๋ฒจ์์ ๋ณ๋์ ํธ๋์ญ์ ์ ๋ถํ์ํ ์ค๋ฒํค๋๊ฐ ๋ ์ ์์ต๋๋ค.ํ์ ์๋น์ค์ ํธ๋์ญ์ ์ค์ ์ ํ์ธํ๊ณ , ํ์ํ ๊ฒฝ์ฐ์๋ง ์ ์งํ์ธ์.
96-132: LGTM! ๋ฐฐ์น ์กฐํ ํจํด์ด ์ ๊ตฌํ๋์ด ์์ต๋๋ค.N+1 ์ฟผ๋ฆฌ ๋ฌธ์ ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ์ํ๊ณผ ๋ธ๋๋๋ฅผ ๋ฐฐ์น๋ก ์กฐํํ๊ณ , ๋๋ฝ๋ ๋ฐ์ดํฐ์ ๋ํด ๋ก๊ทธ๋ฅผ ๋จ๊ธฐ๋ฉฐ ์คํตํ๋ ์ฒ๋ฆฌ๊ฐ ์ ์ ํฉ๋๋ค.
hasNext๊ณ์ฐ ๋ก์ง๋ ๋ช ํํฉ๋๋ค.apps/commerce-api/src/test/java/com/loopers/application/catalog/CatalogFacadeTest.java (1)
90-229: LGTM! ์บ์ ํํธ/๋ฏธ์ค ์๋๋ฆฌ์ค์ ๋ํ ๋ญํน ํตํฉ ํ ์คํธ๊ฐ ํฌ๊ด์ ์ ๋๋ค.4๊ฐ์ง ์ฃผ์ ์๋๋ฆฌ์ค(์บ์ ํํธ + ๋ญํน ์์/์์, ์บ์ ๋ฏธ์ค + ๋ญํน ์์/์์)๋ฅผ ๋ชจ๋ ์ปค๋ฒํ๊ณ ์์ผ๋ฉฐ, mock ์ค์ ๊ณผ ๊ฒ์ฆ์ด ์ ์ ํฉ๋๋ค.
apps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1Dto.java (1)
14-94: LGTM! DTO ์ค๊ณ๊ฐ ๊น๋ํฉ๋๋ค.
from()ํฉํ ๋ฆฌ ๋ฉ์๋๋ฅผ ํตํ ๋๋ฉ์ธ-API ๋งคํ์ด ๋ช ํํ๊ณ , record๋ฅผ ์ฌ์ฉํ์ฌ ๋ถ๋ณ DTO๋ฅผ ๊ตฌํํ ๊ฒ์ด ์ ์ ํฉ๋๋ค.apps/commerce-api/src/test/java/com/loopers/application/ranking/RankingServiceTest.java (1)
71-399: LGTM! RankingService์ ๋ํ ํฌ๊ด์ ์ธ ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง๊ฐ ์ ๊ตฌํ๋์ด ์์ต๋๋ค.๋ค์ ์๋๋ฆฌ์ค๋ค์ด ๋ชจ๋ ํ ์คํธ๋์์ต๋๋ค:
- ๊ธฐ๋ณธ ๋ญํน ์กฐํ ๋ฐ ํ์ด์ง
- ๋น ๊ฒฐ๊ณผ ์ฒ๋ฆฌ
- ๋๋ฝ๋ ์ํ/๋ธ๋๋ ์คํต
hasNextํ๋๊ทธ ๊ณ์ฐ- ๋จ์ผ ์ํ ์์ ์กฐํ
- ๋์ผ ๋ธ๋๋ ์ค๋ณต ์ ๊ฑฐ
๊ฐ ํ ์คํธ๊ฐ ๋ช ํํ arrange/act/assert ๊ตฌ์กฐ๋ฅผ ๋ฐ๋ฅด๊ณ ์์ต๋๋ค.
apps/commerce-streamer/src/test/java/com/loopers/application/ranking/RankingServiceTest.java (1)
24-247: LGTM! commerce-streamer ๋ชจ๋์ RankingService ์ ์ ์ ๋ฐ์ดํธ ๋ก์ง์ ๋ํ ํ ์คํธ๊ฐ ์ ๊ตฌํ๋์ด ์์ต๋๋ค.์กฐํ/์ข์์/์ฃผ๋ฌธ ์ ์ ์ถ๊ฐ, ๋ฐฐ์น ์ฒ๋ฆฌ, ๋น ๋ฐฐ์น ์ฒ๋ฆฌ, ๋ค์ค ๋ ์ง ๋ ๋ฆฝ ์ฒ๋ฆฌ, ์ ์ ๋์ ๋ฑ ํต์ฌ ์๋๋ฆฌ์ค๊ฐ ๋ชจ๋ ์ปค๋ฒ๋์ด ์์ต๋๋ค.
ArgumentCaptor๋ฅผ ์ฌ์ฉํ์ฌ ๋์ ์ ์ ๊ฐ์ ๊ฒ์ฆํ๋ ๊ฒ๋ ์ ์ ํฉ๋๋ค.modules/redis/src/main/java/com/loopers/zset/RedisZSetTemplate.java (1)
102-120: LGTM!getTopRankings๊ตฌํ์ด ์ฌ๋ฐ๋ฆ ๋๋ค.
reverseRangeWithScores๋ฅผ ์ฌ์ฉํ์ฌ ์ ์๊ฐ ๋์ ์์๋ก ์ ๋ ฌ๋ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๊ณ , null ์ฒดํฌ ๋ฐ ์์ธ ์ฒ๋ฆฌ๊ฐ ์ ์ ํฉ๋๋ค.apps/commerce-streamer/src/test/java/com/loopers/interfaces/consumer/RankingConsumerTest.java (2)
415-455: LGTM! ๋ฉฑ๋ฑ์ฑ ํ ์คํธ๊ฐ ์ ๊ตฌํ๋์ด ์์ต๋๋ค.๋์ผํ
eventId๋ฅผ ๊ฐ์ง ์ค๋ณต ๋ฉ์์ง 3๊ฐ๋ฅผ ์ฒ๋ฆฌํ ๋ ์ฒซ ๋ฒ์งธ๋ง ์ค์ ์ฒ๋ฆฌ๋๊ณ ๋๋จธ์ง๋ ์คํต๋๋ ๊ฒ์ ๊ฒ์ฆํฉ๋๋ค.isAlreadyHandled์ ์์ฐจ์ ๋ฐํ๊ฐ ์ค์ ๊ณผ ํธ์ถ ํ์ ๊ฒ์ฆ์ด ์ ์ ํฉ๋๋ค.
1-414: LGTM! RankingConsumer์ ๋ํ ํฌ๊ด์ ์ธ ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง๋ค์ ์๋๋ฆฌ์ค๋ค์ด ์ ํ ์คํธ๋์์ต๋๋ค:
- ๊ฐ ์ด๋ฒคํธ ํ์ (LikeAdded, LikeRemoved, OrderCreated, ProductViewed) ์ฒ๋ฆฌ
- ์ด๋ฏธ ์ฒ๋ฆฌ๋ ์ด๋ฒคํธ ์คํต
- eventId ์๋ ๋ฉ์์ง ์ฒ๋ฆฌ
- ๊ฐ๋ณ ์ด๋ฒคํธ ์คํจ ์ ๋ฐฐ์น ์ฒ๋ฆฌ ๊ณ์
- DataIntegrityViolationException ์ฒ๋ฆฌ (๋์์ฑ ์ํฉ)
- ์ฃผ๋ฌธ ์ด๋ฒคํธ์ ์ฃ์ง ์ผ์ด์ค (totalQuantity=0, subtotal=null)
apps/commerce-streamer/src/main/java/com/loopers/interfaces/consumer/RankingConsumer.java (4)
22-66: ํด๋์ค ๊ตฌ์กฐ ๋ฐ ๋ฌธ์ํ๊ฐ ์ ๋์ด ์์ต๋๋ค.JavaDoc์ด ์์ธํ๊ณ ์์กด์ฑ ์ฃผ์ ์ด ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํ๋์์ต๋๋ค. ์ด๋ฒคํธ ํ์ , Manual Ack ์ ๋ต, CQRS ์ค๊ณ ์์น์ด ๋ช ํํ๊ฒ ๋ฌธ์ํ๋์ด ์์ด ์ฝ๋ ์ดํด์ ๋์์ด ๋ฉ๋๋ค.
139-143: ๊ฐ๋ณ ๋ ์ฝ๋ ์ฒ๋ฆฌ ์คํจ ์ ๋ฐ์ดํฐ ์์ค ๊ฐ๋ฅ์ฑ์ ๊ณ ๋ คํ์ธ์.๊ฐ๋ณ ์ด๋ฒคํธ ์ฒ๋ฆฌ๊ฐ ์คํจํด๋ ๋ก๊ทธ๋ง ๋จ๊ธฐ๊ณ ๊ณ์ ์งํํ๋ฉฐ, ๋ฐฐ์น ์ ์ฒด๋ ์ปค๋ฐ๋ฉ๋๋ค. ์ด๋ "At Most Once" ์๋งจํฑ์ผ๋ก ์คํจํ ์ด๋ฒคํธ๋ ์๊ตฌ์ ์ผ๋ก ์์ค๋ฉ๋๋ค. ๋ญํน ๋ฐ์ดํฐ์ ํน์ฑ์ ์ผ๋ถ ์์ค์ด ํ์ฉ ๊ฐ๋ฅํ๋ค๋ฉด ํ์ฌ ๊ตฌ์กฐ๊ฐ ์ ์ ํ์ง๋ง, ๋ค์์ ๊ณ ๋ คํด๋ณด์ธ์:
- Dead Letter Queue(DLQ)๋ก ์คํจํ ์ด๋ฒคํธ๋ฅผ ๋ณ๋ ํ ํฝ์ ๋ฐํํ์ฌ ์ถํ ์ฌ์ฒ๋ฆฌ
- ๋ฉํธ๋ฆญ/์๋์ ํตํด ์คํจ์จ ๋ชจ๋ํฐ๋ง
ํ์ฌ ์ค๊ณ๊ฐ ์๋์ ์ด๋ผ๋ฉด JavaDoc์ ๋ฐ์ดํฐ ์์ค ๊ฐ๋ฅ์ฑ์ ๋ช ์ํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
352-384: Order ๋ฐ Product ์ด๋ฒคํธ ํ์ฑ ๋ก์ง์ด ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํ๋์์ต๋๋ค.ํ์ ์ฒดํฌ๋ฅผ ๋จผ์ ์ํํ์ฌ ๋ถํ์ํ ์ง๋ ฌํ/์ญ์ง๋ ฌํ๋ฅผ ๋ฐฉ์งํ๊ณ ์์ต๋๋ค. ์ด ํจํด์ Like ์ด๋ฒคํธ ํ์ฑ์๋ ์ ์ฉํ๋ฉด ์ผ๊ด์ฑ์ด ํฅ์๋ฉ๋๋ค.
392-412: ํค๋ ์ถ์ถ ๋ก์ง์ด ์์ ํ๊ฒ ๊ตฌํ๋์์ต๋๋ค.
lastHeader()๋ฅผ ์ฌ์ฉํ๊ณ null ์ฒดํฌ๋ฅผ ์ํํ์ฌ ๋ฐฉ์ด์ ์ผ๋ก ๊ตฌํ๋์์ต๋๋ค.
apps/commerce-streamer/src/main/java/com/loopers/application/ranking/RankingKeyGenerator.java
Outdated
Show resolved
Hide resolved
apps/commerce-streamer/src/main/java/com/loopers/interfaces/consumer/RankingConsumer.java
Outdated
Show resolved
Hide resolved
apps/commerce-streamer/src/main/java/com/loopers/interfaces/consumer/RankingConsumer.java
Outdated
Show resolved
Hide resolved
apps/commerce-streamer/src/main/java/com/loopers/interfaces/consumer/RankingConsumer.java
Outdated
Show resolved
Hide resolved
apps/commerce-streamer/src/main/java/com/loopers/interfaces/consumer/RankingConsumer.java
Outdated
Show resolved
Hide resolved
apps/commerce-streamer/src/main/java/com/loopers/interfaces/consumer/RankingConsumer.java
Outdated
Show resolved
Hide resolved
* feat: zset ๋ชจ๋์ zunionstore ์ฐ์ฐ ์ฒ๋ฆฌ ๋ฉ์๋ ์ถ๊ฐ * test: ๋ญํน ์ง๊ณ์ ํ์ํ ๋ฐ์ดํฐ ์์ง๊ณผ ๋ญํน ๊ณ์ฐ ๋ก์ง์ application event ๊ธฐ์ค์ผ๋ก ๋ถ๋ฆฌํ๋๋ก ํ ์คํธ ์ฝ๋ ์์ * feat: ๋ญํน ์ง๊ณ์ ํ์ํ ๋ฐ์ดํฐ ์์ง๊ณผ ๋ญํน ๊ณ์ฐ ๋ก์ง์ application event ๊ธฐ์ค์ผ๋ก ๋ถ๋ฆฌํ๋๋ก ํจ
* test: ๋ญํน ์กฐํ ์คํจํ ๋์ ํ ์คํธ์ฝ๋ ์ถ๊ฐ * feat: ๋ญํน ์กฐํ ์คํจ์ ์ ๋ ํน์ ์ข์์ ์ ๋ฐ์ดํฐ๋ก ์๋ตํ๋๋ก ๋ณด์ * feat: ๋ญํน fallback ์ ๋ต ๊ตฌํ * test: ๋ญํน fallback ์ ๋ต์ ๋ง์ถฐ ํ ์คํธ์ฝ๋ ์์ * refactor: ์ผ์ ๋จ์ carry over ๋์ ์ ๋ฐ๋ผ unionstore ์ ๊ฑฐ * chore: ํด๋์ค๋ช ๊ณผ ๋์ผํ๊ฒ ํ์ผ ์ด๋ฆ ๋ณ๊ฒฝ * refactor: ๋ญํน ์ด๋ฒคํธ ์ปจ์๋จธ์์ ๋ฉฑ๋ฑ์ฑ ์ฒดํฌ ๋ก์ง, ์๋ฌ ์ฒ๋ฆฌ ๋ก์ง, ๋ฐฐ์น ์ปค๋ฐ ๋ก์ง ๋ฐ๋ณต ์ ๊ฑฐ * refactor: ๋ถํ์ํ stubbing ์ ๊ฑฐ * chore: ์๊ฐ๋ ์ค์ ์ถ๊ฐ
๐ Summary
Kafka Consumer๋ฅผ ํตํด ์์ ํ ์ด๋ฒคํธ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก Redis ZSET์ ์ด์ฉํ ์ค์๊ฐ ๋ญํน ์์คํ ์ ๊ตฌํํ์ต๋๋ค.
์ฃผ์ ๊ตฌํ ๋ด์ฉ:
๊ตฌํ๋ ๊ธฐ๋ฅ:
GET /api/v1/rankings?date=yyyyMMdd&size=20&page=1: ๋ญํน ํ์ด์ง ์กฐํ๐ฌ Review Points
1. ๋ญํน์ ๋๋ฉ์ธ์ด ์๋ ์ ์ค์ผ์ด์ค๋ก ํ๋จ
๋ฐฐ๊ฒฝ ๋ฐ ๋ฌธ์ ์ํฉ:
๋ญํน ์์คํ ์ ๊ตฌํํ ๋, ๋ญํน์ด ๋๋ฉ์ธ์ธ์ง ์ ์ค์ผ์ด์ค์ธ์ง ํ๋จํด์ผ ํ์ต๋๋ค. ๋๋ฉ์ธ์ผ๋ก ์ทจ๊ธํ๋ฉด ๋ณ๋์ ๋๋ฉ์ธ ๋ ์ด์ด๋ฅผ ๋ง๋ค์ด์ผ ํ์ง๋ง, ๋ญํน์ ๋น์ฆ๋์ค ๊ท์น์ ๊ฐ๋ ๋ ๋ฆฝ์ ์ธ ๋๋ฉ์ธ์ด ์๋๋ผ ์กฐํ์ฉ ํ์ ๋ฐ์ดํฐ(Read Model)์ ๋๋ค.
ํด๊ฒฐ ๋ฐฉ์:
๋ญํน์ ๋๋ฉ์ธ์ด ์๋ Application ๋ ์ด์ด์ ์ ์ค์ผ์ด์ค๋ก ํ๋จํ์ฌ, ๋ณ๋์ ๋๋ฉ์ธ ๋ ์ด์ด๋ฅผ ๋ง๋ค์ง ์๊ณ Application ๋ ์ด์ด์๋ง ๊ตฌํํ์ต๋๋ค. ๋ญํน์ CQRS ํจํด์ Read Side๋ก ์ทจ๊ธํ์ฌ, Write Side(๋๋ฉ์ธ ์ด๋ฒคํธ) โ Kafka โ Read Side(๋ญํน ์ง๊ณ) โ Redis ZSET ๊ตฌ์กฐ๋ก ์ค๊ณํ์ต๋๋ค.
๊ด๋ จ ์ฝ๋:
๊ณ ๋ฏผํ ์ :
2. ์ธ๋ถ ์ด๋ฒคํธ์ ๋ด๋ถ ์ด๋ฒคํธ ๊ตฌ๋ถ: Spring ApplicationEvent ์ฌ์ฉ
๋ฐฐ๊ฒฝ ๋ฐ ๋ฌธ์ ์ํฉ:
Kafka Consumer์์ ์ด๋ฒคํธ๋ฅผ ์์ ํ ํ, ๋ญํน ์ง๊ณ์ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ์์งํ๋ ๊ฒ๊ณผ ๋ญํน ์ ์๋ฅผ ๊ณ์ฐํ๋ ๊ฒ์ ์๋ก ๋ค๋ฅธ ์ฑ ์์ ๋๋ค. Kafka๋ก consumeํ๋ ๊ฒ์ ์ธ๋ถ ์์คํ ๊ณผ์ ํต์ (์ธํฐํ์ด์ค ๊ณ์ธต)์ด๊ณ , ZSET์ ์ ์ ๊ณ์ฐ์ ์ ํ๋ฆฌ์ผ์ด์ ๋ด๋ถ ๋ก์ง(์ ํ๋ฆฌ์ผ์ด์ ๊ณ์ธต)์ ๋๋ค.
ํด๊ฒฐ ๋ฐฉ์:
๋๋ฉ์ธ์ ์ฑ ์์ ๋ช ํํ ํ๊ธฐ ์ํด, Kafka Consumer๋ ์ธ๋ถ ์ด๋ฒคํธ๋ฅผ ์์ ํ๊ณ Spring ApplicationEvent๋ก ๋ฐํํ๋ ์ญํ ๋ง ๋ด๋นํ๊ณ , ๋ญํน ๊ณ์ฐ ๋ก์ง์ ApplicationEvent๋ฅผ ๊ตฌ๋ ํ๋ ๋ณ๋์ ํธ๋ค๋ฌ์์ ์ฒ๋ฆฌํ๋๋ก ๋ถ๋ฆฌํ์ต๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด Kafka Consumer๋ ๋ฉ์์ง ์์ /ํ์ฑ๋ง ๋ด๋นํ๊ณ , ๋น์ฆ๋์ค ๋ก์ง์ ์ ํ๋ฆฌ์ผ์ด์ ๊ณ์ธต์์ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
๊ตฌ์กฐ:
๊ด๋ จ ์ฝ๋:
๊ณ ๋ฏผํ ์ :
@Async๋ฅผ ์ฌ์ฉํ์ฌ ๋ญํน ์ง๊ณ ์ฒ๋ฆฌ๋ฅผ ๋น๋๊ธฐ๋ก ์คํํ๋๋ก ํ์ต๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด Kafka Consumer์ ์ฑ๋ฅ์ ์ํฅ์ ์ฃผ์ง ์๊ณ ๋ญํน ์ง๊ณ๋ฅผ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.3. ์ฝ๋ ์คํํธ ๋ฌธ์ ํด๊ฒฐ: Score Carry-Over ๋ฐฉ์
๋ฐฐ๊ฒฝ ๋ฐ ๋ฌธ์ ์ํฉ:
์ผ๋ณ ๋ญํน์ ๋ ๋ฆฝ์ ์ผ๋ก ๊ณ์ฐํ๋ฉด, ๋งค์ผ ์์ ์ ๋ญํน์ด 0์ ์์ ์์ํ๋ ์ฝ๋ ์คํํธ ๋ฌธ์ ๊ฐ ๋ฐ์ํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ์ด์ ์ธ๊ธฐ ์๋ ์ํ์ด ์ค๋ ์์ ์ ๊ฐ์๊ธฐ ๋ญํน์์ ์ฌ๋ผ์ง๋ฉด ์ฌ์ฉ์ ๊ฒฝํ์ด ์ข์ง ์์ต๋๋ค.
ํด๊ฒฐ ๋ฐฉ์ ๋ฐ ๋ฐฉ์ ์ ํ:
์ฝ๋ ์คํํธ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ ๊ฐ์ง ๋ฐฉ์์ ๊ณ ๋ คํ์ต๋๋ค. ์ฒซ ๋ฒ์งธ๋
ZUNIONSTORE๋ช ๋ น์ด๋ฅผ ์ฌ์ฉํ์ฌ ์๊ฐ๋ณ ๋ญํน์ ๋ณ๋๋ก ๊ด๋ฆฌํ๊ณ ์ด๋ฅผ ์ผ๊ฐ ๋ญํน์ผ๋ก ์ง๊ณํ๋ ๋ฐฉ์์ ๋๋ค. ์ด ๋ฐฉ์์ ์๊ฐ๋ณ ๋ญํน ํค(ranking:hourly:yyyyMMddHH)์ ์ผ๊ฐ ๋ญํน ํค(ranking:all:yyyyMMdd)์ ์ด์ค์ผ๋ก ์ ์๋ฅผ ์ ์ฌํ๊ณ ,ZUNIONSTORE๋ก ์๊ฐ๋ณ ๋ญํน์ ์ผ๊ฐ ๋ญํน์ผ๋ก ์ง๊ณํฉ๋๋ค. ์ด ๋ฐฉ์์ ์ฅ์ ์ ์๊ฐ ๋จ์ ๋ญํน ์กฐํ๊ฐ ๊ฐ๋ฅํ๊ณ , ์๊ฐ๋ณ ๊ฐ์ค์น๋ฅผ ์ ์ฉํ ์ ์์ผ๋ฉฐ, ๋ฐฐ์น ์ง๊ณ๊ฐ ์ต์ ํ๋๋ค๋ ์ ์ ๋๋ค. ํ์ง๋ง ๋จ์ ์ผ๋ก๋ Redis ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ์ฝ 2.4๋ฐฐ ์ฆ๊ฐํ๊ณ , ์๊ฐ๋ณ ๋ญํน ์ ์ฌ ๋ก์ง, ์ง๊ณ ์ค์ผ์ค๋ฌ, ์๊ฐ ๋จ์ Carry-Over ์ค์ผ์ค๋ฌ ๋ฑ์ผ๋ก ์ธํด ๊ตฌํ ๋ณต์ก๋๊ฐ ํฌ๊ฒ ์ฆ๊ฐํ๋ฉฐ, ์ค์ผ์ค๋ฌ๋ฅผ 3๊ฐ๋ ๊ด๋ฆฌํด์ผ ํ๊ณ , ๋ ๊ฐ์ ZSET์ ์ ์ฌํด์ผ ํ๋ฏ๋ก ์ค์๊ฐ์ฑ๋ ์ฝ๊ฐ ์ ํ๋ฉ๋๋ค.๋ ๋ฒ์งธ ๋ฐฉ์์ ์ผ๊ฐ ๋ญํน ํค์ ์ง์ ์ ์๋ฅผ ์ ์ฌํ๊ณ , ์ผ๊ฐ ๋ญํน Carry-Over๋ง ๊ตฌํํ๋ ๋ฐฉ์์ ๋๋ค. ์ด ๋ฐฉ์์ ๊ตฌํ์ด ๋จ์ํ๊ณ , Redis ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ ์ต์ํํ๋ฉฐ, ์ค์ผ์ค๋ฌ๋ฅผ 1๊ฐ๋ง ๊ด๋ฆฌํ๋ฉด ๋๊ณ , ๋จ์ผ ZSET์๋ง ์ ์ฌํ๋ฏ๋ก ์ค์๊ฐ์ฑ๋ ์ฐ์ํ๋ฉฐ, ์ฝ๋ ๋ณต์ก๋๊ฐ ๋ฎ์ ์ ์ง๋ณด์๊ฐ ์ฉ์ดํฉ๋๋ค. ๋ค๋ง ์๊ฐ๋ณ ๊ฐ์ค์น ์ ์ฉ์ ๋ถ๊ฐ๋ฅํ์ง๋ง, ํ์ฌ ์๊ตฌ์ฌํญ์๋ ์ด๋ฌํ ๊ธฐ๋ฅ์ด ํ์ํ์ง ์์ต๋๋ค.
์ค๋ฌด ๊ด์ ์์ ZUNIONSTORE๋ฅผ ์ฌ์ฉํ์ง ์๋ ๋ ๋ฒ์งธ ๋ฐฉ์์ ์ต์ข ์ ์ผ๋ก ์ ํํ์ต๋๋ค. ํ์ฌ ์๊ฐ๋ณ ๊ฐ์ค์น ์ ์ฉ์ด ํ์ํ์ง ์์ผ๋ฏ๋ก, ZUNIONSTORE๋ฅผ ์ฌ์ฉํ ๋ ๋ฐ์ํ๋ ๋น์ฉ(Redis ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ์ฝ 2.4๋ฐฐ ์ฆ๊ฐ, ์ค์ผ์ค๋ฌ ๊ด๋ฆฌ ๋ณต์ก๋ ์ฆ๊ฐ) ๋๋น ์ป๋ ์ด์ ์ด ํ์ฌ ์ํฉ์์๋ ์ ๋ค๊ณ ํ๋จํ์ต๋๋ค. ๋ํ ์ผ๊ฐ ๋ญํน์ ์ง์ ์ ์ฌํ๋ ๋ฐฉ์์ด ๋ ๋จ์ํ๊ณ ์ดํดํ๊ธฐ ์ฌ์ฐ๋ฉฐ, ์ด์ํ๊ธฐ๋ ์ฌ์ ์ค๋ฌด์์ ๋ ์ ํฉํฉ๋๋ค. ๋ง์ฝ ํฅํ ์๊ฐ๋ณ ๋ญํน ๊ธฐ๋ฅ์ด ์ค์ ๋ก ํ์ํ๋ค๋ฉด, ๊ทธ๋ ์ถ๊ฐํ๋ ๊ฒ์ด ๋ ํจ์จ์ ์ ๋๋ค.
๊ตฌํ ์ฝ๋:
์ค์ผ์ค๋ฌ ๊ตฌํ:
๊ณ ๋ฏผํ ์ :
4. ์์ธ ์ฒ๋ฆฌ: Graceful Degradation ์ ๋ต
๋ฐฐ๊ฒฝ ๋ฐ ๋ฌธ์ ์ํฉ:
Redis ์ฅ์ ๋ ๋คํธ์ํฌ ์ค๋ฅ๋ก ์ธํด ๋ญํน ์กฐํ๊ฐ ์คํจํ ์ ์์ต๋๋ค. ์ด ๊ฒฝ์ฐ ์ฌ์ฉ์์๊ฒ ์๋ฌ๋ฅผ ๋ฐํํ๋ ๊ฒ๋ณด๋ค, ๋์ฒด ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํ๋ ๊ฒ์ด ๋ ๋์ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํฉ๋๋ค.
ํด๊ฒฐ ๋ฐฉ์:
๋ฉํ ๋ง ์ธ์ ์์ "DB ์ค์๊ฐ ์ฌ๊ณ์ฐ์ ์ํํ๋ฏ๋ก ์ค๋ ์ท ์๋น์ด ํ์ค์ "์ด๋ผ๋ ์กฐ์ธ์ ๋ฐ์์ต๋๋ค. ๋ฐ๋ผ์ Redis ์ฅ์ ์ ์ธ๋ฉ๋ชจ๋ฆฌ ์บ์์ ์ ์ฅ๋ ๋ญํน ์ค๋ ์ท์ ์๋นํ๋๋ก ๊ตฌํํ์ต๋๋ค. ์ค๋ ์ท๋ ์์ ๊ฒฝ์ฐ๋ฅผ ๋๋นํด ๊ธฐ๋ณธ ๋ญํน(์ข์์์)์ ์ต์ข Fallback์ผ๋ก ์ ๊ณตํ์ง๋ง, ์ด๋ ๋ญํน์ ์๋ก ๊ณ์ฐํ๋ ๊ฒ์ด ์๋๋ผ ์ด๋ฏธ ์ง๊ณ๋ ์ข์์ ์๋ฅผ ๋จ์ ์กฐํํ๋ ๊ฒ์ด๋ฏ๋ก DB ๋ถํ๊ฐ ํฌ์ง ์์ต๋๋ค.
๊ตฌํ ์ธ๋ถ์ฌํญ:
๊ด๋ จ ์ฝ๋:
์ค๋ ์ท ์ ์ฅ ๋ฐ ์กฐํ ๊ตฌํ:
๊ณ ๋ฏผํ ์ :
productService.findAll(null, "likes_desc", page, size)๋ ์ธ๋ฑ์ค๊ฐ ์๋ ์ปฌ๋ผ์ ๊ธฐ์ค์ผ๋ก ์ ๋ ฌ๋ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๋ ๋จ์ ์ฟผ๋ฆฌ์ด๋ฏ๋ก, ๋ญํน์ ์ค์๊ฐ์ผ๋ก ๊ณ์ฐํ๋ ๊ฒ๊ณผ๋ ๋ค๋ฅด๊ฒ DB ๋ถํ๊ฐ ํฌ์ง ์์ต๋๋ค. ๋ค๋ง ๋ฉํ ๋ง ์กฐ์ธ์ ๋ฐ๋ผ ์ค๋ ์ท์ ์ฐ์ ์ ์ผ๋ก ์ฌ์ฉํ๊ณ , ๊ธฐ๋ณธ ๋ญํน์ ์ตํ์ ์๋จ์ผ๋ก๋ง ์ฌ์ฉํฉ๋๋ค.5. ZSET์ ๋ณ๋ ๋ชจ๋๋ก ๋ถ๋ฆฌ
๋ฐฐ๊ฒฝ ๋ฐ ๋ฌธ์ ์ํฉ:
Redis ZSET ์กฐ์ ๋ก์ง์ด ์ฌ๋ฌ ๊ณณ์์ ์ฌ์ฉ๋ ์ ์์ผ๋ฏ๋ก, ์ฌ์ฌ์ฉ์ฑ์ ๋์ด๊ธฐ ์ํด ๋ณ๋ ๋ชจ๋๋ก ๋ถ๋ฆฌํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
ํด๊ฒฐ ๋ฐฉ์:
Redis ZSET ์กฐ์ ๋ก์ง์
modules/redis๋ชจ๋์RedisZSetTemplateํด๋์ค๋ก ๋ถ๋ฆฌํ์ฌ, ๋ค๋ฅธ ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ์ฌ์ฌ์ฉํ ์ ์๋๋ก ํ์ต๋๋ค.๊ตฌํ ์ธ๋ถ์ฌํญ:
RedisZSetTemplate: ZSET ์กฐ์ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ํ ํ๋ฆฟ ํด๋์คincrementScore: ์ ์ ์ฆ๊ฐgetTopRankings: ์์ N๊ฐ ์กฐํgetRank: ํน์ ๋ฉค๋ฒ์ ์์ ์กฐํgetSize: ZSET ํฌ๊ธฐ ์กฐํunionStore: ์ฌ๋ฌ ZSET ํฉ์น๊ธฐunionStoreWithWeight: ๊ฐ์ค์น๋ฅผ ์ ์ฉํ์ฌ ZSET ํฉ์น๊ธฐsetTtlIfNotExists: TTL ์ค์ ๊ด๋ จ ์ฝ๋:
๊ณ ๋ฏผํ ์ :
RedisZSetTemplate์@Component๋ก ๋ฑ๋กํ์ฌ ๋ค๋ฅธ ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ์ฝ๊ฒ ์ฌ์ฉํ ์ ์๋๋ก ํ์ต๋๋ค.โ Checklist
Ranking Consumer
๋ญํน ZSET์ TTL, ํค ์ ๋ต์ ์ ์ ํ๊ฒ ๊ตฌ์ฑํ์๋ค
ranking:all:yyyyMMdd(์ผ๊ฐ ๋ญํน)Duration.ofDays(2))apps/commerce-streamer/src/main/java/com/loopers/application/ranking/RankingKeyGenerator.java๋ ์ง๋ณ๋ก ์ ์ฌํ ํค๋ฅผ ๊ณ์ฐํ๋ ๊ธฐ๋ฅ์ ๋ง๋ค์๋ค
RankingKeyGenerator.generateDailyKey(): ์ผ๊ฐ ๋ญํน ํค ์์ฑ (ranking:all:yyyyMMdd)apps/commerce-streamer/src/main/java/com/loopers/application/ranking/RankingKeyGenerator.java์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ํ, ZSET์ ์ ์๊ฐ ์ ์ ํ๊ฒ ๋ฐ์๋๋ค
apps/commerce-streamer/src/main/java/com/loopers/application/ranking/RankingService.javaRanking API
๋ญํน Page ์กฐํ ์ ์ ์์ ์ผ๋ก ๋ญํน ์ ๋ณด๊ฐ ๋ฐํ๋๋ค
GET /api/v1/rankings?date=yyyyMMdd&size=20&page=1apps/commerce-api/src/main/java/com/loopers/interfaces/api/ranking/RankingV1Controller.java๋ญํน Page ์กฐํ ์ ๋จ์ํ ์ํ ID๊ฐ ์๋ ์ํ์ ๋ณด๊ฐ Aggregation ๋์ด ์ ๊ณต๋๋ค
apps/commerce-api/src/main/java/com/loopers/application/ranking/RankingService.java(98-170์ค)์ํ ์์ธ ์กฐํ ์ ํด๋น ์ํ์ ์์๊ฐ ํจ๊ป ๋ฐํ๋๋ค (์์์ ์๋ค๋ฉด null)
RankingService.getProductRank(): ํน์ ์ํ์ ์์ ์กฐํapps/commerce-api/src/main/java/com/loopers/application/catalog/CatalogFacade.java(๋ญํน ์ ๋ณด ํฌํจ)-->
๐ References
Summary by CodeRabbit
๋ฆด๋ฆฌ์ค ๋ ธํธ
โ๏ธ Tip: You can customize this high-level summary in your review settings.