From 886368a4c32a3b3d7922cc9d9b2b94e84c0a12fc Mon Sep 17 00:00:00 2001 From: Steven van Beelen Date: Fri, 19 Mar 2021 11:07:26 +0100 Subject: [PATCH] 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); + } +}