Domain driven design DDD-聚合内的更改实体

Domain driven design DDD-聚合内的更改实体,domain-driven-design,aggregateroot,Domain Driven Design,Aggregateroot,阅读之后,我仍然对聚合中实体更改的实现感到困惑。据我所知,聚合根代表整个(或整个聚合),并将“命令”更改委托给其余部分 这最后一部分,授权给其他人会造成一些问题。在下面的示例中,我想更改特定订单行的数量。我正在寻址根“订单”,并告诉它更改由本地标识符标识的订单行的数量 当满足所有业务规则时,可以在聚合上创建并应用事件。现在,所有事件都应用于聚合根,我认为这是一个很好的实践,因此所有命令都指向根,这会更改聚合的状态。此外,聚合根是唯一一个创建事件的根,让世界知道发生了什么 class Order

阅读之后,我仍然对聚合中实体更改的实现感到困惑。据我所知,聚合根代表整个(或整个聚合),并将“命令”更改委托给其余部分

这最后一部分,授权给其他人会造成一些问题。在下面的示例中,我想更改特定订单行的数量。我正在寻址根“订单”,并告诉它更改由本地标识符标识的订单行的数量

当满足所有业务规则时,可以在聚合上创建并应用事件。现在,所有事件都应用于聚合根,我认为这是一个很好的实践,因此所有命令都指向根,这会更改聚合的状态。此外,聚合根是唯一一个创建事件的根,让世界知道发生了什么

class Order extends AggregateRoot
{
    private $orderLines = [];
    public function changeOrderLineQuantity(string $id, int $quantity)
    {
        if ($quantity < 0) {
            throw new \Exception("Quantity may not be lower than zero.");
        }

        $this->applyChange(new OrderLineQuantityChangedEvent(
            $id, $quantity
        ));
    }

    private function onOrderLineQuantityChangedEvent(OrderLineQuantityChangedEvent $event)
    {
        $orderLine = $this->orderLines[$event->getId()];

        $orderLine->changeQuantity($event->getQuantity());
    }
}

class OrderLine extends Entity
{
    private $quantity = 0;

    public function changeQuantity(int $quantity)
    {
        if ($quantity < 0) {
            throw new \Exception("Quantity may not be lower than zero.");
        }

        $this->quantity = $quantity;
    }
}
确保业务逻辑位于其所属位置,而不是位于两个位置。这是一种选择,但如果复杂性增加,模型变得更大,我可以想象这些实践会变得更复杂

现在我必须回答以下问题: (1) 如何处理从根开始的聚合内部的深层变化? (2) 当业务规则增加时(例如,最大数量为10个,但在星期一多3个,对于产品X,最大数量为3个项目)。在聚合根域服务上提供验证这些业务规则的每个命令/方法是否是良好的做法

我有一个问题,您注意到检查$quantity值的业务规则位于两个类中

从“面向对象”的角度来看,
Order::changeOrderLineQuantity($id,$quantity)
是一条消息。消息具有schema是正常的,schema限制任何给定字段中允许的值的范围

所以这里的代码是:

public function changeOrderLineQuantity(string $id, int $quantity)
{
    if ($quantity < 0) {
        throw new \Exception("Quantity may not be lower than zero.");
    }
在ease案例中,
Orders::changeOrderLineQuantity(…)
理解的
数量
OrderLineQuantityChangedEvent(…)
理解的
数量
OrderLine::changeQuantity(…)
理解的
数量
是相同的领域概念,因此,您可以在任何地方重复使用相同的类型;因此,类型检查器可确保满足正确的约束

编辑 正如在对该问题的评论中所指出的,
数量
不应被理解为某种通用型。相反,它特定于订单和订单行的上下文,以及出于相同原因共享相同约束的代码的其他部分


一个完整的解决方案可能在不同的名称空间中有几种不同的
数量
类型。

如何解释业务规则随时间的变化。当您构造或反序列化以这种方式实现的ValueType时,是否应用了业务规则?如果是这样,如果业务规则的更改意味着现有数据不再“有效”,您会怎么做?这是一个很好的问题。简短的版本是,您需要仔细考虑“不可表示”状态和“不可访问”状态之间的区别。不可访问是“容易的”,因为这完全取决于域模型。如果所有受影响的数据都已清除,“不可呈现”是可管理的。剩下的呢?“这取决于”-您开始对您的选择进行成本效益分析。感谢您的解释!我在其他点上使用了值对象,但我确实清楚地看到了这一部分。我试图创建一个与我的情况如此接近的示例,以便复制概念,但实体也允许这样做吗?我有一个带有注释的任务,用户可以启动带有或不带注释的任务。我目前使用的方法是在我将它们组合到一个实体中的方法中的start(VOUser$user,string$comment),但也允许已经向start方法提供实体注释?我不会-消息通常是不可变的,实体通常是可变的,因此,在消息中包含可变实体时会有一些冲突。但这是一个来自一般原则的论点——在适当的情况下,这种模式可能是“好的”。我同意这里的概括。我要补充的是,在多个地方检查同一个业务规则可能不是最好的主意。您可以放弃签入
changeOrderLineQuantity
,因为事件处理将拾取它,因为此处的不变量属于
OrderLine
。通用值对象可以使事情变得更简单,尽管一个量很可能是一个
十进制
,有些量可能需要为负数,有些可能不是0。这取决于用途。但总的想法仍然站得住脚。
    public function changeQuantity(int $quantity)
    {
        if ($this->canChangeQuantity($quantity) < 0) {
            throw new \Exception("Quantity may not be lower than zero.");
        }

        $this->quantity = $quantity;
    }
    public function changeOrderLineQuantity(string $id, int $quantity)
    {
        $orderLine = $this->orderLines[$event->getId()];
        if ($orderLine->canChangeQuantity($quantity)) {
            throw new \Exception("Quantity may not be lower than zero.");
        }

        $this->applyChange(new OrderLineQuantityChangedEvent(
            $id, $quantity
        ));
    }
public function changeOrderLineQuantity(string $id, int $quantity)
{
    if ($quantity < 0) {
        throw new \Exception("Quantity may not be lower than zero.");
    }
// Disclaimer: PHP is not my first language
class Quantity {
    public function __construct(int $quantity) {
        if ($quantity < 0) {
            throw new \Exception("Quantity may not be lower than zero.");
        }
        $this.quantity = quantity
    }
    //  ...
}