Skip to content

Commit 360a1ac

Browse files
authored
Merge pull request #219 from yeonjiyeon/feature/week9
[volume-9] Product Ranking with Redis
2 parents 4dbd4b0 + 5fe3646 commit 360a1ac

File tree

20 files changed

+358
-55
lines changed

20 files changed

+358
-55
lines changed

apps/commerce-api/src/main/java/com/loopers/application/order/OrderFacade.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ public OrderInfo placeOrder(PlaceOrder command) {
6767
order.getId(),
6868
user,
6969
orderItems,
70+
products,
7071
finalPaymentAmount,
7172
command.paymentType(),
7273
command.cardType(),

apps/commerce-api/src/main/java/com/loopers/application/order/event/OrderCreatedEvent.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22

33
import com.loopers.domain.order.OrderItem;
44
import com.loopers.domain.payment.PaymentType;
5+
import com.loopers.domain.product.Product;
56
import com.loopers.domain.user.User;
67
import java.util.List;
8+
import java.util.Map;
79
import java.util.UUID;
10+
import java.util.function.Function;
11+
import java.util.stream.Collectors;
812

913
public record OrderCreatedEvent(
1014
String eventId,
@@ -18,24 +22,35 @@ public record OrderCreatedEvent(
1822
Long couponId
1923
) {
2024

21-
public record OrderItemInfo(Long productId, int quantity) {
25+
public record OrderItemInfo(Long productId, int quantity, long price, int remainStock) {
2226

2327
}
2428

2529
public static OrderCreatedEvent of(
2630
Long orderId,
2731
User user,
2832
List<OrderItem> orderItems,
33+
List<Product> products,
2934
long finalAmount,
3035
PaymentType paymentType,
3136
String cardType,
3237
String cardNo,
3338
Long couponId
3439
) {
3540

41+
Map<Long, Product> productMap = products.stream()
42+
.collect(Collectors.toMap(Product::getId, p -> p));
43+
3644
List<OrderItemInfo> itemInfos = orderItems.stream()
37-
.map(item -> new OrderItemInfo(item.getProductId(), item.getQuantity()))
38-
.toList();
45+
.map(item -> {
46+
Product product = productMap.get(item.getProductId());
47+
return new OrderItemInfo(
48+
item.getProductId(),
49+
item.getQuantity(),
50+
product != null ? product.getPrice().getValue() : 0,
51+
product != null ? product.getStock() : 0
52+
);
53+
}).toList();
3954

4055
return new OrderCreatedEvent(
4156
UUID.randomUUID().toString(),

apps/commerce-api/src/main/java/com/loopers/application/order/event/OrderEventOutboxHandler.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.loopers.application.order.event;
22

33
import com.loopers.domain.event.OutboxService;
4+
import com.loopers.event.ProductStockEvent;
45
import lombok.RequiredArgsConstructor;
56
import org.springframework.kafka.core.KafkaTemplate;
67
import org.springframework.stereotype.Component;
@@ -14,8 +15,8 @@ public class OrderEventOutboxHandler {
1415
private final OutboxService outboxService;
1516

1617
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
17-
public void handle(OrderCreatedEvent event) {
18-
kafkaTemplate.send("order-events", String.valueOf(event.orderId()), event)
18+
public void handle(ProductStockEvent event) {
19+
kafkaTemplate.send("catalog-events", String.valueOf(event.productId()), event)
1920
.whenComplete((result, ex) -> {
2021
if (ex == null) {
2122
outboxService.markPublished(event.eventId());

apps/commerce-api/src/main/java/com/loopers/application/order/event/OrderSalesAggregateListener.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.loopers.application.order.event;
22

33
import com.loopers.domain.event.OutboxService;
4-
import com.loopers.domain.product.ProductService;
54
import com.loopers.event.ProductStockEvent;
65
import lombok.RequiredArgsConstructor;
76
import org.springframework.context.ApplicationEventPublisher;
@@ -14,7 +13,6 @@
1413
@RequiredArgsConstructor
1514
public class OrderSalesAggregateListener {
1615

17-
private final ProductService productService;
1816
private final OutboxService outboxService;
1917
private final ApplicationEventPublisher eventPublisher;
2018

@@ -24,11 +22,11 @@ public void handleOrderCreated(OrderCreatedEvent event) {
2422

2523
event.items().forEach(item -> {
2624

27-
int currentStock = productService.getStock(item.productId());
2825
ProductStockEvent kafkaEvent = ProductStockEvent.of(
2926
item.productId(),
3027
item.quantity(),
31-
currentStock
28+
item.remainStock(),
29+
item.price()
3230
);
3331

3432
outboxService.saveEvent("STOCKS_METRICS", String.valueOf(item.productId()), kafkaEvent);

apps/commerce-api/src/main/java/com/loopers/application/product/ProductFacade.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.loopers.domain.brand.BrandService;
44
import com.loopers.domain.product.Product;
55
import com.loopers.domain.product.ProductService;
6+
import com.loopers.infrastructure.rank.RankingService;
67
import com.loopers.event.ProductViewEvent;
78
import lombok.RequiredArgsConstructor;
89
import org.springframework.context.ApplicationEventPublisher;
@@ -16,14 +17,15 @@ public class ProductFacade {
1617

1718
private final ProductService productService;
1819
private final BrandService brandService;
20+
private final RankingService rankingService;
1921
private final ApplicationEventPublisher eventPublisher;
2022

2123
public Page<ProductInfo> getProductsInfo(Pageable pageable) {
2224
Page<Product> products = productService.getProducts(pageable);
2325
return products.map(product -> {
2426
String brandName = brandService.getBrand(product.getBrandId())
2527
.getName();
26-
return ProductInfo.from(product, brandName);
28+
return ProductInfo.from(product, brandName, null);
2729
});
2830
}
2931

@@ -32,9 +34,11 @@ public ProductInfo getProductInfo(long id) {
3234
String brandName = brandService.getBrand(product.getBrandId())
3335
.getName();
3436

37+
Integer currentRank = rankingService.getProductRank(id);
38+
3539
eventPublisher.publishEvent(ProductViewEvent.from(id));
3640

37-
return ProductInfo.from(product, brandName);
41+
return ProductInfo.from(product, brandName, currentRank);
3842
}
3943

4044
}

apps/commerce-api/src/main/java/com/loopers/application/product/ProductInfo.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,28 @@ public record ProductInfo(
88
String name,
99
Money price,
1010
String brandName,
11-
int likeCount
11+
int likeCount,
12+
Integer currentRank
1213
) {
1314
public static ProductInfo from(Product product) {
1415
return new ProductInfo(
1516
product.getId(),
1617
product.getName(),
1718
product.getPrice(),
1819
null,
19-
product.getLikeCount()
20+
product.getLikeCount(),
21+
null
2022
);
2123
}
2224

23-
public static ProductInfo from(Product product, String brandName) {
25+
public static ProductInfo from(Product product, String brandName, Integer currentRank) {
2426
return new ProductInfo(
2527
product.getId(),
2628
product.getName(),
2729
product.getPrice(),
2830
brandName,
29-
product.getLikeCount()
31+
product.getLikeCount(),
32+
currentRank
3033
);
3134
}
3235
}

apps/commerce-api/src/main/java/com/loopers/application/product/event/LikeCountAggregateListener.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
import com.loopers.application.event.FailedEventStore;
44
import com.loopers.application.like.event.LikeCreatedEvent;
5-
import com.loopers.event.LikeCountEvent;
65
import com.loopers.domain.product.ProductService;
6+
import com.loopers.event.LikeCountEvent;
77
import lombok.RequiredArgsConstructor;
88
import org.springframework.context.ApplicationEventPublisher;
99
import org.springframework.orm.ObjectOptimisticLockingFailureException;
@@ -30,7 +30,7 @@ public void handleLikeCreatedEvent(LikeCreatedEvent event) {
3030
try {
3131
int updatedLikeCount = performAggregation(event);
3232

33-
eventPublisher.publishEvent(new LikeCountEvent(
33+
eventPublisher.publishEvent(LikeCountEvent.of(
3434
event.eventId(),
3535
event.productId(),
3636
updatedLikeCount
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.loopers.application.rank;
2+
3+
import com.loopers.domain.product.Product;
4+
import com.loopers.domain.product.ProductService;
5+
import com.loopers.infrastructure.rank.RankingService;
6+
import java.util.List;
7+
import java.util.Map;
8+
import java.util.stream.Collectors;
9+
import java.util.stream.IntStream;
10+
import lombok.RequiredArgsConstructor;
11+
import org.springframework.stereotype.Component;
12+
13+
@RequiredArgsConstructor
14+
@Component
15+
public class RankingFacade {
16+
17+
private final RankingService rankingService;
18+
private final ProductService productService;
19+
20+
public List<RankingInfo> getTopRankings(String date, int page, int size) {
21+
List<Long> productIds = rankingService.getTopRankingIds(date, page, size);
22+
23+
if (productIds.isEmpty()) {
24+
return List.of();
25+
}
26+
27+
List<Product> products = productService.getProducts(productIds);
28+
Map<Long, Product> productMap = products.stream()
29+
.collect(Collectors.toMap(Product::getId, p -> p));
30+
31+
return IntStream.range(0, productIds.size())
32+
.mapToObj(i -> {
33+
Long productId = productIds.get(i);
34+
Product product = productMap.get(productId);
35+
36+
int currentRank = ((page - 1) * size) + i + 1;
37+
38+
return RankingInfo.of(product, currentRank);
39+
})
40+
.toList();
41+
}
42+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.loopers.application.rank;
2+
3+
import com.loopers.domain.product.Product;
4+
5+
public record RankingInfo(
6+
Long productId,
7+
String productName,
8+
Long price,
9+
int stock,
10+
int currentRank
11+
) {
12+
13+
public static RankingInfo of(Product product, int currentRank) {
14+
return new RankingInfo(
15+
product.getId(),
16+
product.getName(),
17+
product.getPrice().getValue(),
18+
product.getStock(),
19+
currentRank
20+
);
21+
}
22+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.loopers.application.rank;
2+
3+
import com.loopers.infrastructure.rank.RankingService;
4+
import java.time.LocalDateTime;
5+
import java.time.format.DateTimeFormatter;
6+
import lombok.RequiredArgsConstructor;
7+
import lombok.extern.slf4j.Slf4j;
8+
import org.springframework.scheduling.annotation.Scheduled;
9+
import org.springframework.stereotype.Component;
10+
11+
@Slf4j
12+
@Component
13+
@RequiredArgsConstructor
14+
public class RankingScheduler {
15+
16+
private final RankingService rankingService;
17+
18+
@Scheduled(cron = "0 50 23 * * *")
19+
public void scheduleRankingCarryOver() {
20+
LocalDateTime now = LocalDateTime.now();
21+
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
22+
23+
String today = now.format(formatter);
24+
String tomorrow = now.plusDays(1).format(formatter);
25+
26+
log.info("Starting Ranking Carry-Over: {} -> {}", today, tomorrow);
27+
28+
try {
29+
rankingService.carryOverRanking(today, tomorrow, 0.1);
30+
log.info("Ranking Carry-Over completed successfully.");
31+
} catch (Exception e) {
32+
log.error("Ranking Carry-Over failed", e);
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)