cqrsevent-sourcingaxonaxon-framework

Understanding of @AggregateIdentifier & @TargetAggregateIdentifier


so I'm learning about the axon framework and just want to solidify my understanding of the @TargetAggregateIdentifier annotation.

My Command:

public class IssueCardCommand {

private String cardId;
private String versionNumber;
private Integer amount;

@TargetAggregateIdentifier
private String getAggregateIdentifier() {
    return (null != versionNumber) ? cardId + "V" + versionNumber : cardId;
    }
}

My Aggregate:

@Aggregate
@Slf4j
public class GiftCard {

private String giftCardId;
private String versionNumber;
private Integer amount;

@AggregateIdentifier
private String getAggregateIdentifier() {
    return (null != versionNumber) ? giftCardId + "V" + versionNumber : giftCardId;
}

public GiftCard() {
    log.info("empty noargs constructor");
}

@CommandHandler
public GiftCard(IssueCardCommand cmd) {
    log.info("handling {}",cmd);
    //this.giftCardId = cmd.getCardId();
    //this.versionNumber = cmd.getVersionNumber();
    apply(new CardIssuedEvent(cmd.getCardId(),cmd.getVersionNumber(),cmd.getAmount()));
}

@EventSourcingHandler
public void onCardIssuedEvent(CardIssuedEvent evt) {
    log.info("applying {}",evt);
    this.giftCardId = evt.getCardId();
    this.versionNumber = evt.getVersionNumber();
    this.amount = evt.getAmount();
    }
}

So this all works as expected and the events are being stored correctly. However, I just want to make sure that I understand the @TargetAggregateIdentifier & @AggregateIdentifier annotations correctly.

So, the

@TargetAggregateIdentifier - a command goes to a specific instance of the aggregate and so it is needed to tell the framework which instance it is, so this annotation on a field/method is used to load the events for that particular aggregate?

I noticed that when I didn't have @TargetAggregateIdentifier in the command for the constructor, the code still works. But if it was missing from any subsequent commands it gave the error 'Invalid command, It does not identify the target aggregate', which I feel like confirmed my understanding above?

@AggregateIdentifier - this tells the axon framework that this is the identifier, so that when more commands are introduced it will need to store the events for that particular aggregate?

I would really appreciate it if someone could point out if my understanding is correct and comment where it isn't, so that I can use the framework in the correct way.

Thanks.


Solution

  • I already provided an answer on AxonIQ's discuss platform where the question was originally posted.

    Since answering with a link only goes against SO's etiquette I'm coping my answer here too:

    Your understanding is correct I guess. But let me give you a simplified explanation of the process so you can compare it to your current understanding.

    When you issue a command that creates new aggregate (one that is handled by the Aggregate's constructor) there is no need for identifier. The reason is you are creating a new instance, not loading an existing one. In that case the framework only needs the FQCN of the aggregate in order to create an instance of it. It can easily find the FQCN because it knows (from inspection during aggregate registration) which aggregate can handle such create command. That's why create commands without @TargetAggregateIdentifier work just fine.

    Once the create command is processed the aggregate's state needs to be stored somewhere.

    • for event sourced aggregates all state changing events are stored in a event store
    • for state stored aggregates the entire state is stored in a repository

    In both cases the framework needs to know how to identify this data. That's what @AggregateIdentifier is for. It tells the framework to store the data in way that it is identifiable by specific identifier. You can think of it as primary key in DB terms.

    When you send a command to an existing instance of an aggregate you need to tell which instance that is. You do so by providing a @TargetAggregateIdentifier . The framework will then create a new empty instance of the respective aggregate and then try to load the data into it

    • for event sourced aggregates by reading all the past events related to that instance
    • for state stored aggregates by reading current state from a repository

    In both cases the framework will search for data that's identifiable by the value of @TargetAggregateIdentifier. Once the aggregate data is loaded it will proceed with handling the command.