Ant*_*ite 4 php domain-driven-design specifications doctrine-orm
尝试使用规范模式并遇到使其在不同实现中工作的问题(例如,在内存,orm等中).我的主要ORM是Doctrine,这意味着我的第一选择是让规范使用Criterias,因为它们可以处理ArrayCollections(用于InMemory实现)和ORM.不幸的是,它们在可以运行的各种查询中相当有限(无法执行连接).
举个例子,假设我有一个UserHasBoughtProduct规范,在构造函数中给出了一个产品ID.该规范在初始级别编写非常简单.
public function isSpecifiedBy(User $user)
{
foreach ($user->getProducts() as $product)
{
if ($product->getId() == $this->productId)
{
return true;
}
}
return false;
}
Run Code Online (Sandbox Code Playgroud)
但是,如果我想找到购买该产品的所有用户,该怎么办?我需要通过某种findSpecifiedBy(规范$规范)将此规范传递给我的UserRepository; 方法.但这在生产中不起作用,因为它必须检查数据库中的每个用户.
我的下一个想法是,规范只是一个接口,实现由基础设施处理.所以,在我的persistence\doctrine\user \目录中,我可能有一个UserHasBoughtProduct规范,在我的persistence\InMemory\user目录中,我有另一个.这在某种程度上是有效的,但是在代码中使用非常烦人,因为我需要通过DI容器或某种工厂提供我的所有规格.更不用说如果我有一个需要几个规范的类,我需要通过构造函数注入它们.不好闻.
如果我可以在一个方法中简单地执行以下操作,那将更为可取:
$spec = new UserHasBoughtProductSpecification($productId);
$users = $this->userRepository->findSatisfiedBy($spec);
//or
if ($spec->isSatisfiedby($user))
{
//do something
}
Run Code Online (Sandbox Code Playgroud)
有没有人有过在PHP中这样做的经验?你是如何设法实现规范模式的,它在现实世界中工作,并且可以在不同的后端使用,例如InMemory,ORM,纯SQL或其他任何东西?
mar*_*iva 10
如果将"规范"声明为域中的接口并在基础架构中实现它,则会将业务规则移至基础架构.这与DDD的作用相反.
因此,Specification业务规则必须放在域层中.
何时Specification用于验证对象,效果很好.问题来自用于从集合中选择对象时,在这种情况下Repository,由于内存中可能存在大量对象.
为了避免将业务规则嵌入到Repository泄漏的SQL细节中Domain,Eric Evans在他的DDD一书中给出了几个解决方案:
1.双调度+专业查询
public class UserRepository()
{
public function findOfProductIdBought($productId)
{
// SQL
$result = $this->execute($select);
return $this->buildUsersFromResult($result);
}
public function selectSatisfying(UserHasBoughtProductSpecification $specification)
{
return $specification->satisfyingElementsFrom($this);
}
}
public class UserHasBoughtProductSpecification()
{
// construct...
public function isSatisfyBy(User $user)
{
// business rules here...
}
public function satisfyingElementsFrom($repository)
{
return $repository->findOfProductId($this->productId);
}
}
Run Code Online (Sandbox Code Playgroud)
Repository有一个专门的查询,完全匹配我们的Specification.虽然这种查询是可以接受的,但埃文斯指出我们最有可能只会在这种情况下使用.
2.双调度+通用查询
另一种解决方案是使用更通用的查询
public class UserRepository()
{
public function findWithPurchases()
{
// SQL
$result = $this->execute($select);
return $this->buildUsersFromResult($result);
}
public function selectSatisfying(UserHasBoughtProductSpecification $specification)
{
return $specification->satisfyingElementsFrom($this);
}
}
public class UserHasBoughtProductSpecification()
{
// construct ...
public function isSatisfyBy(User $user)
{
// business rules here...
}
public function satisfyingElementsFrom($repository)
{
$users = $repository->findWithPurchases($this->productId);
return array_filter($users, function(User $user) {
return $this->isSatisfyBy($user);
});
}
}
Run Code Online (Sandbox Code Playgroud)
两种方案: