【发布时间】:2014-09-21 04:04:53
【问题描述】:
我看到的大多数 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); 时,您会得到一个格式错误的对象。原因如下:
- 由于在调用
getId()时Doctrine2 不支持值对象,您将得到一个普通的int,因为它的映射将指向一个整数列。最新的测试版有点支持它们,但仍然不是很灵活,在更复杂的场景中也不容易配置。 - 由于 Doctrine2 在对获取的实体调用
someBehavior()时创建了用于检索的代理对象,因此您会收到一个致命错误,因为代理对象不解析构造函数依赖项。这实际上可以通过创建一些DomainRegistry单例并从那里获取依赖项来克服,但我真的不喜欢我暗示的那样。然而,由于没有调用构造函数,我们仍然跳过验证,而且我不会只依赖数据库完整性。
我应该如何克服它?我宁愿不在我的域模型中使用一些if is int return ProductId(int) 的东西,因为我希望我的域层是持久性无知的。
我想到的一件事是(假设我会坚持使用 ORM,而不仅仅是 DBAL)将 Doctrine 实体视为 DTO(我希望我在这里正确使用该术语)并从中组装域对象。所以除了提到的课程之外,我还有类似的东西:
/**
* 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);
}
}
这样我克服了基础设施的限制,但它增加了另一层复杂性。另外,我应该如何处理需要反映到我的 DTO 的对象的更改,以便我可以更新我的数据库?
我的第二个想法是适应 CQRS,但这对我来说只是一个流行词,因为我还没有进行太多研究。我知道它的原理,但我不知道我应该为此建模哪些类和存储库。
你会如何处理这一切?
【问题讨论】:
-
DDD 不依赖于 Hibernate。在 Java 项目中甚至没有必要使用 Hibernate。
-
@duffymo,我从没这么说过。我用它作为理论对立的例子来说明 Doctrine 做事的方式。就像我说的 - 我真的不知道 Hibernate 是否能解决所有问题,但这超出了问题的范围。
-
就我个人而言,我总是向人们抱怨如何最好地在存储库中切断您的 ORM。列出的原因太多,但请始终考虑“我是否希望我的域模型被以数据为中心的工件和属性污染?”。我强烈建议从 DDD 的角度来看,将您的 ORM 实体视为 DTO,使用它们来填充域实体并从您的存储库中返回这些实体。
-
@AdrianThompsonPhillips,但是您如何处理更新数据库数据?由于 DTO 而不是 DDD 实体在 ORM 下,因此我在域模型中所做的每一项更改都需要以某种方式反映到 DTO。
-
@acid 在我的情况下(.NET 开发)我遇到的实体框架 ORM 最多,我在它的“数据库优先”范式中使用它。所以我只是简单地更改域模型,然后更改数据库并让实体框架自动重新生成 DTO。当我遇到使用“代码优先”范例的 NHibernate 或 EF 时,我也必须维护对 DTO 的更改。或者,在我自己的项目中,我使用了 RavenDB(一个文档数据库),我可以在其中保留域实体,而无需任何 ORM、DTO 或映射。
标签: php orm doctrine-orm domain-driven-design