В демонстрационной версии Axon Giftcard есть класс GiftCard
, который аннотируется как @Aggregate:
@Aggregate
@Profile("command")
public class GiftCard {
private final static Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@AggregateIdentifier
private String id;
private int remainingValue;
@CommandHandler
public GiftCard(IssueCmd cmd) {
log.debug("handling {}", cmd);
if(cmd.getAmount() <= 0) throw new IllegalArgumentException("amount <= 0");
apply(new IssuedEvt(cmd.getId(), cmd.getAmount(), cmd.getCurrency()));
}
@CommandHandler
public void handle(RedeemCmd cmd) {
log.debug("handling {}", cmd);
if(cmd.getAmount() <= 0) throw new IllegalArgumentException("amount <= 0");
if(cmd.getAmount() > remainingValue) throw new IllegalStateException("amount > remaining value");
apply(new RedeemedEvt(id, cmd.getAmount()));
}
...
@EventSourcingHandler
public void on(IssuedEvt evt) {
log.debug("applying {}", evt);
id = evt.getId();
remainingValue = evt.getAmount();
currency = evt.getCurrency();
log.debug("new remaining value: {}", remainingValue);
log.debug("new currency: {}", currency);
}
@EventSourcingHandler
public void on(RedeemedEvt evt) {
log.debug("applying {}", evt);
remainingValue -= evt.getAmount();
log.debug("new remaining value: {}", remainingValue);
}
...
Классы команд и событий определены в коде Kotlin:
data class IssueCmd(@TargetAggregateIdentifier val id: String, val amount: Int)
data class IssuedEvt(val id: String, val amount: Int)
data class RedeemCmd(@TargetAggregateIdentifier val id: String, val amount: Int)
data class RedeemedEvt(val id: String, val amount: Int)
Допустим, на командную шину помещены следующие две команды:
Command # Command Class id amount
--------- ------------- ------- -------------
1 IssueCmd QP34 123.45
2 RedeemCmd QP34 38.10
При обработке первой команды CommandHandler (CH) для IssueCmd
будет помещать IssuedEvt
объект на шине событий.Событие будет обработано EventSourcingHandler (ESH) для IssuedEvt
.Затем у нас будет экземпляр GiftCard
с id
, установленным на " QP34 " и remainingValue
, установленным на 123.45 .
При обработке второгоКоманда CH для RedeemCmd
поместит объект RedeemedEvt
в шину событий.Событие будет обработано ESH для RedeeemedEvt
.Затем у нас будет экземпляр GiftCard
с id
, установленным на " QP34 " и remainingValue
, установленным на 85.35 .
ВОПРОС: После того, как каждое событие обрабатывается назначенным ему ESH, как и где сохраняется полученный экземпляр объекта?
Раньше я слышал ответ: на самом деле это не так.Все, что сохраняется - это объекты событий, которые хранятся в хранилище событий Axon.Когда требуется текущее состояние объекта, Аксон приказывает модели команд инициировать экземпляр класса GiftCard
, и к нему применяются события от самого раннего до самого позднего.Это определение Event Sourcing.
Но, когда Event Sourcing, после обработки IssuedEvt
, 123.45 в remainingValue
должен сохраняться где-то для ESHчтобы RedeemedEvt
имел правильное значение для своей операции вычитания.
Как и где сохраняется состояние объекта между вызовами в ESH?