From b585b6eb7bfe2cd1cb2aa8c5275b9519f052af3a Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Fri, 19 Mar 2021 10:53:34 +0100 Subject: [PATCH 01/12] Update Axon version Update Axon version to 4.4.7 (current most recent) #BAEL-4767 --- axon/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axon/pom.xml b/axon/pom.xml index f6c43c7cbd..f2cdc34fd1 100644 --- a/axon/pom.xml +++ b/axon/pom.xml @@ -53,7 +53,7 @@ - 4.1.2 + 4.4.7 \ No newline at end of file From 10c0f2302e9f2afeb70858c7adcd9a24397f76f7 Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Fri, 19 Mar 2021 10:55:15 +0100 Subject: [PATCH 02/12] Update Command API - Adjust PlaceOrderCommand to only construct the order - Introduce AddProductCommand to be able to add several products to an order - Introduce IncrementProductCountCommand to increase the number of product instances for a given Order - Introduce DecrementProductCountCommand to increase the number of product instances for a given Order #BAEL-4767 --- .../coreapi/commands/AddProductCommand.java | 50 +++++++++++++++++++ .../DecrementProductCountCommand.java | 50 +++++++++++++++++++ .../IncrementProductCountCommand.java | 50 +++++++++++++++++++ .../coreapi/commands/PlaceOrderCommand.java | 36 ++++++------- 4 files changed, 164 insertions(+), 22 deletions(-) create mode 100644 axon/src/main/java/com/baeldung/axon/coreapi/commands/AddProductCommand.java create mode 100644 axon/src/main/java/com/baeldung/axon/coreapi/commands/DecrementProductCountCommand.java create mode 100644 axon/src/main/java/com/baeldung/axon/coreapi/commands/IncrementProductCountCommand.java diff --git a/axon/src/main/java/com/baeldung/axon/coreapi/commands/AddProductCommand.java b/axon/src/main/java/com/baeldung/axon/coreapi/commands/AddProductCommand.java new file mode 100644 index 0000000000..28736aaadc --- /dev/null +++ b/axon/src/main/java/com/baeldung/axon/coreapi/commands/AddProductCommand.java @@ -0,0 +1,50 @@ +package com.baeldung.axon.coreapi.commands; + +import org.axonframework.modelling.command.TargetAggregateIdentifier; + +import java.util.Objects; + +public class AddProductCommand { + + @TargetAggregateIdentifier + private final String orderId; + private final String productId; + + public AddProductCommand(String orderId, String productId) { + this.orderId = orderId; + this.productId = productId; + } + + public String getOrderId() { + return orderId; + } + + public String getProductId() { + return productId; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AddProductCommand that = (AddProductCommand) o; + return Objects.equals(orderId, that.orderId) && Objects.equals(productId, that.productId); + } + + @Override + public int hashCode() { + return Objects.hash(orderId, productId); + } + + @Override + public String toString() { + return "AddProductCommand{" + + "orderId='" + orderId + '\'' + + ", productId='" + productId + '\'' + + '}'; + } +} diff --git a/axon/src/main/java/com/baeldung/axon/coreapi/commands/DecrementProductCountCommand.java b/axon/src/main/java/com/baeldung/axon/coreapi/commands/DecrementProductCountCommand.java new file mode 100644 index 0000000000..f6f4db00fc --- /dev/null +++ b/axon/src/main/java/com/baeldung/axon/coreapi/commands/DecrementProductCountCommand.java @@ -0,0 +1,50 @@ +package com.baeldung.axon.coreapi.commands; + +import org.axonframework.modelling.command.TargetAggregateIdentifier; + +import java.util.Objects; + +public class DecrementProductCountCommand { + + @TargetAggregateIdentifier + private final String orderId; + private final String productId; + + public DecrementProductCountCommand(String orderId, String productId) { + this.orderId = orderId; + this.productId = productId; + } + + public String getOrderId() { + return orderId; + } + + public String getProductId() { + return productId; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DecrementProductCountCommand that = (DecrementProductCountCommand) o; + return Objects.equals(orderId, that.orderId) && Objects.equals(productId, that.productId); + } + + @Override + public int hashCode() { + return Objects.hash(orderId, productId); + } + + @Override + public String toString() { + return "DecrementProductCountCommand{" + + "orderId='" + orderId + '\'' + + ", productId='" + productId + '\'' + + '}'; + } +} diff --git a/axon/src/main/java/com/baeldung/axon/coreapi/commands/IncrementProductCountCommand.java b/axon/src/main/java/com/baeldung/axon/coreapi/commands/IncrementProductCountCommand.java new file mode 100644 index 0000000000..548faabe37 --- /dev/null +++ b/axon/src/main/java/com/baeldung/axon/coreapi/commands/IncrementProductCountCommand.java @@ -0,0 +1,50 @@ +package com.baeldung.axon.coreapi.commands; + +import org.axonframework.modelling.command.TargetAggregateIdentifier; + +import java.util.Objects; + +public class IncrementProductCountCommand { + + @TargetAggregateIdentifier + private final String orderId; + private final String productId; + + public IncrementProductCountCommand(String orderId, String productId) { + this.orderId = orderId; + this.productId = productId; + } + + public String getOrderId() { + return orderId; + } + + public String getProductId() { + return productId; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + IncrementProductCountCommand that = (IncrementProductCountCommand) o; + return Objects.equals(orderId, that.orderId) && Objects.equals(productId, that.productId); + } + + @Override + public int hashCode() { + return Objects.hash(orderId, productId); + } + + @Override + public String toString() { + return "IncrementProductCountCommand{" + + "orderId='" + orderId + '\'' + + ", productId='" + productId + '\'' + + '}'; + } +} diff --git a/axon/src/main/java/com/baeldung/axon/coreapi/commands/PlaceOrderCommand.java b/axon/src/main/java/com/baeldung/axon/coreapi/commands/PlaceOrderCommand.java index c70d503050..b631272366 100644 --- a/axon/src/main/java/com/baeldung/axon/coreapi/commands/PlaceOrderCommand.java +++ b/axon/src/main/java/com/baeldung/axon/coreapi/commands/PlaceOrderCommand.java @@ -1,51 +1,43 @@ package com.baeldung.axon.coreapi.commands; -import java.util.Objects; - import org.axonframework.modelling.command.TargetAggregateIdentifier; +import java.util.Objects; + public class PlaceOrderCommand { @TargetAggregateIdentifier private final String orderId; - private final String product; - public PlaceOrderCommand(String orderId, String product) { + public PlaceOrderCommand(String orderId) { this.orderId = orderId; - this.product = product; } public String getOrderId() { return orderId; } - public String getProduct() { - return product; + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PlaceOrderCommand that = (PlaceOrderCommand) o; + return Objects.equals(orderId, that.orderId); } @Override public int hashCode() { - return Objects.hash(orderId, product); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - final PlaceOrderCommand other = (PlaceOrderCommand) obj; - return Objects.equals(this.orderId, other.orderId) - && Objects.equals(this.product, other.product); + return Objects.hash(orderId); } @Override public String toString() { return "PlaceOrderCommand{" + "orderId='" + orderId + '\'' + - ", product='" + product + '\'' + '}'; } } \ No newline at end of file From a9812052df09cfbacc0dec51fcdf8afffe136c63 Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Fri, 19 Mar 2021 10:59:26 +0100 Subject: [PATCH 03/12] Update Event API - Adjust OrderPlacedEvent to only construct the order - Introduce ProductAddedEvent to be able to add several products to a order - Introduce ProductCountIncrementedEvent to increase the number of product instances for a given Order - Introduce ProductCountDecrementedEvent to increase the number of product instances for a given Order - Introduce ProductRemovedEvent to signal whenever all the product count drops below 1 #BAEL-4767 --- .../axon/coreapi/events/OrderPlacedEvent.java | 32 +++++-------- .../coreapi/events/ProductAddedEvent.java | 47 +++++++++++++++++++ .../events/ProductCountDecrementedEvent.java | 47 +++++++++++++++++++ .../events/ProductCountIncrementedEvent.java | 47 +++++++++++++++++++ .../coreapi/events/ProductRemovedEvent.java | 47 +++++++++++++++++++ 5 files changed, 200 insertions(+), 20 deletions(-) create mode 100644 axon/src/main/java/com/baeldung/axon/coreapi/events/ProductAddedEvent.java create mode 100644 axon/src/main/java/com/baeldung/axon/coreapi/events/ProductCountDecrementedEvent.java create mode 100644 axon/src/main/java/com/baeldung/axon/coreapi/events/ProductCountIncrementedEvent.java create mode 100644 axon/src/main/java/com/baeldung/axon/coreapi/events/ProductRemovedEvent.java diff --git a/axon/src/main/java/com/baeldung/axon/coreapi/events/OrderPlacedEvent.java b/axon/src/main/java/com/baeldung/axon/coreapi/events/OrderPlacedEvent.java index 06de4c5f9f..3b9994fc33 100644 --- a/axon/src/main/java/com/baeldung/axon/coreapi/events/OrderPlacedEvent.java +++ b/axon/src/main/java/com/baeldung/axon/coreapi/events/OrderPlacedEvent.java @@ -5,44 +5,36 @@ import java.util.Objects; public class OrderPlacedEvent { private final String orderId; - private final String product; - public OrderPlacedEvent(String orderId, String product) { + public OrderPlacedEvent(String orderId) { this.orderId = orderId; - this.product = product; } public String getOrderId() { return orderId; } - public String getProduct() { - return product; + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + OrderPlacedEvent that = (OrderPlacedEvent) o; + return Objects.equals(orderId, that.orderId); } @Override public int hashCode() { - return Objects.hash(orderId, product); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - final OrderPlacedEvent other = (OrderPlacedEvent) obj; - return Objects.equals(this.orderId, other.orderId) - && Objects.equals(this.product, other.product); + return Objects.hash(orderId); } @Override public String toString() { return "OrderPlacedEvent{" + "orderId='" + orderId + '\'' + - ", product='" + product + '\'' + '}'; } } \ No newline at end of file diff --git a/axon/src/main/java/com/baeldung/axon/coreapi/events/ProductAddedEvent.java b/axon/src/main/java/com/baeldung/axon/coreapi/events/ProductAddedEvent.java new file mode 100644 index 0000000000..091ef2a570 --- /dev/null +++ b/axon/src/main/java/com/baeldung/axon/coreapi/events/ProductAddedEvent.java @@ -0,0 +1,47 @@ +package com.baeldung.axon.coreapi.events; + +import java.util.Objects; + +public class ProductAddedEvent { + + private final String orderId; + private final String productId; + + public ProductAddedEvent(String orderId, String productId) { + this.orderId = orderId; + this.productId = productId; + } + + public String getOrderId() { + return orderId; + } + + public String getProductId() { + return productId; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ProductAddedEvent that = (ProductAddedEvent) o; + return Objects.equals(orderId, that.orderId) && Objects.equals(productId, that.productId); + } + + @Override + public int hashCode() { + return Objects.hash(orderId, productId); + } + + @Override + public String toString() { + return "ProductAddedEvent{" + + "orderId='" + orderId + '\'' + + ", productId='" + productId + '\'' + + '}'; + } +} diff --git a/axon/src/main/java/com/baeldung/axon/coreapi/events/ProductCountDecrementedEvent.java b/axon/src/main/java/com/baeldung/axon/coreapi/events/ProductCountDecrementedEvent.java new file mode 100644 index 0000000000..4017916791 --- /dev/null +++ b/axon/src/main/java/com/baeldung/axon/coreapi/events/ProductCountDecrementedEvent.java @@ -0,0 +1,47 @@ +package com.baeldung.axon.coreapi.events; + +import java.util.Objects; + +public class ProductCountDecrementedEvent { + + private final String orderId; + private final String productId; + + public ProductCountDecrementedEvent(String orderId, String productId) { + this.orderId = orderId; + this.productId = productId; + } + + public String getOrderId() { + return orderId; + } + + public String getProductId() { + return productId; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ProductCountDecrementedEvent that = (ProductCountDecrementedEvent) o; + return Objects.equals(orderId, that.orderId) && Objects.equals(productId, that.productId); + } + + @Override + public int hashCode() { + return Objects.hash(orderId, productId); + } + + @Override + public String toString() { + return "ProductCountDecrementedEvent{" + + "orderId='" + orderId + '\'' + + ", productId='" + productId + '\'' + + '}'; + } +} diff --git a/axon/src/main/java/com/baeldung/axon/coreapi/events/ProductCountIncrementedEvent.java b/axon/src/main/java/com/baeldung/axon/coreapi/events/ProductCountIncrementedEvent.java new file mode 100644 index 0000000000..2910a9ea6f --- /dev/null +++ b/axon/src/main/java/com/baeldung/axon/coreapi/events/ProductCountIncrementedEvent.java @@ -0,0 +1,47 @@ +package com.baeldung.axon.coreapi.events; + +import java.util.Objects; + +public class ProductCountIncrementedEvent { + + private final String orderId; + private final String productId; + + public ProductCountIncrementedEvent(String orderId, String productId) { + this.orderId = orderId; + this.productId = productId; + } + + public String getOrderId() { + return orderId; + } + + public String getProductId() { + return productId; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ProductCountIncrementedEvent that = (ProductCountIncrementedEvent) o; + return Objects.equals(orderId, that.orderId) && Objects.equals(productId, that.productId); + } + + @Override + public int hashCode() { + return Objects.hash(orderId, productId); + } + + @Override + public String toString() { + return "ProductCountIncrementedEvent{" + + "orderId='" + orderId + '\'' + + ", productId='" + productId + '\'' + + '}'; + } +} diff --git a/axon/src/main/java/com/baeldung/axon/coreapi/events/ProductRemovedEvent.java b/axon/src/main/java/com/baeldung/axon/coreapi/events/ProductRemovedEvent.java new file mode 100644 index 0000000000..7f89ccd1cc --- /dev/null +++ b/axon/src/main/java/com/baeldung/axon/coreapi/events/ProductRemovedEvent.java @@ -0,0 +1,47 @@ +package com.baeldung.axon.coreapi.events; + +import java.util.Objects; + +public class ProductRemovedEvent { + + private final String orderId; + private final String productId; + + public ProductRemovedEvent(String orderId, String productId) { + this.orderId = orderId; + this.productId = productId; + } + + public String getOrderId() { + return orderId; + } + + public String getProductId() { + return productId; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ProductRemovedEvent that = (ProductRemovedEvent) o; + return Objects.equals(orderId, that.orderId) && Objects.equals(productId, that.productId); + } + + @Override + public int hashCode() { + return Objects.hash(orderId, productId); + } + + @Override + public String toString() { + return "ProductRemovedEvent{" + + "orderId='" + orderId + '\'' + + ", productId='" + productId + '\'' + + '}'; + } +} From 2b2aff5ceb4b781cc570a91d0eb22cdd5bf76efd Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Fri, 19 Mar 2021 11:01:19 +0100 Subject: [PATCH 04/12] Update Query API Change the OrderedProduct model such that it contains a map of products having the count as its value, instead of a single product. #BAEL-4767 --- .../axon/coreapi/queries/OrderedProduct.java | 56 ++++++++++++------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/axon/src/main/java/com/baeldung/axon/coreapi/queries/OrderedProduct.java b/axon/src/main/java/com/baeldung/axon/coreapi/queries/OrderedProduct.java index d847bb2a98..69e705c2f6 100644 --- a/axon/src/main/java/com/baeldung/axon/coreapi/queries/OrderedProduct.java +++ b/axon/src/main/java/com/baeldung/axon/coreapi/queries/OrderedProduct.java @@ -1,16 +1,18 @@ package com.baeldung.axon.coreapi.queries; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; public class OrderedProduct { private final String orderId; - private final String product; + private final Map products; private OrderStatus orderStatus; - public OrderedProduct(String orderId, String product) { + public OrderedProduct(String orderId) { this.orderId = orderId; - this.product = product; + this.products = new HashMap<>(); orderStatus = OrderStatus.PLACED; } @@ -18,14 +20,31 @@ public class OrderedProduct { return orderId; } - public String getProduct() { - return product; + public Map getProducts() { + return products; } public OrderStatus getOrderStatus() { return orderStatus; } + public void addProduct(String productId) { + products.putIfAbsent(productId, 1); + } + + public void incrementProductInstance(String productId) { + products.computeIfPresent(productId, (id, count) -> ++count); + } + + public void decrementProductInstance(String productId) { + products.computeIfPresent(productId, (id, count) -> --count); + } + + + public void removeProduct(String productId) { + products.remove(productId); + } + public void setOrderConfirmed() { this.orderStatus = OrderStatus.CONFIRMED; } @@ -35,29 +54,28 @@ public class OrderedProduct { } @Override - public int hashCode() { - return Objects.hash(orderId, product, orderStatus); + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + OrderedProduct that = (OrderedProduct) o; + return Objects.equals(orderId, that.orderId) && Objects.equals(products, that.products) + && orderStatus == that.orderStatus; } @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - final OrderedProduct other = (OrderedProduct) obj; - return Objects.equals(this.orderId, other.orderId) - && Objects.equals(this.product, other.product) - && Objects.equals(this.orderStatus, other.orderStatus); + public int hashCode() { + return Objects.hash(orderId, products, orderStatus); } @Override public String toString() { return "OrderedProduct{" + "orderId='" + orderId + '\'' + - ", product='" + product + '\'' + + ", products=" + products + ", orderStatus=" + orderStatus + '}'; } From ef1167b037973154f6cb0d5e3f58e44a54bf9a25 Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Fri, 19 Mar 2021 11:04:12 +0100 Subject: [PATCH 05/12] Update Exception API - Introduce a DuplicateOrderLineException to signal whenever somebody wants to add the same product twice instead of using the increment command. - Introduce OrderAlreadyConfirmedException to signal whenever the order is confirmed, so that adding products and incrementing/decrementing their count is not possible once the order has been confirmed #BAEL-4767 --- .../coreapi/exceptions/DuplicateOrderLineException.java | 8 ++++++++ .../exceptions/OrderAlreadyConfirmedException.java | 8 ++++++++ 2 files changed, 16 insertions(+) create mode 100644 axon/src/main/java/com/baeldung/axon/coreapi/exceptions/DuplicateOrderLineException.java create mode 100644 axon/src/main/java/com/baeldung/axon/coreapi/exceptions/OrderAlreadyConfirmedException.java diff --git a/axon/src/main/java/com/baeldung/axon/coreapi/exceptions/DuplicateOrderLineException.java b/axon/src/main/java/com/baeldung/axon/coreapi/exceptions/DuplicateOrderLineException.java new file mode 100644 index 0000000000..c8a62a6cf0 --- /dev/null +++ b/axon/src/main/java/com/baeldung/axon/coreapi/exceptions/DuplicateOrderLineException.java @@ -0,0 +1,8 @@ +package com.baeldung.axon.coreapi.exceptions; + +public class DuplicateOrderLineException extends IllegalStateException { + + public DuplicateOrderLineException(String productId) { + super("Cannot duplicate order line for product identifier [" + productId + "]"); + } +} diff --git a/axon/src/main/java/com/baeldung/axon/coreapi/exceptions/OrderAlreadyConfirmedException.java b/axon/src/main/java/com/baeldung/axon/coreapi/exceptions/OrderAlreadyConfirmedException.java new file mode 100644 index 0000000000..5a4d1cdaec --- /dev/null +++ b/axon/src/main/java/com/baeldung/axon/coreapi/exceptions/OrderAlreadyConfirmedException.java @@ -0,0 +1,8 @@ +package com.baeldung.axon.coreapi.exceptions; + +public class OrderAlreadyConfirmedException extends IllegalStateException { + + public OrderAlreadyConfirmedException(String orderId) { + super("Cannot perform operation because order [" + orderId + "] is already confirmed."); + } +} From 886368a4c32a3b3d7922cc9d9b2b94e84c0a12fc Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Fri, 19 Mar 2021 11:07:26 +0100 Subject: [PATCH 06/12] Update Command Model Adjust the OrderAggregate to contain an OrderLine aggregate member. The OrderLine is added as soon as a Product is added through the AddProductCommand. The OrderLine member is capable of handling the increment and decrement product count commands, to increase/decrease the number of instances for that OrderLine. Decreasing the count below 1 will publish the ProductRemovedEvent from within the OrderLine. Additional business validation is added to unsure a given Product isn't added twice (adding a second should go through increment) and that the products aren't adjusted as soon as the product is confirmed. #BAEL-4767 --- .../{ => order}/OrderAggregate.java | 59 ++++++++++--- .../axon/commandmodel/order/OrderLine.java | 83 +++++++++++++++++++ 2 files changed, 132 insertions(+), 10 deletions(-) rename axon/src/main/java/com/baeldung/axon/commandmodel/{ => order}/OrderAggregate.java (53%) create mode 100644 axon/src/main/java/com/baeldung/axon/commandmodel/order/OrderLine.java diff --git a/axon/src/main/java/com/baeldung/axon/commandmodel/OrderAggregate.java b/axon/src/main/java/com/baeldung/axon/commandmodel/order/OrderAggregate.java similarity index 53% rename from axon/src/main/java/com/baeldung/axon/commandmodel/OrderAggregate.java rename to axon/src/main/java/com/baeldung/axon/commandmodel/order/OrderAggregate.java index 4ef02e6b54..883d51241d 100644 --- a/axon/src/main/java/com/baeldung/axon/commandmodel/OrderAggregate.java +++ b/axon/src/main/java/com/baeldung/axon/commandmodel/order/OrderAggregate.java @@ -1,19 +1,27 @@ -package com.baeldung.axon.commandmodel; - -import static org.axonframework.modelling.command.AggregateLifecycle.apply; - -import org.axonframework.commandhandling.CommandHandler; -import org.axonframework.eventsourcing.EventSourcingHandler; -import org.axonframework.modelling.command.AggregateIdentifier; -import org.axonframework.spring.stereotype.Aggregate; +package com.baeldung.axon.commandmodel.order; +import com.baeldung.axon.coreapi.commands.AddProductCommand; import com.baeldung.axon.coreapi.commands.ConfirmOrderCommand; import com.baeldung.axon.coreapi.commands.PlaceOrderCommand; import com.baeldung.axon.coreapi.commands.ShipOrderCommand; import com.baeldung.axon.coreapi.events.OrderConfirmedEvent; import com.baeldung.axon.coreapi.events.OrderPlacedEvent; import com.baeldung.axon.coreapi.events.OrderShippedEvent; +import com.baeldung.axon.coreapi.events.ProductAddedEvent; +import com.baeldung.axon.coreapi.events.ProductRemovedEvent; +import com.baeldung.axon.coreapi.exceptions.DuplicateOrderLineException; +import com.baeldung.axon.coreapi.exceptions.OrderAlreadyConfirmedException; import com.baeldung.axon.coreapi.exceptions.UnconfirmedOrderException; +import org.axonframework.commandhandling.CommandHandler; +import org.axonframework.eventsourcing.EventSourcingHandler; +import org.axonframework.modelling.command.AggregateIdentifier; +import org.axonframework.modelling.command.AggregateMember; +import org.axonframework.spring.stereotype.Aggregate; + +import java.util.HashMap; +import java.util.Map; + +import static org.axonframework.modelling.command.AggregateLifecycle.apply; @Aggregate public class OrderAggregate { @@ -22,13 +30,33 @@ public class OrderAggregate { private String orderId; private boolean orderConfirmed; + @AggregateMember + private Map orderLines; + @CommandHandler public OrderAggregate(PlaceOrderCommand command) { - apply(new OrderPlacedEvent(command.getOrderId(), command.getProduct())); + apply(new OrderPlacedEvent(command.getOrderId())); + } + + @CommandHandler + public void handle(AddProductCommand command) { + if (orderConfirmed) { + throw new OrderAlreadyConfirmedException(orderId); + } + + String productId = command.getProductId(); + if (orderLines.containsKey(productId)) { + throw new DuplicateOrderLineException(productId); + } + apply(new ProductAddedEvent(orderId, productId)); } @CommandHandler public void handle(ConfirmOrderCommand command) { + if (orderConfirmed) { + return; + } + apply(new OrderConfirmedEvent(orderId)); } @@ -45,6 +73,7 @@ public class OrderAggregate { public void on(OrderPlacedEvent event) { this.orderId = event.getOrderId(); this.orderConfirmed = false; + this.orderLines = new HashMap<>(); } @EventSourcingHandler @@ -52,8 +81,18 @@ public class OrderAggregate { this.orderConfirmed = true; } + @EventSourcingHandler + public void on(ProductAddedEvent event) { + String productId = event.getProductId(); + this.orderLines.put(productId, new OrderLine(productId)); + } + + @EventSourcingHandler + public void on(ProductRemovedEvent event) { + this.orderLines.remove(event.getProductId()); + } + protected OrderAggregate() { // Required by Axon to build a default Aggregate prior to Event Sourcing } - } \ No newline at end of file diff --git a/axon/src/main/java/com/baeldung/axon/commandmodel/order/OrderLine.java b/axon/src/main/java/com/baeldung/axon/commandmodel/order/OrderLine.java new file mode 100644 index 0000000000..e471ecbfe0 --- /dev/null +++ b/axon/src/main/java/com/baeldung/axon/commandmodel/order/OrderLine.java @@ -0,0 +1,83 @@ +package com.baeldung.axon.commandmodel.order; + +import com.baeldung.axon.coreapi.commands.DecrementProductCountCommand; +import com.baeldung.axon.coreapi.commands.IncrementProductCountCommand; +import com.baeldung.axon.coreapi.events.OrderConfirmedEvent; +import com.baeldung.axon.coreapi.events.ProductCountDecrementedEvent; +import com.baeldung.axon.coreapi.events.ProductCountIncrementedEvent; +import com.baeldung.axon.coreapi.events.ProductRemovedEvent; +import com.baeldung.axon.coreapi.exceptions.OrderAlreadyConfirmedException; +import org.axonframework.commandhandling.CommandHandler; +import org.axonframework.eventsourcing.EventSourcingHandler; +import org.axonframework.modelling.command.EntityId; + +import java.util.Objects; + +import static org.axonframework.modelling.command.AggregateLifecycle.apply; + +public class OrderLine { + + @EntityId + private final String productId; + private Integer count; + private boolean orderConfirmed; + + public OrderLine(String productId) { + this.productId = productId; + this.count = 1; + } + + @CommandHandler + public void handle(IncrementProductCountCommand command) { + if (orderConfirmed) { + throw new OrderAlreadyConfirmedException(command.getOrderId()); + } + + apply(new ProductCountIncrementedEvent(command.getOrderId(), productId)); + } + + @CommandHandler + public void handle(DecrementProductCountCommand command) { + if (orderConfirmed) { + throw new OrderAlreadyConfirmedException(command.getOrderId()); + } + + if (count <= 1) { + apply(new ProductRemovedEvent(command.getOrderId(), productId)); + } else { + apply(new ProductCountDecrementedEvent(command.getOrderId(), productId)); + } + } + + @EventSourcingHandler + public void on(ProductCountIncrementedEvent event) { + this.count++; + } + + @EventSourcingHandler + public void on(ProductCountDecrementedEvent event) { + this.count--; + } + + @EventSourcingHandler + public void on(OrderConfirmedEvent event) { + this.orderConfirmed = true; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + OrderLine orderLine = (OrderLine) o; + return Objects.equals(productId, orderLine.productId) && Objects.equals(count, orderLine.count); + } + + @Override + public int hashCode() { + return Objects.hash(productId, count); + } +} From 41d47f8aa63ec643af7bced292f9bc356df89170 Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Fri, 19 Mar 2021 11:08:42 +0100 Subject: [PATCH 07/12] Update Projection Add event handlers to update the products map of the OrderedProduct model #BAEL-4767 --- .../OrderedProductsEventHandler.java | 58 +++++++++++++++---- 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/axon/src/main/java/com/baeldung/axon/querymodel/OrderedProductsEventHandler.java b/axon/src/main/java/com/baeldung/axon/querymodel/OrderedProductsEventHandler.java index a37f0111ed..3efd81fd37 100644 --- a/axon/src/main/java/com/baeldung/axon/querymodel/OrderedProductsEventHandler.java +++ b/axon/src/main/java/com/baeldung/axon/querymodel/OrderedProductsEventHandler.java @@ -1,20 +1,23 @@ package com.baeldung.axon.querymodel; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - +import com.baeldung.axon.coreapi.events.OrderConfirmedEvent; +import com.baeldung.axon.coreapi.events.OrderPlacedEvent; +import com.baeldung.axon.coreapi.events.OrderShippedEvent; +import com.baeldung.axon.coreapi.events.ProductAddedEvent; +import com.baeldung.axon.coreapi.events.ProductCountDecrementedEvent; +import com.baeldung.axon.coreapi.events.ProductCountIncrementedEvent; +import com.baeldung.axon.coreapi.events.ProductRemovedEvent; +import com.baeldung.axon.coreapi.queries.FindAllOrderedProductsQuery; +import com.baeldung.axon.coreapi.queries.OrderedProduct; import org.axonframework.config.ProcessingGroup; import org.axonframework.eventhandling.EventHandler; import org.axonframework.queryhandling.QueryHandler; import org.springframework.stereotype.Service; -import com.baeldung.axon.coreapi.events.OrderConfirmedEvent; -import com.baeldung.axon.coreapi.events.OrderPlacedEvent; -import com.baeldung.axon.coreapi.events.OrderShippedEvent; -import com.baeldung.axon.coreapi.queries.FindAllOrderedProductsQuery; -import com.baeldung.axon.coreapi.queries.OrderedProduct; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; @Service @ProcessingGroup("ordered-products") @@ -25,7 +28,39 @@ public class OrderedProductsEventHandler { @EventHandler public void on(OrderPlacedEvent event) { String orderId = event.getOrderId(); - orderedProducts.put(orderId, new OrderedProduct(orderId, event.getProduct())); + orderedProducts.put(orderId, new OrderedProduct(orderId)); + } + + @EventHandler + public void on(ProductAddedEvent event) { + orderedProducts.computeIfPresent(event.getOrderId(), (orderId, orderedProduct) -> { + orderedProduct.addProduct(event.getProductId()); + return orderedProduct; + }); + } + + @EventHandler + public void on(ProductCountIncrementedEvent event) { + orderedProducts.computeIfPresent(event.getOrderId(), (orderId, orderedProduct) -> { + orderedProduct.incrementProductInstance(event.getProductId()); + return orderedProduct; + }); + } + + @EventHandler + public void on(ProductCountDecrementedEvent event) { + orderedProducts.computeIfPresent(event.getOrderId(), (orderId, orderedProduct) -> { + orderedProduct.decrementProductInstance(event.getProductId()); + return orderedProduct; + }); + } + + @EventHandler + public void on(ProductRemovedEvent event) { + orderedProducts.computeIfPresent(event.getOrderId(), (orderId, orderedProduct) -> { + orderedProduct.removeProduct(event.getProductId()); + return orderedProduct; + }); } @EventHandler @@ -48,5 +83,4 @@ public class OrderedProductsEventHandler { public List handle(FindAllOrderedProductsQuery query) { return new ArrayList<>(orderedProducts.values()); } - } \ No newline at end of file From b578c885e4144fb2bdb0a0f246adf83113be1da8 Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Fri, 19 Mar 2021 11:09:48 +0100 Subject: [PATCH 08/12] Add aggregate test cases Add test cases to reach a 100% coverage of all message handling functions of the OrderAggregate and OrderLine member. Added, upgrade to JUnit 5 #BAEL-4767 --- .../commandmodel/OrderAggregateUnitTest.java | 141 ++++++++++++++---- 1 file changed, 109 insertions(+), 32 deletions(-) diff --git a/axon/src/test/java/com/baeldung/axon/commandmodel/OrderAggregateUnitTest.java b/axon/src/test/java/com/baeldung/axon/commandmodel/OrderAggregateUnitTest.java index aaefe49fb1..262a8f1e03 100644 --- a/axon/src/test/java/com/baeldung/axon/commandmodel/OrderAggregateUnitTest.java +++ b/axon/src/test/java/com/baeldung/axon/commandmodel/OrderAggregateUnitTest.java @@ -1,62 +1,139 @@ package com.baeldung.axon.commandmodel; -import java.util.UUID; - -import com.baeldung.axon.coreapi.exceptions.UnconfirmedOrderException; -import org.axonframework.test.aggregate.AggregateTestFixture; -import org.axonframework.test.aggregate.FixtureConfiguration; -import org.junit.*; - +import com.baeldung.axon.commandmodel.order.OrderAggregate; +import com.baeldung.axon.coreapi.commands.AddProductCommand; import com.baeldung.axon.coreapi.commands.ConfirmOrderCommand; +import com.baeldung.axon.coreapi.commands.DecrementProductCountCommand; +import com.baeldung.axon.coreapi.commands.IncrementProductCountCommand; import com.baeldung.axon.coreapi.commands.PlaceOrderCommand; import com.baeldung.axon.coreapi.commands.ShipOrderCommand; import com.baeldung.axon.coreapi.events.OrderConfirmedEvent; import com.baeldung.axon.coreapi.events.OrderPlacedEvent; import com.baeldung.axon.coreapi.events.OrderShippedEvent; +import com.baeldung.axon.coreapi.events.ProductAddedEvent; +import com.baeldung.axon.coreapi.events.ProductCountDecrementedEvent; +import com.baeldung.axon.coreapi.events.ProductCountIncrementedEvent; +import com.baeldung.axon.coreapi.events.ProductRemovedEvent; +import com.baeldung.axon.coreapi.exceptions.DuplicateOrderLineException; +import com.baeldung.axon.coreapi.exceptions.OrderAlreadyConfirmedException; +import com.baeldung.axon.coreapi.exceptions.UnconfirmedOrderException; +import org.axonframework.test.aggregate.AggregateTestFixture; +import org.axonframework.test.aggregate.FixtureConfiguration; +import org.axonframework.test.matchers.Matchers; +import org.junit.jupiter.api.*; -public class OrderAggregateUnitTest { +import java.util.UUID; + +class OrderAggregateUnitTest { + + private static final String ORDER_ID = UUID.randomUUID().toString(); + private static final String PRODUCT_ID = UUID.randomUUID().toString(); private FixtureConfiguration fixture; - @Before - public void setUp() { + @BeforeEach + void setUp() { fixture = new AggregateTestFixture<>(OrderAggregate.class); } @Test - public void giveNoPriorActivity_whenPlaceOrderCommand_thenShouldPublishOrderPlacedEvent() { - String orderId = UUID.randomUUID().toString(); - String product = "Deluxe Chair"; + void giveNoPriorActivity_whenPlaceOrderCommand_thenShouldPublishOrderPlacedEvent() { fixture.givenNoPriorActivity() - .when(new PlaceOrderCommand(orderId, product)) - .expectEvents(new OrderPlacedEvent(orderId, product)); + .when(new PlaceOrderCommand(ORDER_ID)) + .expectEvents(new OrderPlacedEvent(ORDER_ID)); } @Test - public void givenOrderPlacedEvent_whenConfirmOrderCommand_thenShouldPublishOrderConfirmedEvent() { - String orderId = UUID.randomUUID().toString(); - String product = "Deluxe Chair"; - fixture.given(new OrderPlacedEvent(orderId, product)) - .when(new ConfirmOrderCommand(orderId)) - .expectEvents(new OrderConfirmedEvent(orderId)); + void givenOrderPlacedEvent_whenAddProductCommand_thenShouldPublishProductAddedEvent() { + fixture.given(new OrderPlacedEvent(ORDER_ID)) + .when(new AddProductCommand(ORDER_ID, PRODUCT_ID)) + .expectEvents(new ProductAddedEvent(ORDER_ID, PRODUCT_ID)); } @Test - public void givenOrderPlacedEvent_whenShipOrderCommand_thenShouldThrowUnconfirmedOrderException() { - String orderId = UUID.randomUUID().toString(); - String product = "Deluxe Chair"; - fixture.given(new OrderPlacedEvent(orderId, product)) - .when(new ShipOrderCommand(orderId)) + void givenOrderPlacedEventAndProductAddedEvent_whenAddProductCommandForSameProductId_thenShouldThrowDuplicateOrderLineException() { + fixture.given(new OrderPlacedEvent(ORDER_ID), new ProductAddedEvent(ORDER_ID, PRODUCT_ID)) + .when(new AddProductCommand(ORDER_ID, PRODUCT_ID)) + .expectException(DuplicateOrderLineException.class) + .expectExceptionMessage(Matchers.predicate(message -> ((String) message).contains(PRODUCT_ID))); + } + + @Test + void givenOrderPlacedEventAndProductAddedEvent_whenIncrementProductCountCommand_thenShouldPublishProductCountIncrementedEvent() { + fixture.given(new OrderPlacedEvent(ORDER_ID), new ProductAddedEvent(ORDER_ID, PRODUCT_ID)) + .when(new IncrementProductCountCommand(ORDER_ID, PRODUCT_ID)) + .expectEvents(new ProductCountIncrementedEvent(ORDER_ID, PRODUCT_ID)); + } + + @Test + void givenOrderPlacedEventProductAddedEventAndProductCountIncrementedEvent_whenDecrementProductCountCommand_thenShouldPublishProductCountDecrementedEvent() { + fixture.given(new OrderPlacedEvent(ORDER_ID), + new ProductAddedEvent(ORDER_ID, PRODUCT_ID), + new ProductCountIncrementedEvent(ORDER_ID, PRODUCT_ID)) + .when(new DecrementProductCountCommand(ORDER_ID, PRODUCT_ID)) + .expectEvents(new ProductCountDecrementedEvent(ORDER_ID, PRODUCT_ID)); + } + + @Test + void givenOrderPlacedEventAndProductAddedEvent_whenDecrementProductCountCommand_thenShouldPublishProductRemovedEvent() { + fixture.given(new OrderPlacedEvent(ORDER_ID), new ProductAddedEvent(ORDER_ID, PRODUCT_ID)) + .when(new DecrementProductCountCommand(ORDER_ID, PRODUCT_ID)) + .expectEvents(new ProductRemovedEvent(ORDER_ID, PRODUCT_ID)); + } + + @Test + void givenOrderPlacedEvent_whenConfirmOrderCommand_thenShouldPublishOrderConfirmedEvent() { + fixture.given(new OrderPlacedEvent(ORDER_ID)) + .when(new ConfirmOrderCommand(ORDER_ID)) + .expectEvents(new OrderConfirmedEvent(ORDER_ID)); + } + + @Test + void givenOrderPlacedEventAndOrderConfirmedEvent_whenConfirmOrderCommand_thenExpectNoEvents() { + fixture.given(new OrderPlacedEvent(ORDER_ID), new OrderConfirmedEvent(ORDER_ID)) + .when(new ConfirmOrderCommand(ORDER_ID)) + .expectNoEvents(); + } + + @Test + void givenOrderPlacedEvent_whenShipOrderCommand_thenShouldThrowUnconfirmedOrderException() { + fixture.given(new OrderPlacedEvent(ORDER_ID)) + .when(new ShipOrderCommand(ORDER_ID)) .expectException(UnconfirmedOrderException.class); } @Test - public void givenOrderPlacedEventAndOrderConfirmedEvent_whenShipOrderCommand_thenShouldPublishOrderShippedEvent() { - String orderId = UUID.randomUUID().toString(); - String product = "Deluxe Chair"; - fixture.given(new OrderPlacedEvent(orderId, product), new OrderConfirmedEvent(orderId)) - .when(new ShipOrderCommand(orderId)) - .expectEvents(new OrderShippedEvent(orderId)); + void givenOrderPlacedEventAndOrderConfirmedEvent_whenShipOrderCommand_thenShouldPublishOrderShippedEvent() { + fixture.given(new OrderPlacedEvent(ORDER_ID), new OrderConfirmedEvent(ORDER_ID)) + .when(new ShipOrderCommand(ORDER_ID)) + .expectEvents(new OrderShippedEvent(ORDER_ID)); } + @Test + void givenOrderPlacedEventProductAndOrderConfirmedEvent_whenAddProductCommand_thenShouldThrowOrderAlreadyConfirmedException() { + fixture.given(new OrderPlacedEvent(ORDER_ID), new OrderConfirmedEvent(ORDER_ID)) + .when(new AddProductCommand(ORDER_ID, PRODUCT_ID)) + .expectException(OrderAlreadyConfirmedException.class) + .expectExceptionMessage(Matchers.predicate(message -> ((String) message).contains(ORDER_ID))); + } + + @Test + void givenOrderPlacedEventProductAddedEventAndOrderConfirmedEvent_whenIncrementProductCountCommand_thenShouldThrowOrderAlreadyConfirmedException() { + fixture.given(new OrderPlacedEvent(ORDER_ID), + new ProductAddedEvent(ORDER_ID, PRODUCT_ID), + new OrderConfirmedEvent(ORDER_ID)) + .when(new IncrementProductCountCommand(ORDER_ID, PRODUCT_ID)) + .expectException(OrderAlreadyConfirmedException.class) + .expectExceptionMessage(Matchers.predicate(message -> ((String) message).contains(ORDER_ID))); + } + + @Test + void givenOrderPlacedEventProductAddedEventAndOrderConfirmedEvent_whenDecrementProductCountCommand_thenShouldThrowOrderAlreadyConfirmedException() { + fixture.given(new OrderPlacedEvent(ORDER_ID), + new ProductAddedEvent(ORDER_ID, PRODUCT_ID), + new OrderConfirmedEvent(ORDER_ID)) + .when(new DecrementProductCountCommand(ORDER_ID, PRODUCT_ID)) + .expectException(OrderAlreadyConfirmedException.class) + .expectExceptionMessage(Matchers.predicate(message -> ((String) message).contains(ORDER_ID))); + } } \ No newline at end of file From 68276b8041ed5997edbbf7c4137fc791b4b29215 Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Fri, 19 Mar 2021 11:23:46 +0100 Subject: [PATCH 09/12] Allow all operations on the Controller Add post mappings to allow single creation of an Order, influencing all aspects of an Order #BAEL-4767 --- .../baeldung/axon/gui/OrderRestEndpoint.java | 76 +++++++++++++++---- 1 file changed, 60 insertions(+), 16 deletions(-) diff --git a/axon/src/main/java/com/baeldung/axon/gui/OrderRestEndpoint.java b/axon/src/main/java/com/baeldung/axon/gui/OrderRestEndpoint.java index a9f34cc691..f9e1748cbe 100644 --- a/axon/src/main/java/com/baeldung/axon/gui/OrderRestEndpoint.java +++ b/axon/src/main/java/com/baeldung/axon/gui/OrderRestEndpoint.java @@ -1,20 +1,24 @@ package com.baeldung.axon.gui; -import java.util.List; -import java.util.UUID; - -import org.axonframework.commandhandling.gateway.CommandGateway; -import org.axonframework.messaging.responsetypes.ResponseTypes; -import org.axonframework.queryhandling.QueryGateway; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RestController; - +import com.baeldung.axon.coreapi.commands.AddProductCommand; import com.baeldung.axon.coreapi.commands.ConfirmOrderCommand; +import com.baeldung.axon.coreapi.commands.DecrementProductCountCommand; +import com.baeldung.axon.coreapi.commands.IncrementProductCountCommand; import com.baeldung.axon.coreapi.commands.PlaceOrderCommand; import com.baeldung.axon.coreapi.commands.ShipOrderCommand; import com.baeldung.axon.coreapi.queries.FindAllOrderedProductsQuery; import com.baeldung.axon.coreapi.queries.OrderedProduct; +import org.axonframework.commandhandling.gateway.CommandGateway; +import org.axonframework.messaging.responsetypes.ResponseTypes; +import org.axonframework.queryhandling.QueryGateway; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; @RestController public class OrderRestEndpoint { @@ -30,7 +34,8 @@ public class OrderRestEndpoint { @PostMapping("/ship-order") public void shipOrder() { String orderId = UUID.randomUUID().toString(); - commandGateway.send(new PlaceOrderCommand(orderId, "Deluxe Chair")); + commandGateway.send(new PlaceOrderCommand(orderId)); + commandGateway.send(new AddProductCommand(orderId, "Deluxe Chair")); commandGateway.send(new ConfirmOrderCommand(orderId)); commandGateway.send(new ShipOrderCommand(orderId)); } @@ -38,15 +43,54 @@ public class OrderRestEndpoint { @PostMapping("/ship-unconfirmed-order") public void shipUnconfirmedOrder() { String orderId = UUID.randomUUID().toString(); - commandGateway.send(new PlaceOrderCommand(orderId, "Deluxe Chair")); + commandGateway.send(new PlaceOrderCommand(orderId)); + commandGateway.send(new AddProductCommand(orderId, "Deluxe Chair")); // This throws an exception, as an Order cannot be shipped if it has not been confirmed yet. commandGateway.send(new ShipOrderCommand(orderId)); } - @GetMapping("/all-orders") - public List findAllOrderedProducts() { - return queryGateway.query(new FindAllOrderedProductsQuery(), ResponseTypes.multipleInstancesOf(OrderedProduct.class)) - .join(); + @PostMapping("/order") + public CompletableFuture placeOrder() { + return placeOrder(UUID.randomUUID().toString()); } + @PostMapping("/order/{order-id}") + public CompletableFuture placeOrder(@PathVariable("order-id") String orderId) { + return commandGateway.send(new PlaceOrderCommand(orderId)); + } + + @PostMapping("/order/{order-id}/product/{product-id}") + public CompletableFuture addProduct(@PathVariable("order-id") String orderId, + @PathVariable("product-id") String productId) { + return commandGateway.send(new AddProductCommand(orderId, productId)); + } + + @PostMapping("/order/{order-id}/product/{product-id}/increment") + public CompletableFuture incrementProduct(@PathVariable("order-id") String orderId, + @PathVariable("product-id") String productId) { + return commandGateway.send(new IncrementProductCountCommand(orderId, productId)); + } + + @PostMapping("/order/{order-id}/product/{product-id}/decrement") + public CompletableFuture decrementProduct(@PathVariable("order-id") String orderId, + @PathVariable("product-id") String productId) { + return commandGateway.send(new DecrementProductCountCommand(orderId, productId)); + } + + @PostMapping("/order/{order-id}/confirm") + public CompletableFuture confirmOrder(@PathVariable("order-id") String orderId) { + return commandGateway.send(new ConfirmOrderCommand(orderId)); + } + + @PostMapping("/order/{order-id}/ship") + public CompletableFuture shipOrder(@PathVariable("order-id") String orderId) { + return commandGateway.send(new ShipOrderCommand(orderId)); + } + + @GetMapping("/all-orders") + public CompletableFuture> findAllOrderedProducts() { + return queryGateway.query( + new FindAllOrderedProductsQuery(), ResponseTypes.multipleInstancesOf(OrderedProduct.class) + ); + } } From 70189df008298094b7233e3fcb81859b43052437 Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Fri, 19 Mar 2021 11:24:08 +0100 Subject: [PATCH 10/12] Update http file for testing Add additional operations to the order-api.http file to simplify testing #BAEL-4767 --- axon/src/main/resources/order-api.http | 30 ++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/axon/src/main/resources/order-api.http b/axon/src/main/resources/order-api.http index a3c69c72bc..aceb5a97ba 100644 --- a/axon/src/main/resources/order-api.http +++ b/axon/src/main/resources/order-api.http @@ -1,11 +1,37 @@ +### Place Order, Add Product, Confirm and Ship Order + POST http://localhost:8080/ship-order -### +### Place Order, Add Product and Ship Order POST http://localhost:8080/ship-unconfirmed-order -### +### Retrieve all existing Orders GET http://localhost:8080/all-orders +### Place Order with id 666a1661-474d-4046-8b12-8b5896312768 + +POST http://localhost:8080/order/666a1661-474d-4046-8b12-8b5896312768 + +### Add Product a6aa01eb-4e38-4dfb-b53b-b5b82961fbf3 to Order 666a1661-474d-4046-8b12-8b5896312768 + +POST http://localhost:8080/order/666a1661-474d-4046-8b12-8b5896312768/product/a6aa01eb-4e38-4dfb-b53b-b5b82961fbf3 + +### Increment Product a6aa01eb-4e38-4dfb-b53b-b5b82961fbf3 to Order 666a1661-474d-4046-8b12-8b5896312768 + +POST http://localhost:8080/order/666a1661-474d-4046-8b12-8b5896312768/product/a6aa01eb-4e38-4dfb-b53b-b5b82961fbf3/increment + +### Decrement Product a6aa01eb-4e38-4dfb-b53b-b5b82961fbf3 to Order 666a1661-474d-4046-8b12-8b5896312768 + +POST http://localhost:8080/order/666a1661-474d-4046-8b12-8b5896312768/product/a6aa01eb-4e38-4dfb-b53b-b5b82961fbf3/decrement + +### Confirm Order 666a1661-474d-4046-8b12-8b5896312768 + +POST http://localhost:8080/order/666a1661-474d-4046-8b12-8b5896312768/confirm + +### Ship Order 666a1661-474d-4046-8b12-8b5896312768 + +POST http://localhost:8080/order/666a1661-474d-4046-8b12-8b5896312768/ship + ### From 2b351eda7aa2381bf65aaeab027e2eba73772db8 Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Fri, 19 Mar 2021 11:35:31 +0100 Subject: [PATCH 11/12] Rename API in line with adjusted approach Change PlaceOrderCommand/OrderPlacedEvent to CreateOrderCommand/OrderCreatedEvent. Placed suggests it is already placed, meaning products cannot be changed. However with the new OrderLine member and its operations it's no longer correct to state the order placed upon creation. Furthermore, the OrderedProduct is no longer a single product, but contains several. Renaming this to Order is more in line with what this query model resembles #BAEL-4767 --- .../commandmodel/order/OrderAggregate.java | 10 +-- ...erCommand.java => CreateOrderCommand.java} | 8 +- ...lacedEvent.java => OrderCreatedEvent.java} | 8 +- .../{OrderedProduct.java => Order.java} | 13 +-- .../axon/coreapi/queries/OrderStatus.java | 3 +- .../baeldung/axon/gui/OrderRestEndpoint.java | 22 +++-- .../OrderedProductsEventHandler.java | 86 ------------------- .../axon/querymodel/OrdersEventHandler.java | 86 +++++++++++++++++++ axon/src/main/resources/order-api.http | 6 +- .../commandmodel/OrderAggregateUnitTest.java | 58 ++++++------- 10 files changed, 149 insertions(+), 151 deletions(-) rename axon/src/main/java/com/baeldung/axon/coreapi/commands/{PlaceOrderCommand.java => CreateOrderCommand.java} (81%) rename axon/src/main/java/com/baeldung/axon/coreapi/events/{OrderPlacedEvent.java => OrderCreatedEvent.java} (79%) rename axon/src/main/java/com/baeldung/axon/coreapi/queries/{OrderedProduct.java => Order.java} (85%) delete mode 100644 axon/src/main/java/com/baeldung/axon/querymodel/OrderedProductsEventHandler.java create mode 100644 axon/src/main/java/com/baeldung/axon/querymodel/OrdersEventHandler.java diff --git a/axon/src/main/java/com/baeldung/axon/commandmodel/order/OrderAggregate.java b/axon/src/main/java/com/baeldung/axon/commandmodel/order/OrderAggregate.java index 883d51241d..97342bdb3a 100644 --- a/axon/src/main/java/com/baeldung/axon/commandmodel/order/OrderAggregate.java +++ b/axon/src/main/java/com/baeldung/axon/commandmodel/order/OrderAggregate.java @@ -2,10 +2,10 @@ package com.baeldung.axon.commandmodel.order; import com.baeldung.axon.coreapi.commands.AddProductCommand; import com.baeldung.axon.coreapi.commands.ConfirmOrderCommand; -import com.baeldung.axon.coreapi.commands.PlaceOrderCommand; +import com.baeldung.axon.coreapi.commands.CreateOrderCommand; import com.baeldung.axon.coreapi.commands.ShipOrderCommand; import com.baeldung.axon.coreapi.events.OrderConfirmedEvent; -import com.baeldung.axon.coreapi.events.OrderPlacedEvent; +import com.baeldung.axon.coreapi.events.OrderCreatedEvent; import com.baeldung.axon.coreapi.events.OrderShippedEvent; import com.baeldung.axon.coreapi.events.ProductAddedEvent; import com.baeldung.axon.coreapi.events.ProductRemovedEvent; @@ -34,8 +34,8 @@ public class OrderAggregate { private Map orderLines; @CommandHandler - public OrderAggregate(PlaceOrderCommand command) { - apply(new OrderPlacedEvent(command.getOrderId())); + public OrderAggregate(CreateOrderCommand command) { + apply(new OrderCreatedEvent(command.getOrderId())); } @CommandHandler @@ -70,7 +70,7 @@ public class OrderAggregate { } @EventSourcingHandler - public void on(OrderPlacedEvent event) { + public void on(OrderCreatedEvent event) { this.orderId = event.getOrderId(); this.orderConfirmed = false; this.orderLines = new HashMap<>(); diff --git a/axon/src/main/java/com/baeldung/axon/coreapi/commands/PlaceOrderCommand.java b/axon/src/main/java/com/baeldung/axon/coreapi/commands/CreateOrderCommand.java similarity index 81% rename from axon/src/main/java/com/baeldung/axon/coreapi/commands/PlaceOrderCommand.java rename to axon/src/main/java/com/baeldung/axon/coreapi/commands/CreateOrderCommand.java index b631272366..ceb7fd6a08 100644 --- a/axon/src/main/java/com/baeldung/axon/coreapi/commands/PlaceOrderCommand.java +++ b/axon/src/main/java/com/baeldung/axon/coreapi/commands/CreateOrderCommand.java @@ -4,12 +4,12 @@ import org.axonframework.modelling.command.TargetAggregateIdentifier; import java.util.Objects; -public class PlaceOrderCommand { +public class CreateOrderCommand { @TargetAggregateIdentifier private final String orderId; - public PlaceOrderCommand(String orderId) { + public CreateOrderCommand(String orderId) { this.orderId = orderId; } @@ -25,7 +25,7 @@ public class PlaceOrderCommand { if (o == null || getClass() != o.getClass()) { return false; } - PlaceOrderCommand that = (PlaceOrderCommand) o; + CreateOrderCommand that = (CreateOrderCommand) o; return Objects.equals(orderId, that.orderId); } @@ -36,7 +36,7 @@ public class PlaceOrderCommand { @Override public String toString() { - return "PlaceOrderCommand{" + + return "CreateOrderCommand{" + "orderId='" + orderId + '\'' + '}'; } diff --git a/axon/src/main/java/com/baeldung/axon/coreapi/events/OrderPlacedEvent.java b/axon/src/main/java/com/baeldung/axon/coreapi/events/OrderCreatedEvent.java similarity index 79% rename from axon/src/main/java/com/baeldung/axon/coreapi/events/OrderPlacedEvent.java rename to axon/src/main/java/com/baeldung/axon/coreapi/events/OrderCreatedEvent.java index 3b9994fc33..5d2d8b7f55 100644 --- a/axon/src/main/java/com/baeldung/axon/coreapi/events/OrderPlacedEvent.java +++ b/axon/src/main/java/com/baeldung/axon/coreapi/events/OrderCreatedEvent.java @@ -2,11 +2,11 @@ package com.baeldung.axon.coreapi.events; import java.util.Objects; -public class OrderPlacedEvent { +public class OrderCreatedEvent { private final String orderId; - public OrderPlacedEvent(String orderId) { + public OrderCreatedEvent(String orderId) { this.orderId = orderId; } @@ -22,7 +22,7 @@ public class OrderPlacedEvent { if (o == null || getClass() != o.getClass()) { return false; } - OrderPlacedEvent that = (OrderPlacedEvent) o; + OrderCreatedEvent that = (OrderCreatedEvent) o; return Objects.equals(orderId, that.orderId); } @@ -33,7 +33,7 @@ public class OrderPlacedEvent { @Override public String toString() { - return "OrderPlacedEvent{" + + return "OrderCreatedEvent{" + "orderId='" + orderId + '\'' + '}'; } diff --git a/axon/src/main/java/com/baeldung/axon/coreapi/queries/OrderedProduct.java b/axon/src/main/java/com/baeldung/axon/coreapi/queries/Order.java similarity index 85% rename from axon/src/main/java/com/baeldung/axon/coreapi/queries/OrderedProduct.java rename to axon/src/main/java/com/baeldung/axon/coreapi/queries/Order.java index 69e705c2f6..1810a053d3 100644 --- a/axon/src/main/java/com/baeldung/axon/coreapi/queries/OrderedProduct.java +++ b/axon/src/main/java/com/baeldung/axon/coreapi/queries/Order.java @@ -4,16 +4,16 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; -public class OrderedProduct { +public class Order { private final String orderId; private final Map products; private OrderStatus orderStatus; - public OrderedProduct(String orderId) { + public Order(String orderId) { this.orderId = orderId; this.products = new HashMap<>(); - orderStatus = OrderStatus.PLACED; + orderStatus = OrderStatus.CREATED; } public String getOrderId() { @@ -61,8 +61,9 @@ public class OrderedProduct { if (o == null || getClass() != o.getClass()) { return false; } - OrderedProduct that = (OrderedProduct) o; - return Objects.equals(orderId, that.orderId) && Objects.equals(products, that.products) + Order that = (Order) o; + return Objects.equals(orderId, that.orderId) + && Objects.equals(products, that.products) && orderStatus == that.orderStatus; } @@ -73,7 +74,7 @@ public class OrderedProduct { @Override public String toString() { - return "OrderedProduct{" + + return "Order{" + "orderId='" + orderId + '\'' + ", products=" + products + ", orderStatus=" + orderStatus + diff --git a/axon/src/main/java/com/baeldung/axon/coreapi/queries/OrderStatus.java b/axon/src/main/java/com/baeldung/axon/coreapi/queries/OrderStatus.java index d215c5fc32..fc5da5d77e 100644 --- a/axon/src/main/java/com/baeldung/axon/coreapi/queries/OrderStatus.java +++ b/axon/src/main/java/com/baeldung/axon/coreapi/queries/OrderStatus.java @@ -2,6 +2,5 @@ package com.baeldung.axon.coreapi.queries; public enum OrderStatus { - PLACED, CONFIRMED, SHIPPED - + CREATED, CONFIRMED, SHIPPED } diff --git a/axon/src/main/java/com/baeldung/axon/gui/OrderRestEndpoint.java b/axon/src/main/java/com/baeldung/axon/gui/OrderRestEndpoint.java index f9e1748cbe..5c385c487f 100644 --- a/axon/src/main/java/com/baeldung/axon/gui/OrderRestEndpoint.java +++ b/axon/src/main/java/com/baeldung/axon/gui/OrderRestEndpoint.java @@ -2,12 +2,12 @@ package com.baeldung.axon.gui; import com.baeldung.axon.coreapi.commands.AddProductCommand; import com.baeldung.axon.coreapi.commands.ConfirmOrderCommand; +import com.baeldung.axon.coreapi.commands.CreateOrderCommand; import com.baeldung.axon.coreapi.commands.DecrementProductCountCommand; import com.baeldung.axon.coreapi.commands.IncrementProductCountCommand; -import com.baeldung.axon.coreapi.commands.PlaceOrderCommand; import com.baeldung.axon.coreapi.commands.ShipOrderCommand; import com.baeldung.axon.coreapi.queries.FindAllOrderedProductsQuery; -import com.baeldung.axon.coreapi.queries.OrderedProduct; +import com.baeldung.axon.coreapi.queries.Order; import org.axonframework.commandhandling.gateway.CommandGateway; import org.axonframework.messaging.responsetypes.ResponseTypes; import org.axonframework.queryhandling.QueryGateway; @@ -34,7 +34,7 @@ public class OrderRestEndpoint { @PostMapping("/ship-order") public void shipOrder() { String orderId = UUID.randomUUID().toString(); - commandGateway.send(new PlaceOrderCommand(orderId)); + commandGateway.send(new CreateOrderCommand(orderId)); commandGateway.send(new AddProductCommand(orderId, "Deluxe Chair")); commandGateway.send(new ConfirmOrderCommand(orderId)); commandGateway.send(new ShipOrderCommand(orderId)); @@ -43,20 +43,20 @@ public class OrderRestEndpoint { @PostMapping("/ship-unconfirmed-order") public void shipUnconfirmedOrder() { String orderId = UUID.randomUUID().toString(); - commandGateway.send(new PlaceOrderCommand(orderId)); + commandGateway.send(new CreateOrderCommand(orderId)); commandGateway.send(new AddProductCommand(orderId, "Deluxe Chair")); // This throws an exception, as an Order cannot be shipped if it has not been confirmed yet. commandGateway.send(new ShipOrderCommand(orderId)); } @PostMapping("/order") - public CompletableFuture placeOrder() { - return placeOrder(UUID.randomUUID().toString()); + public CompletableFuture createOrder() { + return createOrder(UUID.randomUUID().toString()); } @PostMapping("/order/{order-id}") - public CompletableFuture placeOrder(@PathVariable("order-id") String orderId) { - return commandGateway.send(new PlaceOrderCommand(orderId)); + public CompletableFuture createOrder(@PathVariable("order-id") String orderId) { + return commandGateway.send(new CreateOrderCommand(orderId)); } @PostMapping("/order/{order-id}/product/{product-id}") @@ -88,9 +88,7 @@ public class OrderRestEndpoint { } @GetMapping("/all-orders") - public CompletableFuture> findAllOrderedProducts() { - return queryGateway.query( - new FindAllOrderedProductsQuery(), ResponseTypes.multipleInstancesOf(OrderedProduct.class) - ); + public CompletableFuture> findAllOrders() { + return queryGateway.query(new FindAllOrderedProductsQuery(), ResponseTypes.multipleInstancesOf(Order.class)); } } diff --git a/axon/src/main/java/com/baeldung/axon/querymodel/OrderedProductsEventHandler.java b/axon/src/main/java/com/baeldung/axon/querymodel/OrderedProductsEventHandler.java deleted file mode 100644 index 3efd81fd37..0000000000 --- a/axon/src/main/java/com/baeldung/axon/querymodel/OrderedProductsEventHandler.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.baeldung.axon.querymodel; - -import com.baeldung.axon.coreapi.events.OrderConfirmedEvent; -import com.baeldung.axon.coreapi.events.OrderPlacedEvent; -import com.baeldung.axon.coreapi.events.OrderShippedEvent; -import com.baeldung.axon.coreapi.events.ProductAddedEvent; -import com.baeldung.axon.coreapi.events.ProductCountDecrementedEvent; -import com.baeldung.axon.coreapi.events.ProductCountIncrementedEvent; -import com.baeldung.axon.coreapi.events.ProductRemovedEvent; -import com.baeldung.axon.coreapi.queries.FindAllOrderedProductsQuery; -import com.baeldung.axon.coreapi.queries.OrderedProduct; -import org.axonframework.config.ProcessingGroup; -import org.axonframework.eventhandling.EventHandler; -import org.axonframework.queryhandling.QueryHandler; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@Service -@ProcessingGroup("ordered-products") -public class OrderedProductsEventHandler { - - private final Map orderedProducts = new HashMap<>(); - - @EventHandler - public void on(OrderPlacedEvent event) { - String orderId = event.getOrderId(); - orderedProducts.put(orderId, new OrderedProduct(orderId)); - } - - @EventHandler - public void on(ProductAddedEvent event) { - orderedProducts.computeIfPresent(event.getOrderId(), (orderId, orderedProduct) -> { - orderedProduct.addProduct(event.getProductId()); - return orderedProduct; - }); - } - - @EventHandler - public void on(ProductCountIncrementedEvent event) { - orderedProducts.computeIfPresent(event.getOrderId(), (orderId, orderedProduct) -> { - orderedProduct.incrementProductInstance(event.getProductId()); - return orderedProduct; - }); - } - - @EventHandler - public void on(ProductCountDecrementedEvent event) { - orderedProducts.computeIfPresent(event.getOrderId(), (orderId, orderedProduct) -> { - orderedProduct.decrementProductInstance(event.getProductId()); - return orderedProduct; - }); - } - - @EventHandler - public void on(ProductRemovedEvent event) { - orderedProducts.computeIfPresent(event.getOrderId(), (orderId, orderedProduct) -> { - orderedProduct.removeProduct(event.getProductId()); - return orderedProduct; - }); - } - - @EventHandler - public void on(OrderConfirmedEvent event) { - orderedProducts.computeIfPresent(event.getOrderId(), (orderId, orderedProduct) -> { - orderedProduct.setOrderConfirmed(); - return orderedProduct; - }); - } - - @EventHandler - public void on(OrderShippedEvent event) { - orderedProducts.computeIfPresent(event.getOrderId(), (orderId, orderedProduct) -> { - orderedProduct.setOrderShipped(); - return orderedProduct; - }); - } - - @QueryHandler - public List handle(FindAllOrderedProductsQuery query) { - return new ArrayList<>(orderedProducts.values()); - } -} \ No newline at end of file diff --git a/axon/src/main/java/com/baeldung/axon/querymodel/OrdersEventHandler.java b/axon/src/main/java/com/baeldung/axon/querymodel/OrdersEventHandler.java new file mode 100644 index 0000000000..25666b0bf3 --- /dev/null +++ b/axon/src/main/java/com/baeldung/axon/querymodel/OrdersEventHandler.java @@ -0,0 +1,86 @@ +package com.baeldung.axon.querymodel; + +import com.baeldung.axon.coreapi.events.OrderConfirmedEvent; +import com.baeldung.axon.coreapi.events.OrderCreatedEvent; +import com.baeldung.axon.coreapi.events.OrderShippedEvent; +import com.baeldung.axon.coreapi.events.ProductAddedEvent; +import com.baeldung.axon.coreapi.events.ProductCountDecrementedEvent; +import com.baeldung.axon.coreapi.events.ProductCountIncrementedEvent; +import com.baeldung.axon.coreapi.events.ProductRemovedEvent; +import com.baeldung.axon.coreapi.queries.FindAllOrderedProductsQuery; +import com.baeldung.axon.coreapi.queries.Order; +import org.axonframework.config.ProcessingGroup; +import org.axonframework.eventhandling.EventHandler; +import org.axonframework.queryhandling.QueryHandler; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +@ProcessingGroup("orders") +public class OrdersEventHandler { + + private final Map orders = new HashMap<>(); + + @EventHandler + public void on(OrderCreatedEvent event) { + String orderId = event.getOrderId(); + orders.put(orderId, new Order(orderId)); + } + + @EventHandler + public void on(ProductAddedEvent event) { + orders.computeIfPresent(event.getOrderId(), (orderId, order) -> { + order.addProduct(event.getProductId()); + return order; + }); + } + + @EventHandler + public void on(ProductCountIncrementedEvent event) { + orders.computeIfPresent(event.getOrderId(), (orderId, order) -> { + order.incrementProductInstance(event.getProductId()); + return order; + }); + } + + @EventHandler + public void on(ProductCountDecrementedEvent event) { + orders.computeIfPresent(event.getOrderId(), (orderId, order) -> { + order.decrementProductInstance(event.getProductId()); + return order; + }); + } + + @EventHandler + public void on(ProductRemovedEvent event) { + orders.computeIfPresent(event.getOrderId(), (orderId, order) -> { + order.removeProduct(event.getProductId()); + return order; + }); + } + + @EventHandler + public void on(OrderConfirmedEvent event) { + orders.computeIfPresent(event.getOrderId(), (orderId, order) -> { + order.setOrderConfirmed(); + return order; + }); + } + + @EventHandler + public void on(OrderShippedEvent event) { + orders.computeIfPresent(event.getOrderId(), (orderId, order) -> { + order.setOrderShipped(); + return order; + }); + } + + @QueryHandler + public List handle(FindAllOrderedProductsQuery query) { + return new ArrayList<>(orders.values()); + } +} \ No newline at end of file diff --git a/axon/src/main/resources/order-api.http b/axon/src/main/resources/order-api.http index aceb5a97ba..6c06c48989 100644 --- a/axon/src/main/resources/order-api.http +++ b/axon/src/main/resources/order-api.http @@ -1,8 +1,8 @@ -### Place Order, Add Product, Confirm and Ship Order +### Create Order, Add Product, Confirm and Ship Order POST http://localhost:8080/ship-order -### Place Order, Add Product and Ship Order +### Create Order, Add Product and Ship Order POST http://localhost:8080/ship-unconfirmed-order @@ -10,7 +10,7 @@ POST http://localhost:8080/ship-unconfirmed-order GET http://localhost:8080/all-orders -### Place Order with id 666a1661-474d-4046-8b12-8b5896312768 +### Create Order with id 666a1661-474d-4046-8b12-8b5896312768 POST http://localhost:8080/order/666a1661-474d-4046-8b12-8b5896312768 diff --git a/axon/src/test/java/com/baeldung/axon/commandmodel/OrderAggregateUnitTest.java b/axon/src/test/java/com/baeldung/axon/commandmodel/OrderAggregateUnitTest.java index 262a8f1e03..c1d6bdccc2 100644 --- a/axon/src/test/java/com/baeldung/axon/commandmodel/OrderAggregateUnitTest.java +++ b/axon/src/test/java/com/baeldung/axon/commandmodel/OrderAggregateUnitTest.java @@ -3,12 +3,12 @@ package com.baeldung.axon.commandmodel; import com.baeldung.axon.commandmodel.order.OrderAggregate; import com.baeldung.axon.coreapi.commands.AddProductCommand; import com.baeldung.axon.coreapi.commands.ConfirmOrderCommand; +import com.baeldung.axon.coreapi.commands.CreateOrderCommand; import com.baeldung.axon.coreapi.commands.DecrementProductCountCommand; import com.baeldung.axon.coreapi.commands.IncrementProductCountCommand; -import com.baeldung.axon.coreapi.commands.PlaceOrderCommand; import com.baeldung.axon.coreapi.commands.ShipOrderCommand; import com.baeldung.axon.coreapi.events.OrderConfirmedEvent; -import com.baeldung.axon.coreapi.events.OrderPlacedEvent; +import com.baeldung.axon.coreapi.events.OrderCreatedEvent; import com.baeldung.axon.coreapi.events.OrderShippedEvent; import com.baeldung.axon.coreapi.events.ProductAddedEvent; import com.baeldung.axon.coreapi.events.ProductCountDecrementedEvent; @@ -37,37 +37,37 @@ class OrderAggregateUnitTest { } @Test - void giveNoPriorActivity_whenPlaceOrderCommand_thenShouldPublishOrderPlacedEvent() { + void giveNoPriorActivity_whenCreateOrderCommand_thenShouldPublishOrderCreatedEvent() { fixture.givenNoPriorActivity() - .when(new PlaceOrderCommand(ORDER_ID)) - .expectEvents(new OrderPlacedEvent(ORDER_ID)); + .when(new CreateOrderCommand(ORDER_ID)) + .expectEvents(new OrderCreatedEvent(ORDER_ID)); } @Test - void givenOrderPlacedEvent_whenAddProductCommand_thenShouldPublishProductAddedEvent() { - fixture.given(new OrderPlacedEvent(ORDER_ID)) + void givenOrderCreatedEvent_whenAddProductCommand_thenShouldPublishProductAddedEvent() { + fixture.given(new OrderCreatedEvent(ORDER_ID)) .when(new AddProductCommand(ORDER_ID, PRODUCT_ID)) .expectEvents(new ProductAddedEvent(ORDER_ID, PRODUCT_ID)); } @Test - void givenOrderPlacedEventAndProductAddedEvent_whenAddProductCommandForSameProductId_thenShouldThrowDuplicateOrderLineException() { - fixture.given(new OrderPlacedEvent(ORDER_ID), new ProductAddedEvent(ORDER_ID, PRODUCT_ID)) + void givenOrderCreatedEventAndProductAddedEvent_whenAddProductCommandForSameProductId_thenShouldThrowDuplicateOrderLineException() { + fixture.given(new OrderCreatedEvent(ORDER_ID), new ProductAddedEvent(ORDER_ID, PRODUCT_ID)) .when(new AddProductCommand(ORDER_ID, PRODUCT_ID)) .expectException(DuplicateOrderLineException.class) .expectExceptionMessage(Matchers.predicate(message -> ((String) message).contains(PRODUCT_ID))); } @Test - void givenOrderPlacedEventAndProductAddedEvent_whenIncrementProductCountCommand_thenShouldPublishProductCountIncrementedEvent() { - fixture.given(new OrderPlacedEvent(ORDER_ID), new ProductAddedEvent(ORDER_ID, PRODUCT_ID)) + void givenOrderCreatedEventAndProductAddedEvent_whenIncrementProductCountCommand_thenShouldPublishProductCountIncrementedEvent() { + fixture.given(new OrderCreatedEvent(ORDER_ID), new ProductAddedEvent(ORDER_ID, PRODUCT_ID)) .when(new IncrementProductCountCommand(ORDER_ID, PRODUCT_ID)) .expectEvents(new ProductCountIncrementedEvent(ORDER_ID, PRODUCT_ID)); } @Test - void givenOrderPlacedEventProductAddedEventAndProductCountIncrementedEvent_whenDecrementProductCountCommand_thenShouldPublishProductCountDecrementedEvent() { - fixture.given(new OrderPlacedEvent(ORDER_ID), + void givenOrderCreatedEventProductAddedEventAndProductCountIncrementedEvent_whenDecrementProductCountCommand_thenShouldPublishProductCountDecrementedEvent() { + fixture.given(new OrderCreatedEvent(ORDER_ID), new ProductAddedEvent(ORDER_ID, PRODUCT_ID), new ProductCountIncrementedEvent(ORDER_ID, PRODUCT_ID)) .when(new DecrementProductCountCommand(ORDER_ID, PRODUCT_ID)) @@ -75,51 +75,51 @@ class OrderAggregateUnitTest { } @Test - void givenOrderPlacedEventAndProductAddedEvent_whenDecrementProductCountCommand_thenShouldPublishProductRemovedEvent() { - fixture.given(new OrderPlacedEvent(ORDER_ID), new ProductAddedEvent(ORDER_ID, PRODUCT_ID)) + void givenOrderCreatedEventAndProductAddedEvent_whenDecrementProductCountCommand_thenShouldPublishProductRemovedEvent() { + fixture.given(new OrderCreatedEvent(ORDER_ID), new ProductAddedEvent(ORDER_ID, PRODUCT_ID)) .when(new DecrementProductCountCommand(ORDER_ID, PRODUCT_ID)) .expectEvents(new ProductRemovedEvent(ORDER_ID, PRODUCT_ID)); } @Test - void givenOrderPlacedEvent_whenConfirmOrderCommand_thenShouldPublishOrderConfirmedEvent() { - fixture.given(new OrderPlacedEvent(ORDER_ID)) + void givenOrderCreatedEvent_whenConfirmOrderCommand_thenShouldPublishOrderConfirmedEvent() { + fixture.given(new OrderCreatedEvent(ORDER_ID)) .when(new ConfirmOrderCommand(ORDER_ID)) .expectEvents(new OrderConfirmedEvent(ORDER_ID)); } @Test - void givenOrderPlacedEventAndOrderConfirmedEvent_whenConfirmOrderCommand_thenExpectNoEvents() { - fixture.given(new OrderPlacedEvent(ORDER_ID), new OrderConfirmedEvent(ORDER_ID)) + void givenOrderCreatedEventAndOrderConfirmedEvent_whenConfirmOrderCommand_thenExpectNoEvents() { + fixture.given(new OrderCreatedEvent(ORDER_ID), new OrderConfirmedEvent(ORDER_ID)) .when(new ConfirmOrderCommand(ORDER_ID)) .expectNoEvents(); } @Test - void givenOrderPlacedEvent_whenShipOrderCommand_thenShouldThrowUnconfirmedOrderException() { - fixture.given(new OrderPlacedEvent(ORDER_ID)) + void givenOrderCreatedEvent_whenShipOrderCommand_thenShouldThrowUnconfirmedOrderException() { + fixture.given(new OrderCreatedEvent(ORDER_ID)) .when(new ShipOrderCommand(ORDER_ID)) .expectException(UnconfirmedOrderException.class); } @Test - void givenOrderPlacedEventAndOrderConfirmedEvent_whenShipOrderCommand_thenShouldPublishOrderShippedEvent() { - fixture.given(new OrderPlacedEvent(ORDER_ID), new OrderConfirmedEvent(ORDER_ID)) + void givenOrderCreatedEventAndOrderConfirmedEvent_whenShipOrderCommand_thenShouldPublishOrderShippedEvent() { + fixture.given(new OrderCreatedEvent(ORDER_ID), new OrderConfirmedEvent(ORDER_ID)) .when(new ShipOrderCommand(ORDER_ID)) .expectEvents(new OrderShippedEvent(ORDER_ID)); } @Test - void givenOrderPlacedEventProductAndOrderConfirmedEvent_whenAddProductCommand_thenShouldThrowOrderAlreadyConfirmedException() { - fixture.given(new OrderPlacedEvent(ORDER_ID), new OrderConfirmedEvent(ORDER_ID)) + void givenOrderCreatedEventProductAndOrderConfirmedEvent_whenAddProductCommand_thenShouldThrowOrderAlreadyConfirmedException() { + fixture.given(new OrderCreatedEvent(ORDER_ID), new OrderConfirmedEvent(ORDER_ID)) .when(new AddProductCommand(ORDER_ID, PRODUCT_ID)) .expectException(OrderAlreadyConfirmedException.class) .expectExceptionMessage(Matchers.predicate(message -> ((String) message).contains(ORDER_ID))); } @Test - void givenOrderPlacedEventProductAddedEventAndOrderConfirmedEvent_whenIncrementProductCountCommand_thenShouldThrowOrderAlreadyConfirmedException() { - fixture.given(new OrderPlacedEvent(ORDER_ID), + void givenOrderCreatedEventProductAddedEventAndOrderConfirmedEvent_whenIncrementProductCountCommand_thenShouldThrowOrderAlreadyConfirmedException() { + fixture.given(new OrderCreatedEvent(ORDER_ID), new ProductAddedEvent(ORDER_ID, PRODUCT_ID), new OrderConfirmedEvent(ORDER_ID)) .when(new IncrementProductCountCommand(ORDER_ID, PRODUCT_ID)) @@ -128,8 +128,8 @@ class OrderAggregateUnitTest { } @Test - void givenOrderPlacedEventProductAddedEventAndOrderConfirmedEvent_whenDecrementProductCountCommand_thenShouldThrowOrderAlreadyConfirmedException() { - fixture.given(new OrderPlacedEvent(ORDER_ID), + void givenOrderCreatedEventProductAddedEventAndOrderConfirmedEvent_whenDecrementProductCountCommand_thenShouldThrowOrderAlreadyConfirmedException() { + fixture.given(new OrderCreatedEvent(ORDER_ID), new ProductAddedEvent(ORDER_ID, PRODUCT_ID), new OrderConfirmedEvent(ORDER_ID)) .when(new DecrementProductCountCommand(ORDER_ID, PRODUCT_ID)) From af4e82b0893e1f90d4b432df5e7cb135ff7bd9bf Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Fri, 19 Mar 2021 11:54:20 +0100 Subject: [PATCH 12/12] Update previous complete test endpoints The test endpoints used to create an order, add a product, confirm the order and ship it are not embracing the asynchronous approach of the CommandGateway. Make sure that all commands are composed with one another and return the completable future for Spring to resolve #BAEL-4767 --- .../baeldung/axon/gui/OrderRestEndpoint.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/axon/src/main/java/com/baeldung/axon/gui/OrderRestEndpoint.java b/axon/src/main/java/com/baeldung/axon/gui/OrderRestEndpoint.java index 5c385c487f..11e03bf6a5 100644 --- a/axon/src/main/java/com/baeldung/axon/gui/OrderRestEndpoint.java +++ b/axon/src/main/java/com/baeldung/axon/gui/OrderRestEndpoint.java @@ -32,21 +32,21 @@ public class OrderRestEndpoint { } @PostMapping("/ship-order") - public void shipOrder() { + public CompletableFuture shipOrder() { String orderId = UUID.randomUUID().toString(); - commandGateway.send(new CreateOrderCommand(orderId)); - commandGateway.send(new AddProductCommand(orderId, "Deluxe Chair")); - commandGateway.send(new ConfirmOrderCommand(orderId)); - commandGateway.send(new ShipOrderCommand(orderId)); + return commandGateway.send(new CreateOrderCommand(orderId)) + .thenCompose(result -> commandGateway.send(new AddProductCommand(orderId, "Deluxe Chair"))) + .thenCompose(result -> commandGateway.send(new ConfirmOrderCommand(orderId))) + .thenCompose(result -> commandGateway.send(new ShipOrderCommand(orderId))); } @PostMapping("/ship-unconfirmed-order") - public void shipUnconfirmedOrder() { + public CompletableFuture shipUnconfirmedOrder() { String orderId = UUID.randomUUID().toString(); - commandGateway.send(new CreateOrderCommand(orderId)); - commandGateway.send(new AddProductCommand(orderId, "Deluxe Chair")); - // This throws an exception, as an Order cannot be shipped if it has not been confirmed yet. - commandGateway.send(new ShipOrderCommand(orderId)); + return commandGateway.send(new CreateOrderCommand(orderId)) + .thenCompose(result -> commandGateway.send(new AddProductCommand(orderId, "Deluxe Chair"))) + // This throws an exception, as an Order cannot be shipped if it has not been confirmed yet. + .thenCompose(result -> commandGateway.send(new ShipOrderCommand(orderId))); } @PostMapping("/order")