Reputation: 1334
I am experiencing an issue where an event is being triggered that I would imagine shouldn't be. I am playing with axon and am modeling a letter sending service based on letter sending events. My example is kind of trivial.
I have designed two input commands ScheduleLetterCommand
which will "send a letter" in a few minutes...which allows the user to cancel it if they send another command CancelLetterCommand
within that time period.
I am triggering commands via a REST API Controller...
What I am expecting to see is the log statements that say a letter is scheduled and cancelled if I were to schedule a letter and immediately cancel it within the next five minutes.
What I am curiously seeing is that the letter is scheduled successfully and I see the following log statements:
Received schedule command for letter id e6e037be-3b6d-4ae3-80cd-12426adcd526
LetterScheduledEvent e6e037be-3b6d-4ae3-80cd-12426adcd526 SCHEDULED
But when I cancel the letter I see this behavior:
LetterScheduledEvent e6e037be-3b6d-4ae3-80cd-12426adcd526 SCHEDULED
Received cancel command for letter id e6e037be-3b6d-4ae3-80cd-12426adcd526
Letter e6e037be-3b6d-4ae3-80cd-12426adcd526 cancelled CANCELLED
What am I missing here? Why is the LetterScheduledEvent
handler being triggered again?
Here's my aggregate -
public class Letter {
@AggregateIdentifier
private String letterId;
private ScheduleToken scheduleToken;
@SuppressWarnings("UnusedDeclaration")
protected Letter() {
// Default constructor required by Axon Framework
}
@CommandHandler
public Letter(ScheduleLetterCommand cmd, EventScheduler scheduler) {
String id = cmd.getLetterId();
log.info("Received schedule command for letter id {}", letterId);
AggregateLifecycle.apply(new LetterScheduledEvent(id, LetterEventType.SCHEDULED));
this.scheduleToken = scheduler.schedule(Duration.ofMinutes(5), new BeginSendLetterEvent(id,LetterEventType.BEGIN_SEND));
}
@CommandHandler
public void handle(CancelLetterCommand cmd, EventScheduler eventScheduler) {
String letterId = cmd.getLetterId();
log.info("Received cancel command for letter id {}", letterId);
AggregateLifecycle.apply(new LetterCancelledEvent(letterId, LetterEventType.CANCELLED));
eventScheduler.cancelSchedule(scheduleToken);
}
@EventSourcingHandler
public void on(LetterScheduledEvent event) {
log.info("LetterScheduledEvent {} {}", event.getLetterId(), event.getEventType());
this.letterId = event.getLetterId();
}
@EventSourcingHandler
public void on(LetterCancelledEvent event) {
log.info("Letter {} cancelled {}", event.getLetterId(), event.getEventType());
scheduleToken = null;
}
@EventSourcingHandler
public void on(BeginSendLetterEvent event) {
log.info("Letter sending process started {} {}", event.getLetterId(), event.getEventType());
//complicated letter sending processes...
AggregateLifecycle.apply(new LetterSentEvent(event.getLetterId(), LetterEventType.SENT));
}
@EventSourcingHandler
public void on(LetterSentEvent event) {
log.info("Letter sent {} {}", event.getLetterId(), event.getEventType());
}
}
And here's my events ->
abstract class LetterMovementEvent(open val letterId: String, open val eventType: LetterEventType)
enum class LetterEventType {
SCHEDULED,
CANCELLED,
BEGIN_SEND,
SENT
}
data class LetterScheduledEvent(
override val letterId: String,
override val eventType: LetterEventType = LetterEventType.SCHEDULED
) : LetterMovementEvent(letterId, eventType)
data class LetterCancelledEvent(
override val letterId: String,
override val eventType: LetterEventType = LetterEventType.CANCELLED
) : LetterMovementEvent(letterId, eventType)
data class BeginSendLetterEvent(
override val letterId: String,
override val eventType: LetterEventType = LetterEventType.BEGIN_SEND
) : LetterMovementEvent(letterId, eventType)
data class LetterSentEvent(
override val letterId: String,
override val eventType: LetterEventType = LetterEventType.SENT
) : LetterMovementEvent(letterId, eventType)
Upvotes: 1
Views: 107
Reputation: 7275
What you're noticing @GoldFish, is the framework which is sourcing your aggregate based on the events it has published. So, shortly put, you are seeing "Event Sourcing in action".
With the @EventSourcingHandler
annotation in the Letter
aggregate, you have effectively created the methods used to re-create an aggregate based on events.
Thus, if you send a command to cancel this aggregate's letter, it will firstly recreate the aggregate from the events. Only once this is resolved will the command actually be given to the @CommandHandler
annotated method.
Hope this clarifies it for you @GoldFish!
Upvotes: 3