1

我目前正在尝试消化与 DDD 中的不变量和验证相关的信息。如果我得到正确验证不是域的问题,应该在外部完成以防止不变量发生。另一方面,必须在域中强制执行不变量,特别是在聚合中。

让我感到困惑的实际上是两件事:

  • 如何区分业务规则(不变量)与验证
  • 如何尊重 DRY 原则

让我详细说明一下。考虑我们有一个涵盖Tenders的领域模型。两个主要参与者是投标过程组织者(Organizer)和投标过程参与者(Participant)。主办方发布招标公告,其中包含有关阶段条款和要求的信息(例如起始最高价格)。招标是一个由几个阶段组成的过程。每个阶段都有它的条款。第一阶段是“招标”。在这个阶段参与者被允许发送他的提议(提案)。

有两个基本要求:

  1. 投标价格必须低于起始最高价格
  2. 参与者只能在“招标”阶段提交报价

从技术上讲,我们可以这样实现(细节省略):

class SubmitHandler
{

    /**
    * Send proposal
    *
    * @param SubmitCommand $command
    */
   public function execute($command)
   {
       $this->isReadyToBeSend($command);

       $participant = $this->participantRepository->find($command->id);
       $participant->submitProposal();

   }

   private function isReadyToBeSend($command)
   {
        $result = $this->validate($command);
        if (!$result->isValid()) {
            throw new ProposalException($result->getMessages()[0]->getMessage());
        }
   }

   public function validate($command)
   {
       // Here we check if starting price is provided 
       // and it is less than starting maximum price 
       // as well as the Call for bids Stage is still active 
       // so that we are allowed to submit proposals

       return Validator::validateForSending($command);
   }

   public function canBeExecuted($command)
   {
       return $this->validate($command)->isValid();
   }
}

// In the UI we send command to the handler
$commandHandler->handle($submitCommand);


class Participant extends AggregateRoot
{
   public function submitProposal()
   {
      // here we must enforce the invariants
      // but the code seems to be almost the same as
      // in the validator in the Command Handler
      $this->isReadyToBeSent();
   }

   // throws exceptions if invariants are broken
   private function isReadyToBeSent()
   {
       $this->isPriceCorrect();
       $this->AreTermsCorrect();
   }
}

考虑到上面提到的一切,在给定的上下文中,不变量和验证之间的细微差别是什么?代码是否应该在验证器和聚合中重复?(我不想将验证器注入实体)

非常感谢。

更新

我想我不够清楚。长话短说,我有两件事要考虑:

  1. 业务规则和不变量之间的区别。
  2. 坚持 DRY 违反 SRP,反之亦然。

我和另一位开发人员最近进行了讨论,我们得出的结论如下:

  • 不变量是必须独立于业务规则而遵守的一些规则。即使在概念上它们是两个不同的东西,代码也可能是相同的。
  • 为了遵守 SRP 原则,在这种情况下可能会违反 DRY 原则。

如果我错了,请纠正我。

4

1 回答 1

1

尽管我已经编写了很多 DDD 代码,但坦率地说,我仍然不确定术语,也不确定是否存在社区共识。我基本上已经停止使用 DDD 的行话,并且发现我的问题比你提出的问题要少得多。

因此,使用实用术语来陈述问题的另一种方法是......

被出价较低的人出价确实会激怒您的用户,就像在封闭式拍卖中出价一样。所以我们需要确保不会发生这种情况。

读取数据时的验证

当您显示屏幕供用户输入出价时,您当然会向用户验证出价必须大于先前的出价(例如通过 jQuery),并且只有在以下情况下才能接受出价在CallForBids舞台上(例如,仅显示表格。

您必须进行此验证,否则您将给用户带来非常糟糕的体验 - 允许他们输入出价只是被告知拍卖已结束。所以我们知道在读取数据时必须以某种方式表达这些规则。然而,关键是这样的:

您不能保证在用户执行在时间 B 写入数据的操作时,根据时间 A 的信息在屏幕上显示的任何内容都是真实的。

所以这里的验证不必是密不透风的。不要出那么多汗。即使您通过复制逻辑搞砸了,我们也不能 100% 保证写入无论如何都会通过。

写入数据时的验证

正如我们在上面观察到的,屏幕上的数据变得陈旧:用户可能在拍卖结束后输入了一个出价,或者自从数据显示在屏幕上以来最低出价可能已经上升。因此,为了避免系统状态违反业务规则(因此不可靠且没有完整性)...

写入数据时必须检查业务规则,并且必须在同一事务中执行此操作,否则无法保证一致性。

(也有最终的一致性,但这是另一个超出此答案范围的蜡球。)

那对你来说意味着什么?

  • 您的命令处理程序是聚合/事务边界/无论人们本周如何称呼它。
  • 如果这两条规则中的任何一条被破坏,该命令将无法成功(应该抛出异常)。不应更改任何状态。
  • 应该假定该命令成功。也就是说,从您的 MVC 控制器中,不要为失败的命令添加任何特殊的错误处理。命令失败非常罕见,但它会不时发生。
  • 任何内部实现都无关紧要:如果您想拥有 2 个类,一个来执行每个规则,请继续。 只要规则在单个事务中执行,这对业务来说并不重要。这就是你应该关注的。
  • 这也是您应该测试的内容- 只有当命令的参数相对于系统状态无效时才会引发异常。(只有当它发布将触发其他处理程序的事件时,您才会测试成功。)

希望清除它。

于 2014-03-28T23:58:44.200 回答