BAEL-7143: virtual threads vs webflux
This commit is contained in:
parent
eff24a8f7e
commit
208ff083c8
|
@ -28,6 +28,7 @@
|
|||
<module>spring-reactive-data-couchbase</module>
|
||||
<module>spring-reactive</module>
|
||||
<module>spring-reactive-exceptions</module>
|
||||
<!-- <module>spring-reactive-performance</module>-->
|
||||
<module>spring-reactor</module>
|
||||
<module>spring-webflux-amqp</module>
|
||||
</modules>
|
||||
|
|
|
@ -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/
|
|
@ -0,0 +1,3 @@
|
|||
## Spring Reactive Performance
|
||||
|
||||
This module contains articles about reactive Spring Boot.
|
|
@ -0,0 +1,77 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.baeldung.spring</groupId>
|
||||
<artifactId>spring-reactive-performance</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<name>spring-reactive-performance</name>
|
||||
<packaging>jar</packaging>
|
||||
<description>Spring Reactive Performance</description>
|
||||
|
||||
<parent>
|
||||
<groupId>com.baeldung.spring.reactive</groupId>
|
||||
<artifactId>spring-reactive-modules</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<!-- Import dependency management from Spring Boot -->
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-mongodb</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>21</source>
|
||||
<target>21</target>
|
||||
<!-- Needed due to a bug with JDK 21, described here: https://issues.apache.org/jira/browse/MCOMPILER-546?page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel&focusedCommentId=17767513 -->
|
||||
<debug>false</debug>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<argLine>--enable-preview</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<properties>
|
||||
<java.version>21</java.version>
|
||||
<spring-boot.version>3.2.0</spring-boot.version>
|
||||
</properties>
|
||||
</project>
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package com.baeldung.spring.reactive.performance;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class KafkaTemplate<K, V> {
|
||||
|
||||
// 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<Void> send(String topic, K key, V value) {
|
||||
System.out.println("Sending message to topic: " + topic + " with value: " + value);
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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<Product, String> {
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
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<String, ProductAddedToCartEvent> 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();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package com.baeldung.spring.reactive.performance.webflux;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
class DiscountService {
|
||||
public Mono<BigDecimal> discountForProduct(String productId) {
|
||||
return Mono.just(BigDecimal.valueOf(10));
|
||||
}
|
||||
}
|
|
@ -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<Product, String> {
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
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<String, ProductAddedToCartEvent> 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<Price> computePrice(Product product) {
|
||||
if (product.category()
|
||||
.isEligibleForDiscount()) {
|
||||
return discountService.discountForProduct(product.id())
|
||||
.map(product.basePrice()::applyDiscount);
|
||||
}
|
||||
return Mono.just(product.basePrice());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
|
||||
</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
</configuration>
|
|
@ -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() {
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.baeldung.spring.amqp;
|
||||
package com.baeldung.spring.reactive.performance;
|
||||
|
||||
import java.time.Duration;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.baeldung.spring.amqp;
|
||||
package com.baeldung.spring.reactive.performance;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
|
@ -1,4 +1,4 @@
|
|||
package com.baeldung.spring.amqp;
|
||||
package com.baeldung.spring.reactive.performance;
|
||||
|
||||
import org.springframework.amqp.core.AcknowledgeMode;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
|
@ -1,4 +1,4 @@
|
|||
package com.baeldung.spring.amqp;
|
||||
package com.baeldung.spring.reactive.performance;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|
@ -1,4 +1,4 @@
|
|||
package com.baeldung.spring.amqp;
|
||||
package com.baeldung.spring.reactive.performance;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
Loading…
Reference in New Issue