Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
@@ -1,12 +1,12 @@
package com.loopers.application.like;

import com.loopers.application.like.event.LikeCanceledEvent;
import com.loopers.application.like.event.LikeCreatedEvent;
import com.loopers.domain.like.Like;
import com.loopers.domain.like.LikeService;
import com.loopers.domain.product.Product;
import com.loopers.domain.product.ProductLikeCountService;
import com.loopers.domain.product.ProductService;
import com.loopers.messaging.event.LikeCanceledEvent;
import com.loopers.messaging.event.LikeCreatedEvent;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import com.loopers.domain.common.event.DomainEventEnvelop;
import com.loopers.domain.common.event.DomainEventRepository;
import com.loopers.domain.product.ProductLikeCountService;
import com.loopers.messaging.event.LikeCanceledEvent;
import com.loopers.messaging.event.LikeCreatedEvent;
import com.loopers.support.json.JsonConverter;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Async;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
import com.loopers.application.order.OrderExternalSystemSender;
import com.loopers.application.order.OrderFacade;
import com.loopers.application.payment.event.PaymentFailedEvent;
import com.loopers.application.payment.event.PaymentSucceededEvent;
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.OrderPaidEvent;
import com.loopers.support.json.JsonConverter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -31,8 +31,8 @@ public class OrderEventHandler {

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void handle(PaymentSucceededEvent event) {
log.info("๐Ÿ”ฅ PaymentSucceededEvent handler ์ง„์ž…");
public void handle(OrderPaidEvent event) {
log.info("๐Ÿ”ฅ OrderPaidEvent handler ์ง„์ž…");
orderFacade.handleOrderSucceed(event.orderId());
}

Expand All @@ -48,7 +48,7 @@ public void handle(PaymentFailedEvent event) {
*/
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Async
void handleOrderCreatedExternalSend(PaymentSucceededEvent event) {
void handleOrderCreatedExternalSend(OrderPaidEvent event) {
try {
orderExternalSystemSender.send(event.orderId());
} catch (Exception e) {
Expand All @@ -61,9 +61,9 @@ void handleOrderCreatedExternalSend(PaymentSucceededEvent event) {
* ๊ฒฐ์ œ ์„ฑ๊ณต ์‹œ, outbox ํ…Œ์ด๋ธ”์— ์ด๋ฒคํŠธ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
*/
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void handleOutboxEvent(PaymentSucceededEvent event) {
public void handleOutboxEvent(OrderPaidEvent event) {

DomainEventEnvelop<PaymentSucceededEvent> envelop =
DomainEventEnvelop<OrderPaidEvent> envelop =
DomainEventEnvelop.of(
"ORDER_PAID",
"ORDER",
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package com.loopers.application.payment;

import com.loopers.application.order.event.OrderCreated;
import com.loopers.application.payment.event.PaymentSucceededEvent;
import com.loopers.domain.order.Order;
import com.loopers.domain.order.OrderService;
import com.loopers.domain.payment.Payment;
import com.loopers.domain.payment.PaymentService;
import com.loopers.domain.point.PointService;
import com.loopers.infrastructure.ResilientPgClient;
import com.loopers.interfaces.api.payment.PaymentV1Dto;
import com.loopers.messaging.event.OrderPaidEvent;
import com.loopers.support.error.CoreException;
import com.loopers.support.error.ErrorType;
import lombok.RequiredArgsConstructor;
Expand All @@ -17,6 +17,8 @@
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Slf4j
@Component
@RequiredArgsConstructor
Expand Down Expand Up @@ -51,7 +53,16 @@ public void pay(Long userId, Long orderId) {
case POINT -> {
try {
pointService.usePoint(payment.getUserId(), payment.getAmount());
eventPublisher.publishEvent(PaymentSucceededEvent.from(orderId));

List<OrderPaidEvent.OrderItemData> items =
order.getItems().stream()
.map(item -> new OrderPaidEvent.OrderItemData(
item.getProductId(),
item.getQuantity(),
item.getTotalPrice().getAmount()
)).toList();
Comment on lines +57 to +63
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

โš ๏ธ Potential issue | ๐ŸŸ  Major

๐Ÿงฉ Analysis chain

๐Ÿ Script executed:

# Find OrderItem class and price-related methods
find . -name "*.java" -path "*/domain/order/*" | head -20

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 876


๐Ÿ Script executed:

# Search for OrderItem class definition
rg -l "class OrderItem" --type=java

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 235


๐Ÿ Script executed:

# Look for getTotalPrice and getUnitPrice methods in order-related classes
rg -n "getTotalPrice|getUnitPrice|getPrice\(" --type=java apps/commerce-api/src/main/java/com/loopers/domain/order/ -A 3 -B 1

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 597


๐Ÿ Script executed:

# Search for OrderItemData class definition
rg -n "class OrderItemData" --type=java -A 10

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 70


๐Ÿ Script executed:

# Search for OrderItemData anywhere in the codebase
rg -n "OrderItemData" --type=java -B 2 -A 5

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.java

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 2326


๐Ÿ Script executed:

# Search for all references to unitPrice or totalPrice in OrderItem-related files
rg -n "unitPrice|totalPrice" --type=java apps/commerce-api/src/main/java/com/loopers/domain/order/

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
In
apps/commerce-api/src/main/java/com/loopers/application/payment/PaymentFacade.java
around lines 57 to 63, the code passes item.getTotalPrice().getAmount() into
OrderItemData.unitPrice which should be the per-item unit price; replace that
call with item.getUnitPrice().getAmount() so the unitPrice field receives the
single-item price (matching PaymentProcessService) and avoid downstream
miscalculations.


eventPublisher.publishEvent(OrderPaidEvent.of(orderId, items));
} catch (IllegalArgumentException e) {
throw new CoreException(ErrorType.BAD_REQUEST, e.getMessage());
}
Expand Down
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;

Expand All @@ -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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

โš ๏ธ Potential issue | ๐Ÿ”ด Critical

์ค‘์š”: OrderItemData ์ƒ์„ฑ ์‹œ totalPrice๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Line 46์—์„œ item.getUnitPrice().getAmount()๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์ง€๋งŒ, PaymentFacade.java(line 44)์—์„œ๋Š” ๋™์ผํ•œ ๋ชฉ์ ์œผ๋กœ item.getTotalPrice().getAmount()๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. OrderItemData์˜ ์„ธ ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ํ•ด๋‹น ์ฃผ๋ฌธ ํ•ญ๋ชฉ์˜ ์ด ๊ธˆ์•ก(์ˆ˜๋Ÿ‰ ร— ๋‹จ๊ฐ€)์„ ๋‚˜ํƒ€๋‚ด์•ผ ํ•˜๋ฏ€๋กœ, unitPrice ๋Œ€์‹  totalPrice๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ด ๋ถˆ์ผ์น˜๋กœ ์ธํ•ด:

  • ํŒ๋งค ๋ฐ์ดํ„ฐ๊ฐ€ ์‹ค์ œ๋ณด๋‹ค ์ ๊ฒŒ ์ง‘๊ณ„๋ฉ๋‹ˆ๋‹ค
  • POINT ๊ฒฐ์ œ(PaymentFacade)์™€ PG ๊ฒฐ์ œ(PaymentProcessService) ๊ฐ„ ๋ฐ์ดํ„ฐ ๋ถˆ์ผ์น˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค
  • ์ƒํ’ˆ ๋žญํ‚น ์ง‘๊ณ„์— ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค
๐Ÿ”Ž ์ˆ˜์ • ์ œ์•ˆ
             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
In
apps/commerce-api/src/main/java/com/loopers/application/payment/PaymentProcessService.java
around lines 42 to 49, OrderItemData is being constructed with
item.getUnitPrice().getAmount() but must use the item's total price; replace the
third constructor argument to use item.getTotalPrice().getAmount() so the
OrderItemData reflects quantity ร— unit price (matching PaymentFacade) and then
publish the event with the corrected list.

} catch (Exception e) {
// ์ด์™ธ ์„œ๋ฒ„ ํƒ€์ž„์•„์›ƒ ๋“ฑ์€ retry -> pending์ƒํƒœ๋กœ ์Šค์ผ€์ค„๋ง ์‹œ๋„
log.error("์™ธ๋ถ€ PG ๊ฒฐ์ œ ์‹คํŒจ, ์ฃผ๋ฌธ ID: {}", orderId, e);
eventPublisher.publishEvent(PaymentFailedEvent.of(userId, orderId, e));
}
Expand Down

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
Expand Up @@ -26,6 +26,9 @@ public class ProductMetrics {
@Column(nullable = false)
private long salesCount;

@Column(nullable = false)
private long salesAmount;

@Column(nullable = false)
private long viewCount;

Expand All @@ -39,6 +42,7 @@ public ProductMetrics(Long productId) {
this.productId = productId;
this.likeCount = 0;
this.salesCount = 0;
this.salesAmount = 0;
this.viewCount = 0;
this.createdAt = ZonedDateTime.now();
this.updatedAt = ZonedDateTime.now();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.loopers.domain.productmetrics;

import java.math.BigDecimal;
import java.util.Optional;

public interface ProductMetricsRepository {
Expand All @@ -10,7 +11,7 @@ public interface ProductMetricsRepository {

void upsertUnlikeCount(Long productId);

void upsertSalesCount(Long productId, int quantity);
void upsertSalesCount(Long productId, int quantity, BigDecimal amount);

void upsertViewCount(Long productId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Optional;

@Slf4j
@Service
@RequiredArgsConstructor
Expand All @@ -14,12 +17,16 @@ public class ProductMetricsService {
private final ProductMetricsJpaRepository productMetricsRepository;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐Ÿ› ๏ธ Refactor suggestion | ๐ŸŸ  Major

์ถ”์ƒํ™” ์œ„๋ฐ˜: ๊ตฌํ˜„์ฒด ์ง์ ‘ ์ฃผ์ž…

ProductMetricsRepository ์ธํ„ฐํŽ˜์ด์Šค ๋Œ€์‹  ProductMetricsJpaRepository ๊ตฌํ˜„์ฒด๋ฅผ ์ง์ ‘ ์ฃผ์ž…ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋„๋ฉ”์ธ ๋ ˆ์ด์–ด์—์„œ ์ธํ”„๋ผ ๋ ˆ์ด์–ด์— ์ง์ ‘ ์˜์กดํ•˜๋ฉด ํ…Œ์ŠคํŠธ ์šฉ์ด์„ฑ๊ณผ ์œ ์—ฐ์„ฑ์ด ์ €ํ•˜๋ฉ๋‹ˆ๋‹ค.

๐Ÿ”Ž ์ˆ˜์ • ์ œ์•ˆ
-    private final ProductMetricsJpaRepository productMetricsRepository;
+    private final ProductMetricsRepository productMetricsRepository;

import ๋ฌธ๋„ ํ•จ๊ป˜ ์ˆ˜์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:

-import com.loopers.infrastructure.productmetrics.ProductMetricsJpaRepository;
+import com.loopers.domain.productmetrics.ProductMetricsRepository;

Committable suggestion skipped: line range outside the PR's diff.

๐Ÿค– Prompt for AI Agents
In
apps/commerce-streamer/src/main/java/com/loopers/domain/productmetrics/ProductMetricsService.java
around line 17, the service is directly depending on the implementation
ProductMetricsJpaRepository which breaks abstraction; change the field and
constructor parameter type to the interface ProductMetricsRepository and update
the import to the interface package; ensure any casts or Jpa-specific calls are
removed or moved to the infrastructure layer so the service only uses the
repository interface methods, and update tests/beans to provide the Jpa
implementation where needed.


@Transactional
public void increaseSalesCount(Long productId, int quantity) {
public void increaseSalesCount(Long productId, int quantity, BigDecimal amount) {
if (quantity <= 0) {
log.warn("ํŒ๋งค ์ˆ˜๋Ÿ‰์ด 0 ์ดํ•˜์ผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ˆ˜๋Ÿ‰:{}, ์ƒํ’ˆID:{}", quantity, productId);
return;
}
productMetricsRepository.upsertSalesCount(productId, quantity);
if (amount.compareTo(BigDecimal.ZERO) < 0){
log.warn("ํŒ๋งค ๊ธˆ์•ก์ด 0 ์ดํ•˜์ผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๊ธˆ์•ก:{}, ์ƒํ’ˆID:{}", amount, productId);
return;
}
Comment on lines 26 to 29
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

โš ๏ธ Potential issue | ๐ŸŸก Minor

์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋กœ์ง๊ณผ ๋กœ๊ทธ ๋ฉ”์‹œ์ง€ ๋ถˆ์ผ์น˜

์กฐ๊ฑด์€ < 0 (์Œ์ˆ˜๋งŒ ๊ฑฐ๋ถ€)์ด์ง€๋งŒ, ๋กœ๊ทธ ๋ฉ”์‹œ์ง€๋Š” "0 ์ดํ•˜์ผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค"๋กœ ๋˜์–ด ์žˆ์–ด ๋ถˆ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ amount๊ฐ€ null์ผ ๊ฒฝ์šฐ NullPointerException์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ”Ž ์ˆ˜์ • ์ œ์•ˆ
-        if (amount.compareTo(BigDecimal.ZERO) < 0){
-            log.warn("ํŒ๋งค ๊ธˆ์•ก์ด 0 ์ดํ•˜์ผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๊ธˆ์•ก:{}, ์ƒํ’ˆID:{}", amount, productId);
+        if (amount == null || amount.compareTo(BigDecimal.ZERO) < 0) {
+            log.warn("ํŒ๋งค ๊ธˆ์•ก์ด 0 ๋ฏธ๋งŒ์ผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๊ธˆ์•ก:{}, ์ƒํ’ˆID:{}", amount, productId);
             return;
         }

๋˜๋Š” 0๋„ ๊ฑฐ๋ถ€ํ•ด์•ผ ํ•œ๋‹ค๋ฉด:

-        if (amount.compareTo(BigDecimal.ZERO) < 0){
+        if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
๐Ÿค– Prompt for AI Agents
In
apps/commerce-streamer/src/main/java/com/loopers/domain/productmetrics/ProductMetricsService.java
around lines 25 to 28, the validation condition currently checks
amount.compareTo(BigDecimal.ZERO) < 0 but the log states "0 ์ดํ•˜์ผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค" and a
null amount will throw NullPointerException; update the validation to first
guard against a null amount (log and return when amount == null), then either
change the condition to <= 0 if zero should also be rejected (keep the existing
log) or keep < 0 and change the log text to indicate "์Œ์ˆ˜์ผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค" so message
matches the check; implement one of these two fixes consistently.

productMetricsRepository.upsertSalesCount(productId, quantity, amount);
}

@Transactional
Expand All @@ -36,4 +43,8 @@ public void decreaseLikeCount(Long productId) {
public void increaseViewCount(Long productId) {
productMetricsRepository.upsertViewCount(productId);
}

public Optional<ProductMetrics> findByProductId(Long productId) {
return productMetricsRepository.findByProductId(productId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.math.BigDecimal;
import java.util.Optional;

public interface ProductMetricsJpaRepository extends JpaRepository<ProductMetrics, Long> {
Expand Down Expand Up @@ -38,15 +39,16 @@ INSERT INTO product_metrics (product_id, like_count, sales_count, view_count, cr

@Modifying
@Query(value = """
INSERT INTO product_metrics (product_id, like_count, sales_count, view_count, created_at, updated_at)
VALUES (:productId, 0, :quantity, 0, NOW(), NOW())
INSERT INTO product_metrics (product_id, like_count, sales_count, sales_amount, view_count, created_at, updated_at)
VALUES (:productId, 0, :quantity, amount, 0, NOW(), NOW())
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

โš ๏ธ Potential issue | ๐Ÿ”ด Critical

SQL ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ ์˜ค๋ฅ˜

VALUES ์ ˆ์—์„œ :amount ๋Œ€์‹  amount๋กœ ์ž‘์„ฑ๋˜์–ด ์žˆ์–ด ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ์ด ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋Ÿฐํƒ€์ž„์— SQL ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ”Ž ์ˆ˜์ • ์ œ์•ˆ
-        VALUES (:productId, 0, :quantity, amount, 0, NOW(), NOW())
+        VALUES (:productId, 0, :quantity, :amount, 0, NOW(), NOW())
๐Ÿ“ Committable suggestion

โ€ผ๏ธ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
INSERT INTO product_metrics (product_id, like_count, sales_count, sales_amount, view_count, created_at, updated_at)
VALUES (:productId, 0, :quantity, amount, 0, NOW(), NOW())
INSERT INTO product_metrics (product_id, like_count, sales_count, sales_amount, view_count, created_at, updated_at)
VALUES (:productId, 0, :quantity, :amount, 0, NOW(), NOW())
๐Ÿค– Prompt for AI Agents
In
apps/commerce-streamer/src/main/java/com/loopers/infrastructure/productmetrics/ProductMetricsJpaRepository.java
around lines 42 to 43, the INSERT uses an unbound literal "amount" instead of
the named parameter ":amount", causing SQL parameter binding to fail at runtime;
change "amount" to ":amount" in the VALUES clause and ensure the repository
method supplies a matching ":amount" parameter (correct name and type) when
executing the query.

ON DUPLICATE KEY UPDATE
sales_count = sales_count + :quantity,
sales_amount = sales_amount + :amount,
updated_at = NOW()
""",
nativeQuery = true
)
void upsertSalesCount(@Param("productId") Long productId, @Param("quantity") int quantity);
void upsertSalesCount(@Param("productId") Long productId, @Param("quantity") int quantity, @Param("amount") BigDecimal amount);

@Modifying
@Query(value = """
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

import java.math.BigDecimal;
import java.util.Optional;

@RequiredArgsConstructor
Expand All @@ -28,8 +29,8 @@ public void upsertUnlikeCount(Long productId) {
}

@Override
public void upsertSalesCount(Long productId, int quantity) {
productMetricsJpaRepository.upsertSalesCount(productId, quantity);
public void upsertSalesCount(Long productId, int quantity, BigDecimal amount) {
productMetricsJpaRepository.upsertSalesCount(productId, quantity, amount);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.loopers.application.like.event;
package com.loopers.messaging.event;

import java.time.Instant;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.loopers.application.like.event;
package com.loopers.messaging.event;

import java.time.Instant;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.loopers.messaging.event;

import java.math.BigDecimal;
import java.time.Instant;
import java.util.List;

public record OrderPaidEvent(
Long orderId,
List<OrderItemData> items,
Instant occurredAt
) {
public static OrderPaidEvent of(Long orderId, List<OrderItemData> items) {
return new OrderPaidEvent(
orderId,
items,
Instant.now()
);
}

public record OrderItemData(
Long productId,
int quantity,
BigDecimal unitPrice // ์ƒํ’ˆ ๋‹จ๊ฐ€
) {}
}
Loading