实现规范模式
Implement specification pattern
尝试使用规范模式并运行考虑让它在不同实现中工作的问题(例如,在内存、orm 等中)。我的主要 ORM 是 Doctrine,这意味着我的第一选择是让规范在 ArrayCollections(用于 InMemory 实现)和 ORM 上工作时使用 Criterias。不幸的是,他们可以运行(无法执行连接)的查询种类相当有限。
举个例子,假设我有一个 UserHasBoughtProduct 规范,它在构造函数中指定了一个产品 ID。该规范非常简单,可以在天真的级别编写。
public function isSpecifiedBy(User $user)
{
foreach ($user->getProducts() as $product)
{
if ($product->getId() == $this->productId)
{
return true;
}
}
return false;
}
但是,如果我想找到所有购买过该产品的用户怎么办?我需要通过某种 findSpecifiedBy(Specification $specification); 将此规范传递给我的 UserRepository;方法。但这在生产中不起作用,因为它必须检查数据库中的每个用户。
我的下一个想法是规范只是一个接口,实现由基础设施处理。因此,在我的 persistence\doctrine\user\ 目录中,我可能有一个 UserHasBoughtProduct 规范,在我的 persistence\InMemory\user 目录中我有另一个。这在某种程度上是可行的,但是必须在代码中使用非常烦人,因为我需要我的所有规范都可以通过 DI 容器或某种工厂获得。更不用说如果我有一个 class 需要多个规范,我需要通过构造函数将它们全部注入。难闻。
如果我能简单地在方法中执行以下操作,那就更好了:
$spec = new UserHasBoughtProductSpecification($productId);
$users = $this->userRepository->findSatisfiedBy($spec);
//or
if ($spec->isSatisfiedby($user))
{
//do something
}
有没有人在 PHP 有过这样做的经验?您是如何设法以一种在现实世界中工作并可用于不同后端(例如 InMemory、ORM、pure SQL 或其他任何后端)的方式实现规范模式的?
如果您将规范声明为域中的接口并在基础架构中实现它,那么您就是在将业务规则转移到基础架构中。这与 DDD 的作用相反。
因此,Specification
业务规则必须放在域层中。
当 Specification
用于 验证 对象时,效果很好。当习惯于从select集合中获取一个对象时,问题就来了,在这种情况下,来自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);
}
}
Repository
有一个专门的查询,与我们的 Specification
完全匹配。
虽然这种查询是可以接受的,但 E. Evans 指出我们很可能只会在这种情况下使用。
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);
});
}
}
两种解决方案:
- 将业务规则保存在一个地方,域。
- 将 SQL 放入存储库。
- 规范控制应该使用什么查询。
- 从存储库中设置 return(部分或全部)过滤器。
尝试使用规范模式并运行考虑让它在不同实现中工作的问题(例如,在内存、orm 等中)。我的主要 ORM 是 Doctrine,这意味着我的第一选择是让规范在 ArrayCollections(用于 InMemory 实现)和 ORM 上工作时使用 Criterias。不幸的是,他们可以运行(无法执行连接)的查询种类相当有限。
举个例子,假设我有一个 UserHasBoughtProduct 规范,它在构造函数中指定了一个产品 ID。该规范非常简单,可以在天真的级别编写。
public function isSpecifiedBy(User $user)
{
foreach ($user->getProducts() as $product)
{
if ($product->getId() == $this->productId)
{
return true;
}
}
return false;
}
但是,如果我想找到所有购买过该产品的用户怎么办?我需要通过某种 findSpecifiedBy(Specification $specification); 将此规范传递给我的 UserRepository;方法。但这在生产中不起作用,因为它必须检查数据库中的每个用户。
我的下一个想法是规范只是一个接口,实现由基础设施处理。因此,在我的 persistence\doctrine\user\ 目录中,我可能有一个 UserHasBoughtProduct 规范,在我的 persistence\InMemory\user 目录中我有另一个。这在某种程度上是可行的,但是必须在代码中使用非常烦人,因为我需要我的所有规范都可以通过 DI 容器或某种工厂获得。更不用说如果我有一个 class 需要多个规范,我需要通过构造函数将它们全部注入。难闻。
如果我能简单地在方法中执行以下操作,那就更好了:
$spec = new UserHasBoughtProductSpecification($productId);
$users = $this->userRepository->findSatisfiedBy($spec);
//or
if ($spec->isSatisfiedby($user))
{
//do something
}
有没有人在 PHP 有过这样做的经验?您是如何设法以一种在现实世界中工作并可用于不同后端(例如 InMemory、ORM、pure SQL 或其他任何后端)的方式实现规范模式的?
如果您将规范声明为域中的接口并在基础架构中实现它,那么您就是在将业务规则转移到基础架构中。这与 DDD 的作用相反。
因此,Specification
业务规则必须放在域层中。
当 Specification
用于 验证 对象时,效果很好。当习惯于从select集合中获取一个对象时,问题就来了,在这种情况下,来自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);
}
}
Repository
有一个专门的查询,与我们的 Specification
完全匹配。
虽然这种查询是可以接受的,但 E. Evans 指出我们很可能只会在这种情况下使用。
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);
});
}
}
两种解决方案:
- 将业务规则保存在一个地方,域。
- 将 SQL 放入存储库。
- 规范控制应该使用什么查询。
- 从存储库中设置 return(部分或全部)过滤器。