Search code examples
domain-driven-designaxon

Multi-Entity Aggregate Best Practices : when to create the first "sub" entity in an Axon Aggregate


(Relocating this discussion here from the Axon Forum https://discuss.axoniq.io/t/multi-entity-aggregate-best-practices-when-to-create-the-first-entity/3827)

The Axon documentation recommends adding new entities to an aggregate through the EventSourcingHandler…

from: Multi-Entity Aggregates - Axon Reference Guide

The creation of the Entity takes place in an event sourcing handler of it’s parent. It is thus not possible to have a ‘command handling constructor’ on the entity class as with the aggregate root.

(sic: note the apostrophe in “it’s” is a typo)

But when/where do you create the initial entity?

Your GiftCard aggregate starts with no entities. We have a similar model but want an initial entity to be created when the aggregate is created. e.g. imagine if a new GiftCard always came with a “default” GiftCardTransaction. Where would you do this?

a) in the constructor of the GiftCard:

 public GiftCard(IssueCardCommand cmd) {
    ... 
    // problem: never gets replayed!
    transactions.add(new GiftCardTransaction(/*defaults*/)

b) in the initializing event handler

public void on(CardIssuedEvent evt) {    // initialize aggregate
      // create the first entity
      // gets replayed when the aggregate creation is replayed
     transactions.add(new GiftCardTransaction(/*defaults*/)
}

c) in the event handler but by firing the event

public void on(CardIssuedEvent evt) {     // initialize aggregate
       // fire the event to create the first entity
      // problem: gets replayed twice: when the aggregate creation is replayed and the subsequent event is replayed!!?
      apply(new CardRedeemedEvent(/*defaults*/);
   }

d) same as c but use an annotation to prevent the double replay:

public void on(CardIssuedEvent evt) {    // initialize aggregate
      // call a non-replayable method to fire the event
      createFirstEntity();
 }

@DisallowReplay  
public void createFirstEntity() {
      // The replay of this CardRedeemedEvent will create the first entity on replay, 
      // but the replay of the CardIssuedEvent will not, thanks to the @DisallowReplay... right??
      apply(new CardRedeemedEvent(/*defaults*/);
 }

Solution

  • When using the event sourcing repository the correct answer is b. You should not apply an event in an event sourcing handler. The Event sourcing handler should only update the state of the Aggregate. Every time a new command is handled, all the events with the AggregateId for that Command will be replayed against an empty instance of that Aggregate before the Command handling starts. That is why you should not apply an event in an EventSourcing handler. The creation of the entity is a side effect of the first event. Disallow replay could be used in methods or classes that use the EventHandler annotation and not for EventSourcingHandlers.