Попытка использовать шаблон спецификации и столкнуться с проблемой обеспечения его работы в различных реализациях (например, в памяти, orm и т. Д.). Мой основной ORM — это Doctrine, что означает, что я выбрал в спецификации использование критериев, так как они работают с ArrayCollections (для реализаций InMemory) и ORM. К сожалению, они довольно ограничены в видах запросов, которые они могут выполнять (не могут выполнить соединение).
В качестве примера, скажем, у меня есть спецификация UserHasBoughtProduct, которой дается идентификатор продукта в конструкторе. Спецификация очень проста для написания на наивном уровне.
public function isSpecifiedBy(User $user)
{
foreach ($user->getProducts() as $product)
{
if ($product->getId() == $this->productId)
{
return true;
}
}
return false;
}
Однако что, если я хочу найти всех пользователей, которые купили продукт? Мне нужно будет передать эту спецификацию в мой UserRepository через какой-то вид findSpecifiedBy (Specification $ification); метод. Но это не работает в производственном процессе, так как нужно будет проверять каждого пользователя в базе данных.
Моя следующая идея заключалась в том, что спецификация должна быть только интерфейсом, а реализация — инфраструктурой. Итак, в моем каталоге persistence \ doctrine \ user \ у меня может быть спецификация UserHasBoughtProduct, а в моем каталоге persistence \ InMemory \ user — другая. Это работает, в некотором смысле, но очень раздражает необходимость использовать в коде, так как мне нужно, чтобы все мои спецификации были доступны либо в контейнере DI, либо на какой-то фабрике. Не говоря уже о том, что если у меня есть класс, требующий нескольких спецификаций, мне нужно внедрить их все через конструктор. Плохо пахнет.
Было бы гораздо предпочтительнее, если бы я мог просто сделать следующее в методе:
$spec = new UserHasBoughtProductSpecification($productId);
$users = $this->userRepository->findSatisfiedBy($spec);
//or
if ($spec->isSatisfiedby($user))
{
//do something
}
У кого-нибудь был опыт в PHP? Как вам удалось реализовать шаблон спецификации таким образом, чтобы он работал в реальном мире и мог использоваться в различных бэкэндах, таких как InMemory, ORM, чистый SQL или что-то еще?
Если вы объявляете спецификацию как интерфейс в своем домене и внедряете ее в инфраструктуру, вы переносите бизнес-правила в инфраструктуру. Это противоположно тому, что делает DDD.
Итак Specification
бизнес-правила должны быть размещены на уровне домена.
когда Specification
используется для утверждать объекты, работает очень хорошо. Проблема возникает, когда используется для Выбрать объект из коллекции, в данном случае из Repository
, из-за большого количества объектов в памяти может быть.
Во избежание встраивания бизнес-правил в Repository
и просочились детали SQL в Domain
Эрик Эванс в своей книге 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
,
Хотя этот тип запроса может быть приемлемым, Э. Эванс указывает нам, что, скорее всего, он будет использоваться только в этом случае.
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);
});
}
}
Оба решения:
Других решений пока нет …