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/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 new file mode 100644 index 0000000..ef17652 --- /dev/null +++ b/src/main/java/org/javaspringcourse/order/eventListener/PaymentProcessedEventListener.java @@ -0,0 +1,31 @@ +package org.javaspringcourse.order.eventListener; + +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; +import org.springframework.transaction.event.TransactionalEventListener; + +@Log4j2 +@Component +@RequiredArgsConstructor +public class PaymentProcessedEventListener { + private final ApplicationEventPublisher eventPublisher; + + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void handlePaymentSuccessEvent(PaymentProcessedEvent event) { + log.info("Handle PaymentProcessedEvent (AFTER_COMMIT)..."); + log.info("Publish OrderCreatedEvent."); + eventPublisher.publishEvent(new OrderCreatedEvent()); + } + + @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/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..26437b2 --- /dev/null +++ b/src/main/java/org/javaspringcourse/order/service/OrderService.java @@ -0,0 +1,24 @@ +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..."); + + 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 new file mode 100644 index 0000000..07057e9 --- /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 handleOrderCreatingEvent(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..8af6230 --- /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 { + + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void handlePaymentSuccessEvent(PaymentProcessedEvent event) { + log.info("Handle PaymentProcessedEvent (AFTER_COMMIT)..."); + log.info("Payment was successful."); + } + + @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/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..33b854f --- /dev/null +++ b/src/main/java/org/javaspringcourse/payment/service/PaymentService.java @@ -0,0 +1,34 @@ +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); + log.info("Publish PaymentProcessedEvent."); + 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