Specification pattern
Specification pattern
Кто может объяснить, как используют Specification pattern совместно с Repository pattern? В интернете пишут, что с помощью него можно убрать из репозитория все методы такого характера UserRepository::findByFirstName($firstName), UserRepository::findByLastName($lastName), UserRepository::findByFirstAndLastName($firstName, $lastName) и оставить вместо них только UserRepository::findAll(ISpecification $specification).
Но это же получается, что я должен сначала выгрузить все данные из базы, даже если там 1 млн. строк на основе этих данных собрать 1 млн. сущностей и только потом отфильтровать и вернуть из них те, которые удовлетворяют спецификации. Даже если данные выбирать с помощью batch то есть небольшими порциями, чтобы не уронить память сервера, обрабатывать и выбирать дальше, то всё равно это как-то странно для того, чтобы найти нужные сущности вытряхивать на запрос все данные, что есть в базе. Вот пример паттерна на php https://github.com/mbrevda/SpecificationPattern но я же не хочу загружать 1 млрд. инвойсов, что есть в базе, чтобы отобрать из них те, что удовлетворяют спецификации. Или как это работает?
Но это же получается, что я должен сначала выгрузить все данные из базы, даже если там 1 млн. строк на основе этих данных собрать 1 млн. сущностей и только потом отфильтровать и вернуть из них те, которые удовлетворяют спецификации. Даже если данные выбирать с помощью batch то есть небольшими порциями, чтобы не уронить память сервера, обрабатывать и выбирать дальше, то всё равно это как-то странно для того, чтобы найти нужные сущности вытряхивать на запрос все данные, что есть в базе. Вот пример паттерна на php https://github.com/mbrevda/SpecificationPattern но я же не хочу загружать 1 млрд. инвойсов, что есть в базе, чтобы отобрать из них те, что удовлетворяют спецификации. Или как это работает?
- samdark
- Администратор
- Сообщения: 9489
- Зарегистрирован: 2009.04.02, 13:46
- Откуда: Воронеж
- Контактная информация:
Re: Specification pattern
Наверняка имеется ввиду что-то вроде нашего query builder из Yii 1.1. То есть отдельно формируется CDbCriteria и передаётся в метод findAll(), а там уже внутри на основе критерии формируется запрос.
Нравится Yii? Давайте сделаем его лучше!.
Re: Specification pattern
да, CDbCriteria это по сути реализация спецификации, только со стороны базы, а не хранилища.
Я вижу так:
реализация спецификации может быть разной. Мы можем написать как всеохватывающую библиотеку, покрывающую различного типа запросы, либо под каждый репозиторий писать свою маленькую спецификацию. Мы можем сделать либу в виде билдера с удобным языком составления спецификации или аскетичный класс с передачей параметром в конструктор.
Но вместе со спецификацией мы сразу должны писать транслятор спецификации в язык запросов конкретного репозитория, будь то sql, http-запросы итд.
PS В моем примере QueryBuilder не делает запрос в БД как в yii, а строит непосредственно sql-выражение в виде строки, т.е. возвращает sql + забинденные параметры (опустил в примере).
Я вижу так:
Код: Выделить всё
// нам нужны посты с именем 'Новости отпуска' и с датой, ранее '2015-05-12'
$specification = (new PostSpecification)
->nameEqual('Новости отпуска')
->dateEarlierThan(new Date('2015-05-12'));
$postRepository->find($specification);
// PostRepository
public function find(PostSpecification $specification)
{
$sql = (new SqlSpecificationTranslator)->toQuery($specification); // адаптер спецификации в запрос sql
return $this->sqlConnection->select($sql);
}
// SqlSpecificationTranslator
public function toQuery(PostSpecification $specification)
{
$queryBuilder = new QueryBuilder;
if($specification->getName()) {
$queryBuilder->andWhere(['name' => $specification->getName()); // $specification->getName() на самом деле вернул EqualCondition
}
if($specification->getDate()) {
$queryBuilder
->andWhere(['date > :date']) // $specification->getDate() на самом деле вернул CompareCondition
->bind(':date', $specification->getDate());
}
return $queryBuilder->getSql();
}
Но вместе со спецификацией мы сразу должны писать транслятор спецификации в язык запросов конкретного репозитория, будь то sql, http-запросы итд.
PS В моем примере QueryBuilder не делает запрос в БД как в yii, а строит непосредственно sql-выражение в виде строки, т.е. возвращает sql + забинденные параметры (опустил в примере).
Re: Specification pattern
или какая-то абстрактная спецификация:
Код: Выделить всё
$specification = new CollectionSpecification([
new EqualCondition(['id' => 5, 'status' => Post::STATUS_PUBLISHED]),
]);
Код: Выделить всё
$specification = new CollectionSpecification([
new PageCondition(2, 15), // 2-я страница по 15
new EqualCondition(['status' => Post::STATUS_PUBLISHED]),
new SignCondition(['date' , '>', '2015-01-01']),
new LikeCondition(['name' , 'отпуск']),
]);
Код: Выделить всё
$specification = (new SpecificationBuilder)
->page(2, 15)
->equal(['status' => Post::STATUS_PUBLISHED])
->sign(['date' , '>', '2015-01-01'])
->like(['name' , 'отпуск']);
Re: Specification pattern
Ну вот у Фаулера в его книге Patterns of Enterprise Application Architecture на странице 275 я нашел описание Query object и Criteria, он запихал в эту Criteria метод Criteria::generateSql() который и генерирует часть sql запроса. Затем на странице 283-284 он рассказывает про репозиторий и там есть примеры создания двух стратегий RelationalStrategy и InMemoryStrategy, одна ищет по реляционной базе, а другая ищет в ОЗУ. Так вот та стратегия, что ищет в ОЗУ использует в качестве Criteria тот самый Specification pattern в том виде, в каком он описан в вики с его isSatisfiedBy() методом, который ищет исключительно уже по готовым доменным объектам. Ну а RelationalStrategy судя по всему использует Criteria с generateSql() методом. И в книге это похоже на один и тот же Criteria объект.
Так вот я не понимаю до конца мысли Фаулера, я должен создавать Criteria под разные хранилища или как? Вот исходя из примеров в книге Фаулера каждая его Criteria связана с реляционной бд. Вот беру я документоориентированную бд и какие мои действия тогда должны быть по Фаулеру? А если я хочу еще и в памяти уметь хранить для тестов, тогда какие? Под каждое хранилище свой набор Criteria ? Вообще ничего не понятно...
Так вот я не понимаю до конца мысли Фаулера, я должен создавать Criteria под разные хранилища или как? Вот исходя из примеров в книге Фаулера каждая его Criteria связана с реляционной бд. Вот беру я документоориентированную бд и какие мои действия тогда должны быть по Фаулеру? А если я хочу еще и в памяти уметь хранить для тестов, тогда какие? Под каждое хранилище свой набор Criteria ? Вообще ничего не понятно...
Re: Specification pattern
Я нашел, что многие делают метод типа generateSql как в книге Фаулера для Specification pattern например вот http://marcaube.ca/2015/05/specificatio ... ith-a-spec и автор тоже поднимает проблему, когда в базе много данных. Но предлагает в качестве компромисса дать протечь инфраструктуре в доменный слой или использовать inversion of control и как я понял автор этой статьи предлагает примерно такое решение http://stackoverflow.com/a/33341503/2868530
Но я тогда не пойму, зачем это всё если double dispatch точно также требует эти кастомные методы в репозитории для выборки из базы от которых мы изначально и хотели избавиться. Зачем нужен такой Specification pattern?
Но я тогда не пойму, зачем это всё если double dispatch точно также требует эти кастомные методы в репозитории для выборки из базы от которых мы изначально и хотели избавиться. Зачем нужен такой Specification pattern?
- slavcodev
- Сообщения: 3134
- Зарегистрирован: 2009.04.02, 21:42
- Откуда: Valencia
- Контактная информация:
Re: Specification pattern
Обратите внимание что Спецификация это часть домена, это часть Ubiquitous Language. Так что варианты типа Query Buidler не очень подходят
не очень хороший пример имхо. Когда один два сервиса юзается это сойдет (хотя ту и ActiveRecord сойдет ) но на больших количествах сервисов, такое вот использование будет проблематичным. Это ничем не отличается от использования SQL buildera в сервисах или Yii scopes, слишком много деталей.
Мне кажется Эванс под Спецификациями имел виду уточняющие условия для группы сущностей, уже полученных из БД.
Т.е. Эванс подразумевал фильтрацию уже восстановленных сущностей, а не запросы в БД, для уточняющих запросов в БД есть репозитории и его методы.
Конечно же соблазн огромный, не делать детальные репозитории, а делать генерик методы типа $repository->getBySpecification($spec),
в этом случае я видел толко одну реализацию этого дела "Double Dispatch", когда класс спецификации имплементирует два интерфейса, один доменный другой инфраструктырный. Это подход, кроме того что мешает имплементацию двух слоев, нарушает SRP, еще он нарушает, что часто упускается, LSP. Но это работает, и в отсутствии других вариантов (по крайней мере я других еще не видел), так что как говорит дядюшка Боб, SOLID можно нарушать если это делается осознано для решения каких-то задач.
Код: Выделить всё
->page(2, 15)
->equal(['status' => Post::STATUS_PUBLISHED])
->sign(['date' , '>', '2015-01-01'])
->like(['name' , 'отпуск']);
Мне кажется Эванс под Спецификациями имел виду уточняющие условия для группы сущностей, уже полученных из БД.
Код: Выделить всё
$posts = $repository->getAll();
// или более детальная выборка
$posts = $repository->getPublishedPosts();
// и когда уже есть группа нужных сущностей,
// но выделать и еще какую-то группу, то вводится спецификации
$popularPosts = array_filter($posts, new FiveMostPopularPosts(/* $viewCount > 1000 */));
$this->pinToSidebar($popularPosts);
Конечно же соблазн огромный, не делать детальные репозитории, а делать генерик методы типа $repository->getBySpecification($spec),
в этом случае я видел толко одну реализацию этого дела "Double Dispatch", когда класс спецификации имплементирует два интерфейса, один доменный другой инфраструктырный. Это подход, кроме того что мешает имплементацию двух слоев, нарушает SRP, еще он нарушает, что часто упускается, LSP. Но это работает, и в отсутствии других вариантов (по крайней мере я других еще не видел), так что как говорит дядюшка Боб, SOLID можно нарушать если это делается осознано для решения каких-то задач.
Жду Yii 3!
Re: Specification pattern
slavcodev у меня точно такое же восприятие этого паттерна как и у вас. Но я все равно не понимаю как сделать $repository->getBySpecification($spec) и при этом избавиться от детальных методов с помощью double dispatch, ведь детальные методы в этом случае все равно остаются в репозитории, просто теперь эти методы вызываются внутри спецификации. У меня выше ссылка на стековерфлоу где есть примеры, которые это демонстрируют. Поэтому я не понимаю, что это дает и зачем так делать, если это не дает никаких преимуществ перед тем как если эти детальные методы вызывать сразу в application layer без всяких спецификаций.
Re: Specification pattern
поясни мысль.slavcodev писал(а):Обратите внимание что Спецификация это часть домена, это часть Ubiquitous Language. Так что варианты типа Query Buidler не очень подходят
не очень хороший пример имхо. Когда один два сервиса юзается это сойдет (хотя ту и ActiveRecord сойдет ) но на больших количествах сервисов, такое вот использование будет проблематичным. Это ничем не отличается от использования SQL buildera в сервисах или Yii scopes, слишком много деталей.Код: Выделить всё
->page(2, 15) ->equal(['status' => Post::STATUS_PUBLISHED]) ->sign(['date' , '>', '2015-01-01']) ->like(['name' , 'отпуск']);
про Эванса ясно, но в таком виде спецификация мало пригодна для работы. Поэтому например Фаулер ее развил до generateSql() (в книге DDD in PHP насколько помню тоже специфкация используется в таком же виде)slavcodev писал(а):Мне кажется Эванс под Спецификациями имел виду уточняющие условия для группы сущностей, уже полученных из БД.
Т.е. Эванс подразумевал фильтрацию уже восстановленных сущностей, а не запросы в БД, для уточняющих запросов в БД есть репозитории и его методы.Код: Выделить всё
$posts = $repository->getAll(); // или более детальная выборка $posts = $repository->getPublishedPosts(); // и когда уже есть группа нужных сущностей, // но выделать и еще какую-то группу, то вводится спецификации $popularPosts = array_filter($posts, new FiveMostPopularPosts(/* $viewCount > 1000 */)); $this->pinToSidebar($popularPosts);
slavcodev писал(а):Код: Выделить всё
Конечно же соблазн огромный, не делать детальные репозитории, а делать генерик методы типа $repository->getBySpecification($spec), в этом случае я видел толко одну реализацию этого дела "Double Dispatch", когда класс спецификации имплементирует два интерфейса, один доменный другой инфраструктырный. Это подход, кроме того что мешает имплементацию двух слоев, нарушает SRP, еще он нарушает, что часто упускается, LSP. Но это работает, и в отсутствии других вариантов (по крайней мере я других еще не видел), так что как говорит дядюшка Боб, SOLID можно нарушать если это делается осознано для решения каких-то задач.[/quote]я не стал разбираться в приведенных sda ссылок, но я предложил вариант без протечек и double dispatch. У нас есть в домене сущность, репозиторий с generic методом findBySpec(...) и спецификации запросов в репозиторий. Реализуя интерфейс репозитория в инфраструктуре мы также реализуем трансляцию модели спецификации в нативный язык запросов для реализации репозитория.
- samdark
- Администратор
- Сообщения: 9489
- Зарегистрирован: 2009.04.02, 13:46
- Откуда: Воронеж
- Контактная информация:
Re: Specification pattern
Вообще это палка о двух концах. Мы избавляемся от тучи конкретных методов, но взамен получаем тучу раздутого кода по формированию спецификации. Не от похожего ли кода мы избавлялись, вводя репозитории?
Нравится Yii? Давайте сделаем его лучше!.
Re: Specification pattern
репозитории мы вводили для абстракции от хранилища.Sam Dark писал(а):Вообще это палка о двух концах. Мы избавляемся от тучи конкретных методов, но взамен получаем тучу раздутого кода по формированию спецификации. Не от похожего ли кода мы избавлялись, вводя репозитории?
на реальном проекте (не из книги) мы сразу же столкнемся с тем, что репозитории должны подерживать пагинацию. плюс через 2-3 года разработки репозитории могут раздуться до 50 методов, поддерживающих различные типа выборок. Спецификация поможет сохранить чистоту.
ну как мне представляется, в случае с билдером спецификаций общего вида (в виде библиотеки) нам понадобится реализовать универсальный транслятор строк в 50 и все.Sam Dark писал(а):Мы избавляемся от тучи конкретных методов, но взамен получаем тучу раздутого кода по формированию спецификации.
Возможно не стоило называть предложенное мной решение спецификацией - скорее это QueryObject, что впрочем не отменяет его предназначения.
https://github.com/dddinphp/book-issues ... -198759985 вот примерно об этом я и говорю
- slavcodev
- Сообщения: 3134
- Зарегистрирован: 2009.04.02, 21:42
- Откуда: Valencia
- Контактная информация:
Re: Specification pattern
И того же мнения. Спецификации, как фильтры сущностей, удобны, избавляют от кучи foreach, в боготом логикой домене. А вот генерация SQL в спецификациях, может быть интересна только как какой-то повторяющий код. Так что Double Dispatch мне не нравится, и я экспериментирую с другим решением.
Жду Yii 3!
- slavcodev
- Сообщения: 3134
- Зарегистрирован: 2009.04.02, 21:42
- Откуда: Valencia
- Контактная информация:
Re: Specification pattern
Чем плохи репозитории с 50 методов? Все хранится в одном классе, чей смысл это как список сущностей? Очень даже хорошо получается все контролировать. Имплеентацию этих 50 методов, можно сделать использую query buidler + scopes какие-нибудь, и 50 методов буду выгялдит красиво и чисто. Но за то вся логика по фильтрации и группировке сущностей в одном классе ответственность которого как раз в этом.zelenin писал(а):плюс через 2-3 года разработки репозитории могут раздуться до 50 методов, поддерживающих различные типа выборок. Спецификация поможет сохранить чистоту.
Жду Yii 3!
Re: Specification pattern
я предложил генерить sql репозиторию на основе переданной спеки. Сама спека ничего не генерит, а только представляет информацию о запросе в репо.slavcodev писал(а):И того же мнения. Спецификации, как фильтры сущностей, удобны, избавляют от кучи foreach, в боготом логикой домене. А вот генерация SQL в спецификациях, может быть интересна только как какой-то повторяющий код. Так что Double Dispatch мне не нравится, и я экспериментирую с другим решением.
Re: Specification pattern
50 методов не отменяют наличия более общего метода. а scope + пагинация - это и есть вариация QueryObject.slavcodev писал(а):Чем плохи репозитории с 50 методов? Все хранится в одном классе, чей смысл это как список сущностей? Очень даже хорошо получается все контролировать. Имплеентацию этих 50 методов, можно сделать использую query buidler + scopes какие-нибудь, и 50 методов буду выгялдит красиво и чисто.zelenin писал(а):плюс через 2-3 года разработки репозитории могут раздуться до 50 методов, поддерживающих различные типа выборок. Спецификация поможет сохранить чистоту.
так я и не говорю куда-то выносить фильтрацию/группировку.slavcodev писал(а): Но за то вся логика по фильтрации и группировке сущностей в одном классе ответственность которого как раз в этом.
Если у нас в домене есть Repository+QueryObject, то реализация репозитория должна понимать этот QueryObject, преобразуя в свой нативный язык (sql ли это будет либо http-запросы к апи).
- slavcodev
- Сообщения: 3134
- Зарегистрирован: 2009.04.02, 21:42
- Откуда: Valencia
- Контактная информация:
Re: Specification pattern
Да именно похожее решение я эксперементально интегрирую сейчас в два баундед контекста, один CRUD другой CQRS, посмотрим во что это превратится .
Жду Yii 3!
- slavcodev
- Сообщения: 3134
- Зарегистрирован: 2009.04.02, 21:42
- Откуда: Valencia
- Контактная информация:
uery
Использование QueryBuilder, имхо, уходит далеко за пределы UL, и уменьшает всю красоту DDD. QueryBuilder это уже какой-то технический прием программиста, чтоб облегчить себе жизнь а не DDD.zelenin писал(а):так я и не говорю куда-то выносить фильтрацию/группировку.
Если у нас в домене есть Repository+QueryObject, то реализация репозитория должна понимать этот QueryObject, преобразуя в свой нативный язык (sql ли это будет либо http-запросы к апи).
Код: Выделить всё
class MyService
{
public function handle()
{
$query = new QueryBuilder();
$query->page(2, 15)
->equal(['status' => Post::STATUS_PUBLISHED])
->sign(['date' , '>', '2015-01-01'])
->like(['name' , 'отпуск']);
$posts = $repository->getPostsByQuery($query);
}
}
Он так и сказал: "Хочу иметь билдер, с кучей настроек, и искать посты по этим значениям"? Сомневаюсь.
Он попросит Вас:
- "Найти все посты разбитые на страницы"
- "Найти новые посты разбитые на страницы"
- "Найти посты по заданной строке разбитые на страницы"
50 методов в репозитории? Тоже сомневаюсь, а если со временем и появится, то скорее всего прийдет время рефакторинга и пересмотра UL, может быть эти методы можно объединить, переопределить.
Жду Yii 3!
Re: uery
это всего лишь паттерн Builder, позволяющий в более простой манере (да, для программиста) составлять QueryObject (я специально привел три варианта реализации, чтобы показать как можно более удобно реализовать обертку над спекой с UL).slavcodev писал(а):Использование QueryBuilder, имхо, уходит далеко за пределы UL, и уменьшает всю красоту DDD. QueryBuilder это уже какой-то технический прием программиста, чтоб облегчить себе жизнь а не DDD.zelenin писал(а):так я и не говорю куда-то выносить фильтрацию/группировку.
Если у нас в домене есть Repository+QueryObject, то реализация репозитория должна понимать этот QueryObject, преобразуя в свой нативный язык (sql ли это будет либо http-запросы к апи).
WUT???? Что за query? Что что значит все слова из цепочки? Кто их о них рассказал? Доменный эксперт? Он думаете знает что такое билдер, не думает ли он что это строитель домов?Код: Выделить всё
class MyService { public function handle() { $query = new QueryBuilder(); $query->page(2, 15) ->equal(['status' => Post::STATUS_PUBLISHED]) ->sign(['date' , '>', '2015-01-01']) ->like(['name' , 'отпуск']); $posts = $repository->getPostsByQuery($query); } }
Он так и сказал: "Хочу иметь билдер, с кучей настроек, и искать посты по этим значениям"? Сомневаюсь.
- slavcodev
- Сообщения: 3134
- Зарегистрирован: 2009.04.02, 21:42
- Откуда: Valencia
- Контактная информация:
Re: Specification pattern
И еще, вот написал ты в N сервисах билдер типа этого
И поменялся у тебя домен, и нет больше "name" атрибута, есть "firstName" + "lastName", вот весело будет. Всего-то надо было в репозиторий добавить
И в нем рулить какие поля можно делать like а по каким нет.
Код: Выделить всё
class MyService
{
public function handle()
{
$query = new QueryBuilder();
$query->page(2, 15)
->equal(['status' => Post::STATUS_PUBLISHED])
->sign(['date' , '>', '2015-01-01'])
->like(['name' , 'отпуск']);
$posts = $repository->getPostsByQuery($query);
}
}
Код: Выделить всё
public function getPostsBySearchString($q) : array;
Жду Yii 3!
- slavcodev
- Сообщения: 3134
- Зарегистрирован: 2009.04.02, 21:42
- Откуда: Valencia
- Контактная информация:
Re: uery
Я обсуждаю только вариант с билдером, он никак не относится к UL имхо.zelenin писал(а):я специально привел три варианта реализации, чтобы показать как можно более удобно реализовать обертку над спекой с UL.
Жду Yii 3!