【发布时间】:2013-09-10 09:00:09
【问题描述】:
我对如何将数据同步到查询数据库感到困惑。
假设我有一个聚合:CreditAccount,某些命令可能会产生 CreditAccountBalanceChangedEvent:
public class CreditAccount extends AbstractAnnotatedAggregateRoot<Long> {
@AggregateIdentifier
private Long id;
private int balance;
private DateRange effectiveDateRange;
@CommandHandler
public CreditAccount(CreateCreditAccountCommand command) {
apply(new CreditAccountCreatedEvent(command.getAccountId(),
command.getEffectiveDateRange()));
apply(new CreditAccountBalanceChangedEvent(command.getAccountId(),
command.getAmount()));
}
@EventHandler
private void on(CreditAccountCreatedEvent event) {
this.id = event.getAccountId();
this.effectiveDateRange = event.getEffectiveDateRange();
}
@EventHandler
private void on(CreditAccountBalanceChangedEvent event) {
//notice this line, some domain logic here
this.balance = add(this.balance, event.getAmount());
}
private int add(int current, int amount) {
return current + amount;
}
}
public class CreditAccountBalanceChangedEvent {
private final long accountId;
private final int amount;
//omitted constructors and getters
}
在命令处理程序端一切正常。然后我开始查询,但我发现我在这里写了一些重复的域逻辑:
@Transactional
@Slf4j
public class CreditAccountEventHandler {
private CreditAccountReadModelStore creditAccountReadModelStore;
@EventHandler
public void handle(CreditAccountCreatedEvent event) {
log.info("Received " + event);
creditAccountReadModelStore.store(accountDevriveFrom(event));
}
@EventHandler
public void handle(CreditAccountBalanceChangedEvent event) {
log.info("Received " + event);
final CreditAccountReadModel account = creditAccountReadModelStore
.findBy(event.getAccountId());
//notice this line, some domain logic here
account.setBalance(account.getBalance() + event.getAmount());
creditAccountReadModelStore.store(account);
}
//omitted setters and private methods
}
如您所见,我在命令端和查询端都编写了余额计算代码。我的问题是这在某些情况下是不可避免的还是我在错误的地方编写了域逻辑?
到目前为止,根据我的研究,事件代表已经发生的事情,因此其中没有业务逻辑,它们只是数据持有者(但揭示了用户的意图)。那么我应该在 CreditAccountBalanceChangedEvent 中添加一个“余额”字段并将余额计算代码移动到命令处理程序方法吗?
public class CreditAccount extends AbstractAnnotatedAggregateRoot<Long> {
//omitted fields
@CommandHandler
public CreditAccount(CreateCreditAccountCommand command) {
apply(new CreditAccountCreatedEvent(command.getAccountId(),
command.getEffectiveDateRange()));
apply(new CreditAccountBalanceChangedEvent(command.getAccountId(),
command.getAmount(), add(this.balance, command.getAmount())));
}
@EventHandler
private void on(CreditAccountBalanceChangedEvent event) {
//notice this line, some domain logic here
//event.getAmount() is no use here, just for auditing?
this.balance = event.getBalance();
}
}
在这种情况下,我可以使用 event.getBalance() 删除查询端的余额计算。
很抱歉出现全屏问题,任何想法都值得赞赏。
【问题讨论】:
-
事件应该传达已经发生的事情,所以在我看来,您的 CreditAccountBalanceChangedEvent 类应该包含命令处理程序已经计算的旧金额和新金额。
-
我认为作为被加数的金额可以解释余额如何变化。似乎 oldBalance 和 newBalance 在命令端是多余的,但我需要在没有它们的查询端重复计算。另一个问题是当发生一些错误并且我想通过重播事件来纠正它们时,它们会妨碍重播吗?
标签: domain-driven-design cqrs event-sourcing axon