-
Notifications
You must be signed in to change notification settings - Fork 34
[volume-9] Product Ranking with Redis #218
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: lim-jaein
Are you sure you want to change the base?
Changes from all commits
4e675ae
a9095fc
7c78bd0
2c29f5f
730f8f9
4719d09
7576cf7
6996b64
c9d8ea3
bb79cb0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,19 +1,26 @@ | ||
| package com.loopers.application.payment; | ||
|
|
||
| import com.loopers.application.payment.event.PaymentFailedEvent; | ||
| import com.loopers.application.payment.event.PaymentSucceededEvent; | ||
| import com.loopers.domain.order.Order; | ||
| import com.loopers.domain.order.OrderService; | ||
| import com.loopers.messaging.event.OrderPaidEvent; | ||
| import com.loopers.messaging.event.OrderPaidEvent.OrderItemData; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.context.ApplicationEventPublisher; | ||
| import org.springframework.stereotype.Component; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import java.util.List; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| @Slf4j | ||
| @Component | ||
| @RequiredArgsConstructor | ||
| public class PaymentProcessService { | ||
|
|
||
| private final PaymentFacade paymentFacade; | ||
| private final OrderService orderService; // Added OrderService injection | ||
|
|
||
| private final ApplicationEventPublisher eventPublisher; | ||
|
|
||
|
|
@@ -28,12 +35,19 @@ public void process(Long userId, Long orderId) { | |
| } | ||
|
|
||
| @Transactional | ||
| public void processPg(Long userId, Long orderId) { | ||
| public void processPg(Long userId, Long orderId) { // Added userId parameter | ||
| try { | ||
| paymentFacade.payPg(orderId); | ||
| eventPublisher.publishEvent(PaymentSucceededEvent.from(orderId)); | ||
|
|
||
| Order order = orderService.findOrderById(orderId) | ||
| .orElseThrow(() -> new IllegalStateException("๊ฒฐ์ ๋ ์ฃผ๋ฌธ ์ ๋ณด๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค. orderId: " + orderId)); | ||
|
|
||
| List<OrderItemData> orderItemDataList = order.getItems().stream() | ||
| .map(item -> new OrderItemData(item.getProductId(), item.getQuantity(), item.getUnitPrice().getAmount())) | ||
| .collect(Collectors.toList()); | ||
|
|
||
| eventPublisher.publishEvent(OrderPaidEvent.of(orderId, orderItemDataList)); | ||
|
Comment on lines
+42
to
+49
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ์ค์: OrderItemData ์์ฑ ์ totalPrice๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค. Line 46์์ ์ด ๋ถ์ผ์น๋ก ์ธํด:
๐ ์์ ์ ์ List<OrderItemData> orderItemDataList = order.getItems().stream()
- .map(item -> new OrderItemData(item.getProductId(), item.getQuantity(), item.getUnitPrice().getAmount()))
+ .map(item -> new OrderItemData(item.getProductId(), item.getQuantity(), item.getTotalPrice().getAmount()))
.collect(Collectors.toList());๐ค Prompt for AI Agents |
||
| } catch (Exception e) { | ||
| // ์ด์ธ ์๋ฒ ํ์์์ ๋ฑ์ retry -> pending์ํ๋ก ์ค์ผ์ค๋ง ์๋ | ||
| log.error("์ธ๋ถ PG ๊ฒฐ์ ์คํจ, ์ฃผ๋ฌธ ID: {}", orderId, e); | ||
| eventPublisher.publishEvent(PaymentFailedEvent.of(userId, orderId, e)); | ||
| } | ||
|
|
||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| package com.loopers.application.product.event; | ||
|
|
||
| import com.loopers.domain.common.event.DomainEvent; | ||
| import com.loopers.domain.common.event.DomainEventEnvelop; | ||
| import com.loopers.domain.common.event.DomainEventRepository; | ||
| import com.loopers.messaging.event.ProductViewedEvent; | ||
| import com.loopers.support.json.JsonConverter; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.stereotype.Component; | ||
| import org.springframework.transaction.event.TransactionPhase; | ||
| import org.springframework.transaction.event.TransactionalEventListener; | ||
|
|
||
| @Slf4j | ||
| @Component | ||
| @RequiredArgsConstructor | ||
| public class ProductViewedEventHandler { | ||
|
|
||
| private final DomainEventRepository eventRepository; | ||
| private final JsonConverter jsonConverter; | ||
|
|
||
| @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) | ||
| public void handleProductViewedEvent(ProductViewedEvent event) { | ||
| log.info("ProductViewedEvent received: productId={}, occurredAt={}", event.productId(), event.occurredAt()); | ||
|
|
||
| DomainEventEnvelop<ProductViewedEvent> envelop = | ||
| DomainEventEnvelop.of( | ||
| "PRODUCT_VIEWED", | ||
| "PRODUCT", | ||
| event.productId(), | ||
| event | ||
| ); | ||
|
|
||
| eventRepository.save( | ||
| DomainEvent.pending( | ||
| "catalog-events", | ||
| envelop, | ||
| jsonConverter.serialize(envelop) | ||
| ) | ||
| ); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| package com.loopers.application.ranking; | ||
|
|
||
| import com.loopers.domain.product.Product; | ||
| import com.loopers.domain.product.ProductLikeCountService; | ||
| import com.loopers.domain.product.ProductService; | ||
| import com.loopers.domain.ranking.RankingService; | ||
| import com.loopers.interfaces.api.ranking.RankingV1Dto.RankingProductResponse; | ||
| import com.loopers.ranking.streamer.RankingInfo; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.data.domain.Page; | ||
| import org.springframework.data.domain.PageImpl; | ||
| import org.springframework.data.domain.Pageable; | ||
| import org.springframework.stereotype.Service; | ||
|
|
||
| import java.time.LocalDate; | ||
| import java.time.LocalDateTime; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Objects; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| @Slf4j | ||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class RankingFacade { | ||
| private final RankingService rankingService; | ||
| private final ProductService productService; | ||
| private final ProductLikeCountService productLikeCountService; | ||
|
|
||
| public Page<RankingProductResponse> getRankings(LocalDate rankingDate, Pageable pageable) { | ||
|
|
||
| // 1. Redis ZSET์์ ๋ญํน ์ํ ID์ ์ ์๋ฅผ ์กฐํ | ||
| List<RankingInfo> rankedProducts = rankingService.getRankings(rankingDate, pageable); | ||
| long totalCount = rankingService.getTotalCount(rankingDate); | ||
|
|
||
| // 2. ๋ญํน ์ํ ID๋ค์ ์ถ์ถ | ||
| List<Long> productIds = rankedProducts.stream() | ||
| .map(RankingInfo::id) | ||
| .toList(); | ||
|
|
||
| // 3. ProductService ํตํด ์ํ ์์ธ ์ ๋ณด ์กฐํ (์ํ ID ์์ ์ ์ง๋ฅผ ์ํด Map ์ฌ์ฉ) | ||
| Map<Long, Product> productInfos = productService.getProductsMapByIds(productIds); | ||
|
|
||
| // 4. ์กฐํ๋ ์ํ ์ ๋ณด์ ๋ญํน ๋ฐ์ดํฐ๋ฅผ ๊ฒฐํฉํ์ฌ ์๋ต DTO ์์ฑ | ||
| List<RankingProductResponse> rankingResponses = rankedProducts.stream() | ||
| .map(ranking -> { | ||
| Product product = productInfos.get(ranking.id()); | ||
|
|
||
| if (product == null) { | ||
| log.warn("์ํ ์ ๋ณด๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค. ์ํID : {}", ranking.id()); | ||
| return null; | ||
| } | ||
|
|
||
| // ์ข์์ ์๋ฅผ ProductLikeCountService์์ ์กฐํ (์์ผ๋ฉด 0) | ||
| int likeCount = productLikeCountService.findById(product.getId()) | ||
| .map(pc -> pc.getLikeCount()) | ||
| .orElse(0); | ||
|
|
||
| return new RankingProductResponse( | ||
| product.getId(), | ||
| product.getName(), | ||
| product.getBrandId(), | ||
| product.getPrice().getAmount(), | ||
| likeCount, | ||
| ranking.score(), | ||
| ranking.rank(), | ||
| LocalDateTime.now() // createdAt? | ||
| ); | ||
| }) | ||
| .filter(Objects::nonNull) | ||
| .collect(Collectors.toList()); | ||
|
|
||
| Page<RankingProductResponse> responsePage = new PageImpl<>( | ||
| rankingResponses, | ||
| pageable, | ||
| totalCount | ||
| ); | ||
|
|
||
| return responsePage; | ||
| }; | ||
| } |
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.
๐งฉ Analysis chain
๐ Script executed:
Repository: Loopers-dev-lab/loopers-spring-java-template
Length of output: 876
๐ Script executed:
Repository: Loopers-dev-lab/loopers-spring-java-template
Length of output: 235
๐ Script executed:
Repository: Loopers-dev-lab/loopers-spring-java-template
Length of output: 597
๐ Script executed:
Repository: Loopers-dev-lab/loopers-spring-java-template
Length of output: 70
๐ Script executed:
Repository: Loopers-dev-lab/loopers-spring-java-template
Length of output: 6069
๐ Script executed:
# Read the OrderItem class to understand its structure cat -n apps/commerce-api/src/main/java/com/loopers/domain/order/OrderItem.javaRepository: Loopers-dev-lab/loopers-spring-java-template
Length of output: 2326
๐ Script executed:
Repository: Loopers-dev-lab/loopers-spring-java-template
Length of output: 1770
getTotalPrice()๋์getUnitPrice()๋ฅผ ์ฌ์ฉํ์ธ์.OrderItemData์unitPriceํ๋๋ ์ํ ๋จ๊ฐ๋ฅผ ์๋ํ๊ณ ์์ผ๋, ํ์ฌ ์ฝ๋๋item.getTotalPrice().getAmount()๋ฅผ ์ ๋ฌํ๊ณ ์์ต๋๋ค.OrderItem์totalPrice๋ ์๋ ร ๋จ๊ฐ์ด๋ฏ๋ก, ๋ค์ด์คํธ๋ฆผ ์ปจ์๋จธ์์ ์๋ชป๋ ๊ณ์ฐ์ด ๋ฐ์ํ ์ ์์ต๋๋ค.๊ฐ์ ๋ก์ง์ด
PaymentProcessService์์๋item.getUnitPrice().getAmount()๋ก ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํ๋์ด ์์ต๋๋ค. ์ผ๊ด์ฑ์ ์ํด 62๋ฒ ์ค์item.getUnitPrice().getAmount()๋ก ๋ณ๊ฒฝํ์ธ์.๐ค Prompt for AI Agents