Всё просто.
Если вашей сущности не хватает данных для проверки бизнес ограничения вы можете:
А) передать ей эти данные в аргументе функции
Б) вынести проверку бизнес ограничения в сервис доменного слоя
У Вернона есть упоминание о сервисах доменного слоя. Рекомендую почитать.
Я видел уже эту схему, но она мне показалась странной. Я ее не понял и прошел мимо.
Теперь я понял эту архитектуру.
Спасибо вам за это.
Правда мне всё равно она кажется немного странной.
Мне больше по душе классическая архитектуру DDD приложений + CQRS.
И зачем ты дурачком прикидываешся? Прекраснож понимаешь откуда.
Тегетируем наши сервисы репозитории и возвращаем в фабрике.
Естественно ids мы получаем из Compiler Passes.
Если тебе нужны зависимости в репозитории, то ты в любом случае должен объявить их как сервисы. А чтоб можно было их получить из EntityManager, мы просто добавляем им метку.
Эээ… Вы читать не умеете? Я уже тысячу раз сказал, что я не хочу давать пользователю интерфейс ObjectRepository. На каком ещё языке мне это сказать чтоб вы меня поняли? Может на белорусском?
Хотя мы можем добавить интерфейс ObjectRepository к DoctrineArticleRepository и не добавлять его к ArticleRepository и таки образом не будем нарушать контракт, но смысла в этом особого нет, так как создаст ненужные, скрытые, пустые методы в репозитории.
Ну это ваши личные трудности. Меня тоже много чего не устраивает и что с того?
только через setter injection, что опять же меня не устраивает.
В смысле? Использовать constructor injection религия не позволяет?
не чувствую. Information hiding надо рассматривать исключительно с точки зрения клиента
Разницы для клиента между двумя реализациями нет. Они обе дают конкретный интерфейс ArticleRepository.
Или вы намекаете что "это чудаки могут где-то заинджектить EntityManager в обход Dependency Injection?
А почему в обход? Если EntityManager вы инжектите в репозиторий, то и в любой другой сервис его можно спокойно инжектить.
Ваш же способ просто позволяет сразу всегда и везде получать инстанс EntityRepository. Вообще никакой изоляции.
Как раз наоборот. Мой вариант позволяет полностью заблокировать возможность получения EntityRepository. Полная изоляция.
Хотя я должен признаться. Я забыл что метод EntityManagerInterface::getRepository() должен возвращать ObjectRepository. Он может возвращать и другие объекты, но это будет нарушением контракта.
Так что мой вариант нарушает контракт и ваше решение правильней, хотя я считаю его неприемлемым, от слова совсем.
А что тут представлять?
Если компания хочет выходить на мировой рынок, то ей нужно переводить на другие языки:
Документациию к своему оборудованию или ПО
UI программного обеспечения
UI сайта
Стандартом для Российских компаний является наличие документов на русском и английском языках. Следующим как правило идут немецкий, французский и итальянский.
Если компания хочет развивать свою деятельность на востоке, то к этому списку очень быстро добавляются такие специфические языки как японский, китайский, корейский и арабский.
Всё делается для пользователей.
Вам ведь легче читать инструкцию на русском языке чем на английском, по например, эксплуатации стиральной машины произведенной в Японии, японский компанией.
нас интересовать должно не это, а information hiding. Мы не добавлять должны методы а изолировать текущее. Все методы "инфраструктурные" должны быть приватными и изолированы красивым интерфейсом исключающим "неправильное использование".
Вот с изоляцией то у вас и проблемы.
Вместо того чтобы сделать один репозиторий с единой точкой доступа. Вы создали второй репозиторий и создали вторую точку доступа к данным и старательно пытаетесь оправдаться инверсией зависимостей и интерфейсом доменного слоя.
А вы не думали что доктрина может возвращать вам репозиторий реализацующий интерфейс доменного слоя?
Вы можете все так же использовать зависимости в репозитории. Все также можете внедрять репозиторий как зависимость, но ещё вы можете получать его из EntityManager-а. И точка доступа к данным у вас в этом случае одна (если не брать в расчет прямые запросы к EntityManager и Connection).
use GuzzleHttp/Client;
class DoctrineArticleRepository implements ArticleRepository
{
private $em;
private $client;
public function __construct(EntityManagerInterface $em, Client $client)
{
$this->em = $em;
$this->client = $client;
}
public function get(ArticleId $id): Article
{
$article = $this->em->find(Article::class, $id);
if (!$article instanceof Article) {
throw new \RuntimeException();
}
return $article;
}
public function add(Article $article): bool
{
$response = $this->client->request(
'put',
sprintf('/article/%s/', $article->id()),
['body' => $article->text]
);
return $response->getStatusCode() == 201;
}
}
Условный пример использования в зависимостях сервиса доменного слоя
class ArticleService
{
private $rep;
public function __construct(ArticleRepository $rep)
{
$this->rep = $rep;
}
public function createWithText(
ArticleId $id,
ArticleText $text,
ArticleEditor $editor
): bool {
return $this->rep->add(new Article($id, $text, $editor);
}
}
Чувствуете разницу? Нет лишней прослойки. Вот это и есть information hiding, а не то что вы предлагаете.
И это решение, в отличии от вашего, ни сколько не нарушает использование устоявшегося и нормального способа получения репозитория из EntityManager. Поди объясни новичку, что то, что он привык делать годами у вас делается через… иначе.
Да. Использовать EntityManager для получения репозитория не всегда хорошо, а иногда и плохо, но это лучше чем возможность получить через него репозиторий который вскрывает все кишки наружу, а не репозиторий реализующий интерфейс доменного слоя.
Давайте не будем уходить от темы статьи. Мы здесь обсуждаем Doctrine Specification.
Вернёмся к первоисточнику.
Let your repositories extend Happyr\DoctrineSpecification\EntitySpecificationRepository instead of Doctrine\ORM\EntityRepository.
Это значит что вы должны создать класс \App\Infrastructure\Doctrine\DoctrineSpecificationArticleRepository для работы со спецификациями.
Вот об этих двух репозиториях на инфраструктурном слое я говорю.
Особенно это актуально если вы хотите в репозиторий добавить какие-то методы которых вам не хватает в основном репозитории. Для меня это countOf().
Да. Мы можем изменить базовый репозиторий для Doctrine. И даже можем создать свой базовый репозиторий наследующийся от EntitySpecificationRepository и добавить в него нужные нам методы. А что если нам нужно добавить методы не в общий репозиторий и не в репозиторий DoctrineArticleRepository реализующий доменный интерфейс, а нужно создать именно DoctrineSpecificationArticleRepository? Мы получим два репозитория на инфраструктурном слое.
Ну так, Fesor совершенно четко написал \App\Infrastructure\Doctrine\DoctrineArticleRepository. То есть это репозиторий на инфраструктурном уровне.
Про ArticleRepository я ничего не говорил. Понятное дело что он на доменном слое.
Мы вроде говорим о Doctrine Specification. В Doctrine Specification ты вынужден использовать реализацию репозитория с публичными методами на подобии match().
Согласен, лучше если интерфейс репозитория максимально чист и полностью принадлежит тебе, но в контексте Doctrine Specification для того чтоб этого добиться этого, придется переопределять уровень доступа для всех методов в каждом репозитории
class ArticleRepositoryDoctrine implements ArticleRepository
{
use EntitySpecificationRepositoryTrait {
match as private;
matchSingleResult as private;
matchOneOrNullResult as private;
getQuery as private;
setAlias as private;
getAlias as private;
}
// ...
}
и еще придется реализовывать в каждом репозитории аналог EntityRepository::createQueryBuilder(), только приватный.
Натыкался на реализацию спецификаций на .NET через Expression.
Я не знаком с .NET, но на сколько я могу судить, такие спецификации могут как проверять конкретный инстанс, так и могут быть трансформированы в SQL.
Интересно былоб увидеть что-то подобное в PHP.
Всё просто.
Если вашей сущности не хватает данных для проверки бизнес ограничения вы можете:
А) передать ей эти данные в аргументе функции
Б) вынести проверку бизнес ограничения в сервис доменного слоя
У Вернона есть упоминание о сервисах доменного слоя. Рекомендую почитать.
Я видел уже эту схему, но она мне показалась странной. Я ее не понял и прошел мимо.
Теперь я понял эту архитектуру.
Спасибо вам за это.
Правда мне всё равно она кажется немного странной.
Мне больше по душе классическая архитектуру DDD приложений + CQRS.
Всё здорово, но этого ещё нет в LTS. Со следующего года можно внедрять.
Я уже исправился. Я уже не считаю его "неправильным". Он просто мне не нравится.
И зачем ты дурачком прикидываешся? Прекраснож понимаешь откуда.
Естественно
ids
мы получаем из Compiler Passes.Если тебе нужны зависимости в репозитории, то ты в любом случае должен объявить их как сервисы. А чтоб можно было их получить из
EntityManager
, мы просто добавляем им метку.А зачем много фабрик? Одной достаточно. Кода то всего на 10 строчек.
Эээ… Вы читать не умеете? Я уже тысячу раз сказал, что я не хочу давать пользователю интерфейс
ObjectRepository
. На каком ещё языке мне это сказать чтоб вы меня поняли? Может на белорусском?А что тут сложного?
Берём и реализовывает свой
RepositoryFactory
для доктрины. Тегетируем наши сервисы репозитории и возвращаем в фабрике.Вот простейший пример реализации фабрики репозиториев.
Хотя мы можем добавить интерфейс
ObjectRepository
кDoctrineArticleRepository
и не добавлять его кArticleRepository
и таки образом не будем нарушать контракт, но смысла в этом особого нет, так как создаст ненужные, скрытые, пустые методы в репозитории.Ну это ваши личные трудности. Меня тоже много чего не устраивает и что с того?
В смысле? Использовать constructor injection религия не позволяет?
Разницы для клиента между двумя реализациями нет. Они обе дают конкретный интерфейс
ArticleRepository
.А почему в обход? Если
EntityManager
вы инжектите в репозиторий, то и в любой другой сервис его можно спокойно инжектить.Как раз наоборот. Мой вариант позволяет полностью заблокировать возможность получения
EntityRepository
. Полная изоляция.Хотя я должен признаться. Я забыл что метод
EntityManagerInterface::getRepository()
должен возвращатьObjectRepository
. Он может возвращать и другие объекты, но это будет нарушением контракта.Так что мой вариант нарушает контракт и ваше решение правильней, хотя я считаю его неприемлемым, от слова совсем.
А что тут представлять?
Если компания хочет выходить на мировой рынок, то ей нужно переводить на другие языки:
Стандартом для Российских компаний является наличие документов на русском и английском языках. Следующим как правило идут немецкий, французский и итальянский.
Если компания хочет развивать свою деятельность на востоке, то к этому списку очень быстро добавляются такие специфические языки как японский, китайский, корейский и арабский.
Всё делается для пользователей.
Вам ведь легче читать инструкцию на русском языке чем на английском, по например, эксплуатации стиральной машины произведенной в Японии, японский компанией.
Вот с изоляцией то у вас и проблемы.
Вместо того чтобы сделать один репозиторий с единой точкой доступа. Вы создали второй репозиторий и создали вторую точку доступа к данным и старательно пытаетесь оправдаться инверсией зависимостей и интерфейсом доменного слоя.
А вы не думали что доктрина может возвращать вам репозиторий реализацующий интерфейс доменного слоя?
Вы можете все так же использовать зависимости в репозитории. Все также можете внедрять репозиторий как зависимость, но ещё вы можете получать его из EntityManager-а. И точка доступа к данным у вас в этом случае одна (если не брать в расчет прямые запросы к
EntityManager
иConnection
).Условный пример использования в зависимостях сервиса доменного слоя
Чувствуете разницу? Нет лишней прослойки. Вот это и есть information hiding, а не то что вы предлагаете.
И это решение, в отличии от вашего, ни сколько не нарушает использование устоявшегося и нормального способа получения репозитория из EntityManager.
Поди объясни новичку, что то, что он привык делать годами у вас делается через… иначе.
Да. Использовать EntityManager для получения репозитория не всегда хорошо, а иногда и плохо, но это лучше чем возможность получить через него репозиторий который вскрывает все кишки наружу, а не репозиторий реализующий интерфейс доменного слоя.
Сами сказали:
Читайте мой комментарий выше.
Давайте не будем уходить от темы статьи. Мы здесь обсуждаем Doctrine Specification.
Вернёмся к первоисточнику.
Это значит что вы должны создать класс
\App\Infrastructure\Doctrine\DoctrineSpecificationArticleRepository
для работы со спецификациями.Вот об этих двух репозиториях на инфраструктурном слое я говорю.
Особенно это актуально если вы хотите в репозиторий добавить какие-то методы которых вам не хватает в основном репозитории. Для меня это countOf().
Да. Мы можем изменить базовый репозиторий для Doctrine. И даже можем создать свой базовый репозиторий наследующийся от
EntitySpecificationRepository
и добавить в него нужные нам методы. А что если нам нужно добавить методы не в общий репозиторий и не в репозиторийDoctrineArticleRepository
реализующий доменный интерфейс, а нужно создать именноDoctrineSpecificationArticleRepository
? Мы получим два репозитория на инфраструктурном слое.Ну так, Fesor совершенно четко написал
\App\Infrastructure\Doctrine\DoctrineArticleRepository
. То есть это репозиторий на инфраструктурном уровне.Про
ArticleRepository
я ничего не говорил. Понятное дело что он на доменном слое.Интересует японский
Один репозиторий завернули в другой репозиторий.
Два репозитория на инфраструктурном уровне для одной сущности.
Да вы батенька, мастер извращений.
Мы вроде говорим о Doctrine Specification. В Doctrine Specification ты вынужден использовать реализацию репозитория с публичными методами на подобии
match()
.Согласен, лучше если интерфейс репозитория максимально чист и полностью принадлежит тебе, но в контексте Doctrine Specification для того чтоб этого добиться этого, придется переопределять уровень доступа для всех методов в каждом репозитории
и еще придется реализовывать в каждом репозитории аналог
EntityRepository::createQueryBuilder()
, только приватный.Зачем
findBySpecification
если есть match?Это RulerZ. Я знаю о нем и о нем уже упоминали ниже. Мне он как-то не очень нравится.
Надо будет при случае по глубже изучить.
Натыкался на реализацию спецификаций на .NET через Expression.
Я не знаком с .NET, но на сколько я могу судить, такие спецификации могут как проверять конкретный инстанс, так и могут быть трансформированы в SQL.
Интересно былоб увидеть что-то подобное в PHP.