Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.loopers.application.brand.BrandService;
import com.loopers.application.product.ProductCacheService;
import com.loopers.application.product.ProductService;
import com.loopers.application.ranking.RankingService;
import com.loopers.domain.brand.Brand;
import com.loopers.domain.product.Product;
import com.loopers.domain.product.ProductDetail;
Expand All @@ -14,6 +15,7 @@
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
Expand All @@ -34,6 +36,7 @@ public class CatalogFacade {
private final ProductService productService;
private final ProductCacheService productCacheService;
private final ProductEventPublisher productEventPublisher;
private final RankingService rankingService;

/**
* ์ƒํ’ˆ ๋ชฉ๋ก์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
Expand Down Expand Up @@ -90,7 +93,7 @@ public ProductInfoList getProducts(Long brandId, String sort, int page, int size
}
// โœ… Product.likeCount ํ•„๋“œ ์‚ฌ์šฉ (๋น„๋™๊ธฐ ์ง‘๊ณ„๋œ ๊ฐ’)
ProductDetail productDetail = ProductDetail.from(product, brand.getName(), product.getLikeCount());
return new ProductInfo(productDetail);
return ProductInfo.withoutRank(productDetail);
})
.toList();

Expand All @@ -108,10 +111,11 @@ public ProductInfoList getProducts(Long brandId, String sort, int page, int size
* <p>
* Redis ์บ์‹œ๋ฅผ ๋จผ์ € ํ™•์ธํ•˜๊ณ , ์บ์‹œ์— ์—†์œผ๋ฉด DB์—์„œ ์กฐํšŒํ•œ ํ›„ ์บ์‹œ์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
* ์ƒํ’ˆ ์กฐํšŒ ์‹œ ProductViewed ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•˜์—ฌ ๋ฉ”ํŠธ๋ฆญ ์ง‘๊ณ„์— ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
* ๋žญํ‚น ์ •๋ณด๋„ ํ•จ๊ป˜ ์กฐํšŒํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
* </p>
*
* @param productId ์ƒํ’ˆ ID
* @return ์ƒํ’ˆ ์ •๋ณด์™€ ์ข‹์•„์š” ์ˆ˜
* @return ์ƒํ’ˆ ์ •๋ณด์™€ ์ข‹์•„์š” ์ˆ˜, ๋žญํ‚น ์ˆœ์œ„
* @throws CoreException ์ƒํ’ˆ์„ ์ฐพ์„ ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ
*/
@Transactional(readOnly = true)
Expand All @@ -121,7 +125,11 @@ public ProductInfo getProduct(Long productId) {
if (cachedResult != null) {
// ์บ์‹œ ํžˆํŠธ ์‹œ์—๋„ ์กฐํšŒ ์ˆ˜ ์ง‘๊ณ„๋ฅผ ์œ„ํ•ด ์ด๋ฒคํŠธ ๋ฐœํ–‰
productEventPublisher.publish(ProductEvent.ProductViewed.from(productId));
return cachedResult;

// ๋žญํ‚น ์ •๋ณด ์กฐํšŒ (์บ์‹œ๋œ ๊ฒฐ๊ณผ์— ๋žญํ‚น ์ •๋ณด ์ถ”๊ฐ€)
LocalDate today = LocalDate.now();
Long rank = rankingService.getProductRank(productId, today);
return ProductInfo.withRank(cachedResult.productDetail(), rank);
}

// ์บ์‹œ์— ์—†์œผ๋ฉด DB์—์„œ ์กฐํšŒ
Expand All @@ -136,16 +144,19 @@ public ProductInfo getProduct(Long productId) {
// ProductDetail ์ƒ์„ฑ (Aggregate ๊ฒฝ๊ณ„ ์ค€์ˆ˜: Brand ์—”ํ‹ฐํ‹ฐ ๋Œ€์‹  brandName๋งŒ ์ „๋‹ฌ)
ProductDetail productDetail = ProductDetail.from(product, brand.getName(), likesCount);

ProductInfo result = new ProductInfo(productDetail);
// ๋žญํ‚น ์ •๋ณด ์กฐํšŒ
LocalDate today = LocalDate.now();
Long rank = rankingService.getProductRank(productId, today);

// ์บ์‹œ์— ์ €์žฅ
productCacheService.cacheProduct(productId, result);
// ์บ์‹œ์— ์ €์žฅ (๋žญํ‚น ์ •๋ณด๋Š” ์ œ์™ธํ•˜๊ณ  ์ €์žฅ - ๋žญํ‚น์€ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์กฐํšŒ)
productCacheService.cacheProduct(productId, ProductInfo.withoutRank(productDetail));

// โœ… ์ƒํ’ˆ ์กฐํšŒ ์ด๋ฒคํŠธ ๋ฐœํ–‰ (๋ฉ”ํŠธ๋ฆญ ์ง‘๊ณ„์šฉ)
productEventPublisher.publish(ProductEvent.ProductViewed.from(productId));

// ๋กœ์ปฌ ์บ์‹œ์˜ ์ข‹์•„์š” ์ˆ˜ ๋ธํƒ€ ์ ์šฉ (DB ์กฐํšŒ ๊ฒฐ๊ณผ์—๋„ ๋ธํƒ€ ๋ฐ˜์˜)
return productCacheService.applyLikeCountDelta(result);
ProductInfo deltaApplied = productCacheService.applyLikeCountDelta(ProductInfo.withoutRank(productDetail));
return ProductInfo.withRank(deltaApplied.productDetail(), rank);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,28 @@
* ์ƒํ’ˆ ์ƒ์„ธ ์ •๋ณด๋ฅผ ๋‹ด๋Š” ๋ ˆ์ฝ”๋“œ.
*
* @param productDetail ์ƒํ’ˆ ์ƒ์„ธ ์ •๋ณด (Product + Brand + ์ข‹์•„์š” ์ˆ˜)
* @param rank ๋žญํ‚น ์ˆœ์œ„ (1๋ถ€ํ„ฐ ์‹œ์ž‘, ๋žญํ‚น์— ์—†์œผ๋ฉด null)
*/
public record ProductInfo(ProductDetail productDetail) {
public record ProductInfo(ProductDetail productDetail, Long rank) {
/**
* ๋žญํ‚น ์ •๋ณด ์—†์ด ProductInfo๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
*
* @param productDetail ์ƒํ’ˆ ์ƒ์„ธ ์ •๋ณด
* @return ProductInfo (rank๋Š” null)
*/
public static ProductInfo withoutRank(ProductDetail productDetail) {
return new ProductInfo(productDetail, null);
}

/**
* ๋žญํ‚น ์ •๋ณด์™€ ํ•จ๊ป˜ ProductInfo๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
*
* @param productDetail ์ƒํ’ˆ ์ƒ์„ธ ์ •๋ณด
* @param rank ๋žญํ‚น ์ˆœ์œ„ (1๋ถ€ํ„ฐ ์‹œ์ž‘, ๋žญํ‚น์— ์—†์œผ๋ฉด null)
* @return ProductInfo
*/
public static ProductInfo withRank(ProductDetail productDetail, Long rank) {
return new ProductInfo(productDetail, rank);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ public ProductInfo applyLikeCountDelta(ProductInfo productInfo) {
updatedLikesCount
);

return new ProductInfo(updatedDetail);
return ProductInfo.withoutRank(updatedDetail);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.loopers.application.ranking;

import org.springframework.stereotype.Component;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
* ๋žญํ‚น ํ‚ค ์ƒ์„ฑ ์œ ํ‹ธ๋ฆฌํ‹ฐ.
* <p>
* Redis ZSET ๋žญํ‚น ํ‚ค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
* </p>
*
* @author Loopers
* @version 1.0
*/
@Component
public class RankingKeyGenerator {
private static final String DAILY_KEY_PREFIX = "ranking:all:";
private static final String HOURLY_KEY_PREFIX = "ranking:hourly:";
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHH");

/**
* ์ผ๊ฐ„ ๋žญํ‚น ํ‚ค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
* <p>
* ์˜ˆ: ranking:all:20241215
* </p>
*
* @param date ๋‚ ์งœ
* @return ์ผ๊ฐ„ ๋žญํ‚น ํ‚ค
*/
public String generateDailyKey(LocalDate date) {
String dateStr = date.format(DATE_FORMATTER);
return DAILY_KEY_PREFIX + dateStr;
}

/**
* ์‹œ๊ฐ„ ๋‹จ์œ„ ๋žญํ‚น ํ‚ค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
* <p>
* ์˜ˆ: ranking:hourly:2024121514
* </p>
*
* @param dateTime ๋‚ ์งœ ๋ฐ ์‹œ๊ฐ„
* @return ์‹œ๊ฐ„ ๋‹จ์œ„ ๋žญํ‚น ํ‚ค
*/
public String generateHourlyKey(LocalDateTime dateTime) {
String dateTimeStr = dateTime.format(DATE_TIME_FORMATTER);
return HOURLY_KEY_PREFIX + dateTimeStr;
}
}
Loading