From 7a65b4027e8a360ab787d30f32a6759a233c05d7 Mon Sep 17 00:00:00 2001 From: David Kopp Date: Sat, 16 Dec 2023 12:54:10 +0100 Subject: [PATCH] Add simulation of compute intensive task --- README.md | 22 ++++--- docker-compose.yml | 2 + .../t2/modulith/order/OrderService.java | 60 +++++++++--------- .../ComputeIntensiveTotalCalculator.java | 62 +++++++++++++++++++ .../order/calculation/ITotalCalculator.java | 6 ++ .../calculation/SimpleTotalCalculator.java | 48 ++++++++++++++ src/main/resources/application.yaml | 4 ++ .../de/unistuttgart/t2/modulith/TestData.java | 10 ++- .../t2/modulith/order/CalculatorTests.java | 52 ++++++++++++++++ .../t2/modulith/order/OrderServiceTests.java | 12 ++-- src/test/resources/application.yaml | 4 ++ 11 files changed, 236 insertions(+), 46 deletions(-) create mode 100644 src/main/java/de/unistuttgart/t2/modulith/order/calculation/ComputeIntensiveTotalCalculator.java create mode 100644 src/main/java/de/unistuttgart/t2/modulith/order/calculation/ITotalCalculator.java create mode 100644 src/main/java/de/unistuttgart/t2/modulith/order/calculation/SimpleTotalCalculator.java create mode 100644 src/test/java/de/unistuttgart/t2/modulith/order/CalculatorTests.java diff --git a/README.md b/README.md index db45159..b8493e1 100644 --- a/README.md +++ b/README.md @@ -23,16 +23,18 @@ Depending on your active Spring profiles, different property files are used. See **T2 configuration:** -| property | read from env var | description | -|-------------------------------|-------------------------------|-------------------------------------------------------------------------------------------------| -| t2.cart.TTL | T2_CART_TTL | time to live of items in cart (in seconds) | -| t2.cart.taskRate | T2_CART_TASKRATE | rate at which the cart checks for items that exceeded their TTL (in milliseconds) | -| t2.inventory.size | T2_INVENTORY_SIZE | number of items to be generated into the inventory repository on start up | -| t2.inventory.TTL | T2_INVENTORY_TTL | time to live of reservations (in seconds) | -| t2.inventory.taskRate | T2_INVENTORY_TASKRATE | rate at which the inventory checks for reservations that exceeded their TTL (in milliseconds). | -| t2.payment.provider.enabled | T2_PAYMENT_PROVIDER_ENABLED | boolean value, defaults to true. if false, no connection to payment provider is made. | -| t2.payment.provider.timeout | T2_PAYMENT_PROVIDER_TIMEOUT | timeout in seconds. the payment service waits this long for an reply from the payment provider. | -| t2.payment.provider.dummy.url | T2_PAYMENT_PROVIDER_DUMMY_URL | url of the payment provider. | +| property | read from env var | description | +|--------------------------------------------------|-----------------------------------------------|------------------------------------------------------------------------------------------------------------------------------| +| t2.cart.TTL | T2_CART_TTL | time to live of items in cart (in seconds) | +| t2.cart.taskRate | T2_CART_TASKRATE | rate at which the cart checks for items that exceeded their TTL (in milliseconds) | +| t2.inventory.size | T2_INVENTORY_SIZE | number of items to be generated into the inventory repository on start up | +| t2.inventory.TTL | T2_INVENTORY_TTL | time to live of reservations (in seconds) | +| t2.inventory.taskRate | T2_INVENTORY_TASKRATE | rate at which the inventory checks for reservations that exceeded their TTL (in milliseconds). | +| t2.payment.provider.enabled | T2_PAYMENT_PROVIDER_ENABLED | boolean value, defaults to true. if false, no connection to payment provider is made. | +| t2.payment.provider.timeout | T2_PAYMENT_PROVIDER_TIMEOUT | timeout in seconds. the payment service waits this long for an reply from the payment provider. | +| t2.payment.provider.dummy.url | T2_PAYMENT_PROVIDER_DUMMY_URL | url of the payment provider. | +| t2.order.simulateComputeIntensiveTask.enabled | T2_SIMULATE_COMPUTE_INTENSIVE_TASK_ENABLED | boolean value, defaults to false. if true, a compute intensive calculation method gets used to calculate the order total | +| t2.order.simulateComputeIntensiveTask.iterations | T2_SIMULATE_COMPUTE_INTENSIVE_TASK_ITERATIONS | number of iterations the compute intensive calculation method uses. 1000000000 needs around 10 sec (depends on your machine) | Setting either `TTL` or `taskrate` to a value less or equal to zero disables the collection of expired entries (cart module and inventory module). diff --git a/docker-compose.yml b/docker-compose.yml index 1cd2eeb..1269b63 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,6 +19,8 @@ services: T2_PAYMENT_PROVIDER_DUMMY_URL: http://creditinstitute:8080/pay T2_PAYMENT_PROVIDER_TIMEOUT: 5 T2_PAYMENT_PROVIDER_ENABLED: true + T2_SIMULATE_COMPUTE_INTENSIVE_TASK_ENABLED: false + T2_SIMULATE_COMPUTE_INTENSIVE_TASK_ITERATIONS: 1000000000 SPRING_DATASOURCE_DRIVER_CLASS_NAME: org.postgresql.Driver SPRING_DATASOURCE_USERNAME: postgres SPRING_DATASOURCE_PASSWORD: postgres diff --git a/src/main/java/de/unistuttgart/t2/modulith/order/OrderService.java b/src/main/java/de/unistuttgart/t2/modulith/order/OrderService.java index c8c9f47..f2eab5c 100644 --- a/src/main/java/de/unistuttgart/t2/modulith/order/OrderService.java +++ b/src/main/java/de/unistuttgart/t2/modulith/order/OrderService.java @@ -1,9 +1,10 @@ package de.unistuttgart.t2.modulith.order; -import de.unistuttgart.t2.modulith.cart.CartContent; import de.unistuttgart.t2.modulith.cart.CartService; import de.unistuttgart.t2.modulith.inventory.InventoryService; -import de.unistuttgart.t2.modulith.inventory.Product; +import de.unistuttgart.t2.modulith.order.calculation.ComputeIntensiveTotalCalculator; +import de.unistuttgart.t2.modulith.order.calculation.SimpleTotalCalculator; +import de.unistuttgart.t2.modulith.order.calculation.ITotalCalculator; import de.unistuttgart.t2.modulith.order.repository.OrderItem; import de.unistuttgart.t2.modulith.order.repository.OrderRepository; import de.unistuttgart.t2.modulith.order.repository.OrderStatus; @@ -12,11 +13,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; import org.springframework.stereotype.Service; import java.util.NoSuchElementException; -import java.util.Optional; /** * Creates and updates orders. @@ -36,16 +37,41 @@ public class OrderService { private final OrderRepository orderRepository; + private final ITotalCalculator totalCalculator; + private final Logger LOG = LoggerFactory.getLogger(getClass()); + public OrderService(CartService cartService, + InventoryService inventoryService, + PaymentService paymentService, + OrderRepository orderRepository) { + this.cartService = cartService; + this.inventoryService = inventoryService; + this.paymentService = paymentService; + this.orderRepository = orderRepository; + + this.totalCalculator = new SimpleTotalCalculator(cartService, inventoryService); + } + + @Autowired public OrderService(@Autowired CartService cartService, @Autowired InventoryService inventoryService, @Autowired PaymentService paymentService, - @Autowired OrderRepository orderRepository) { + @Autowired OrderRepository orderRepository, + @Value("${t2.order.simulateComputeIntensiveTask.enabled}") boolean simulateComputeIntensiveTask, + @Value("${t2.order.simulateComputeIntensiveTask.iterations}") int simulateComputeIntensiveTaskIterations) { this.cartService = cartService; this.inventoryService = inventoryService; this.paymentService = paymentService; this.orderRepository = orderRepository; + + if (!simulateComputeIntensiveTask) { + this.totalCalculator = new SimpleTotalCalculator(cartService, inventoryService); + } else { + this.totalCalculator = new ComputeIntensiveTotalCalculator(cartService, inventoryService, simulateComputeIntensiveTaskIterations); + LOG.warn("Simulate compute intensive task enabled! Order total will be calculated {} times.", + simulateComputeIntensiveTaskIterations); + } } /** @@ -96,7 +122,7 @@ public String confirmOrder(String sessionId, String cardNumber, String cardOwner // Calculating total double total; try { - total = getTotal(sessionId); + total = totalCalculator.calculate(sessionId); } catch (Exception e) { throw new Exception(String.format("No order placed for session '%s'. Calculating total failed.", sessionId), e); } @@ -128,28 +154,4 @@ public String confirmOrder(String sessionId, String cardNumber, String cardOwner return orderId; } - - /** - * Calculates the total of a users cart. - *

- * Depends on the cart module to get the cart content and depends on the inventory module to get the price per - * unit. - * - * @param sessionId identifies the session to get total for - * @return the total money to pay for products in the cart - */ - private double getTotal(String sessionId) { - CartContent cart = cartService.getCart(sessionId).orElse(new CartContent()); - - double total = 0; - - for (String productId : cart.getProductIds()) { - Optional product = inventoryService.getSingleProduct(productId); - if (product.isEmpty()) { - return 0; - } - total += product.get().getPrice() * cart.getUnits(productId); - } - return total; - } } diff --git a/src/main/java/de/unistuttgart/t2/modulith/order/calculation/ComputeIntensiveTotalCalculator.java b/src/main/java/de/unistuttgart/t2/modulith/order/calculation/ComputeIntensiveTotalCalculator.java new file mode 100644 index 0000000..7ecbd7e --- /dev/null +++ b/src/main/java/de/unistuttgart/t2/modulith/order/calculation/ComputeIntensiveTotalCalculator.java @@ -0,0 +1,62 @@ +package de.unistuttgart.t2.modulith.order.calculation; + +import de.unistuttgart.t2.modulith.cart.CartContent; +import de.unistuttgart.t2.modulith.cart.CartService; +import de.unistuttgart.t2.modulith.inventory.InventoryService; +import de.unistuttgart.t2.modulith.inventory.Product; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Optional; + +/** + * Calculator to simulate a compute intensive task. + * Calculates the total sum of order. + * + * @author davidkopp + */ +public class ComputeIntensiveTotalCalculator implements ITotalCalculator { + + private final CartService cartService; + private final InventoryService inventoryService; + private final int iterations; + + private final Logger LOG = LoggerFactory.getLogger(getClass()); + + public ComputeIntensiveTotalCalculator(CartService cartService, InventoryService inventoryService, int iterations) { + this.cartService = cartService; + this.inventoryService = inventoryService; + this.iterations = iterations; + } + + /** + * Calculates the total of a users cart many times to simulate a compute intensive task. + *

+ * Depends on the cart module to get the cart content and depends on the inventory module to get the price per + * unit. + * + * @param sessionId identifies the session to get total for + * @return the total money to pay for products in the cart + */ + public double calculate(String sessionId) { + LOG.debug("Compute intensive order calculation started with {} iterations", iterations); + CartContent cart = cartService.getCart(sessionId).orElse(new CartContent()); + + double total = 0; + for (String productId : cart.getProductIds()) { + Optional product = inventoryService.getSingleProduct(productId); + if (product.isEmpty()) { + return 0; + } + + // simulate compute intensive task + double temp = 0; + for (int i = 0; i < iterations; i++) { + temp += product.get().getPrice() * cart.getUnits(productId); + } + total += temp / iterations; + } + LOG.debug("Compute intensive order calculation finished"); + return total; + } +} diff --git a/src/main/java/de/unistuttgart/t2/modulith/order/calculation/ITotalCalculator.java b/src/main/java/de/unistuttgart/t2/modulith/order/calculation/ITotalCalculator.java new file mode 100644 index 0000000..355221d --- /dev/null +++ b/src/main/java/de/unistuttgart/t2/modulith/order/calculation/ITotalCalculator.java @@ -0,0 +1,6 @@ +package de.unistuttgart.t2.modulith.order.calculation; + +public interface ITotalCalculator { + + double calculate(String sessionId); +} diff --git a/src/main/java/de/unistuttgart/t2/modulith/order/calculation/SimpleTotalCalculator.java b/src/main/java/de/unistuttgart/t2/modulith/order/calculation/SimpleTotalCalculator.java new file mode 100644 index 0000000..682d111 --- /dev/null +++ b/src/main/java/de/unistuttgart/t2/modulith/order/calculation/SimpleTotalCalculator.java @@ -0,0 +1,48 @@ +package de.unistuttgart.t2.modulith.order.calculation; + +import de.unistuttgart.t2.modulith.cart.CartContent; +import de.unistuttgart.t2.modulith.cart.CartService; +import de.unistuttgart.t2.modulith.inventory.InventoryService; +import de.unistuttgart.t2.modulith.inventory.Product; + +import java.util.Optional; + +/** + * Simple calculator to get total sum of order (default). + * + * @author davidkopp + */ +public class SimpleTotalCalculator implements ITotalCalculator { + + private final CartService cartService; + private final InventoryService inventoryService; + + public SimpleTotalCalculator(CartService cartService, InventoryService inventoryService) { + this.cartService = cartService; + this.inventoryService = inventoryService; + } + + /** + * Calculates the total of a users cart. + *

+ * Depends on the cart module to get the cart content and depends on the inventory module to get the price per + * unit. + * + * @param sessionId identifies the session to get total for + * @return the total money to pay for products in the cart + */ + public double calculate(String sessionId) { + CartContent cart = cartService.getCart(sessionId).orElse(new CartContent()); + + double total = 0; + + for (String productId : cart.getProductIds()) { + Optional product = inventoryService.getSingleProduct(productId); + if (product.isEmpty()) { + return 0; + } + total += product.get().getPrice() * cart.getUnits(productId); + } + return total; + } +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 85b9e45..b4278d5 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -32,3 +32,7 @@ t2: timeout: ${T2_PAYMENT_PROVIDER_TIMEOUT:5} dummy: url: ${T2_PAYMENT_PROVIDER_DUMMY_URL} + order: + simulateComputeIntensiveTask: + enabled: ${T2_SIMULATE_COMPUTE_INTENSIVE_TASK_ENABLED:false} + iterations: ${T2_SIMULATE_COMPUTE_INTENSIVE_TASK_ITERATIONS:1000000000} diff --git a/src/test/java/de/unistuttgart/t2/modulith/TestData.java b/src/test/java/de/unistuttgart/t2/modulith/TestData.java index f9c2e5a..6d06883 100644 --- a/src/test/java/de/unistuttgart/t2/modulith/TestData.java +++ b/src/test/java/de/unistuttgart/t2/modulith/TestData.java @@ -18,6 +18,10 @@ public final class TestData { public static String anotherProductId = "foo2"; public static int anotherUnits = 42; public static String sessionId = "bar"; + private static final double price = 1.0; + private static final double anotherPrice = 2.0; + public static double totalOfCart = units * price; + public static double totalOfCartMulti = (units * price) + (anotherUnits * anotherPrice); public static Optional cartResponse() { return Optional.of(new CartContent(new HashMap<>(Map.of(productId, units)))); @@ -45,15 +49,15 @@ public static List productsBasedOnCartContent(CartContent cartContent) } public static Product productBase(String productId, int units) { - return new Product(productId, "name", "description", units, 1.0); + return new Product(productId, "name", "description", units, price); } public static Optional inventoryResponse() { - return Optional.of(new Product(productId, "name", "description", 5, 1.0)); + return Optional.of(new Product(productId, "name", "description", 5, price)); } public static Optional anotherInventoryResponse() { - return Optional.of(new Product(anotherProductId, "name2", "description2", 5, 1.0)); + return Optional.of(new Product(anotherProductId, "name2", "description2", 5, anotherPrice)); } public static List inventoryResponseAllProducts() { diff --git a/src/test/java/de/unistuttgart/t2/modulith/order/CalculatorTests.java b/src/test/java/de/unistuttgart/t2/modulith/order/CalculatorTests.java new file mode 100644 index 0000000..45d765c --- /dev/null +++ b/src/test/java/de/unistuttgart/t2/modulith/order/CalculatorTests.java @@ -0,0 +1,52 @@ +package de.unistuttgart.t2.modulith.order; + +import de.unistuttgart.t2.modulith.cart.CartService; +import de.unistuttgart.t2.modulith.inventory.InventoryService; +import de.unistuttgart.t2.modulith.order.calculation.ComputeIntensiveTotalCalculator; +import de.unistuttgart.t2.modulith.order.calculation.SimpleTotalCalculator; +import de.unistuttgart.t2.modulith.order.calculation.ITotalCalculator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.context.ActiveProfiles; + +import static de.unistuttgart.t2.modulith.TestData.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@ActiveProfiles("test") +public class CalculatorTests { + + @Mock + CartService cartService; + + @Mock + InventoryService inventoryService; + + @BeforeEach + public void beforeEach() { + when(cartService.getCart(sessionId)).thenReturn(cartResponseMulti()); + when(inventoryService.getSingleProduct(productId)).thenReturn(inventoryResponse()); + when(inventoryService.getSingleProduct(anotherProductId)).thenReturn(anotherInventoryResponse()); + } + + @Test + public void defaultCalculator() { + ITotalCalculator calculator = new SimpleTotalCalculator(cartService, inventoryService); + double result = calculator.calculate(sessionId); + + assertEquals(totalOfCartMulti, result); + } + + @Test + public void computeIntensiveCalculator() { + int iterations = 1_000_0000; // use a higher number like e.g. 1_000_000_000 to see that it is actually compute intensive + ITotalCalculator calculator = new ComputeIntensiveTotalCalculator(cartService, inventoryService, iterations); + double result = calculator.calculate(sessionId); + + assertEquals(totalOfCartMulti, result); + } +} diff --git a/src/test/java/de/unistuttgart/t2/modulith/order/OrderServiceTests.java b/src/test/java/de/unistuttgart/t2/modulith/order/OrderServiceTests.java index 102de2b..2c0b32a 100644 --- a/src/test/java/de/unistuttgart/t2/modulith/order/OrderServiceTests.java +++ b/src/test/java/de/unistuttgart/t2/modulith/order/OrderServiceTests.java @@ -5,9 +5,9 @@ import de.unistuttgart.t2.modulith.order.repository.OrderItem; import de.unistuttgart.t2.modulith.order.repository.OrderRepository; import de.unistuttgart.t2.modulith.payment.PaymentService; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.context.ActiveProfiles; @@ -19,9 +19,6 @@ @ActiveProfiles("test") public class OrderServiceTests { - @InjectMocks - OrderService orderService; - @Mock CartService cartService; @@ -34,6 +31,13 @@ public class OrderServiceTests { @Mock OrderRepository orderRepository; + OrderService orderService; + + @BeforeEach + public void beforeEach() { + this.orderService = new OrderService(cartService, inventoryService, paymentService, orderRepository); + } + @Test public void confirmOrderSucceeds() throws Exception { diff --git a/src/test/resources/application.yaml b/src/test/resources/application.yaml index 34fc71f..bb88cb0 100644 --- a/src/test/resources/application.yaml +++ b/src/test/resources/application.yaml @@ -42,3 +42,7 @@ t2: timeout: 5 dummy: url: http://foo.bar/pay + order: + simulateComputeIntensiveTask: + enabled: false + iterations: 1