CQRS / Event Sourcing,如何获得一致的数据来应用业务规则?

时间:2012-12-11 10:21:41

标签: domain-driven-design cqrs event-sourcing

有时我正在使用CQRS模式和事件采购开发一个小项目。 我有一个结构性问题,我不知道采取哪种解决方案来解决它。

想象一下以下示例: 发送命令,其中包含银行客户存入一定金额的信息(DepositCommand)。 在命令处理程序/实体/聚合(对讨论来说不重要)中,必须应用业务规则; 如果客户是前10%中的一个,在账户中有更多的钱赢得一些奖金。

问题是我如何获得最新,一致的数据,以了解客户在存款后是否位于前10%。

  • 我无法使用事件存储,因为无法进行此类查询;
  • 我不确定我是否可以使用读模型,因为不是100% 确定这是最新的。

如果您需要数据库中的数据来应用业务规则,您会怎么做?如果我不注意最新数据,我会遇到各种可能性 给两个不同的客户奖励

期待听到您的意见。

1 个答案:

答案 0 :(得分:8)

聚合需要做出业务决策的任何信息都应存储为聚合状态的一部分。因此,当收到将钱存入客户账户的命令时,您应该已经拥有该客户的当前/更新状态,该状态可以包含其每个账户的当前余额。

我还建议聚合永远不要去读模型来提取信息。根据您要实现的目标,您可以使用读取模型中的其他详细信息(状态不重要)来丰富命令,但聚合本身应该从它自己的已知状态中拉出来。


修改

重新阅读问题之后,我意识到您正在谈论跨多个聚合跟踪状态。这属于传奇的范畴。您可以创建一个跟踪前10%所需阈值的传奇。因此,无论何时客户进行存款,该传奇都可以追踪它在排名中的位置。如果该客户端遍历threadshold,则可以从该saga发布命令以指示它们符合所需的条件。

在您的情况下,您的传奇可能会跟踪所有存款的总金额,因此在存款时,可以决定客户当前是否在前10%。您可能想问自己的其他问题......如果客户存入了X美元的金额,并且立即宽幅拖欠美元以回落至威胁下;应该怎么办?等


非常粗糙的聚合/传奇处理方法......

public class Client : Aggregate
{
    public void Handle(DepositMoney command)
    {
        // What if the account is not known? Has insufficient funds? Is locked? etc...
        // Track the minimum amount of state required to make whatever choice is required.
        var account = State.Accounts[command.AccountId]; 

        // Balance here would reflect a point in time, and should not be directly persisted to the read model;
        // use an atomic update to increment the balance for the read-model in your denormalizer.
        Raise(new MoneyDeposited { Amount = command.Amount, Balance = account.Balance + command.Amount }); 
    }

    public void Handle(ElevateClientStatus command)
    {
        // you are now a VIP... raise event to update state accordingly...
    }
}

public class TopClientSaga : Saga
{
    public void Handle(MoneyDeposited e)
    {
        // Increment the total deposits... sagas need to be thread-safe (i.e., locked while state is changing).
        State.TotalDeposits += e.Amount;

        //TODO: Check if client is already a VIP; if yes, nothing needs to happen...

        // Depositing money itself changes the 10% threshold; what happens to clients that are no longer in the top 10%?
        if (e.Balance > State.TotalDeposits * 0.10)
        {
            // you are a top 10% client... publish some command to do whatever needs to be done.
            Publish(new ElevateClientStatus { ClientId = e.ClientId, ... });
        }
    }

    // handle withdrawls, money tranfers etc?
}