php - 领域驱动设计和 ORM 限制

标签 php orm doctrine-orm domain-driven-design

我看到的大多数 DDD 示例都是用 Java 编写的,并且绝大多数使用 Hibernate 来持久化和获取实体。我真的对两者都没有任何经验,我假设 Hibernate 是一个足以解决依赖关系、处理值对象等的工具。我选择的 ORM 是 Doctrine2,据我所知,这是 PHP 目前拥有的最好的工具,但我认为它不足以支持 DDD 原则。

这是领域层的一个例子:

/**
 * Simple value object
 */
class ProductId
{
    private $value;

    function __construct($value)
    {
        $this->value = $value;
    }

    public function value()
    {
        return $this->value;
    }
}

/**
 * Example dependency
 */
class Dependency
{
    public function doNothing()
    {

    }
}

/**
 * Game class done in a DDD manner
 */
class Game
{
    /**
     * @var ProductId
     */
    private $id;

    /**
     * @var string
     */
    private $title;

    /**
     * @var Dependency
     */
    private $dependency;

    function __construct(ProductId $id, $title, Dependency $dependency)
    {
        $this->id    = $id;
        $this->title = $title;

        // Validation
        Assertion::minLength(25, $title);
    }

    /**
     * @return ProductId
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @return string
     */
    public function getTitle()
    {
        return $this->title;
    }

    public function someBehavior()
    {
        $this->dependency->doNothing();
    }
}

现在有了 Doctrine,您可以使用 XML 或 YAML 映射将 Game 映射到某个表。 但是在调用 $gameRepository->productOfId($someId); 时,您会得到一个格式错误的对象。原因如下:

  1. 因为 Doctrine2 在调用 getId() 时不支持值对象,你会得到一个普通的 int,因为它的映射将指向一个整数列。最新的测试版有点支持它们,但它仍然不是很灵活,在更复杂的场景中也不容易配置。
  2. 由于 Doctrine2 在调用 someBehavior() 时创建代理对象以获取实体,因此您会收到 fatal error ,因为代理对象不解析构造函数依赖性。这实际上可以通过创建一些 DomainRegistry 单例并从那里获取依赖项来克服,但我真的不喜欢我暗示的那样。但是,由于未调用构造函数,我们仍在跳过验证,我不会仅依赖数据库完整性。

我应该如何克服它?我宁愿不在我的域模型中使用一些 if is int return ProductId(int) 东西,因为我希望我的域层是持久性无知的。

我想到的一件事是(假设我会坚持使用 ORM,而不仅仅是 DBAL)来处理像 DTO 这样的 Doctrine 实体(我希望我在这里正确使用该术语)并从中组装域对象。所以除了提到的类之外,我还有类似的东西:

/**
 * Doctrine entity treated like a pure value container
 */
class GameDTO
{
    private $id;
    private $title;

    /**
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @return string
     */
    public function getTitle()
    {
        return $this->title;
    }
}

class DoctrineGameRepository
{
    /**
     * @var Dependency
     */
    private $dependency;

    /**
     * Doctrine's entity manager
     */
    private $em;

    function __construct(Dependency $dependency, $em)
    {
        $this->dependency = $dependency;
        $this->em = $em;
    }

    public function productOfId($id)
    {        
        /** @var GameDTO $gameDto */
        $gameDto = $this->em->find($id);

        return new Game($gameDto->getId(), $gameDto->getTitle(), $this->dependency);        
    }
}

这样我就克服了基础设施的限制,但它又增加了一层复杂性。另外,我应该如何处理需要反射(reflect)到我的 DTO 的对象更改,以便我可以更新我的数据库?

我的第二个想法是采用 CQRS,但这对我来说只是一个流行词,因为我还没有对它进行太多研究。我知道它的原理,但我不知道我应该为哪些类和存储库建模。

你会如何处理这一切?

最佳答案

我认为这个问题提出了一个很好的观点。

ORM 的限制是否会影响您的 DDD 设计?

我大部分时间都在 java 中编写代码,我知道 hibernate 是如何发展的,3-4 年前甚至 hibernate 都不支持值对象,对嵌入式对象的支持也很幼稚,看起来 Doctrine2 在那个阶段

我认为一旦您倾向于 CQRS,就不再需要 ORM 的魔力,我认为 CQRS 和 DDD 齐头并进,CQRS 很漂亮 simple如果单独实现,只有当 ES 被添加到等式中时,它才会变得复杂(ES 有它的好处)。

因此,我建议您为所有要使用 DDD 概念的项目使用基本的 CQRS。所以从这里我们可以假设您有一个 Finder/Query 类来处理您的查询,这些可能应该是直接的 SQL

不让我们深入了解存储库。一旦我们对存储库进行查询,存储库实际上很简单,它公开的接口(interface)将非常紧凑,这里是一个示例。

public interface Repository<T> {

    T load(Object aggregateIdentifier);    
    void add(T aggregate);
    void update (T aggregate);
}

您可以为系统中的每个聚合实现一个实现。

在这里,您可以按照您认为合适的方式从结果集中整理聚合。这可能需要一些努力,但成本可以在项目之间分摊,并且您可以摆脱所有 ORM 黑魔法:)

关于php - 领域驱动设计和 ORM 限制,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25021154/

相关文章:

doctrine-orm - Symfony2,学说 2 : getResult Object

postgresql - 每次迁移时改变 uuid 列的学说

php - 坚持创建租金表

php - Doctrine SQL/表生成失败

javascript - Sequelize : Changing the nested data structure

java - 如何使用 Select 子句中的构造函数为多个表的选定列编写 HQL JOIN 查询

php - Doctrine 实体和特征。正确的方法

php - 如何在 userpro 插件中通过 url wordpress 获取用户标识?

php - iOS 8 PHP 文件上传错误(服务器文件大小 = 0)

php - 动态改变 CSS 背景图片