Domain driven design CQR/事件来源,如何获得一致的数据以应用业务规则?

Domain driven design CQR/事件来源,如何获得一致的数据以应用业务规则?,domain-driven-design,cqrs,event-sourcing,Domain Driven Design,Cqrs,Event Sourcing,有时,我正在使用CQRS模式和事件源开发一个小项目。 我有一个结构性问题,我不知道该采取哪种解决方案来解决它 想象一下下面的例子: 发送一个命令,其中包含银行客户已存入一定金额资金的信息(存款命令)。 在命令处理程序/实体/聚合(对于讨论来说并不重要)中,必须应用业务规则; 如果客户是账户中存款最多的10%客户之一,则可以赢得一些奖金 问题是,我如何获得最新、一致的数据,以了解客户在存款后是否处于前10%的水平 我无法使用事件存储,因为无法进行此类查询 我不确定是否可以使用read模型,因为它

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

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

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

  • 我无法使用事件存储,因为无法进行此类查询
  • 我不确定是否可以使用read模型,因为它不是100% 当然那是最新的
当您需要数据库中的数据来应用业务规则时,您该如何做?如果我不注意最新的数据,我就会遇到各种可能性 把奖品给两个不同的客户


期待听取您的意见。

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

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


编辑

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

在您的案例中,您的传奇故事可能会跟踪所有存款的总额,因此在进行存款时,可以决定客户现在是否在前10%。你可能想问自己的其他问题。。。如果客户存款X美元,并立即提取Y美元,将其放回threashold项下;该怎么办?等等


非常粗糙的骨料/saga处理方法

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?
}

谢谢你的回复卡尔加里,你明白我的意思了。我必须阅读更多关于Saga模式的内容,然后我以形成的观点回答。所以你认为我有一个Saga,它是通过应用程序创建和处理的,就像一个singleton。那个传奇故事记录了存款总额。每个存款的客户将该金额添加到传奇总金额中。要存储总数量,我使用事件存储?我这样问是因为当我启动应用程序时,我需要加载当前的总数量。这有道理吗?thanks@JP-传奇将管理自己的国家。在某个时刻,您将创建一个
分行
银行
,这可能会启动跟踪存款总额的saga。无论何时发生存款或其他相关事件,都会检索saga状态(理想情况下是缓存在内存中),以便它始终知道运行总数。如果您的应用程序出于其他原因需要知道总数,那么总数可能会成为
银行
分行
聚合状态的属性。您的聚合/传奇是所有数据的权威,您不想使用读取模型来获取此类信息。有道理吗?你说的完全正确。我只想确定你是否认为这部传奇有着独身的一生。我认为应该为系统恢复存储总计。这让我想到是否应该在事件中存储saga数据/状态store@JP-是的,在处理每个事件后,saga状态确实需要持久化到db(类似于提交)。我会在内存中缓存一个副本,以节省在事件之间重新加载的成本。在你的情况下,你可能只有一个例子,但我不会把这个传奇称为单身。传奇故事将从某个事件开始(即BranchHoped),并将根据需要一直持续到显式完成(可能是BranchClosed)。通常,您可以使用不同的关联ID(即BranchA的顶级客户机和BranchB的顶级客户机)运行同一传奇的多个实例。