From 88c7cb707c6a7a216547207f506f6cddba65d675 Mon Sep 17 00:00:00 2001 From: Kama-Pushka Date: Mon, 28 Apr 2025 13:03:46 +0500 Subject: [PATCH 1/2] linking services via events --- build.gradle.kts | 4 +++ .../javaspringcourse/config/AsyncConfig.java | 16 +++++++++ .../order/controller/OrderController.java | 21 ++++++++++++ .../javaspringcourse/order/dto/OrderIn.java | 3 ++ .../PaymentProcessedEventListener.java | 27 +++++++++++++++ .../event/OrderCreatingEvent.java | 4 +++ .../order/service/OrderService.java | 23 +++++++++++++ .../OrderCreatingEventListener.java | 27 +++++++++++++++ .../eventListener/PaymentEventListener.java | 27 +++++++++++++++ .../event/PaymentProcessedEvent.java | 4 +++ .../payment/service/PaymentService.java | 33 +++++++++++++++++++ src/main/resources/application.properties | 2 ++ 12 files changed, 191 insertions(+) create mode 100644 src/main/java/org/javaspringcourse/config/AsyncConfig.java create mode 100644 src/main/java/org/javaspringcourse/order/controller/OrderController.java create mode 100644 src/main/java/org/javaspringcourse/order/dto/OrderIn.java create mode 100644 src/main/java/org/javaspringcourse/order/eventListener/PaymentProcessedEventListener.java create mode 100644 src/main/java/org/javaspringcourse/order/eventListener/event/OrderCreatingEvent.java create mode 100644 src/main/java/org/javaspringcourse/order/service/OrderService.java create mode 100644 src/main/java/org/javaspringcourse/payment/eventListener/OrderCreatingEventListener.java create mode 100644 src/main/java/org/javaspringcourse/payment/eventListener/PaymentEventListener.java create mode 100644 src/main/java/org/javaspringcourse/payment/eventListener/event/PaymentProcessedEvent.java create mode 100644 src/main/java/org/javaspringcourse/payment/service/PaymentService.java diff --git a/build.gradle.kts b/build.gradle.kts index fc5abdc..e20441c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,6 +19,10 @@ repositories { dependencies { implementation("org.springframework.boot:spring-boot-starter") + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-data-jpa") + compileOnly("org.projectlombok:lombok") + annotationProcessor("org.projectlombok:lombok") testImplementation("org.springframework.boot:spring-boot-starter-test") testRuntimeOnly("org.junit.platform:junit-platform-launcher") } diff --git a/src/main/java/org/javaspringcourse/config/AsyncConfig.java b/src/main/java/org/javaspringcourse/config/AsyncConfig.java new file mode 100644 index 0000000..5158e3d --- /dev/null +++ b/src/main/java/org/javaspringcourse/config/AsyncConfig.java @@ -0,0 +1,16 @@ +package org.javaspringcourse.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +@Configuration +@EnableAsync +public class AsyncConfig { + + @Bean + ThreadPoolTaskExecutor threadPoolTaskExecutor() { + return new ThreadPoolTaskExecutor(); // TODO здесь можно определить ограничения для потоков асинхронных ивентов + }; +} \ No newline at end of file diff --git a/src/main/java/org/javaspringcourse/order/controller/OrderController.java b/src/main/java/org/javaspringcourse/order/controller/OrderController.java new file mode 100644 index 0000000..6fdb97e --- /dev/null +++ b/src/main/java/org/javaspringcourse/order/controller/OrderController.java @@ -0,0 +1,21 @@ +package org.javaspringcourse.order.controller; + +import lombok.RequiredArgsConstructor; +import org.javaspringcourse.order.dto.OrderIn; +import org.javaspringcourse.order.service.OrderService; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/orders") +@RequiredArgsConstructor +public class OrderController { + private final OrderService orderService; + + @PostMapping("/create") + public void getOrders(@RequestBody OrderIn order) { + orderService.createOrder(order.cost(), order.balance()); + } +} diff --git a/src/main/java/org/javaspringcourse/order/dto/OrderIn.java b/src/main/java/org/javaspringcourse/order/dto/OrderIn.java new file mode 100644 index 0000000..3561955 --- /dev/null +++ b/src/main/java/org/javaspringcourse/order/dto/OrderIn.java @@ -0,0 +1,3 @@ +package org.javaspringcourse.order.dto; + +public record OrderIn(float cost, float balance) {} \ No newline at end of file diff --git a/src/main/java/org/javaspringcourse/order/eventListener/PaymentProcessedEventListener.java b/src/main/java/org/javaspringcourse/order/eventListener/PaymentProcessedEventListener.java new file mode 100644 index 0000000..a71b49e --- /dev/null +++ b/src/main/java/org/javaspringcourse/order/eventListener/PaymentProcessedEventListener.java @@ -0,0 +1,27 @@ +package org.javaspringcourse.order.eventListener; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.javaspringcourse.payment.eventListener.event.PaymentProcessedEvent; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Log4j2 +@Component +@RequiredArgsConstructor +public class PaymentProcessedEventListener { + + @Async("threadPoolTaskExecutor") + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void handlePaymentSuccessEvent(PaymentProcessedEvent event) { + log.info("Order was created."); + } + + @Async("threadPoolTaskExecutor") + @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK) + public void handlePaymentFailEvent(PaymentProcessedEvent event) { + log.info("Order has been cancelled."); + } +} diff --git a/src/main/java/org/javaspringcourse/order/eventListener/event/OrderCreatingEvent.java b/src/main/java/org/javaspringcourse/order/eventListener/event/OrderCreatingEvent.java new file mode 100644 index 0000000..f252cf8 --- /dev/null +++ b/src/main/java/org/javaspringcourse/order/eventListener/event/OrderCreatingEvent.java @@ -0,0 +1,4 @@ +package org.javaspringcourse.order.eventListener.event; + +public record OrderCreatingEvent(float cost, float money) { +} diff --git a/src/main/java/org/javaspringcourse/order/service/OrderService.java b/src/main/java/org/javaspringcourse/order/service/OrderService.java new file mode 100644 index 0000000..cc90b52 --- /dev/null +++ b/src/main/java/org/javaspringcourse/order/service/OrderService.java @@ -0,0 +1,23 @@ +package org.javaspringcourse.order.service; + +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.javaspringcourse.order.eventListener.event.OrderCreatingEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +@Log4j2 +@Service +@RequiredArgsConstructor +public class OrderService { + private final ApplicationEventPublisher eventPublisher; + + public void createOrder(float cost, float money) { + log.info("Creating an order..."); + log.info("Waiting for payment..."); + + eventPublisher.publishEvent(new OrderCreatingEvent(cost, money)); + } +} diff --git a/src/main/java/org/javaspringcourse/payment/eventListener/OrderCreatingEventListener.java b/src/main/java/org/javaspringcourse/payment/eventListener/OrderCreatingEventListener.java new file mode 100644 index 0000000..3a530ee --- /dev/null +++ b/src/main/java/org/javaspringcourse/payment/eventListener/OrderCreatingEventListener.java @@ -0,0 +1,27 @@ +package org.javaspringcourse.payment.eventListener; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.javaspringcourse.order.eventListener.event.OrderCreatingEvent; +import org.javaspringcourse.payment.service.PaymentService; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +@Log4j2 +@Component +@RequiredArgsConstructor +public class OrderCreatingEventListener { + private final PaymentService paymentService; + + @EventListener + @Async("threadPoolTaskExecutor") + public void handleOrderCreatedEvent(OrderCreatingEvent event) throws InterruptedException { + Thread.sleep(2000); + try { + paymentService.processPayment(event.money(), event.cost()); + } catch (Exception e) { + log.warn(e); + } + } +} diff --git a/src/main/java/org/javaspringcourse/payment/eventListener/PaymentEventListener.java b/src/main/java/org/javaspringcourse/payment/eventListener/PaymentEventListener.java new file mode 100644 index 0000000..5260451 --- /dev/null +++ b/src/main/java/org/javaspringcourse/payment/eventListener/PaymentEventListener.java @@ -0,0 +1,27 @@ +package org.javaspringcourse.payment.eventListener; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.javaspringcourse.payment.eventListener.event.PaymentProcessedEvent; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Log4j2 +@Component +@RequiredArgsConstructor +public class PaymentEventListener { + + @Async("threadPoolTaskExecutor") + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void handlePaymentSuccessEvent(PaymentProcessedEvent event) { + log.info("Payment was successful."); + } + + @Async("threadPoolTaskExecutor") + @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK) + public void handlePaymentFailEvent(PaymentProcessedEvent event) { + log.info("Payment failed."); + } +} diff --git a/src/main/java/org/javaspringcourse/payment/eventListener/event/PaymentProcessedEvent.java b/src/main/java/org/javaspringcourse/payment/eventListener/event/PaymentProcessedEvent.java new file mode 100644 index 0000000..50f1cd4 --- /dev/null +++ b/src/main/java/org/javaspringcourse/payment/eventListener/event/PaymentProcessedEvent.java @@ -0,0 +1,4 @@ +package org.javaspringcourse.payment.eventListener.event; + +public record PaymentProcessedEvent() { +} diff --git a/src/main/java/org/javaspringcourse/payment/service/PaymentService.java b/src/main/java/org/javaspringcourse/payment/service/PaymentService.java new file mode 100644 index 0000000..74ef15f --- /dev/null +++ b/src/main/java/org/javaspringcourse/payment/service/PaymentService.java @@ -0,0 +1,33 @@ +package org.javaspringcourse.payment.service; + +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.javaspringcourse.payment.eventListener.event.PaymentProcessedEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +import static jakarta.transaction.Transactional.TxType.REQUIRES_NEW; + +@Log4j2 +@Service +@RequiredArgsConstructor +public class PaymentService { + private final ApplicationEventPublisher eventPublisher; + + @Transactional(value = REQUIRES_NEW, rollbackOn = Exception.class) + public void processPayment(float money, float need) { + log.info("Trying to pay for order. Current money: {}", money); + eventPublisher.publishEvent(new PaymentProcessedEvent()); + + log.info("In transaction: {}", TransactionSynchronizationManager.isActualTransactionActive()); + + if (money > need) { + log.info("Payment Working..."); + log.info("Fixing in DB..."); + } else { + throw new RuntimeException("NO MONEY TO PAY"); + } + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 61bae71..2b78ee6 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,3 @@ spring.application.name=java-spring-course +spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration +server.port=8080 \ No newline at end of file From 483e632c2d5ff101368708086bb8b32a9e6c50f5 Mon Sep 17 00:00:00 2001 From: Kama-Pushka Date: Mon, 28 Apr 2025 13:19:16 +0500 Subject: [PATCH 2/2] fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit добавил логирование, ивент и обычный листенер к нему --- .../order/eventListener/OrderEventListener.java | 17 +++++++++++++++++ .../PaymentProcessedEventListener.java | 10 +++++++--- .../eventListener/event/OrderCreatedEvent.java | 4 ++++ .../order/service/OrderService.java | 1 + .../OrderCreatingEventListener.java | 4 ++-- .../eventListener/PaymentEventListener.java | 4 ++-- .../payment/service/PaymentService.java | 1 + 7 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 src/main/java/org/javaspringcourse/order/eventListener/OrderEventListener.java create mode 100644 src/main/java/org/javaspringcourse/order/eventListener/event/OrderCreatedEvent.java diff --git a/src/main/java/org/javaspringcourse/order/eventListener/OrderEventListener.java b/src/main/java/org/javaspringcourse/order/eventListener/OrderEventListener.java new file mode 100644 index 0000000..36af697 --- /dev/null +++ b/src/main/java/org/javaspringcourse/order/eventListener/OrderEventListener.java @@ -0,0 +1,17 @@ +package org.javaspringcourse.order.eventListener; + +import lombok.extern.log4j.Log4j2; +import org.javaspringcourse.order.eventListener.event.OrderCreatedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +@Log4j2 +@Component +public class OrderEventListener { + + @EventListener + public void handleOrderCreatedEvent(OrderCreatedEvent event) { + log.info("Handle OrderCreatedEvent..."); + log.info("Order was created."); + } +} diff --git a/src/main/java/org/javaspringcourse/order/eventListener/PaymentProcessedEventListener.java b/src/main/java/org/javaspringcourse/order/eventListener/PaymentProcessedEventListener.java index a71b49e..ef17652 100644 --- a/src/main/java/org/javaspringcourse/order/eventListener/PaymentProcessedEventListener.java +++ b/src/main/java/org/javaspringcourse/order/eventListener/PaymentProcessedEventListener.java @@ -2,7 +2,9 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.javaspringcourse.order.eventListener.event.OrderCreatedEvent; import org.javaspringcourse.payment.eventListener.event.PaymentProcessedEvent; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import org.springframework.transaction.event.TransactionPhase; @@ -12,16 +14,18 @@ @Component @RequiredArgsConstructor public class PaymentProcessedEventListener { + private final ApplicationEventPublisher eventPublisher; - @Async("threadPoolTaskExecutor") @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void handlePaymentSuccessEvent(PaymentProcessedEvent event) { - log.info("Order was created."); + log.info("Handle PaymentProcessedEvent (AFTER_COMMIT)..."); + log.info("Publish OrderCreatedEvent."); + eventPublisher.publishEvent(new OrderCreatedEvent()); } - @Async("threadPoolTaskExecutor") @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK) public void handlePaymentFailEvent(PaymentProcessedEvent event) { + log.info("Handle PaymentProcessedEvent (AFTER_ROLLBACK)..."); log.info("Order has been cancelled."); } } diff --git a/src/main/java/org/javaspringcourse/order/eventListener/event/OrderCreatedEvent.java b/src/main/java/org/javaspringcourse/order/eventListener/event/OrderCreatedEvent.java new file mode 100644 index 0000000..eb2b28b --- /dev/null +++ b/src/main/java/org/javaspringcourse/order/eventListener/event/OrderCreatedEvent.java @@ -0,0 +1,4 @@ +package org.javaspringcourse.order.eventListener.event; + +public record OrderCreatedEvent() { +} diff --git a/src/main/java/org/javaspringcourse/order/service/OrderService.java b/src/main/java/org/javaspringcourse/order/service/OrderService.java index cc90b52..26437b2 100644 --- a/src/main/java/org/javaspringcourse/order/service/OrderService.java +++ b/src/main/java/org/javaspringcourse/order/service/OrderService.java @@ -18,6 +18,7 @@ public void createOrder(float cost, float money) { log.info("Creating an order..."); log.info("Waiting for payment..."); + log.info("Publish OrderCreatingEvent."); eventPublisher.publishEvent(new OrderCreatingEvent(cost, money)); } } diff --git a/src/main/java/org/javaspringcourse/payment/eventListener/OrderCreatingEventListener.java b/src/main/java/org/javaspringcourse/payment/eventListener/OrderCreatingEventListener.java index 3a530ee..07057e9 100644 --- a/src/main/java/org/javaspringcourse/payment/eventListener/OrderCreatingEventListener.java +++ b/src/main/java/org/javaspringcourse/payment/eventListener/OrderCreatingEventListener.java @@ -16,8 +16,8 @@ public class OrderCreatingEventListener { @EventListener @Async("threadPoolTaskExecutor") - public void handleOrderCreatedEvent(OrderCreatingEvent event) throws InterruptedException { - Thread.sleep(2000); + public void handleOrderCreatingEvent(OrderCreatingEvent event) throws InterruptedException { + Thread.sleep(2000); // имитация долгой работы try { paymentService.processPayment(event.money(), event.cost()); } catch (Exception e) { diff --git a/src/main/java/org/javaspringcourse/payment/eventListener/PaymentEventListener.java b/src/main/java/org/javaspringcourse/payment/eventListener/PaymentEventListener.java index 5260451..8af6230 100644 --- a/src/main/java/org/javaspringcourse/payment/eventListener/PaymentEventListener.java +++ b/src/main/java/org/javaspringcourse/payment/eventListener/PaymentEventListener.java @@ -13,15 +13,15 @@ @RequiredArgsConstructor public class PaymentEventListener { - @Async("threadPoolTaskExecutor") @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void handlePaymentSuccessEvent(PaymentProcessedEvent event) { + log.info("Handle PaymentProcessedEvent (AFTER_COMMIT)..."); log.info("Payment was successful."); } - @Async("threadPoolTaskExecutor") @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK) public void handlePaymentFailEvent(PaymentProcessedEvent event) { + log.info("Handle PaymentProcessedEvent (AFTER_ROLLBACK)..."); log.info("Payment failed."); } } diff --git a/src/main/java/org/javaspringcourse/payment/service/PaymentService.java b/src/main/java/org/javaspringcourse/payment/service/PaymentService.java index 74ef15f..33b854f 100644 --- a/src/main/java/org/javaspringcourse/payment/service/PaymentService.java +++ b/src/main/java/org/javaspringcourse/payment/service/PaymentService.java @@ -19,6 +19,7 @@ public class PaymentService { @Transactional(value = REQUIRES_NEW, rollbackOn = Exception.class) public void processPayment(float money, float need) { log.info("Trying to pay for order. Current money: {}", money); + log.info("Publish PaymentProcessedEvent."); eventPublisher.publishEvent(new PaymentProcessedEvent()); log.info("In transaction: {}", TransactionSynchronizationManager.isActualTransactionActive());