Реализовать шаблон спецификации

Попытка использовать шаблон спецификации и столкнуться с проблемой обеспечения его работы в различных реализациях (например, в памяти, 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 или что-то еще?

4

Решение

Если вы объявляете спецификацию как интерфейс в своем домене и внедряете ее в инфраструктуру, вы переносите бизнес-правила в инфраструктуру. Это противоположно тому, что делает 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);
});
}
}

Оба решения:

  • Храните бизнес-правила в одном месте, Домене.
  • Ставит SQL в репозиторий.
  • Спецификация определяет, какой запрос следует использовать.
  • Фильтры устанавливают возврат (частичный или полный) из хранилища.
10

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]