0

assume we have an stock. this stock should persist product id and available quantity. the user of this stock can frequently update(InitAvailableQuantityCommand) available quantity. if some product has been sold, our system will get a soldEvent(DecreaseAvailableQuantityCommand) and available quantity for sold product should be decress.

it works well with aggregate below, until one thing, if i try again to re-initialize stock with InitAvailableQuantityCommand, the event will be ignored and an error is thrown

An event for aggregate [3333] at sequence [0] was already inserted"

What i try to achive is following:

  1. InitAvailableQuantityCommand (productId =1, quantity = 10)
  2. DecreaseAvailableQuantityCommand (productId =1, quantity = 1)
  3. DecreaseAvailableQuantityCommand (productId =1, quantity = 1)
  4. now hier we have 8 available products more.
  5. and it this moment user will re-initialize stock with 20 available products for productId 1. the user will send a new InitAvailableQuantityCommand (productId =1, quantity = 20) and it this moment it fail and doesn't work.

What do i wrong?

thx.

@NoArgsConstructor
@Aggregate
@Data
public class AvailableQuantityAggregate {

private String partnerId;
private String productId;

@AggregateIdentifier
private String productVariationId;
private int quantity;

@CommandHandler
public AvailableQuantityAggregate(InitAvailableQuantityCommand cmd) {
    final ApplyMore apply = AggregateLifecycle.apply(AvailableQuantityInitializedEvent.builder()
                                                             .partnerId(cmd.getPartnerId())
                                                             .productId(cmd.getProductId())
                                                             .productVariationId(cmd.getProductVariationId())
                                                             .quantity(cmd.getQuantity())
                                                             .build());
}

@CommandHandler
public void handle(DecreaseAvailableQuantityCommand cmd) {
    AggregateLifecycle.apply(AvailableQuantityDecreasedEvent.builder()
                                     .productVariationId(cmd.getProductVariationId())
                                     .quantity(cmd.getQuantity())
                                     .build());
}

@EventSourcingHandler
protected void on(AvailableQuantityInitializedEvent event) {
    this.productVariationId = event.getProductVariationId();
    this.partnerId = event.getPartnerId();
    this.productId = event.getProductId();
    this.quantity = event.getQuantity();
}

@EventSourcingHandler
protected void on(AvailableQuantityDecreasedEvent event) {
    this.quantity = this.quantity-event.getQuantity();
}
}
user1167253
  • 813
  • 1
  • 11
  • 27

2 Answers2

1

The InitAvailableQuantityCommand instantiates an Aggregate. Aggregates inherently have identity. As such, the Aggregate Identifier is in place to denote whom/what it is. When you are event sourcing, which you are by default in Axon, the Event Store will ensure that you will not add events with the same aggregate id and sequence number. When you are publishing the InitAvailableQuantityCommand a second time however, you are telling the framework to publish an event with the same aggregate id and sequence number.

Hence, your modelling solution should be a little different. The action (aka, the command) of instantiating the aggregate is different from resetting it. Thus I'd suggest to add a different command to reset your aggregate to it's initial values.

Steven
  • 6,936
  • 1
  • 16
  • 31
1

Judging from your code snippet, the InitAvailableQuantityCommand is handled by a constructor. This means that Axon expects to need to create a new instance of an aggregate. But as you are expecting to load an instance, there is a collision of identifiers (fortunately). What you'd need to do, is create a different command that contains the same information, but is handled by an instance method. This might be what you want to do anyway, because there a conceptual/functional different between first-time initialization, and "resetting".

In future versions of Axon, we will support "create-or-update" kind functionality, where a single Command could fulfill both roles.

Allard
  • 2,640
  • 13
  • 13