【问题标题】:Am I writing duplicate domain logics when synchronise query database in CQRS and EventSourcing?在 CQRS 和 EventSourcing 中同步查询数据库时,我是否编写了重复的域逻辑?
【发布时间】: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


【解决方案1】:

我看到了两个选项。

一个是包含余额变化的命令,计算新余额的命令处理程序,以及包含新余额的事件。如果在事件处理程序中没有重新计算任何内容,它可以确保如果未来业务规则发生变化,当从事件中重构对象时,它们不会影响对象的历史记录。

另一种方法是将业务规则放在一个单独的类中,该类从命令处理程序和事件处理程序都调用以避免重复,然后对这些业务规则进行版本化——例如通过子类化。因此,您可以拥有一个名为CalculateBalanceRule 的抽象类,其中包含一个CalculateBalanceRuleVersion1 的子类,最初由两者引用。如果规则发生更改,则创建 CalculateBalanceRuleVersion2,更改命令处理程序以引用它,但在事件处理程序中保留对 Version1 的引用,以便它始终重放最初执行的规则。

第二种方法肯定是更多的维护,但如果这对您的业务很重要的话,它可以回答如何改变,而不仅仅是改变了什么。

编辑:第三个选项是让事件只包含第一个选项中的新平衡,但对事件进行版本化。所以你有 BalanceChangedEvent、BalanceChangedEvent_v2 等等。这是我可以采取的方向,因为我并不真正关心记录事情如何变化的历史,但我确实需要考虑事件本身可能会吸引更多成员或重命名其成员的可能性。然后需要逻辑来确定在每个步骤中使用哪个事件版本来重构对象。

【讨论】:

  • +1 告诉我事件重播应该基于原始业务规则。
猜你喜欢
  • 2014-09-20
  • 1970-01-01
  • 2019-01-13
  • 2020-12-31
  • 2021-07-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多