Command Handler reliant on another aggregate root for instantiation

115 views
Skip to first unread message

Matt Goodwin

unread,
Jan 25, 2016, 8:01:06 AM1/25/16
to Axon Framework Users
Hi, 

I am having difficulties testing my command handler as the aggregate root in test, relies on another aggregate root for instantiation. I'm sure im not the first problem to have this problem so I am probably missing something, my code is as follows:

public class DayTicketWater extends AbstractAnnotatedAggregateRoot {

    public DayTicketWater(DayTicketWaterId id, String name) {
        this.id = id;
        apply(new DayTicketWaterBuilt(id, name));
    }

    public DayTicketSession scheduleSession(DayTicketSessionId sessionId, SessionPeriod period) {
        return new DayTicketSession(sessionId, this.id, period, this.pegs);
    }
}

public class DayTicketSession extends AbstractAnnotatedAggregateRoot {

    @AggregateIdentifier
    private DayTicketSessionId id;
    private ArrayList<Peg> availablePegs = new ArrayList<>();

    @SuppressWarnings("UnusedDeclaration")
    DayTicketSession() {}

    public DayTicketSession(DayTicketSessionId id, DayTicketWaterId waterId, SessionPeriod period, ArrayList<Peg> pegs) {
        apply(new DayTicketSessionScheduled(id, waterId, period, pegs));
    }
}

public class DayTicketSessionCommandHandler {

    private Repository<DayTicketSession> repository;
    private Repository<DayTicketWater> waterRepository;

    @Autowired
    public DayTicketSessionCommandHandler(Repository<DayTicketSession> repository, Repository<DayTicketWater> waterRepository) {
        this.repository = repository;
        this.waterRepository = waterRepository;
    }

    @CommandHandler
    public void handle(ScheduleDayTicketSession command) {
        DayTicketWater water = waterRepository.load(command.getWaterId());
        DayTicketSession session = water.scheduleSession(command.getId(), command.getPeriod());

        repository.add(session);
    }
}

public class DayTicketSessionCommandHandlerTest {

    private FixtureConfiguration<DayTicketSession> fixture;
    private FixtureConfiguration<DayTicketWater> waterFixture;

    @Before
    public void setUp() {
        fixture = Fixtures.newGivenWhenThenFixture(DayTicketSession.class);
        waterFixture = Fixtures.newGivenWhenThenFixture(DayTicketWater.class);
        DayTicketSessionCommandHandler commandHandler = new DayTicketSessionCommandHandler(fixture.getRepository(), waterFixture.getRepository());
        fixture.registerAnnotatedCommandHandler(commandHandler);
    }

    @Test
    public void it_should_schedule_session_with_pegged_water() {

        DayTicketSessionId id = DayTicketSessionId.generate();
        SessionPeriod sessionPeriod = new SessionPeriod(LocalDate.of(2010, 10, 1), LocalDate.of(2010, 10, 2));
        DayTicketWaterId waterId = DayTicketWaterId.generate();
        Peg peg = new Peg("The Shallows");

        waterFixture.given(
                new DayTicketWaterBuilt(waterId, "Fishery 1"),
                new PegAddedToDayTicketWater(waterId, peg)
        );

        fixture.given()
                .when(new ScheduleDayTicketSession(id, waterId, sessionPeriod))
                .expectVoidReturnType()
                .expectEvents(new DayTicketSessionScheduled(id, waterId, sessionPeriod, new ArrayList<>(Arrays.asList(peg))));
    }
}

As you can see the DayTicketSession is instantiated by DayTicketWater when invoking the scheduleSession method. I obviously want to add tests for this, if you check my DayTicketSessionCommandHandlerTest, this is where the problem is. The error is as follows:

The aggregate loaded based on the generated events seems to be of another type than the original.
Working type: <DayTicketWater>
Event Sourced type: <DayTicketSession>

Matt Goodwin

unread,
Jan 25, 2016, 8:14:12 AM1/25/16
to Axon Framework Users
I think this may be a duplicate of https://groups.google.com/forum/#!msg/axonframework/4Fc26pCtpWI/8t01OfB1wnMJ, does the outcome of mocking still apply?

Allard Buijze

unread,
Jan 25, 2016, 8:36:54 AM1/25/16
to Axon Framework Users
Hi Matt,

yes, I believe the outcome is still the same. Accessing an aggregate root from another is at least a design smell in cqrs. A single command should invoke a single aggregate. If it needs external data for anything, use a query model instead.

Cheers,

Allard
--
You received this message because you are subscribed to the Google Groups "Axon Framework Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to axonframewor...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Matt Goodwin

unread,
Jan 25, 2016, 4:13:24 PM1/25/16
to Axon Framework Users
Thanks for the quick reply! 

Using the DayTicketWater as a Factory class with the method of scheduleSession better suits the ubiquitious language. I have also seen this type of instantiating in other DDD / CQRS applications so i dont think its a design smell, see https://groups.google.com/forum/#!topic/dddcqrs/B6kxs7FK8_I 

Allard Buijze

unread,
Feb 2, 2016, 4:52:12 AM2/2/16
to Axon Framework Users
Hi Matt,

I wasn't aware that one aggregate was the factory of the other. So it's not really one aggregate that is accessing another, but one that uses its state to validate the creation of another. That is in fact a design that is considered quite the opposite of bad design. 

I haven't tried, but it might be possible to do this in the current Axon API. Just create an aggregate instance from another aggregate and have it apply events. You do need to add() that newly created instance to an appropriate repository.

In Axon 3, we will take this use case into account in the API design, to allow for a cleaner solution.

Cheers,

Allard

Matt Goodwin

unread,
Feb 2, 2016, 5:36:43 AM2/2/16
to Axon Framework Users
Hi Allard,

Yes this is what i have done (see code above). However its not working as expecting, maybe i'm doing something wrong?

Should i be using two fixtures side by side, the first fixture setting up the state of the factory aggregate, then the second fixture will create the new instance using the factory method?

Thanks

Allard Buijze

unread,
Feb 3, 2016, 2:23:25 AM2/3/16
to Axon Framework Users
Hi Matt,

I am afraid the fixtures can't cope with this situation. The fixtures are hard-wired to support only a single aggregate instance. You'd probably have to test the behavior without the fixtures, for this case.

Cheers,

Allard
Reply all
Reply to author
Forward
0 new messages