-
Notifications
You must be signed in to change notification settings - Fork 34
[volume - 8] Decoupling with Kafka #206
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
Changes from all commits
6a03748
c41ffe6
afcae1c
df0fb03
7b1111b
5e36c3f
8fb6e51
23b9170
a1ffe5e
f1205ab
c5f6eaf
8c6d050
0e3419f
20d6942
7d12217
38838b4
ff83042
482bed2
859eefc
743262e
11bcdfb
dd8cb1a
cf6c0c5
ef5fd9d
94b5739
4ab9eba
96bcccd
5fa1a90
548647b
e0d5737
987d68a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package com.loopers.application.order; | ||
|
|
||
| public record OrderPaymentCommand( | ||
| String cardType, | ||
| String cardNo | ||
| ) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| package com.loopers.application.order; | ||
|
|
||
| import com.loopers.domain.order.Order; | ||
| import com.loopers.domain.order.OrderItem; | ||
| import com.loopers.domain.order.OrderService; | ||
| import com.loopers.domain.order.OrderStatus; | ||
| import com.loopers.domain.payment.Payment; | ||
| import com.loopers.domain.payment.PaymentService; | ||
| import com.loopers.domain.point.Point; | ||
| import com.loopers.domain.point.PointService; | ||
| import com.loopers.domain.product.Product; | ||
| import com.loopers.domain.product.ProductService; | ||
| import com.loopers.infrastructure.dataplatform.OrderDataPlatformClient; | ||
| import com.loopers.infrastructure.payment.PgPaymentClient; | ||
| import com.loopers.infrastructure.payment.dto.PgPaymentV1Dto; | ||
| import com.loopers.interfaces.api.ApiResponse; | ||
| import com.loopers.support.error.CoreException; | ||
| import com.loopers.support.error.ErrorType; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
| import org.springframework.stereotype.Component; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| @Component | ||
| @RequiredArgsConstructor | ||
| public class OrderPaymentProcessor { | ||
|
|
||
| private static final Logger log = LoggerFactory.getLogger(OrderPaymentProcessor.class); | ||
|
|
||
| private final PaymentService paymentService; | ||
| private final OrderService orderService; | ||
| private final ProductService productService; | ||
| private final PointService pointService; | ||
| private final PgPaymentClient pgPaymentClient; | ||
| private final OrderDataPlatformClient orderDataPlatformClient; | ||
|
|
||
| @Transactional | ||
| public void handlePaymentCallback(String orderReference, PgPaymentV1Dto.TransactionStatus status, String transactionKey, String reason) { | ||
| Payment payment = paymentService.findByOrderReference(orderReference) | ||
| .orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "๊ฒฐ์ ์ ๋ณด๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.")); | ||
|
|
||
| if (payment.getTransactionKey() != null && payment.getTransactionKey().equals(transactionKey)) { | ||
| return; | ||
| } | ||
|
|
||
| Order order = orderService.findById(payment.getOrderId()); | ||
| applyPaymentResult(order, payment, status, transactionKey, reason); | ||
| } | ||
|
|
||
| @Transactional | ||
| public void syncPayment(Long orderId) { | ||
| Payment payment = paymentService.findByOrderId(orderId) | ||
| .orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "๊ฒฐ์ ์ ๋ณด๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.")); | ||
| Order order = orderService.findById(orderId); | ||
| ApiResponse<PgPaymentV1Dto.OrderResponse> response = pgPaymentClient.getPayments(payment.getUserId(), payment.getOrderReference()); | ||
| PgPaymentV1Dto.OrderResponse data = response != null ? response.data() : null; | ||
|
|
||
| if (data == null || data.transactions() == null || data.transactions().isEmpty()) { | ||
| log.warn("๊ฒฐ์ ๋ด์ญ์ ์ฐพ์ ์ ์์ต๋๋ค. orderId={}, orderReference={}", orderId, payment.getOrderReference()); | ||
| return; | ||
| } | ||
|
|
||
| PgPaymentV1Dto.TransactionRecord record = data.transactions().get(data.transactions().size() - 1); | ||
| applyPaymentResult(order, payment, record.status(), record.transactionKey(), record.reason()); | ||
| } | ||
|
|
||
| @Transactional | ||
| public void handlePaymentResult(Long orderId, PgPaymentV1Dto.TransactionStatus status, String transactionKey, String reason) { | ||
| Payment payment = paymentService.findByOrderId(orderId) | ||
| .orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "๊ฒฐ์ ์ ๋ณด๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.")); | ||
| Order order = orderService.findById(orderId); | ||
| applyPaymentResult(order, payment, status, transactionKey, reason); | ||
| } | ||
|
|
||
| private void applyPaymentResult( | ||
| Order order, | ||
| Payment payment, | ||
| PgPaymentV1Dto.TransactionStatus status, | ||
| String transactionKey, | ||
| String reason | ||
| ) { | ||
| OrderStatus newStatus = OrderPaymentSupport.mapOrderStatus(status); | ||
| payment.updateStatus(OrderPaymentSupport.mapPaymentStatus(status), transactionKey, reason); | ||
| paymentService.save(payment); | ||
|
|
||
| if (newStatus == OrderStatus.COMPLETE) { | ||
| order.updateStatus(OrderStatus.COMPLETE); | ||
| } else if (newStatus == OrderStatus.FAIL) { | ||
| revertOrder(order, payment.getUserId()); | ||
| } else { | ||
| order.updateStatus(OrderStatus.PENDING); | ||
| } | ||
|
|
||
| orderDataPlatformClient.send(order, payment); | ||
| } | ||
|
|
||
| private void revertOrder(Order order, String userId) { | ||
| for (OrderItem item : order.getOrderItems()) { | ||
| Product product = productService.getProduct(item.getProductId()); | ||
| product.increaseStock(item.getQuantity()); | ||
| } | ||
| Point point = pointService.findPointByUserId(userId); | ||
| if (point != null) { | ||
| point.refund(order.getTotalAmount()); | ||
| } | ||
| order.updateStatus(OrderStatus.FAIL); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| package com.loopers.application.order; | ||
|
|
||
| import com.loopers.domain.order.OrderStatus; | ||
| import com.loopers.domain.payment.PaymentStatus; | ||
| import com.loopers.infrastructure.payment.dto.PgPaymentV1Dto.Response; | ||
| import com.loopers.infrastructure.payment.dto.PgPaymentV1Dto.TransactionStatus; | ||
| import com.loopers.interfaces.api.ApiResponse; | ||
| import com.loopers.support.error.CoreException; | ||
| import com.loopers.support.error.ErrorType; | ||
|
|
||
| import static com.loopers.infrastructure.payment.dto.PgPaymentV1Dto.TransactionStatus.SUCCESS; | ||
|
|
||
| public final class OrderPaymentSupport { | ||
|
|
||
| private OrderPaymentSupport() { | ||
| } | ||
|
|
||
| public static OrderStatus mapOrderStatus(TransactionStatus status) { | ||
| if (status == SUCCESS) { | ||
| return OrderStatus.COMPLETE; | ||
| } | ||
| if (status == TransactionStatus.FAILED) { | ||
| return OrderStatus.FAIL; | ||
| } | ||
| return OrderStatus.PENDING; | ||
| } | ||
|
|
||
| public static PaymentStatus mapPaymentStatus(TransactionStatus status) { | ||
| if (status == SUCCESS) { | ||
| return PaymentStatus.SUCCESS; | ||
| } | ||
| if (status == TransactionStatus.FAILED) { | ||
| return PaymentStatus.FAIL; | ||
| } | ||
| return PaymentStatus.PENDING; | ||
| } | ||
|
|
||
| public static Response requirePgResponse(ApiResponse<Response> response) { | ||
| Response data = response != null ? response.data() : null; | ||
| if (data == null) { | ||
| throw new CoreException(ErrorType.INTERNAL_ERROR, "PG ์๋ต์ ํ์ธํ ์ ์์ต๋๋ค."); | ||
| } | ||
| return data; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| package com.loopers.application.user; | ||
|
|
||
| import com.loopers.domain.point.PointService; | ||
| import com.loopers.domain.user.User; | ||
| import com.loopers.domain.user.UserService; | ||
| import com.loopers.support.error.CoreException; | ||
|
|
@@ -11,9 +12,11 @@ | |
| @Component | ||
| public class UserFacade { | ||
| private final UserService userService; | ||
| private final PointService pointService; | ||
|
|
||
| public UserInfo register(String userId, String email, String birth, String gender) { | ||
| User user = userService.register(userId, email, birth, gender); | ||
| pointService.initPoint(user.getUserId()); | ||
| return UserInfo.from(user); | ||
| } | ||
|
Comment on lines
17
to
21
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. ๐งฉ Analysis chain๐ Script executed: cd /tmp && find . -name "UserFacade.java" 2>/dev/null | head -5Repository: Loopers-dev-lab/loopers-spring-java-template Length of output: 70 ๐ Script executed: rg -n --type java -A 3 -B 10 'public UserInfo register' apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.javaRepository: Loopers-dev-lab/loopers-spring-java-template Length of output: 657 ๐ Script executed: rg -n --type java '@Transactional' apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.javaRepository: Loopers-dev-lab/loopers-spring-java-template Length of output: 70 ํธ๋์ญ์ ๊ฒฝ๊ณ๋ฅผ ํ์ธํ๊ณ ํด๊ฒฐํ์ธ์.
๋ฉ์๋์ ๐ค Prompt for AI Agents |
||
|
|
||
|
|
||
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: 886
๐ Script executed:
Repository: Loopers-dev-lab/loopers-spring-java-template
Length of output: 176
๐ Script executed:
Repository: Loopers-dev-lab/loopers-spring-java-template
Length of output: 5972
resilience4j-spring-boot3์ ๋ฒ์ ๊ด๋ฆฌ ์ ๋ต ํ์ธ ํ์
Spring Cloud BOM์ root build.gradle.kts์ dependencyManagement ๋ธ๋ก(lines 42-46)์์ ์ด๋ฏธ ์ ์ธ๋์ด ์์ผ๋ฏ๋ก
spring-cloud-starter-openfeign์ ๋ฒ์ ์ ์ ์ ํ ๊ด๋ฆฌ๋ฉ๋๋ค.๋ค๋ง
resilience4j-spring-boot3์ resilience4j-bom์ด ํ๋ก์ ํธ์ ์ ์ธ๋์ด ์์ง ์์ ๋ช ์์ ์ธ ๋ฒ์ ๊ด๋ฆฌ๊ฐ ํ์ํฉ๋๋ค. ํ์ฌ ๋ฒ์ ์ด ๋ช ์๋์ง ์์ ์ํ์ด๋ฏ๋ก, ๋ค์ ์ค ํ๋๋ฅผ ๊ฒํ ํ์ธ์:์ถ๊ฐ๋ก Feign ๋ฐ resilience4j ํตํฉ ํ ์คํธ๋ฅผ ์ํ ์ ์ฉ ํ ์คํธ ์์กด์ฑ ์ถ๊ฐ๋ ๊ฒํ ํ์ธ์.