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
TestRunnerclass, the Command Gateway dispatches theissueCardCommandto it'sCommandHandlerusing the command bus, which is then creating a new instance of theGiftCardaggregate. In thisCommandHandlerwe can perform any logic, and then we use thisapplymethod.The
apply(event)method is used to publish theCardIssuedEventas anEventMessagewithin the scope of theGiftCardaggregate and it also invokes theEventSourcingHandlerfor 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 theGiftCardaggregate and we are also persisting the event to theDOMAIN_EVENT_ENTRYtable using spring Jpa.Once this
CommandHandleris finished executing, the aggregate object doesn't exist anymore.Now in my
TestRunnerclass again, the Command Gateway dispatches theRedeemCardCommandto itsCommandHandlerand 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_ENTRYtable and it replays all the events (EventSourcingHandlers) for theGiftCardaggregate instance in order to get it's current state (which is why the@AggregateIdentifieris important).The
RedeemCardCommandHandlermethod is then executed, it performs any logic and applies the event, which is published within the aggregate and it invokes it'sEventSourcingHandler. ThisEventSourcingHandlerthen updates the state of theGiftCardaggregate / persists to theDOMAIN_EVENT_ENTRYtable.
Is my understanding of how the event sourcing works correct?