Axon 2-4 migration: Change to apply() semantics

FYI for anyone who is working on porting from Axon 2 to Axon 4: the semantics of apply() changed in a way that may require changes to aggregate code. Happily, my JUnit tests caught this because a bunch of command handlers stopped generating the same events as under Axon 2. Below are the two options I came up with; would love to hear others if I’m missing something better.

Command handlers on event-sourced aggregates aren’t supposed to update state; that has to be done in event handlers. However, in Axon 2, the apply() method was synchronous: when it returned, the aggregate’s state would reflect the applied events, and command handlers could thus use that state to decide what to do next. For example:

`
@CommandHandler
public void handle(MyCommand command) {
FirstEvent event = makeEvent(command);
apply(event);
if (flagThatGetsSetByFirstEventHandler) {
apply(makeSecondEvent());
}
if (flagThatGetsSetByFirstOrSecondEventHandler) {
apply(makeThirdEvent());
}
}

`

In Axon 4, this won’t work, because apply() defers event handling until after the command handler returns. So the second and third events would never be published. There appear to be two ways to port this logic. First is to use callbacks:

@CommandHandler public void handle(MyCommand command) { FirstEvent event = makeEvent(command); apply(event).andThen(() -> { if (flagThatGetsSetByFirstEventHandler) { apply(makeSecondEvent()).andThen(() -> { if (flagThatGetsSetByFirstOrSecondEventHandler) { apply(makeThirdEvent()); } }); } else if (flagThatGetsSetByFirstOrSecondEventHandler) { apply(makeThirdEvent()); } }); }

Second is to move the dependent logic into event handlers, guarded by isLive().

`
@CommandHandler
public void handle(MyCommand command) {
apply(makeEvent(command));
}

@EventSourcingHandler
public void on(FirstEvent event) {
updateState(event);
if (isLive() && flagThatGetsSetByFirstEventHandler) {
apply(makeSecondEvent());
}
if (flagThatGetsSetByFirstOrSecondEventHandler) {
if (isLive()) {
apply(makeThirdEvent());
}
thirdEventAlreadyApplied = true;
}
}

@EventSourcingHandler
public void on(SecondEvent event) {
updateState(event);
if (isLive() && flagThatGetsSetByFirstOrSecondEventHandler && !thirdEventAlreadyApplied) {
apply(makeThirdEvent());
}
}

`

Feedback welcome!

-Steve

One subtlety here that I forgot to mention, which is actually the thing that’s taking me the most effort to unravel: Because of Axon 2’s synchronous behavior, there was implicit nesting of cascading events. That is, if you had an event handler that itself applied a new event, and did

apply(event1); // event1 handler applies event1A
apply(event2);

in Axon 2 the sequence of events would be 1, 1A, 2. But in Axon 4, the sequence will be 1, 2, 1A, since the handler for event1 doesn’t get called until the command handler returns and the deferral mechanism is a single queue. So that’s something else to watch out for. I don’t have a good solution for this yet; the best idea I’ve come up with is to implement an event deferral mechanism in the aggregate code itself that keeps track of the tree of events and makes sure they get applied in depth-first order, but that seems pretty ugly. Presumably there is now a lot of code in the wild that depends on the existing Axon 4 behavior, so changing the framework to do nesting-aware deferral would break too many things.

Of course, if I’m just flat-out wrong about how all this works, I’d love to find out!

-Steve

Hi Steven,

if I recall correctly (and unless there is a bug somewhere), it is safe to apply events withing EventSourcingHandlers. Axon will not (re)apply those when initializing state based on historic state. Obviously, there is a small object creation overhead, as events are still created.
When applying an event within an Event Sourcing handler, and the aggregate is “live”, then applying the internal event is deferred until after the first event has finished handling. It doesn’t actually wait for the command handler to complete.
The logic is in the org.axonframework.modelling.command.inspection.AnnotatedAggregate#doApply method.

Also note that for instance methods, after calling apply(), the state has been updated by the event sourcing handler. In the constructor, this is unfortunately not the case. There, you would need to use .andThen() to ensure logic is executed after state changes have been applied. This is due to the instance reference not being available until after the constructor has completed.

Hope this helps.
Cheers,

Allard