我知道事件源背后的一般概念是应该能够从事件流中重播应用程序的状态。
但是,有时我们需要从系统的其他部分获取业务规则的信息。即一个帐户有一个用户。用户具有黑名单状态,这是检查用户是否可以访问/编辑帐户所必需的状态。
在下面的示例中(仅出于演示目的),用户尝试从其帐户中减去10美元。如果用户已被列入黑名单,那么我们不想允许他们从帐户中删除任何资金,但是我们希望记录他们已尝试这样做。
发出请求后,我们可以查询用户模型以查看黑名单是否存在。如果为true,那么我们可以记录它并抛出异常。
用户表/模型当前不是事件源。
现在,当我们尝试重播事件流以重新构建投影而用户状态未存储在事件中时,将不再可能。
因此,假设我当前的示例不起作用,我的问题是:
如果我们要将用户转移到事件存储系统中(以不同的聚合方式,但是同一事件流中的所有事件),那么在业务规则内使用读取模型是否可以接受?
当事件规则和CRUD可能彼此依赖于业务规则时,有什么方法可以将它们混合到同一系统中?
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包甚至不支持它)。它仅应由事件组成,然后这些事件才成为真理的唯一来源。
以您的示例为例,似乎阻止用户的原因不是由于其帐户上的负面事件,而是由于与用户(帐户所有者)有关的某些事情。这里的关键字似乎是“所有者”。如果要存储用户尝试取款的操作已发生的事实,则仍然可以应用该事件,但是在这种情况下,原因将来自另一个汇总“用户”。用户本身是否是事件源无关紧要,但是用户实体具有检查用户是否被阻止的方法,因此,根据系统的业务规则,不允许他从帐户中提款。如果不能同时为这两个模型建模,那么建议您设计一个可以处理此命令的域服务。如果可以的话,请尝试将它们保留为模型的一部分,以避免使域模型失去生气。
<?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注入处理提款的实际方法中。我认为,对此进行了高度讨论。