Command Handler 是否只是从总线接收命令并发布事件?

Is Command Handler is just receiving commands from the bus and publish the event?

我实际上是 CQRS 和事件溯源的新手。

当我看到这段代码时,我对聚合中的命令处理程序感到困惑:

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Aggregate
public class BankAccountAggregate {

    @AggregateIdentifier
    private UUID id;
    private BigDecimal balance;
    private String owner;

    @CommandHandler
    public BankAccountAggregate(CreateAccountCommand command){
        AggregateLifecycle.apply(
                new AccountCreatedEvent(
                        command.getAccountId(),
                        command.getInitialBalance(),
                        command.getOwner()
                )
        );
    }

该命令处理程序只是发布一个事件。如您所见,对象的命名是 AccountCreatedEvent 但这并不意味着 Account is created 对吗?

帐户创建在订阅的事件处理程序中 AccountCreatedEvent :

@Slf4j
@RequiredArgsConstructor
@Component
public class BankAccountProjection {

    private final BankAccountRepository repository;
    private final QueryUpdateEmitter updateEmitter;


    @EventHandler
    public void on(AccountCreatedEvent event) throws Exception {
        log.debug("Handling a Bank Account creation command {}", event.getId());
        BankAccount bankAccount = new BankAccount(
                event.getId(),
                event.getOwner(),
                event.getInitialBalance()
        );
        this.repository.save(bankAccount);
        Boolean isActive =  AggregateLifecycle.isLive();

    }
}

我知道的是:

那么为什么我们不把Account creation的逻辑放在Command Handler中呢?这样做的目的是什么?

此处 link 的完整源代码。

你在混淆一些概念。让我们澄清一下。

事件溯源

如果您实施事件溯源,则意味着您的真实来源是事件本身。您存储事件,而不是具体的“状态”(实体)。让我们看一些伪代码:

要创建一个新帐户:

function createAccount(data) {
  event = new AccountCreatedEvent(data)
  eventStore.save(event)
}

要从帐户中提款,例如:

  function withdraw(data) {
    events = eventStore.getEvents(data.accountId)
    account = new Account()
    account.apply(events)
    account.withdraw(data)
    newEvents = account.newEvents
    eventStore.save(newEvents)
  }

(此方法将由您的命令处理程序调用)

如您所见,您从其事件中生成 account,而不是从存储库中读取它。帐户 class 将是这样的:

class Account {

  amount = 0
  newEvents = []

  function apply(events) {
    events.forEach(event => {
      if event == AccountCreatedEvent {
        this.amount = event.initialAmount
      } else if (event == WithdrawalApplied) {
        this.amount = this.amount - event.amount
      } // more event types
    })
  }

  function withdraw(data) {
    // here is where you ensure aggregate invariants (business rules)
    if this.amount == 0 {
      throw Error("no money")
    }
    this.amount = this.amount - data.amount
    newEvents.add(new WithdrawalApplied(data))
  }

}

 

所以对于你的问题:

that command handler is just publishing an event. as you can see the naming of the object is AccountCreatedEvent but it doesn't mean the Account is created right?

答案是您应该将事件存储在命令处理程序中。这恰恰意味着帐户已创建。然后,您可以根据需要发布活动。继续阅读。

CQRS

使用 CQRS,您只需将查询和命令分开,但这种技术完全可以在没有事件源的情况下应用。

预测

由于您的真实来源由一堆事件组成,例如,当客户想要通过 ID 查询帐户时,您需要查询所有事件并从中构建您的帐户。这可能很慢。因此,当使用 CQRS 和事件源时,为了实现更快的读取,您可以应用此技术。

基本上,它包括侦听事件和构建聚合的预构建投影。此投影可以存储在 MongoDB、PostgreSQL 甚至文件中。那是一个实现细节。

下图说明了三种技术(CQRS、事件溯源和投影)如何协同工作。

I am actually new in CQRS and Event Sourcing. I got confused by Command Handler in Aggregate, when i saw this code

不是你的错 - 我研究 CQRS/ES 已经有一段时间了,我发现 Nebrass Lamouchi 的设计是 外星人

在阅读这篇文章时,我看到了其他一些危险信号;因此,如果您想了解与这些设计理念相关的“最佳实践”,我鼓励您寻找其他资源。

Is Command Handler is just receiving commands from the bus and publish the event?

差不多;一般来说,当我们谈论命令处理程序时,我们通常是在描述一段 application 代码,它使用队列外的消息,并在这样做时更新我们的持久数据存储。

所以如果你手写代码,通常的模式看起来像

void handle(Command c) {
    root = repo.get(c.accountId())
    root.updateMyself(c)
    repo.save(root)
}

如果此时您还想广播事件,那么这通常也会发生在应用层

void handle(Command c) {
    root = repo.get(c.accountId())
    root.updateMyself(c)
    repo.save(root)

    // In "event sourced" designs, this sort of publish is
    // normally "outside" of the transaction.  When using
    // a relational data store, it might not be.  Tradeoffs
    publish(root.events())
}

但我们在这里看不到,因为 Lamouchi 也在 演示 Axon 框架,它减少了样板代码的数量 - 实际上隐藏了管道。

这是个好主意吗?花些时间观看 Greg Young 的 8 Lines of Code