Events 如何";查询「;查看是否可以执行命令的聚合

Events 如何";查询「;查看是否可以执行命令的聚合,events,domain-driven-design,cqrs,event-sourcing,aggregateroot,Events,Domain Driven Design,Cqrs,Event Sourcing,Aggregateroot,我有一个电子邮件草稿作为聚合根目录,其中包含以下命令:addToRecipient,addCcRecipient,addbcrecipient,updateBodyText,uploadAttachment,removeAttachment,如果草稿未准备好发送,我想在UI中禁用发送按钮(即至少有一个收件人,正文中有文本)。我知道我不允许查询聚合,但它是唯一可以告诉我我可以或不能发送电子邮件的人 如果我要应用我对事件源和CQR的了解,那么聚合将发出一个EmailIsReadyToBeSent事件

我有一个电子邮件草稿作为聚合根目录,其中包含以下命令:
addToRecipient
addCcRecipient
addbcrecipient
updateBodyText
uploadAttachment
removeAttachment
,如果草稿未准备好发送,我想在UI中禁用发送按钮(即至少有一个收件人,正文中有文本)。我知道我不允许查询聚合,但它是唯一可以告诉我我可以或不能发送电子邮件的人

如果我要应用我对事件源和CQR的了解,那么聚合将发出一个
EmailIsReadyToBeSent
事件,我的
UserEmailDrafts
read模型将选择该事件并以某种方式更新UI,但随后,我必须在每个命令后进行检查并发送一个取消事件,即
EmailIsNotReadyToBeSent


这感觉很复杂,你怎么看?

除非有收件人,否则电子邮件无法发送,而且正文接近应用程序逻辑,因为一天结束时,更多的是表单上填写的字段,而不是复杂的域不变量

我将在UI中注入一些关于这些基本规则的知识,以便在指定收件人和正文时立即重新启用按钮,而不是依赖于在每次屏幕上发生更改时查询读取模型的完整跨层往返

事实上,当您看到客户端逻辑在表单上执行所需的字段验证时,您不会感到震惊。这是一个完全有效且公认的折衷方案,因为该逻辑简单且通用


请注意,这并不妨碍您将这些规则汇总在一起,拒绝任何不符合这些规则的命令。

除非有收件人和正文,否则电子邮件无法发送的事实接近于应用程序逻辑,因为在一天结束时,更多的是表单上填写的字段,而不是complex域不变量

我将在UI中注入一些关于这些基本规则的知识,以便在指定收件人和正文时立即重新启用按钮,而不是依赖于在每次屏幕上发生更改时查询读取模型的完整跨层往返

事实上,当您看到客户端逻辑在表单上执行所需的字段验证时,您不会感到震惊。这是一个完全有效且公认的折衷方案,因为该逻辑简单且通用


请注意,这并不妨碍您在聚合中使用这些规则,拒绝任何不满足这些规则的命令。

我将尝试用
规范
模式的示例扩展@plalx给出的答案

在本例中,我将使用中的一些类,特别是定义接口以使用规范模式的类(由@martinezdelariva提供)

首先,让我们忘掉UI,把重点放在你必须满足的领域不变量上。所以你说为了发送电子邮件,电子邮件需要:

  • 不包含禁止的关键字
  • 至少包含一个收件人和正文内容
  • 必须是唯一的,这意味着类似的电子邮件尚未发送

现在让我们来看看应用程序服务(用例)在进入细节之前看到的大画面:

class SendEmailService implements ApplicationService
{
    /**
     * @var EmailRepository
     */
    private $emailRepository;

    /**
     * @var CanSendEmailSpecificationFactory
     */
    private $canSendEmailSpecFactory;

    /**
     * @var EmailMessagingService
     */
    private $emailMessagingService;

    /**
     * @param EmailRepository $emailRepository
     * @param CanSendEmailSpecificationFactory $canSendEmailSpecFactory
     */
    public function __construct(
        EmailRepository $emailRepository,
        CanSendEmailSpecificationFactory $canSendEmailSpecFactory,
        EmailMessagingService $emailMessagingService
    ) {
        $this->emailRepository = $emailRepository;
        $this->canSendEmailSpecFactory = $canSendEmailSpecFactory;
        $this->emailMessagingService = $emailMessagingService;
    }

    /**
     * @param $request
     *
     * @return mixed
     */
    public function execute($request = null)
    {
        $email = $this->emailRepository->findOfId(new EmailId($request->emailId()));
        $canSendEmailSpec = $this->canSendEmailSpecFactory->create();

        if ($email->canBeSent($canSendEmailSpec)) {
            $this->emailMessagingService->send($email);
        }
    }
}
我们从repo获取电子邮件,检查是否可以发送并发送。因此,让我们看看聚合根(电子邮件)如何处理不变量,这里是
canBeSent
方法:

/**
 * @param CanSendEmailSpecification $specification
 *
 * @return bool
 */
public function canBeSent(CanSendEmailSpecification $specification)
{
    return $specification->isSatisfiedBy($this);
}
到目前为止还不错,现在让我们看看合成
CanSendEmailSpecification
以满足不变量有多容易:

class CanSendEmailSpecification extends AbstractSpecification
{
    /**
     * @var Specification
     */
    private $compoundSpec;

    /**
     * @param EmailFullyFilledSpecification               $emailFullyFilledSpecification
     * @param SameEmailTypeAlreadySentSpecification       $sameEmailTypeAlreadySentSpec
     * @param ForbiddenKeywordsInBodyContentSpecification $forbiddenKeywordsInBodyContentSpec
     */
    public function __construct(
        EmailFullyFilledSpecification $emailFullyFilledSpecification,
        SameEmailTypeAlreadySentSpecification $sameEmailTypeAlreadySentSpec,
        ForbiddenKeywordsInBodyContentSpecification $forbiddenKeywordsInBodyContentSpec
    ) {
        $this->compoundSpec = $emailFullyFilledSpecification
            ->andSpecification($sameEmailTypeAlreadySentSpec->not())
            ->andSpecification($forbiddenKeywordsInBodyContentSpec->not());
    }

    /**
     * @param mixed $object
     *
     * @return bool
     */
    public function isSatisfiedBy($object)
    {
        return $this->compoundSpec->isSatisfiedBy($object);
    }
}
正如您所见,我们在这里说,为了发送电子邮件,我们必须满足以下要求:

  • 电子邮件已满(您可以在此处检查正文内容是否为空,并且至少有一个收件人)
  • 并且尚未发送相同的电子邮件类型
  • 正文内容中没有禁止使用的词语
以下是前两个规范的实施情况:

class EmailFullyFilledSpecification extends AbstractSpecification
{
    /**
     * @param EmailFake $email
     *
     * @return bool
     */
    public function isSatisfiedBy($email)
    {
        return $email->hasRecipient() && !empty($email->bodyContent());
    }
}
class SameEmailTypeAlreadySentSpecification extends AbstractSpecification
{
    /**
     * @var EmailRepository
     */
    private $emailRepository;

    /**
     * @param EmailRepository $emailRepository
     */
    public function __construct(EmailRepository $emailRepository)
    {
        $this->emailRepository = $emailRepository;
    }

    /**
     * @param EmailFake $email
     *
     * @return bool
     */
    public function isSatisfiedBy($email)
    {
        $result = $this->emailRepository->findAllOfType($email->type());

        return count($result) > 0 ? true : false;
    }
}
多亏了规范模式,您现在可以在不修改现有代码的情况下管理上司要求您添加的不变量。您还可以非常轻松地为每个规范创建单元测试


另一方面,您可以将UI设置得尽可能复杂,以便让用户知道电子邮件已准备好发送。我将创建另一个用例
ValidateEmailService
,当用户单击验证按钮或从一个输入切换时,该用例只会从聚合根调用方法
canBeSent
(填充接收者)到另一个(填充身体)…这取决于您。

我将尝试用
规范的示例扩展@plalx给出的答案

在本例中,我将使用中的一些类,特别是定义接口以使用规范模式的类(由@martinezdelariva提供)

首先,让我们忘掉UI,把重点放在你必须满足的领域不变量上。所以你说为了发送电子邮件,电子邮件需要:

  • 不包含禁止的关键字
  • 至少包含一个收件人和正文内容
  • 必须是唯一的,这意味着类似的电子邮件尚未发送

现在让我们来看看应用程序服务(用例)在进入细节之前看到的大画面:

class SendEmailService implements ApplicationService
{
    /**
     * @var EmailRepository
     */
    private $emailRepository;

    /**
     * @var CanSendEmailSpecificationFactory
     */
    private $canSendEmailSpecFactory;

    /**
     * @var EmailMessagingService
     */
    private $emailMessagingService;

    /**
     * @param EmailRepository $emailRepository
     * @param CanSendEmailSpecificationFactory $canSendEmailSpecFactory
     */
    public function __construct(
        EmailRepository $emailRepository,
        CanSendEmailSpecificationFactory $canSendEmailSpecFactory,
        EmailMessagingService $emailMessagingService
    ) {
        $this->emailRepository = $emailRepository;
        $this->canSendEmailSpecFactory = $canSendEmailSpecFactory;
        $this->emailMessagingService = $emailMessagingService;
    }

    /**
     * @param $request
     *
     * @return mixed
     */
    public function execute($request = null)
    {
        $email = $this->emailRepository->findOfId(new EmailId($request->emailId()));
        $canSendEmailSpec = $this->canSendEmailSpecFactory->create();

        if ($email->canBeSent($canSendEmailSpec)) {
            $this->emailMessagingService->send($email);
        }
    }
}
我们从repo获取电子邮件,检查是否可以发送并发送。因此,让我们看看聚合根(电子邮件)如何处理不变量,这里是
canBeSent
方法:

/**
 * @param CanSendEmailSpecification $specification
 *
 * @return bool
 */
public function canBeSent(CanSendEmailSpecification $specification)
{
    return $specification->isSatisfiedBy($this);
}
到目前为止还不错,现在让我们看看合成
CanSendEmailSpecification
以满足我们的需求有多容易