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
This commit is contained in:
Steven van Beelen 2021-03-19 11:07:26 +01:00
parent ef1167b037
commit 886368a4c3
2 changed files with 132 additions and 10 deletions

View File

@ -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<String, OrderLine> 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
}
}

View File

@ -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);
}
}