diff --git a/spring-reactive-modules/pom.xml b/spring-reactive-modules/pom.xml index c349707027..6ab85c88b4 100644 --- a/spring-reactive-modules/pom.xml +++ b/spring-reactive-modules/pom.xml @@ -30,6 +30,9 @@ spring-reactive-exceptions spring-reactor spring-webflux-amqp + + + diff --git a/spring-reactive-modules/spring-reactive-performance/.gitignore b/spring-reactive-modules/spring-reactive-performance/.gitignore new file mode 100644 index 0000000000..82eca336e3 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-performance/.gitignore @@ -0,0 +1,25 @@ +/target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/build/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ \ No newline at end of file diff --git a/spring-reactive-modules/spring-reactive-performance/README.md b/spring-reactive-modules/spring-reactive-performance/README.md new file mode 100644 index 0000000000..16aac3d419 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-performance/README.md @@ -0,0 +1,3 @@ +## Spring Reactive Performance + +This module contains articles about reactive Spring Boot. diff --git a/spring-reactive-modules/spring-reactive-performance/pom.xml b/spring-reactive-modules/spring-reactive-performance/pom.xml new file mode 100644 index 0000000000..d7a69560dd --- /dev/null +++ b/spring-reactive-modules/spring-reactive-performance/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + com.baeldung.spring + spring-reactive-performance + 1.0.0-SNAPSHOT + spring-reactive-performance + jar + Spring Reactive Performance + + + com.baeldung.spring.reactive + spring-reactive-modules + 1.0.0-SNAPSHOT + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + + + org.springframework.boot + spring-boot-starter-webflux + + + + org.springframework.boot + spring-boot-starter-webflux + + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 21 + 21 + + false + + + + org.apache.maven.plugins + maven-surefire-plugin + + --enable-preview + + + + + + + 21 + 3.2.0 + + \ No newline at end of file diff --git a/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/Application.java b/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/Application.java new file mode 100644 index 0000000000..284ddfce30 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/Application.java @@ -0,0 +1,13 @@ +package com.baeldung.spring.reactive.performance; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + +} diff --git a/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/KafkaTemplate.java b/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/KafkaTemplate.java new file mode 100644 index 0000000000..cf8684690e --- /dev/null +++ b/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/KafkaTemplate.java @@ -0,0 +1,14 @@ +package com.baeldung.spring.reactive.performance; + +import java.util.concurrent.CompletableFuture; + +public class KafkaTemplate { + + // For simplicity in this example and article, an actual Kafka client isn't utilized. + // The focus remains on demonstrating the basic principles without the complexities of a full Kafka client setup. + + public CompletableFuture send(String topic, K key, V value) { + System.out.println("Sending message to topic: " + topic + " with value: " + value); + return CompletableFuture.completedFuture(null); + } +} \ No newline at end of file diff --git a/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/ProductAddedToCartEvent.java b/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/ProductAddedToCartEvent.java new file mode 100644 index 0000000000..08743d109c --- /dev/null +++ b/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/ProductAddedToCartEvent.java @@ -0,0 +1,6 @@ +package com.baeldung.spring.reactive.performance; + +import java.math.BigDecimal; + +public record ProductAddedToCartEvent(String productId, BigDecimal price, String currency, String cartId) { +} \ No newline at end of file diff --git a/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/model/Price.java b/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/model/Price.java new file mode 100644 index 0000000000..271ca26c96 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/model/Price.java @@ -0,0 +1,9 @@ +package com.baeldung.spring.reactive.performance.model; + +import java.math.BigDecimal; + +public record Price(BigDecimal value, String currency) { + public Price applyDiscount(BigDecimal discount) { + return new Price(value.subtract(discount), currency); + } +} \ No newline at end of file diff --git a/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/model/Product.java b/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/model/Product.java new file mode 100644 index 0000000000..d4aa751089 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/model/Product.java @@ -0,0 +1,43 @@ +package com.baeldung.spring.reactive.performance.model; + +import java.math.BigDecimal; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document(collation = "products") +public record Product ( + @Id + String id, + String name, + BigDecimal basePriceValue, + String currency, + Category category +) { + + public Price basePrice() { + return new Price(basePriceValue, currency); + } + + public enum Category { + ELECTRONICS(false), + CLOTHING(true), + ACCESSORIES(false), + GARDENING(false), + SPORTS(true); + + private final boolean eligibleForPromotion; + + Category(boolean eligibleForPromotion) { + this.eligibleForPromotion = eligibleForPromotion; + } + + public boolean isEligibleForDiscount() { + return eligibleForPromotion; + } + + public boolean notEligibleForPromotion() { + return !eligibleForPromotion; + } + } +} \ No newline at end of file diff --git a/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/virtualthreads/DiscountService.java b/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/virtualthreads/DiscountService.java new file mode 100644 index 0000000000..89554396b2 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/virtualthreads/DiscountService.java @@ -0,0 +1,11 @@ +package com.baeldung.spring.reactive.performance.virtualthreads; + +import java.math.BigDecimal; + +class DiscountService { + + public BigDecimal discountForProduct(String productId) { + return BigDecimal.valueOf(10); + } + +} \ No newline at end of file diff --git a/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/virtualthreads/ProductRepository.java b/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/virtualthreads/ProductRepository.java new file mode 100644 index 0000000000..f0ebff5cfd --- /dev/null +++ b/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/virtualthreads/ProductRepository.java @@ -0,0 +1,8 @@ +package com.baeldung.spring.reactive.performance.virtualthreads; + +import org.springframework.data.mongodb.repository.MongoRepository; + +import com.baeldung.spring.reactive.performance.model.Product; + +interface ProductRepository extends MongoRepository { +} \ No newline at end of file diff --git a/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/virtualthreads/ProductService.java b/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/virtualthreads/ProductService.java new file mode 100644 index 0000000000..f174e4c6fe --- /dev/null +++ b/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/virtualthreads/ProductService.java @@ -0,0 +1,45 @@ +package com.baeldung.spring.reactive.performance.virtualthreads; + +import java.math.BigDecimal; + +import com.baeldung.spring.reactive.performance.KafkaTemplate; +import com.baeldung.spring.reactive.performance.ProductAddedToCartEvent; +import com.baeldung.spring.reactive.performance.model.Price; +import com.baeldung.spring.reactive.performance.model.Product; + +class ProductService { + private final String PRODUCT_ADDED_TO_CART_TOPIC = "product-added-to-cart"; + + private final ProductRepository repository; + private final DiscountService discountService; + private final KafkaTemplate kafkaTemplate; + + public ProductService(ProductRepository repository, DiscountService discountService) { + this.repository = repository; + this.discountService = discountService; + this.kafkaTemplate = new KafkaTemplate<>(); + } + + public void addProductToCart(String productId, String cartId) { + Thread.startVirtualThread(() -> computePriceAndPublishMessage(productId, cartId)); + } + + private void computePriceAndPublishMessage(String productId, String cartId) { + Product product = repository.findById(productId) + .orElseThrow(() -> new IllegalArgumentException("not found!")); + + Price price = computePrice(productId, product); + + var event = new ProductAddedToCartEvent(productId, price.value(), price.currency(), cartId); + kafkaTemplate.send(PRODUCT_ADDED_TO_CART_TOPIC, cartId, event); + } + + private Price computePrice(String productId, Product product) { + if (product.category().isEligibleForDiscount()) { + BigDecimal discount = discountService.discountForProduct(productId); + return product.basePrice().applyDiscount(discount); + } + return product.basePrice(); + } + +} \ No newline at end of file diff --git a/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/webflux/DiscountService.java b/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/webflux/DiscountService.java new file mode 100644 index 0000000000..ebe6b9a8a4 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/webflux/DiscountService.java @@ -0,0 +1,11 @@ +package com.baeldung.spring.reactive.performance.webflux; + +import java.math.BigDecimal; + +import reactor.core.publisher.Mono; + +class DiscountService { + public Mono discountForProduct(String productId) { + return Mono.just(BigDecimal.valueOf(10)); + } +} \ No newline at end of file diff --git a/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/webflux/ProductRepository.java b/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/webflux/ProductRepository.java new file mode 100644 index 0000000000..5ab4497a27 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/webflux/ProductRepository.java @@ -0,0 +1,8 @@ +package com.baeldung.spring.reactive.performance.webflux; + +import org.springframework.data.mongodb.repository.ReactiveMongoRepository; + +import com.baeldung.spring.reactive.performance.model.Product; + +interface ProductRepository extends ReactiveMongoRepository { +} \ No newline at end of file diff --git a/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/webflux/ProductService.java b/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/webflux/ProductService.java new file mode 100644 index 0000000000..90519ec627 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-performance/src/main/java/com/baeldung/spring/reactive/performance/webflux/ProductService.java @@ -0,0 +1,39 @@ +package com.baeldung.spring.reactive.performance.webflux; + +import com.baeldung.spring.reactive.performance.KafkaTemplate; +import com.baeldung.spring.reactive.performance.ProductAddedToCartEvent; +import com.baeldung.spring.reactive.performance.model.Price; +import com.baeldung.spring.reactive.performance.model.Product; + +import reactor.core.publisher.Mono; + +class ProductService { + private final String PRODUCT_ADDED_TO_CART_TOPIC = "product-added-to-cart"; + + private final ProductRepository repository; + private final DiscountService discountService; + private final KafkaTemplate kafkaTemplate; + + public ProductService(ProductRepository repository, DiscountService discountService) { + this.repository = repository; + this.discountService = discountService; + this.kafkaTemplate = new KafkaTemplate<>(); + } + + public void addProductToCart(String productId, String cartId) { + repository.findById(productId) + .switchIfEmpty(Mono.error(() -> new IllegalArgumentException("not found!"))) + .flatMap(this::computePrice) + .map(price -> new ProductAddedToCartEvent(productId, price.value(), price.currency(), cartId)) + .subscribe(event -> kafkaTemplate.send(PRODUCT_ADDED_TO_CART_TOPIC, cartId, event)); + } + + private Mono computePrice(Product product) { + if (product.category().isEligibleForDiscount()) { + return discountService.discountForProduct(product.id()) + .map(product.basePrice()::applyDiscount); + } + return Mono.just(product.basePrice()); + } + +} \ No newline at end of file diff --git a/spring-reactive-modules/spring-reactive-performance/src/main/resources/logback.xml b/spring-reactive-modules/spring-reactive-performance/src/main/resources/logback.xml new file mode 100644 index 0000000000..7d900d8ea8 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-performance/src/main/resources/logback.xml @@ -0,0 +1,13 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/spring-reactive-modules/spring-reactive-performance/src/test/java/com/baeldung/spring/reactive/performance/ApplicationUnitTest.java b/spring-reactive-modules/spring-reactive-performance/src/test/java/com/baeldung/spring/reactive/performance/ApplicationUnitTest.java new file mode 100644 index 0000000000..df69e7eb72 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-performance/src/test/java/com/baeldung/spring/reactive/performance/ApplicationUnitTest.java @@ -0,0 +1,12 @@ +package com.baeldung.spring.reactive.performance; + +import org.junit.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class ApplicationUnitTest { + + @Test + public void whenSpringContextIsBootstrapped_thenNoExceptions() { + } +}