Laravel Event Sourcing(Spatie)-在业务规则中使用预测 [英] Laravel Event Sourcing (Spatie) - Using projections within business rules

查看:134
本文介绍了Laravel Event Sourcing(Spatie)-在业务规则中使用预测的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道事件源背后的一般概念是应该能够从事件流中重播应用程序的状态.

I know that the general concept behind event sourcing is that the state of the application should be able to be replayed from the event stream.

但是,有时候,我们需要从系统的其他部分获取业务规则的信息.即一个帐户有一个用户.用户具有黑名单状态,该状态必须检查用户是否可以访问/编辑帐户.

Sometimes, however, we need to get information for business rules from other parts of the system. i.e. An account has a user. A user has a blacklist status which is required to check if they can access/edit the account.

在下面的示例中(仅出于演示目的),用户尝试从其帐户中减去10美元.如果某个用户已被列入黑名单,那么我们不想允许他们从帐户中删除任何资金,但是我们希望记录他们已经尝试过.

In the below example (purely for demonstration purposes), a user tries to subtract $10 from their account. If a user has been blacklisted, then we do not want to allow them to remove any funds from the account but we do want to record that they have tried to.

发出请求后,我们可以查询用户模型以查看黑名单是否存在.如果为true,那么我们可以记录下来并引发异常.

After the request is made, we could query the user model to see if the blacklist exists. If true then we can record it and throw the exception.

用户表/模型当前不是事件源的.

The user table/model is currently not event-sourced.

现在,当我们尝试重播事件流以重新构建投影而用户状态未存储在事件中时,将不再可能.

Now when we try to replay the event stream to re-build the projections with the state of the user is not being stored in events, it is no longer possible.

因此,假设我当前的示例不起作用,我的问题是:

So assuming my current example does not work my questions are:

  1. 如果我们要将用户转移到事件存储系统中(以不同的聚合方式,但同一事件流中的所有事件),那么在业务规则内使用读取模型是否可以接受?

  1. If we were to move the user into an event stored system (in a different aggregate but all events within the same event-stream) then would it be acceptable to use read models within business rules?

当事件源和CRUD可能彼此依赖于业务规则时,有什么方法可以将它们混合到同一系统中.

Is there any way we can mix event-sourced and CRUD into the same system when they may depend on each other for business rules.

public function subtractMoney(int $amount)
{
    if ($this->accountOwnerIsBlacklisted()){
        $this->recordThat(new UserActionBlocked());

        throw CouldNotSubtractMoney::ownerBlocked();
    }

    if (!$this->hasSufficientFundsToSubtractAmount($amount)) {
        $this->recordThat(new AccountLimitHit());

        if ($this->needsMoreMoney()) {
            $this->recordThat(new MoreMoneyNeeded());
        }

        $this->persist();

        throw CouldNotSubtractMoney::notEnoughFunds($amount);
    }

    $this->recordThat(new MoneySubtracted($amount));
}

private function accountOwnerIsBlacklisted(): bool
{
    return $this->accountRepositry()->ownerUser()->isBlackListed();
}

推荐答案

由于您基本上是在使用DDD(不加提及),因此答案可能就在于该定义中.在DDD中,应该定义每个聚合根的边界.每个聚合根都不应存储对其他聚合根的任何依赖关系(Spatie包甚至不支持它).它应该仅由事件构成,然后才成为真理的唯一来源.

Since you are basically working with DDD (without mentioning it) the answer could lie in the definitions there. In DDD you are supposed to define the boundaries of each aggregate root. Each aggregate root should not store any dependencies to other aggregate roots (The Spatie package doesn't even support it). It should only be made up of the events, which then become the single source of truth.

以您的示例为例,似乎阻止用户的原因不是由于其帐户上的负面事件,而是由于与用户(帐户所有者)有关的某件事.这里的关键字似乎是所有者".如果要存储用户尝试取款的操作已发生的事实,则仍可以应用该事件,但是在这种情况下,原因将来自另一个汇总的用户".用户本身是否是事件源无关紧要,但是用户实体具有检查用户是否被阻止的方法,因此,根据系统的业务规则,不允许他从帐户中进行提款. 如果您不能同时对这两个模型进行建模,那么我建议您设计一个可以处理此命令的域服务.如果可以的话,请尽量将它们保留为模型的一部分,以避免使域模型失去生气.

Given your example, it seems that the blocking of a user is not due to negative events on his account, but rather due to something that happened in relation to his user (account owner). The keyword here seems to be "owner". If you want to store the fact that the user action of trying to withdraw money happened, then you could still apply the event, but the reason would, in this case, come from another aggregate "the user". It doesn't matter if the user itself is event sourced, but the user entity has the method to check if the user is blocked, and therefore it is the business rule in your system that he is not allowed to make withdrawals from the account. If you cannot model these two together, then I would suggest that you design a domain service which can handle this command. Try to keep them as a part of your model to avoid making your domain model anaemic, if you can.

<?php 
class AccountWithdrawalService
{
    public function __construct(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }

    public function withdraw($userId, $accountId, $amount)
    {
        $user = $this->userRepository->find($userId);

        // You might inject AccountAggregateRoot too.
        $account = AccountAggregateRoot::retrieve($accountId);

        if(!$user->isBlackListed())
        {
            $account->subtractMoney($amount);
        }
        else
        {
            // Here we record the unhappy road :-( 
            $account->moneySubtractionBlocked($amount);
        }

        $account->persist();
    }
}

PS:另一种可能性是,只要userRepository不是AccountAggregateRoot的完全依赖项,便将您的userRepository注入处理提款的实际方法中.我相信,这已经受到了广泛的讨论.

PS: A further possibility is to inject your userRepository in the actual method handling the withdrawal, as long as the userRepository is not a full dependency of the AccountAggregateRoot. This, I believe, is highly discussed.

这篇关于Laravel Event Sourcing(Spatie)-在业务规则中使用预测的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆