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
diff --git a/axon/src/main/java/com/baeldung/axon/commandmodel/OrderAggregate.java b/axon/src/main/java/com/baeldung/axon/commandmodel/OrderAggregate.java
deleted file mode 100644
index 4ef02e6b54..0000000000
--- a/axon/src/main/java/com/baeldung/axon/commandmodel/OrderAggregate.java
+++ /dev/null
@@ -1,59 +0,0 @@
-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;
-
-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.exceptions.UnconfirmedOrderException;
-
-@Aggregate
-public class OrderAggregate {
-
- @AggregateIdentifier
- private String orderId;
- private boolean orderConfirmed;
-
- @CommandHandler
- public OrderAggregate(PlaceOrderCommand command) {
- apply(new OrderPlacedEvent(command.getOrderId(), command.getProduct()));
- }
-
- @CommandHandler
- public void handle(ConfirmOrderCommand command) {
- apply(new OrderConfirmedEvent(orderId));
- }
-
- @CommandHandler
- public void handle(ShipOrderCommand command) {
- if (!orderConfirmed) {
- throw new UnconfirmedOrderException();
- }
-
- apply(new OrderShippedEvent(orderId));
- }
-
- @EventSourcingHandler
- public void on(OrderPlacedEvent event) {
- this.orderId = event.getOrderId();
- this.orderConfirmed = false;
- }
-
- @EventSourcingHandler
- public void on(OrderConfirmedEvent event) {
- this.orderConfirmed = true;
- }
-
- 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/OrderAggregate.java b/axon/src/main/java/com/baeldung/axon/commandmodel/order/OrderAggregate.java
new file mode 100644
index 0000000000..97342bdb3a
--- /dev/null
+++ b/axon/src/main/java/com/baeldung/axon/commandmodel/order/OrderAggregate.java
@@ -0,0 +1,98 @@
+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.CreateOrderCommand;
+import com.baeldung.axon.coreapi.commands.ShipOrderCommand;
+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.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 {
+
+ @AggregateIdentifier
+ private String orderId;
+ private boolean orderConfirmed;
+
+ @AggregateMember
+ private Map orderLines;
+
+ @CommandHandler
+ public OrderAggregate(CreateOrderCommand command) {
+ apply(new OrderCreatedEvent(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));
+ }
+
+ @CommandHandler
+ public void handle(ShipOrderCommand command) {
+ if (!orderConfirmed) {
+ throw new UnconfirmedOrderException();
+ }
+
+ apply(new OrderShippedEvent(orderId));
+ }
+
+ @EventSourcingHandler
+ public void on(OrderCreatedEvent event) {
+ this.orderId = event.getOrderId();
+ this.orderConfirmed = false;
+ this.orderLines = new HashMap<>();
+ }
+
+ @EventSourcingHandler
+ public void on(OrderConfirmedEvent event) {
+ 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);
+ }
+}
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/CreateOrderCommand.java b/axon/src/main/java/com/baeldung/axon/coreapi/commands/CreateOrderCommand.java
new file mode 100644
index 0000000000..ceb7fd6a08
--- /dev/null
+++ b/axon/src/main/java/com/baeldung/axon/coreapi/commands/CreateOrderCommand.java
@@ -0,0 +1,43 @@
+package com.baeldung.axon.coreapi.commands;
+
+import org.axonframework.modelling.command.TargetAggregateIdentifier;
+
+import java.util.Objects;
+
+public class CreateOrderCommand {
+
+ @TargetAggregateIdentifier
+ private final String orderId;
+
+ public CreateOrderCommand(String orderId) {
+ this.orderId = orderId;
+ }
+
+ public String getOrderId() {
+ return orderId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ CreateOrderCommand that = (CreateOrderCommand) o;
+ return Objects.equals(orderId, that.orderId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(orderId);
+ }
+
+ @Override
+ public String toString() {
+ return "CreateOrderCommand{" +
+ "orderId='" + orderId + '\'' +
+ '}';
+ }
+}
\ No newline at end of file
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
deleted file mode 100644
index c70d503050..0000000000
--- a/axon/src/main/java/com/baeldung/axon/coreapi/commands/PlaceOrderCommand.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package com.baeldung.axon.coreapi.commands;
-
-import java.util.Objects;
-
-import org.axonframework.modelling.command.TargetAggregateIdentifier;
-
-public class PlaceOrderCommand {
-
- @TargetAggregateIdentifier
- private final String orderId;
- private final String product;
-
- public PlaceOrderCommand(String orderId, String product) {
- this.orderId = orderId;
- this.product = product;
- }
-
- public String getOrderId() {
- return orderId;
- }
-
- public String getProduct() {
- return product;
- }
-
- @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);
- }
-
- @Override
- public String toString() {
- return "PlaceOrderCommand{" +
- "orderId='" + orderId + '\'' +
- ", product='" + product + '\'' +
- '}';
- }
-}
\ No newline at end of file
diff --git a/axon/src/main/java/com/baeldung/axon/coreapi/events/OrderCreatedEvent.java b/axon/src/main/java/com/baeldung/axon/coreapi/events/OrderCreatedEvent.java
new file mode 100644
index 0000000000..5d2d8b7f55
--- /dev/null
+++ b/axon/src/main/java/com/baeldung/axon/coreapi/events/OrderCreatedEvent.java
@@ -0,0 +1,40 @@
+package com.baeldung.axon.coreapi.events;
+
+import java.util.Objects;
+
+public class OrderCreatedEvent {
+
+ private final String orderId;
+
+ public OrderCreatedEvent(String orderId) {
+ this.orderId = orderId;
+ }
+
+ public String getOrderId() {
+ return orderId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ OrderCreatedEvent that = (OrderCreatedEvent) o;
+ return Objects.equals(orderId, that.orderId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(orderId);
+ }
+
+ @Override
+ public String toString() {
+ return "OrderCreatedEvent{" +
+ "orderId='" + orderId + '\'' +
+ '}';
+ }
+}
\ No newline at end of file
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
deleted file mode 100644
index 06de4c5f9f..0000000000
--- a/axon/src/main/java/com/baeldung/axon/coreapi/events/OrderPlacedEvent.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package com.baeldung.axon.coreapi.events;
-
-import java.util.Objects;
-
-public class OrderPlacedEvent {
-
- private final String orderId;
- private final String product;
-
- public OrderPlacedEvent(String orderId, String product) {
- this.orderId = orderId;
- this.product = product;
- }
-
- public String getOrderId() {
- return orderId;
- }
-
- public String getProduct() {
- return product;
- }
-
- @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);
- }
-
- @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 + '\'' +
+ '}';
+ }
+}
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.");
+ }
+}
diff --git a/axon/src/main/java/com/baeldung/axon/coreapi/queries/Order.java b/axon/src/main/java/com/baeldung/axon/coreapi/queries/Order.java
new file mode 100644
index 0000000000..1810a053d3
--- /dev/null
+++ b/axon/src/main/java/com/baeldung/axon/coreapi/queries/Order.java
@@ -0,0 +1,83 @@
+package com.baeldung.axon.coreapi.queries;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+public class Order {
+
+ private final String orderId;
+ private final Map products;
+ private OrderStatus orderStatus;
+
+ public Order(String orderId) {
+ this.orderId = orderId;
+ this.products = new HashMap<>();
+ orderStatus = OrderStatus.CREATED;
+ }
+
+ public String getOrderId() {
+ return orderId;
+ }
+
+ 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;
+ }
+
+ public void setOrderShipped() {
+ this.orderStatus = OrderStatus.SHIPPED;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Order that = (Order) o;
+ return Objects.equals(orderId, that.orderId)
+ && Objects.equals(products, that.products)
+ && orderStatus == that.orderStatus;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(orderId, products, orderStatus);
+ }
+
+ @Override
+ public String toString() {
+ 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/coreapi/queries/OrderedProduct.java b/axon/src/main/java/com/baeldung/axon/coreapi/queries/OrderedProduct.java
deleted file mode 100644
index d847bb2a98..0000000000
--- a/axon/src/main/java/com/baeldung/axon/coreapi/queries/OrderedProduct.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package com.baeldung.axon.coreapi.queries;
-
-import java.util.Objects;
-
-public class OrderedProduct {
-
- private final String orderId;
- private final String product;
- private OrderStatus orderStatus;
-
- public OrderedProduct(String orderId, String product) {
- this.orderId = orderId;
- this.product = product;
- orderStatus = OrderStatus.PLACED;
- }
-
- public String getOrderId() {
- return orderId;
- }
-
- public String getProduct() {
- return product;
- }
-
- public OrderStatus getOrderStatus() {
- return orderStatus;
- }
-
- public void setOrderConfirmed() {
- this.orderStatus = OrderStatus.CONFIRMED;
- }
-
- public void setOrderShipped() {
- this.orderStatus = OrderStatus.SHIPPED;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(orderId, product, 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);
- }
-
- @Override
- public String toString() {
- return "OrderedProduct{" +
- "orderId='" + orderId + '\'' +
- ", product='" + product + '\'' +
- ", orderStatus=" + orderStatus +
- '}';
- }
-}
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..11e03bf6a5 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 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.ShipOrderCommand;
+import com.baeldung.axon.coreapi.queries.FindAllOrderedProductsQuery;
+import com.baeldung.axon.coreapi.queries.Order;
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 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.queries.FindAllOrderedProductsQuery;
-import com.baeldung.axon.coreapi.queries.OrderedProduct;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
@RestController
public class OrderRestEndpoint {
@@ -28,25 +32,63 @@ public class OrderRestEndpoint {
}
@PostMapping("/ship-order")
- public void shipOrder() {
+ public CompletableFuture shipOrder() {
String orderId = UUID.randomUUID().toString();
- commandGateway.send(new PlaceOrderCommand(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 PlaceOrderCommand(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")
+ public CompletableFuture createOrder() {
+ return createOrder(UUID.randomUUID().toString());
+ }
+
+ @PostMapping("/order/{order-id}")
+ public CompletableFuture createOrder(@PathVariable("order-id") String orderId) {
+ return commandGateway.send(new CreateOrderCommand(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 List findAllOrderedProducts() {
- return queryGateway.query(new FindAllOrderedProductsQuery(), ResponseTypes.multipleInstancesOf(OrderedProduct.class))
- .join();
+ 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 a37f0111ed..0000000000
--- a/axon/src/main/java/com/baeldung/axon/querymodel/OrderedProductsEventHandler.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package com.baeldung.axon.querymodel;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-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;
-
-@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, event.getProduct()));
- }
-
- @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 a3c69c72bc..6c06c48989 100644
--- a/axon/src/main/resources/order-api.http
+++ b/axon/src/main/resources/order-api.http
@@ -1,11 +1,37 @@
+### Create Order, Add Product, Confirm and Ship Order
+
POST http://localhost:8080/ship-order
-###
+### Create Order, Add Product and Ship Order
POST http://localhost:8080/ship-unconfirmed-order
-###
+### Retrieve all existing Orders
GET http://localhost:8080/all-orders
+### Create 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
+
###
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..c1d6bdccc2 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.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.ShipOrderCommand;
+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.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.junit.*;
+import org.axonframework.test.matchers.Matchers;
+import org.junit.jupiter.api.*;
-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 java.util.UUID;
-public class OrderAggregateUnitTest {
+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_whenCreateOrderCommand_thenShouldPublishOrderCreatedEvent() {
fixture.givenNoPriorActivity()
- .when(new PlaceOrderCommand(orderId, product))
- .expectEvents(new OrderPlacedEvent(orderId, product));
+ .when(new CreateOrderCommand(ORDER_ID))
+ .expectEvents(new OrderCreatedEvent(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 givenOrderCreatedEvent_whenAddProductCommand_thenShouldPublishProductAddedEvent() {
+ fixture.given(new OrderCreatedEvent(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 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 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 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))
+ .expectEvents(new ProductCountDecrementedEvent(ORDER_ID, PRODUCT_ID));
+ }
+
+ @Test
+ 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 givenOrderCreatedEvent_whenConfirmOrderCommand_thenShouldPublishOrderConfirmedEvent() {
+ fixture.given(new OrderCreatedEvent(ORDER_ID))
+ .when(new ConfirmOrderCommand(ORDER_ID))
+ .expectEvents(new OrderConfirmedEvent(ORDER_ID));
+ }
+
+ @Test
+ void givenOrderCreatedEventAndOrderConfirmedEvent_whenConfirmOrderCommand_thenExpectNoEvents() {
+ fixture.given(new OrderCreatedEvent(ORDER_ID), new OrderConfirmedEvent(ORDER_ID))
+ .when(new ConfirmOrderCommand(ORDER_ID))
+ .expectNoEvents();
+ }
+
+ @Test
+ void givenOrderCreatedEvent_whenShipOrderCommand_thenShouldThrowUnconfirmedOrderException() {
+ fixture.given(new OrderCreatedEvent(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 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 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 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))
+ .expectException(OrderAlreadyConfirmedException.class)
+ .expectExceptionMessage(Matchers.predicate(message -> ((String) message).contains(ORDER_ID)));
+ }
+
+ @Test
+ 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))
+ .expectException(OrderAlreadyConfirmedException.class)
+ .expectExceptionMessage(Matchers.predicate(message -> ((String) message).contains(ORDER_ID)));
+ }
}
\ No newline at end of file