正确的DDD方式在oneToMany关系中创建和删除子项?

时间:2015-06-30 08:01:40

标签: php doctrine-orm domain-driven-design one-to-many referential-integrity

我正在努力寻找合适的DDD方式来建立父/子oneToMany关系:

  • 确保实体不能以无效状态存在
  • 不暴露任何不需要的方法(即干净的API)

我正在使用PHP和Doctrine2,但我想这也适用于许多其他语言/平台。这是我的基本实体代码。我有ParentChild个对象。没有父母就不能存在Child

/**
 * @ORM\Entity
 */
class ParentClass
{
    /**
     * @ORM\OneToMany(targetEntity="Child", mappedBy="parent", orphanRemoval=true, cascade={"persist", "remove"})
     */
    private $children;
}

/**
 * @ORM\Entity
 */
class Child
{
    /**
     * @ORM\ManyToOne(targetEntity="Base", inversedBy="children")
     * @ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE", nullable=false)
     */
    private $parent;
}

但是我应该如何创建和删除子实体呢?

为了提高一致性,我可以让父作为Child的构造函数参数:

class ParentClass
{
    public function addChild(Child $child)
    {
        $this->children[] = $child;
    }

    public function removeChild(Child $child)
    {
        $this->children->removeElement($child);
    }
}

class Child
{
    public function __construct(ParentClass $parent)
    {
        $this->parent = $parent;
        $this->parent->addChild($this);
    }
}

$parent = new ParentClass();
$child = new Child($parent);

这个问题是它暴露了addChild,现在开发人员真的不应该使用它。这将需要一大堆额外的检查,以确保您不能在父母之间移动孩子。

作为替代方案,我可以使用setter:

class ParentClass
{
    public function addChild(Child $child)
    {
        $child->setParent($this);
        $this->children[] = $child;
    }

    public function removeChild(Child $child)
    {
        $this->children->removeElement($child);
    }
}

class Child
{
    public function setParent(ParentClass $parent)
    {
        $this->parent = $parent;
    }
}

$parent = new ParentClass();
$parent->addChild(new Child());

这里的问题是,在调用addChild之前,Child将处于无效状态。

第三个选项可能是让addChild创建一个新子项:

class ParentClass
{
    public function addChild()
    {
        $child = new Child($parent);
        $this->children[] = $child;
        return $child;
    }

    public function removeChild(Child $child)
    {
        $this->children->removeElement($child);
    }
}

class Child
{
    public function __construct(ParentClass $parent)
    {
        $this->parent = $parent;
    }
}

$parent = new ParentClass();
$child = $parent->addChild();

这个问题是子构造函数暴露给开发人员。另外,我的(Symfony)表单库可能会讨厌我,导致我有一堆DTO和映射器只是为了一个简单的用例。

可能有更多可能的方法来解决这个问题。确保干净域模型的首选方法是什么?

1 个答案:

答案 0 :(得分:2)

确保干净的域模型意味着您忽略与数据库相关的所有内容,例如一对多关系。您的父/子问题是一种气味,暗示您正在使用数据库驱动的设计。

在域级别,聚合根(AR)充当“父级”,尽管该术语是错误的。 Aggregate代表域概念,而AR负责确保其一致性。 “儿童”是没有这些概念的元素。您将始终使用AR来处理“孩子”,因为这是确保一致性的唯一方法。基本上,AR负责创建实际的“儿童”对象。

像容器一样处理AR是一种反模式。 DDD中的 表示定义,而不是包含。我几年前曾写过关于它的some posts但它们仍然有效。

您的Symfony表单库不应该讨厌您,因为这是 UI 关注点,而不是域名关注点。您应该使用将发送到应用程序服务的特定视图模型/输入,该服务将使用它来创建/更新域模型。如果你可以直接使用域模型用于UI目的,那么你所拥有的只是一个你不需要DDD的CRUD应用程序。