I have been learning about axon and event sourcing and I think I have finally understood part of it and it makes sense in my head, but I want to make sure that my understanding of it is correct and that I am not making any mistakes. The code works, and I can see the events in my DOMAIN_EVENT_ENTRY table also.
I will post my code below (simple GiftCard example from the docs) and explain my thought process. If I have not understood it correctly, please could you help me on understanding that part in the correct way.
I've not included the commands/events as they are very simple with fields for id,amount
First my code:
TestRunner.java
package com.example.demoaxon;
import java.util.UUID;
import org.axonframework.commandhandling.gateway.CommandGateway;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Component
@Slf4j
public class TestRunner implements CommandLineRunner {
private final CommandGateway commandGateway;
@Autowired
public TestRunner(CommandGateway commandGateway) {
this.commandGateway = commandGateway;
}
@Override
public void run(String... args) throws Exception {
log.info("send command");
String id = UUID.randomUUID().toString();
commandGateway.sendAndWait(new IssueCardCommand(id,100));
commandGateway.sendAndWait(new RedeemCardCommand(id,90));
}
}
GiftCard.java
package com.example.demoaxon;
import org.axonframework.commandhandling.CommandHandler;
import org.axonframework.eventsourcing.EventSourcingHandler;
import org.axonframework.modelling.command.AggregateIdentifier;
import static org.axonframework.modelling.command.AggregateLifecycle.apply;
import org.axonframework.spring.stereotype.Aggregate;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@NoArgsConstructor
@Aggregate
@Slf4j
public class GiftCard {
@AggregateIdentifier
private String giftCardId;
private Integer amount;
@CommandHandler
public GiftCard(IssueCardCommand cmd) {
log.info("handling {}",cmd);
apply(new CardIssuedEvent(cmd.getCardId(),cmd.getAmount()));
}
@EventSourcingHandler
public void onCardIssuedEvent(CardIssuedEvent evt) {
log.info("applying {}",evt);
this.giftCardId = evt.getCardId();
this.amount = evt.getAmount();
}
@CommandHandler
public void redeemCardCommandHandler(RedeemCardCommand cmd) {
log.info("handling {}",cmd);
this.amount -= cmd.getAmount();
apply(new CardRedeemedEvent(cmd.getCardId(),cmd.getTransactionId(),this.amount));
}
@EventSourcingHandler
public void onCardRedeemedEvent(CardRedeemedEvent evt) {
log.info("applying {}",evt);
this.amount = evt.getAmount();
}
}
From what I can understand:
In my
TestRunner
class, the Command Gateway dispatches theissueCardCommand
to it'sCommandHandler
using the command bus, which is then creating a new instance of theGiftCard
aggregate. In thisCommandHandler
we can perform any logic, and then we use thisapply
method.The
apply(event)
method is used to publish theCardIssuedEvent
as anEventMessage
within the scope of theGiftCard
aggregate and it also invokes theEventSourcingHandler
for that particular event, so in this caseonCardIssuedEvent
. It publishes the EventMessage to the EventBus and is sent to EventHandlers.In the
@EventSourcingHandler onCardIssuedEvent
, we can make any state changes to theGiftCard
aggregate and we are also persisting the event to theDOMAIN_EVENT_ENTRY
table using spring Jpa.Once this
CommandHandler
is finished executing, the aggregate object doesn't exist anymore.Now in my
TestRunner
class again, the Command Gateway dispatches theRedeemCardCommand
to itsCommandHandler
and as the first command no longer exists, the empty no args constructor is used to create the objects. The axon framework retrieves all events from thisDOMAIN_EVENT_ENTRY
table and it replays all the events (EventSourcingHandlers) for theGiftCard
aggregate instance in order to get it's current state (which is why the@AggregateIdentifier
is important).The
RedeemCardCommandHandler
method is then executed, it performs any logic and applies the event, which is published within the aggregate and it invokes it'sEventSourcingHandler
. ThisEventSourcingHandler
then updates the state of theGiftCard
aggregate / persists to theDOMAIN_EVENT_ENTRY
table.
Is my understanding of how the event sourcing works correct?