Согласен. Пожалуй да. Работа с DDD это бесконечный процесс.
Мой предыдущий комментарий был как раз о том что былоб интересно почитать про итерации переработки.
Некоторые попытки описания итераций были у Вернона, но они не полные.
Многие описывают итерации переработки, но редко больше двух.
Интересно былоб почитать именно про эволюцию проекта. Например разобрать штук 5 итераций, чтоб было понятней как это вообще происходит и почему выбираются те или иные решения, а почему какие-то решения отклоняются. Почитать про архитектурные ошибки и способы их решения. И я не говорю о конечных итерациях переработки кода. Многие решения не уходят дальше головы или бумаги. Их просто отбрасывают за ненадобностью или проскакивают, но для обучения и понимания они важны ИМХО.
Советую переосмыслить и хорошенечко подумать над всей схемой. И не пару часов, а лучше несколько дней/недель.
Тогда вы возможно сможете лучше понять вашу предметную область и написать статью — работу над ошибками. В ней можно больше сконцентрировать на описании и формулировании предметной области, а не на конкретной реализации. В статью можно будет добавить схемы взаимодействия, схемы транзакций и все прочее. Вот это точно будет взрывня статью.
DDD это не про реализацию, а про проектирование. Качественно продуманная и сформулированная предметная область легко реализуется на любом языке программирования. А вот проработать эту самую предметную область и есть основная проблема.
И не подумайте что я вас критикую. Ваше стремление похвально.
PS: Рекомендую к прочтению книгу Вон Вернона — Implementing Domain-Driven Design.
Уберите раздел Предыстория из статьи.
Вы в начале конфигурируете проект под Symfony, Docker, VueJs и прочее, но в статье они ни как не фигурируют больше.
Ваща статья исключительно про DDD. VueJs только пару раз упомянулся, но на практике не использовался.
Возможно вы будете их использовать в следующих статьях.
Вот тогда и напишете про Docker и VueJs.
Сходу проблемы с DDD.
у каждого желания есть стоимость, начальный фонд и накопленные средства — фонд
Вы Фонд потеряли в своем проекте. Все финансовые транзакции делаются через Фонд накопления средств, а не через сущность Желание.
Я бы их вообще разделил на 2 отдельных контекста (Bounded Context).
Как уже сказали, AbstractId::next() лучше вынести в сервис генерации id.
interface WishIdGenerator
{
public function next(): WishId;
}
Я бы не привязывался так явно к UUID.
Вдруг захотите сменить генератор id.
Я сейчас готовлю статью по использованию более оптимального id чем UUID.
Статические фабричные методы AbstractId::fromString() и Expense::fromCurrencyAndScalars вам вообще не нужны.
Вы все должны деалть через конструктор и передавать в него явные значения, а не генерировать VO внутри.
Использование getter-ов и setter-ов это известный DDD антипаттерн.
Лучше переименовать методы:
AbstractId::getId() в AbstractId::id()
WishName::getValue() в WishName::name()
Expense::getCurrency() в Expense::сurrency()
Wish::getFund() в Wish::fund()
и т.д.
Кто-то может со мной не согласится, но по мне так префикс get тут лишний.
publish/unpublish
например, вы можете отложить его до лучших времен
Вы явно описали действие: отложить
Тоесть действия у вас будут:
отложить до лучших времен — postpone
возобновить накопление — resume
В некоторых местах вы говорите:
Публиковать и убирать в черновики
Что немного противоречит. Черновики это отдельная история и тоже делается не через unpublish.
Вангуем дату исполнения.
Логика расчитана на то, что вы вклад делаете каждый день, но этого нет в условии.
В нашей стране распространеное двух этапная выплата зарплаты — аванс и зарплата.
Соответсвенно, делать вклады в таком случае чаще 2 раз в месяц затруднительно.
Вообще это все сильно зависит от возможостей делать вклады.
Я бы закладывал ежемесечные вклады, но это сильно зависит от бизнеса.
С вычетами и удалениями депозитов у вас тоже не все впорядке.
Ну, или, например, если вы откладывали на желание достаточно большие суммы, а потом просто «промахнулись», не уследив за количеством уже имеющихся средств.
Вклад это фиксированная величина. Вы не можете удалить вклад после внесения его в фонд, так как он растворяется в нем.
В фонде у вас хранится общая сумма и история вкладов (если она вам нужна).
Внесение вклада это внесение средств. Вы делаете вклад в копилку и вносите в нее деньги и теперь денег в копилке стало больше.
Вклада в ней нет. В ней только деньги. По сути вклад обертка над Money.
Если вы по ошибке сделаи вклад не на то желание, то вы можете сделать транзакцию по переводу средств из одного фонда в другой на размер последнего вклада или любую другую величину.
Если вы внесли в фонд больше денег чем хотели, то вы можете изьять сумму из конкретного фонда.
Вы не обязаны извлекать из фонда сумму равную какому-то вкладу.
Например, вы внесли 50 рублей, а потом поняли что 24 рубля 74 копейки из них были лишними (я утрирую но мысль я думаю вы поняли) и хотите извлеч из вклада конкретную сумму денег.
Да и вообще, вам по жизни может потребоваться извлечь произвольную суммму из вклада.
Вы уверены что накопленные средства = исполнению желания?
По мере накопления достаточного количества средств желание становится исполненным.
Я бы не ставил между ними равно.
Исполнение желания это одно, а накопление достаточной суммы для исполнения желания это савсем другое.
И не ограничивайте явно потолок накопления суммы. Вспоминаем Kickstarter.
Вклад не должен знать о Желании.
Вы сделали рекурсивную ссылку, а это плохо.
Правильней так:
Есть Желание и Фонд накопления средств на конкретное Желание.
Если Фонд выносить в отдельный контекст, то лучше делать связь от Фонда к Желанию и тогда:
У Желания есть цена.
Желание не знает о Фонде.
Фонд знает о Желании и как следствие о его цену.
Фонд накапливает сумму на исполнение Желания.
Вклад не знает ничего ни о Фонде, ни о Желании. Это просто деньги.
Мы сами определяем в какой Фонд внести Вклад.
Вклад это VO.
После внесения Вклада в Фонд он превращается (если это нам нужно) в Транзакцию в Истории транзакций фонда.
Транзакции нельзя удалять или изменять. Это уже история.
Там еще целая гора мелких недочетов с реализацией финансовой части. Я не буду тут вдаваться в подробности.
То есть, вы признаете что сходство ролей очевидно? Я не предлагаю что-то менять. Хотите, называйте Repository, хотите Catalog. Это ваше право.
мой репозиторий выглядит так
Мне сложно представить зачем вам может быть нужен одновременно и ApiClient и EntityManager в одном таком репозитории.
Разве что у вас разделены read и write хранилища и вы в одном репозитории читаете из одного хранилища, а пишете в другое. Хотя это явно неправильно.
Всё что мне приходит в голову делается через доменные события.
Расскажите, зачем вам ApiClient и EntityManager в одном репозитории? Мне просто любопытно.
У вас 2 класса называются одинаково — repository (назови вы второй manager вопросов бы не было);
Они оба находятся на одном слое — инфраструктурном;
У них одинаковая роль — управление сущностью в хранилище (интерфейсы разные, но роль одна).
Одного этого достаточно чтоб задуматься, что что-то не так.
Потому я и предложил не разделять функции на 2 класса.
Также вы можете переименовать ваш репозиторий в manager, gateway или что-то более близкое к его роли или вы можете перенести этот репозиторий в другой слой.
И да. Я проверил. С Symfony 3.3 нам не нужно тегетируем сервис репозитория, нам не нужен компилятор для меток, нам не нужна своя фабрика репозиториев. Достаточно просто создать сервис и прописать его в аннотациях к сущности.
Свой репозиторий в Symfony 3.3
Прописываем репозиторий в аннотациях сущность
/**
* @ORM\Table(name="article")
* @ORM\Entity(repositoryClass=DoctrineArticleRepository")
*/
final class Article
{
// ...
}
Реализация репозитория
class DoctrineArticleRepository
extends EntityRepositoryDummy
implements ArticleRepository
{
private $em;
private $client;
public function __construct(
EntityManagerInterface $em,
ApiClient $client
) {
$this->em = $em;
$this->client = $client;
}
// ...
}
Для соблюдения контракта нам придется сделать заглушку
abstract class EntityRepositoryDummy implements ObjectRepository
{
final public function find($id)
{
throw new \RuntimeException('This method is not implemented.');
}
// ...
}
И мы можем получить наш сервис репозиторий двумя способами в любом месте.
class SomeService
{
public function __construct(
EntityManagerInterface $em,
ArticleRepository $repository
) {
// true
$em->getRepository(Article::class) === $repository;
}
}
Я не предлагаю сейчас возвращаться к обсуждению целесообразности такого решения, но возможность такая есть и без единой строчки в конфиге не считая импорта.
То есть, для того чтоб перейти от вашего решения к моему, достаточно добавить extends и прописать репозиторий в аннотациях сущности.
И все. Больше ничего делать не надо.
И это гораздо проще чем настраивать deptrac.
И еще, ваши фразы
позволяет юзать не только доктрину но и скажем дергать внешние API, просто инджектишь не EM а Guzzle\Client например.
и
В этом случае у вас по сути "репозиторий" на каждую выборку (что мы выше и обсуждали) что в целом меня устраивает и я даже так иногда делаю.
наводят меня на мысли, что ваши репозитории делают больше чем должны.
PS: Если вам не нравятся лишние методы в ObjectRepository, то вы можете сделать PR или fork.
На мой взгляд Clean Architecture не очень подходит для больших и долгоиграющих проектов со сложной бизнес логики (читай DDD).
Классическая архитектура DDD приложений имеет слои:
Presentation layer
Application layer
Domain layer
Infrastructure layer
Persistence layer
И вот вся это катавасия из Clean Architecture:
Controller
Presenter
Reques modal
Rrsponse model
Interactor
Очень похожа на CQRS и находится на одном уровне — Application. Мы просто разделяем потоки и то что могло выполнятся в контроллере выполняется в Interactor или Command Heandlers в CQRS подходе.
А все самое интересное из DDD оказалось спрятано за неоднозначным понятием Entities.
Application layer оказался эквивалентен Infrastructure layer, а Presentation layer эквивалентен Persistence layer.
В общем, очень странная архитектура на мой взгляд. Хотя многие могут не согласиться со мной.
Вообще-то, по Эванса, сущность это слой бизнес лигики (domain), а контроллеры это слой приложения (application).
Репозитории это тоже слой бизнес логики, а реализация репозиторий под конкретное хранилище это вообще слой инфраструктуры которого нет в Clean Architecture
Именно. Сущности можно передавать сервис доменного слоя, а бизнес логику всё равно лучше выполнять в сущности.
Можно сделать интерфейс сервиса доменного слоя который должен возвращать данные для сущности которая будет выполнять бизнес ограничение, а реализацию сервиса который будет стучаться в БД или ещё куда можно разместить на инфраструктурном слое.
Всё просто.
Если вашей сущности не хватает данных для проверки бизнес ограничения вы можете:
А) передать ей эти данные в аргументе функции
Б) вынести проверку бизнес ограничения в сервис доменного слоя
У Вернона есть упоминание о сервисах доменного слоя. Рекомендую почитать.
Я видел уже эту схему, но она мне показалась странной. Я ее не понял и прошел мимо.
Теперь я понял эту архитектуру.
Спасибо вам за это.
Правда мне всё равно она кажется немного странной.
Мне больше по душе классическая архитектуру 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 для получения репозитория не всегда хорошо, а иногда и плохо, но это лучше чем возможность получить через него репозиторий который вскрывает все кишки наружу, а не репозиторий реализующий интерфейс доменного слоя.
Согласен. Пожалуй да. Работа с DDD это бесконечный процесс.
Мой предыдущий комментарий был как раз о том что былоб интересно почитать про итерации переработки.
Некоторые попытки описания итераций были у Вернона, но они не полные.
Многие описывают итерации переработки, но редко больше двух.
Интересно былоб почитать именно про эволюцию проекта. Например разобрать штук 5 итераций, чтоб было понятней как это вообще происходит и почему выбираются те или иные решения, а почему какие-то решения отклоняются. Почитать про архитектурные ошибки и способы их решения. И я не говорю о конечных итерациях переработки кода. Многие решения не уходят дальше головы или бумаги. Их просто отбрасывают за ненадобностью или проскакивают, но для обучения и понимания они важны ИМХО.
Советую переосмыслить и хорошенечко подумать над всей схемой. И не пару часов, а лучше несколько дней/недель.
Тогда вы возможно сможете лучше понять вашу предметную область и написать статью — работу над ошибками. В ней можно больше сконцентрировать на описании и формулировании предметной области, а не на конкретной реализации. В статью можно будет добавить схемы взаимодействия, схемы транзакций и все прочее. Вот это точно будет взрывня статью.
DDD это не про реализацию, а про проектирование. Качественно продуманная и сформулированная предметная область легко реализуется на любом языке программирования. А вот проработать эту самую предметную область и есть основная проблема.
И не подумайте что я вас критикую. Ваше стремление похвально.
PS: Рекомендую к прочтению книгу Вон Вернона — Implementing Domain-Driven Design.
У вас тут целая куча проблем.
Уберите раздел Предыстория из статьи.
Вы в начале конфигурируете проект под Symfony, Docker, VueJs и прочее, но в статье они ни как не фигурируют больше.
Ваща статья исключительно про DDD. VueJs только пару раз упомянулся, но на практике не использовался.
Возможно вы будете их использовать в следующих статьях.
Вот тогда и напишете про Docker и VueJs.
Сходу проблемы с DDD.
Вы Фонд потеряли в своем проекте. Все финансовые транзакции делаются через Фонд накопления средств, а не через сущность Желание.
Я бы их вообще разделил на 2 отдельных контекста (Bounded Context).
Как уже сказали,
AbstractId::next()лучше вынести в сервис генерации id.Я бы не привязывался так явно к UUID.
Вдруг захотите сменить генератор id.
Я сейчас готовлю статью по использованию более оптимального id чем UUID.
Статические фабричные методы
AbstractId::fromString()иExpense::fromCurrencyAndScalarsвам вообще не нужны.Вы все должны деалть через конструктор и передавать в него явные значения, а не генерировать VO внутри.
Использование getter-ов и setter-ов это известный DDD антипаттерн.
Лучше переименовать методы:
AbstractId::getId()вAbstractId::id()WishName::getValue()вWishName::name()Expense::getCurrency()вExpense::сurrency()Wish::getFund()вWish::fund()и т.д.
Кто-то может со мной не согласится, но по мне так префикс get тут лишний.
publish/unpublish
Вы явно описали действие: отложить
Тоесть действия у вас будут:
В некоторых местах вы говорите:
Что немного противоречит. Черновики это отдельная история и тоже делается не через
unpublish.Вангуем дату исполнения.
Логика расчитана на то, что вы вклад делаете каждый день, но этого нет в условии.
В нашей стране распространеное двух этапная выплата зарплаты — аванс и зарплата.
Соответсвенно, делать вклады в таком случае чаще 2 раз в месяц затруднительно.
Вообще это все сильно зависит от возможостей делать вклады.
Я бы закладывал ежемесечные вклады, но это сильно зависит от бизнеса.
С вычетами и удалениями депозитов у вас тоже не все впорядке.
Вклад это фиксированная величина. Вы не можете удалить вклад после внесения его в фонд, так как он растворяется в нем.
В фонде у вас хранится общая сумма и история вкладов (если она вам нужна).
Внесение вклада это внесение средств. Вы делаете вклад в копилку и вносите в нее деньги и теперь денег в копилке стало больше.
Вклада в ней нет. В ней только деньги. По сути вклад обертка над
Money.Если вы по ошибке сделаи вклад не на то желание, то вы можете сделать транзакцию по переводу средств из одного фонда в другой на размер последнего вклада или любую другую величину.
Если вы внесли в фонд больше денег чем хотели, то вы можете изьять сумму из конкретного фонда.
Вы не обязаны извлекать из фонда сумму равную какому-то вкладу.
Например, вы внесли 50 рублей, а потом поняли что 24 рубля 74 копейки из них были лишними (я утрирую но мысль я думаю вы поняли) и хотите извлеч из вклада конкретную сумму денег.
Да и вообще, вам по жизни может потребоваться извлечь произвольную суммму из вклада.
Вы уверены что накопленные средства = исполнению желания?
Я бы не ставил между ними равно.
Исполнение желания это одно, а накопление достаточной суммы для исполнения желания это савсем другое.
И не ограничивайте явно потолок накопления суммы. Вспоминаем Kickstarter.
Вы сделали рекурсивную ссылку, а это плохо.
Правильней так:
Там еще целая гора мелких недочетов с реализацией финансовой части. Я не буду тут вдаваться в подробности.
Точно. Что-то я не так прочитал. Тогда все логично)
То есть, вы признаете что сходство ролей очевидно?
Я не предлагаю что-то менять. Хотите, называйте Repository, хотите Catalog. Это ваше право.
Мне сложно представить зачем вам может быть нужен одновременно и
ApiClientиEntityManagerв одном таком репозитории.Разве что у вас разделены read и write хранилища и вы в одном репозитории читаете из одного хранилища, а пишете в другое. Хотя это явно неправильно.
Всё что мне приходит в голову делается через доменные события.
Расскажите, зачем вам
ApiClientиEntityManagerв одном репозитории? Мне просто любопытно.Мне не нравится хотя бы вот это:
Одного этого достаточно чтоб задуматься, что что-то не так.
Потому я и предложил не разделять функции на 2 класса.
Также вы можете переименовать ваш репозиторий в manager, gateway или что-то более близкое к его роли или вы можете перенести этот репозиторий в другой слой.
И да. Я проверил. С Symfony 3.3 нам не нужно тегетируем сервис репозитория, нам не нужен компилятор для меток, нам не нужна своя фабрика репозиториев. Достаточно просто создать сервис и прописать его в аннотациях к сущности.
Прописываем репозиторий в аннотациях сущность
Реализация репозитория
Для соблюдения контракта нам придется сделать заглушку
И мы можем получить наш сервис репозиторий двумя способами в любом месте.
Я не предлагаю сейчас возвращаться к обсуждению целесообразности такого решения, но возможность такая есть и без единой строчки в конфиге не считая импорта.
То есть, для того чтоб перейти от вашего решения к моему, достаточно добавить
extendsи прописать репозиторий в аннотациях сущности.И все. Больше ничего делать не надо.
И это гораздо проще чем настраивать deptrac.
И еще, ваши фразы
и
наводят меня на мысли, что ваши репозитории делают больше чем должны.
PS: Если вам не нравятся лишние методы в
ObjectRepository, то вы можете сделать PR или fork.На мой взгляд Clean Architecture не очень подходит для больших и долгоиграющих проектов со сложной бизнес логики (читай DDD).
Классическая архитектура DDD приложений имеет слои:
И вот вся это катавасия из Clean Architecture:
Очень похожа на CQRS и находится на одном уровне — Application. Мы просто разделяем потоки и то что могло выполнятся в контроллере выполняется в Interactor или Command Heandlers в CQRS подходе.
А все самое интересное из DDD оказалось спрятано за неоднозначным понятием Entities.
Application layer оказался эквивалентен Infrastructure layer, а Presentation layer эквивалентен Persistence layer.
В общем, очень странная архитектура на мой взгляд. Хотя многие могут не согласиться со мной.
Вообще-то, по Эванса, сущность это слой бизнес лигики (domain), а контроллеры это слой приложения (application).
Репозитории это тоже слой бизнес логики, а реализация репозиторий под конкретное хранилище это вообще слой инфраструктуры которого нет в Clean Architecture
Именно. Сущности можно передавать сервис доменного слоя, а бизнес логику всё равно лучше выполнять в сущности.
Можно сделать интерфейс сервиса доменного слоя который должен возвращать данные для сущности которая будет выполнять бизнес ограничение, а реализацию сервиса который будет стучаться в БД или ещё куда можно разместить на инфраструктурном слое.
Всё просто.
Если вашей сущности не хватает данных для проверки бизнес ограничения вы можете:
А) передать ей эти данные в аргументе функции
Б) вынести проверку бизнес ограничения в сервис доменного слоя
У Вернона есть упоминание о сервисах доменного слоя. Рекомендую почитать.
Я видел уже эту схему, но она мне показалась странной. Я ее не понял и прошел мимо.
Теперь я понял эту архитектуру.
Спасибо вам за это.
Правда мне всё равно она кажется немного странной.
Мне больше по душе классическая архитектуру 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 для получения репозитория не всегда хорошо, а иногда и плохо, но это лучше чем возможность получить через него репозиторий который вскрывает все кишки наружу, а не репозиторий реализующий интерфейс доменного слоя.
Сами сказали: