Skip to content

Commit 73c0244

Browse files
authored
Merge pull request #17 from Kimjipang/round08
Round08
2 parents 2f23ca1 + fee55a1 commit 73c0244

9 files changed

Lines changed: 121 additions & 72 deletions

File tree

apps/commerce-api/src/main/java/com/loopers/application/like/LikeFacade.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
package com.loopers.application.like;
22

3-
import com.loopers.application.product.UserActionEvent;
4-
import com.loopers.domain.actionlog.ActionType;
53
import com.loopers.domain.like.Like;
64
import com.loopers.domain.like.LikeRepository;
5+
import com.loopers.domain.outbox.AggregateType;
6+
import com.loopers.domain.outbox.OutboxEvent;
7+
import com.loopers.domain.outbox.OutboxRepository;
8+
import com.loopers.domain.outbox.OutboxType;
79
import com.loopers.domain.product.ProductRepository;
810
import com.loopers.domain.user.UserRepository;
911
import com.loopers.interfaces.api.like.LikeV1Dto;
1012
import com.loopers.support.error.CoreException;
1113
import com.loopers.support.error.ErrorType;
1214
import lombok.RequiredArgsConstructor;
13-
import org.springframework.context.ApplicationEventPublisher;
1415
import org.springframework.stereotype.Component;
1516
import org.springframework.transaction.annotation.Transactional;
1617

@@ -20,7 +21,7 @@ public class LikeFacade {
2021
private final LikeRepository likeRepository;
2122
private final UserRepository userRepository;
2223
private final ProductRepository productRepository;
23-
private final ApplicationEventPublisher publisher;
24+
private final OutboxRepository outBoxRepository;
2425

2526
@Transactional
2627
public LikeInfo doLike(LikeV1Dto.LikeRequest request) {
@@ -46,8 +47,13 @@ public LikeInfo doLike(LikeV1Dto.LikeRequest request) {
4647
Like newLike = request.toEntity();
4748
likeRepository.save(newLike);
4849

49-
publisher.publishEvent(new LikeCreateEvent(userId, productId));
50-
publisher.publishEvent(new UserActionEvent(userId, productId, ActionType.DO_LIKE));
50+
OutboxEvent outBoxEvent = OutboxEvent.of(
51+
AggregateType.PRODUCT,
52+
productId,
53+
OutboxType.PRODUCT_LIKED
54+
);
55+
56+
outBoxRepository.save(outBoxEvent);
5157

5258
return LikeInfo.from(newLike);
5359
});
Lines changed: 41 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
package com.loopers.application.order;
22

33
import com.loopers.application.orderitem.OrderItemInfo;
4-
import com.loopers.application.product.UserActionEvent;
54
import com.loopers.domain.coupon.Coupon;
65
import com.loopers.domain.coupon.CouponRepository;
76
import com.loopers.domain.order.Order;
87
import com.loopers.domain.order.OrderRepository;
98
import com.loopers.domain.orderitem.OrderItem;
109
import com.loopers.domain.orderitem.OrderItemRepository;
10+
import com.loopers.domain.outbox.AggregateType;
11+
import com.loopers.domain.outbox.OutboxEvent;
12+
import com.loopers.domain.outbox.OutboxRepository;
13+
import com.loopers.domain.outbox.OutboxType;
1114
import com.loopers.domain.product.Product;
1215
import com.loopers.domain.product.ProductRepository;
1316
import com.loopers.domain.user.UserRepository;
1417
import com.loopers.interfaces.api.order.OrderV1Dto;
15-
import com.loopers.interfaces.api.payment.PaymentV1Dto;
1618
import com.loopers.support.error.CoreException;
1719
import com.loopers.support.error.ErrorType;
1820
import lombok.RequiredArgsConstructor;
19-
import org.springframework.context.ApplicationEventPublisher;
2021
import org.springframework.stereotype.Component;
2122
import org.springframework.transaction.annotation.Transactional;
2223

@@ -31,7 +32,7 @@ public class OrderFacade {
3132
private final UserRepository userRepository;
3233
private final ProductRepository productRepository;
3334
private final CouponRepository couponRepository;
34-
private final ApplicationEventPublisher publisher;
35+
private final OutboxRepository outBoxRepository;
3536

3637
@Transactional
3738
public OrderResultInfo createOrder(OrderV1Dto.OrderRequest request) {
@@ -50,31 +51,31 @@ public OrderResultInfo createOrder(OrderV1Dto.OrderRequest request) {
5051
List<OrderV1Dto.OrderItemRequest> orderItemRequests = request.orderItems();
5152

5253
List<OrderItem> orderItems = orderItemRequests.stream()
53-
.map(item -> {
54-
// 상품 검증
55-
Long productId = item.productId();
56-
Product product = productRepository.findById(productId).orElseThrow(
57-
() -> new CoreException(ErrorType.NOT_FOUND, "존재하는 상품이 아닙니다.")
58-
);
59-
60-
if (product.getStock() < item.quantity()) {
61-
throw new CoreException(ErrorType.BAD_REQUEST, product.getName() + " 상품의 재고가 부족합니다.");
62-
}
63-
64-
product.decreaseStock(item.quantity());
65-
66-
OrderItem orderItem = item.toEntity(
67-
null,
68-
product.getPrice().multiply(BigDecimal.valueOf(item.quantity()))
69-
);
70-
return orderItem;
71-
72-
})
73-
.toList();
54+
.map(item -> {
55+
// 상품 검증
56+
Long productId = item.productId();
57+
Product product = productRepository.findById(productId).orElseThrow(
58+
() -> new CoreException(ErrorType.NOT_FOUND, "존재하는 상품이 아닙니다.")
59+
);
60+
61+
if (product.getStock() < item.quantity()) {
62+
throw new CoreException(ErrorType.BAD_REQUEST, product.getName() + " 상품의 재고가 부족합니다.");
63+
}
64+
65+
product.decreaseStock(item.quantity());
66+
67+
OrderItem orderItem = item.toEntity(
68+
null,
69+
product.getPrice().multiply(BigDecimal.valueOf(item.quantity()))
70+
);
71+
return orderItem;
72+
73+
})
74+
.toList();
7475

7576
BigDecimal totalPrice = orderItems.stream()
76-
.map(OrderItem::getOrderPrice)
77-
.reduce(BigDecimal.ZERO, BigDecimal::add);
77+
.map(OrderItem::getOrderPrice)
78+
.reduce(BigDecimal.ZERO, BigDecimal::add);
7879

7980
Long couponId = request.couponId();
8081

@@ -92,27 +93,28 @@ public OrderResultInfo createOrder(OrderV1Dto.OrderRequest request) {
9293
.multiply(BigDecimal.valueOf(100 - rate))
9394
.divide(BigDecimal.valueOf(100));
9495

95-
publisher.publishEvent(new OrderCreatedEvent(couponId, null, null, null, null, null));
96+
coupon.useCoupon();
9697

9798
Order order = request.toEntity(totalPrice);
9899
Order saved = orderRepository.save(order);
99100

100101
orderItems.forEach(item -> item.assignOrderId(saved.getId()));
101-
orderItemRepository.saveAll(orderItems);
102+
List<OrderItem> savedOrderItems = orderItemRepository.saveAll(orderItems);
103+
104+
savedOrderItems.forEach(orderItem -> {
105+
OutboxEvent outboxEvent = OutboxEvent.of(
106+
AggregateType.PRODUCT,
107+
orderItem.getProductId(),
108+
OutboxType.PRODUCT_SALES
109+
);
102110

103-
List<OrderItemInfo> orderItemInfos = orderItems.stream()
111+
outBoxRepository.save(outboxEvent);
112+
});
113+
114+
List<OrderItemInfo> orderItemInfos = savedOrderItems.stream()
104115
.map(orderItem -> OrderItemInfo.from(orderItem, orderItem.getOrderPrice()))
105116
.toList();
106117

107-
publisher.publishEvent(new OrderCreatedEvent(
108-
couponId,
109-
"1351039135",
110-
PaymentV1Dto.CardTypeDto.HYUNDAI,
111-
"1234-5678-9814-1451",
112-
100000L,
113-
"http://localhost:8080/api/v1/examples/callback"
114-
));
115-
116118
return new OrderResultInfo(OrderInfo.from(saved), orderItemInfos);
117119
}
118120
}

apps/commerce-api/src/main/java/com/loopers/domain/outbox/OutboxEvent.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public class OutboxEvent extends BaseEntity {
2222
@Column(name = "aggregate_id", nullable = false)
2323
private Long aggregateId;
2424

25-
// @Enumerated(EnumType.STRING)
25+
@Enumerated(EnumType.STRING)
2626
@Column(name = "event_type", nullable = false)
2727
private OutboxType eventType;
2828

apps/commerce-api/src/main/java/com/loopers/domain/outbox/OutboxType.java

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

33
public enum OutboxType {
44
PRODUCT_VIEWED,
5+
PRODUCT_LIKED,
6+
PRODUCT_SALES,
57
ORDER_CREATED,
68
PAYMENT_COMPLETED
79
}

apps/commerce-api/src/main/java/com/loopers/infrastructure/outbox/KafkaOutboxPublisher.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.loopers.domain.outbox.OutboxEvent;
44
import com.loopers.domain.outbox.OutboxRepository;
5+
import com.loopers.domain.outbox.OutboxType;
56
import lombok.RequiredArgsConstructor;
67
import org.springframework.kafka.core.KafkaTemplate;
78
import org.springframework.scheduling.annotation.Scheduled;
@@ -17,13 +18,25 @@ public class KafkaOutboxPublisher {
1718
private final OutboxRepository outboxRepository;
1819
private final KafkaTemplate<Object, Object> kafkaTemplate;
1920

20-
@Scheduled(fixedDelayString = "1000")
21+
@Scheduled(fixedDelayString = "60000")
2122
@Transactional
22-
public void publish() {
23-
List<OutboxEvent> events = outboxRepository.findPending(5);
23+
public void publishProductViewed() {
24+
List<OutboxEvent> events = outboxRepository.findPending(10);
2425

2526
for (OutboxEvent event : events) {
26-
kafkaTemplate.send("product-viewed", String.valueOf(event.getAggregateId()), event);
27+
OutboxType type = event.getEventType();
28+
29+
if (type.equals(OutboxType.PRODUCT_VIEWED)) {
30+
kafkaTemplate.send("product-viewed", String.valueOf(event.getAggregateId()), event);
31+
}
32+
33+
else if (type.equals(OutboxType.PRODUCT_LIKED)) {
34+
kafkaTemplate.send("product-liked", String.valueOf(event.getAggregateId()), event);
35+
}
36+
37+
else if (type.equals(OutboxType.PRODUCT_SALES)) {
38+
kafkaTemplate.send("product-sales", String.valueOf(event.getAggregateId()), event);
39+
}
2740

2841
event.markAsProcessed();
2942
}

apps/commerce-api/src/main/java/com/loopers/infrastructure/outbox/OutboxJpaRepository.java

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

33
import com.loopers.domain.outbox.OutboxEvent;
44
import org.springframework.data.jpa.repository.JpaRepository;
5-
import org.springframework.data.jpa.repository.Modifying;
6-
import org.springframework.data.jpa.repository.Query;
7-
import org.springframework.data.repository.query.Param;
85

9-
import java.util.List;
106

117
public interface OutboxJpaRepository extends JpaRepository<OutboxEvent, Long> {
12-
@Query("SELECT o FROM OutboxEvent o WHERE o.status = 'PENDING' ORDER BY o.createdAt ASC")
13-
List<OutboxEvent> findPending(int limit);
148

159
}

apps/commerce-api/src/main/java/com/loopers/infrastructure/outbox/OutboxRepositoryImpl.java

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

33
import com.loopers.domain.outbox.OutboxEvent;
44
import com.loopers.domain.outbox.OutboxRepository;
5-
import lombok.RequiredArgsConstructor;
5+
import com.loopers.domain.outbox.OutboxStatus;
6+
import com.loopers.domain.outbox.QOutboxEvent;
7+
import com.querydsl.jpa.impl.JPAQueryFactory;
8+
import jakarta.persistence.EntityManager;
69
import org.springframework.stereotype.Component;
710

811
import java.util.List;
912

13+
import static com.loopers.domain.outbox.QOutboxEvent.outboxEvent;
14+
1015
@Component
11-
@RequiredArgsConstructor
1216
public class OutboxRepositoryImpl implements OutboxRepository {
1317
private final OutboxJpaRepository outBoxJpaRepository;
18+
private final JPAQueryFactory queryFactory;
19+
20+
public OutboxRepositoryImpl(OutboxJpaRepository outBoxJpaRepository, EntityManager entityManager) {
21+
this.outBoxJpaRepository = outBoxJpaRepository;
22+
this.queryFactory = new JPAQueryFactory(entityManager);
23+
}
1424

1525
@Override
1626
public OutboxEvent save(OutboxEvent outBoxEvent) {
@@ -19,7 +29,12 @@ public OutboxEvent save(OutboxEvent outBoxEvent) {
1929

2030
@Override
2131
public List<OutboxEvent> findPending(int limit) {
22-
return outBoxJpaRepository.findPending(limit);
32+
return queryFactory
33+
.selectFrom(outboxEvent)
34+
.where(outboxEvent.status.eq(OutboxStatus.PENDING))
35+
.orderBy(outboxEvent.createdAt.asc())
36+
.limit(limit)
37+
.fetch();
2338
}
2439

2540
}

apps/commerce-streamer/src/main/java/com/loopers/domain/ProductMetric.java

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,31 @@ public ProductMetric(Long productId, Long viewCount, Long likeCount, Long salesV
3434
this.eventType = eventType;
3535
}
3636

37-
public static ProductMetric of(Long productId, Long viewCount, Long likeCount, Long salesVolume, ProductEventType eventType) {
38-
return new ProductMetric(productId, viewCount, likeCount, salesVolume, eventType);
37+
/*
38+
- [ ] 리팩토링 예정
39+
*/
40+
public static ProductMetric of(Long productId, ProductEventType eventType) {
41+
if (eventType == null) {
42+
throw new IllegalArgumentException("정의되지 않은 event type입니다.");
43+
}
44+
45+
return switch (eventType) {
46+
case PRODUCT_VIEWED -> ofProductViewed(productId);
47+
case PRODUCT_LIKED -> ofProductLiked(productId);
48+
case PRODUCT_SALES -> ofProductSales(productId);
49+
};
50+
}
51+
52+
public static ProductMetric ofProductViewed(Long productId) {
53+
return new ProductMetric(productId, 1L, 0L, 0L, ProductEventType.PRODUCT_VIEWED);
54+
}
55+
56+
public static ProductMetric ofProductLiked(Long productId) {
57+
return new ProductMetric(productId, 0L, 1L, 0L, ProductEventType.PRODUCT_LIKED);
58+
}
59+
60+
public static ProductMetric ofProductSales(Long productId) {
61+
return new ProductMetric(productId, 0L, 0L, 1L, ProductEventType.PRODUCT_SALES);
3962
}
4063

4164
public void increaseProductMetric(ProductEventType eventType) {

apps/commerce-streamer/src/main/java/com/loopers/interfaces/consumer/KafkaOutboxConsumer.java

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ public class KafkaOutboxConsumer {
2323
private final ObjectMapper objectMapper;
2424

2525
@KafkaListener(
26-
topics = {"product-viewed"},
26+
topics = {"product-viewed", "product-liked"},
2727
containerFactory = KafkaConfig.BATCH_LISTENER
2828
)
2929
@Transactional
30-
public void demoListener(
30+
public void productViewedListener(
3131
List<ConsumerRecord<String, String>> messages,
3232
Acknowledgment acknowledgment
3333
) throws JsonProcessingException {
@@ -39,15 +39,9 @@ public void demoListener(
3939
ProductMetric productMetric = productMetricRepository.findByProductId(productId);
4040

4141
if (productMetric == null) {
42-
productMetricRepository.save(
43-
ProductMetric.of(
44-
productId,
45-
0L,
46-
0L,
47-
0L,
48-
ProductEventType.valueOf(eventType)
49-
)
50-
);
42+
ProductMetric newProductMetric = ProductMetric.of(productId, ProductEventType.valueOf(eventType));
43+
44+
productMetricRepository.save(newProductMetric);
5145
}
5246
else {
5347
productMetric.increaseProductMetric(ProductEventType.valueOf(eventType));

0 commit comments

Comments
 (0)