Правильное ли использование принципов DDD, и чем заменить Repository?
В XML-файле хранятся пользователи, необходимо выводить в шаблон список всех пользователей, с возможностью просмотра детальной информации.
Для этого первым делом я создал сущность пользователя UserEntity, которое содержит в себе только private-свойства + геттеры, в конструктор передаю все данные пользователя.
Далее, создал коллекцию UserСollection, для работы с сущностями UserEntity.
Дальше, как я понимаю, нужно как-то получить список пользователей из источника (в моем случае это XML-файл), и добавить их в коллекцию:
Если бы данные у меня были в БД, то для этого насколько понимаю, можно было бы использовать Repository, который возвращался коллекцию пользователей.
Можно ли все-таки использовать Repository или что-то другое надо ?
Уточню: перед инициализацией UserEntity, мне необходимо немного форматировать данные из хранилища (xml) - где мне это нужно сделать ? Каком этапе ?
И последнее:
с какой сущностью должен работать контроллер ?
Обычно, от модели получаем данные и в шаблон отдаем.
Но что у меня будет являться моделью ?
Вообще-то как раз Repository - это тот слой абстракции, который нужен, чтобы на уровне бизнес-логики (слой сервисов) абстрагироваться от способа хранения данных. То есть, очевидно, нужно использовать Repository.
Контроллер должен работать только с сервисным слоем. Моделью (Entity) является тот класс, на который вы маппите пользователей, прочитанных из XML-файла. То есть в репозитории надо сделать метод, читающий XML, формирующий коллекцию из Entity (юзеры) и возвращающий ее. На уровне выше (в сервисном слое) надо сделать метод, который как раз вызывает этот метод репозитория. Методы сервиса в свою очередь надо дергать из контроллера, либо из класса консольной команды, если код кроном вызывается. То есть из контроллера дергать репозиторий не принято. А сервисный слой нужен, чтобы в нем держать всю бизнес-логику.
Вот кстати не согласен. Имплементации репозиториев как раз таки относятся к сервисному слою, и как любой другой сервис может быть использован в контроллера (если речь идет только об извлечении данных для дальнейшего представления).
galliard, тогда бизнес-логика намертво завязывается на реляционные базы данных, а если не используется ORM, то вообще на конкретную СУБД. Слой репозиториев позволяет сделать при желании разные классы, реализующие единый интерфейс, но с разной реализацией методов работы с хранилищем.
Дмитрий Свиридов, ну вот смотри, ты инжектишь репозитерий в конструктор, вызываешь там метод ->getById($id) и отправляешь результат на рендер. Все. Ты ни привизался ни к ОРМ, ни к базе.
galliard, на мой взгляд, контроллеры должны работать только с сервисным слоем. Если где-то из контроллеров дергать сервисы, а где-то напрямую DAO, то нарушается архитектура проекта.
К тому же на практике для отображения одной сущности можно не обойтись одним обращением к БД, либо же надо как-то обрабатывать данные, обращаясь к методам других сервисов.
Вообще я в конструкторы контроллеров всегда передаю DI-контейнер целиком. В таком случае чтобы из контроллера обратиться к репозиторию, придется его держать в контейнере, как сервис, что как бы не ок.
Дмитрий Свиридов, репозитории не являются DAO и не относятся к DBAL. Это все еще слой сервисов. Дергая репозиторий ты дергаешь сервис, потому что репозиторий - это и есть сервис. И держать его в контейнере вместе с другими сервисами абсолютно нормально (а вот тащить в контроллер весь контейнер - нет).
Если конечно требуется какая-то дополнительная работа с данными, не связанная с их презентацией, то это уже бизнес-логика и ее конечно стоит вынести в отдельный класс. Но если ее нет, то зачем создавать бессмысленный прокси-класс?
Что касается "И держать его в контейнере вместе с другими сервисами абсолютно нормально" - не соглашусь, но это, наверное, вкусовщина. Вы про доктрину и симфони толкуете? Ну, там - может быть. По крайней мере, по дефолту автовайринг в Симфони репозитории (вроде) в контейнер пихает, что позволяет сделать вывод, что они рассматриваются как сервисы.
Но если ее нет, то зачем создавать бессмысленный прокси-класс?
Но можно entity manager передать в конструктор контроллера и дергать напрямую прямо из экшена. Зачем бессмысленная прослойка в виде репозитория? Но, если серьезно, то я просто не сторонник смешивания слоев абстракции, а репозитории для меня не сервисы. Кроме того, я могу по пальцам пересчитать случаи, когда метод в сервисе просто прокси и все. Даже просто getById. В том или ином виде рано или поздно там появляется бизнес-логика так или иначе.
а вот тащить в контроллер весь контейнер - нет
Я раньше тоже так считал, но теперь не соглашусь, потому что контроллер вполне может требовать обращение к нескольким сервисам. Если их пробрасывать по отдельности через конструктор, то он вполне может принимать аж за десяток параметров. Если их два-три, пять, то это ок, но иногда их значительно больше. Выход - делить контроллер? Но ведь не всегда это логично.
Дмитрий Свиридов ну в симфони изначально много чего неправильно сделано, но ребята это понимают и постепенно переделывают. Я и до появления автовайринга все репозитории в контейнер запихивал.
Вообще я не вижу ни одной причины не считать репозиторий сервисом. Аргументы типа "а может быть когда нибудь там придется добавить логики" я считаю не состоятельными, ну ок, если такая задача появится - добавим обертку, а до того момента не стоит плодить сущности без необходимости.
Я раньше тоже так считал, но теперь не соглашусь, потому что контроллер вполне может требовать обращение к нескольким сервисам. Если их пробрасывать по отдельности через конструктор, то он вполне может принимать аж за десяток параметров. Если их два-три, пять, то это ок, но иногда их значительно больше. Выход - делить контроллер? Но ведь не всегда это логично.
Ну то есть entity manager в контроллер пихать нельзя, а di контейнер можно? Странная логика. По моему, если в контроллер нужно инжектить по 10+ сервисов, то это повод пересмотреть архитектуру. Возможно, на один контроллер возложено слишком много разных обязанностей и его следует разделить на несколько. Или наоборот пересмотреть архитектуру сервисов и что-то объеденить.
galliard, если говорить не конкретно о Doctrine, то слой репозиториев подразумевает то, что в них хранятся методы, делающие запросы к СУБД или к другим хранилищам. Репозиториев, реализующих общий интерфейс, может быть несколько. Например, один для MySQL, второй - для PostgreSQL, третий - вообще для какого-то NoSQL-хранилища. Понятное дело, что в один момент времени используют лишь одну реализацию репозитория (которую, как я понял, ты предлагаешь передавать в конструктор контроллера) в зависимости от того, где и как хранятся те или иные сущности.
Вот и скажи мне, если ты везде рассуешь обращение к КОНКРЕТНОЙ реализации репозитория, то удобно ли тебе будет переезжать, скажем, с MySQL на PostgreSQL? В одном случае поменяешь только лишь в сервисе обращение к другому репозиторию - и все. В другом - будешь лазать по коду, искать, где в контроллер передаются какие репозитории - и менять там на другую реализацию репозитория.
Аргументы типа "а может быть когда нибудь там придется добавить логики" я считаю не состоятельными
Ок, но я считаю иначе. Я не говорю про сервисы, все методы которых сплошь прокси, но если из 10 методов сервиса 2 будет проксями, то не вижу в этом особой проблемы.
то это повод пересмотреть архитектуру
В каждом контроллере нужен, как минимум, кеш, фабрика валидаторов, фабрика респонсов (чтобы ответы API были в едином формате) - это уже 3 параметра (сервиса) в конструктор. Ну, допустим, еще пара репозиториев, как ты предлагаешь, и пара сервисов. Итого уже 7 параметров на контроллер, и это я по-минимуму взял. Удобно? На мой взгляд, нет.
Дмитрий Свиридов советую посмотреть на такую штуку, как интерфейсы, они вполне решают обозначенную тобой проблему. А вот твой вариант с оберткой ее как раз НЕ решает, так как при смене реализации тебе опять же придется побегать по всем сервисам.
Ок, но я считаю иначе. Я не говорю про сервисы, все методы которых сплошь прокси, но если из 10 методов сервиса 2 будет проксями, то не вижу в этом особой проблемы.
Но если в контроллере только эти 2 метода и нужны, то зачем тащить туда весь сервис остальными 8ю ненужными?
В каждом контроллере нужен, как минимум, кеш, фабрика валидаторов, фабрика респонсов (чтобы ответы API были в едином формате) - это уже 3 параметра (сервиса) в конструктор. Ну, допустим, еще пара репозиториев, как ты предлагаешь, и пара сервисов. Итого уже 7 параметров на контроллер, и это я по-минимуму взял. Удобно? На мой взгляд, нет.
Никогда не юзал в контроллере такие вещи, как "кеш" и "фабрика валидаторов", но допустим. Да, я нахожу это намного более удобным, потому что:
1) Автокомплит.
2) Сразу видно из каких компонентов состоит контроллер, что важно при рефакторинге, гораздо легче найти где какие компоненты используются.
советую посмотреть на такую штуку, как интерфейсы, они вполне решают обозначенную тобой проблему
Вообще-то я про интерфейс для репозитория и написал в предыдущем сообщении. "Репозиториев, реализующих общий интерфейс, может быть несколько. Например, один для MySQL, второй - для PostgreSQL, третий - вообще для какого-то NoSQL-хранилища". Только вот интерфейс как раз-таки не решает то, что я описал.
Но если в контроллере только эти 2 метода и нужны, то зачем тащить туда весь сервис остальными 8ю ненужными?
Потому что репозиторий, как я уже сказал, это не сервис. Если ты считаешь иначе - твое право, переубеждать не буду. :)
1) Автокомплит.
Автокомплит легко добавляется с помощью phpdoc, который используются в php повсеместно
2) Сразу видно из каких компонентов состоит контроллер, что важно при рефакторинге, гораздо легче найти где какие компоненты используются.
Насчет этого согласен, но при наличии кучи аргументов в контроллере этот плюс становится не таким весомым.
Никогда не юзал в контроллере такие вещи, как "кеш" и "фабрика валидаторов", но допустим.
А где ты делаешь тогда валидацию и кеширование? Не в сервисном слое, надеюсь?
Вообще-то я про интерфейс для репозитория и написал в предыдущем сообщении. "Репозиториев, реализующих общий интерфейс, может быть несколько. Например, один для MySQL, второй - для PostgreSQL, третий - вообще для какого-то NoSQL-хранилища". Только вот интерфейс как раз-таки не решает то, что я описал.
Почему не решает? У тебя в зависимостях указан интерфейс, конкретная реализация прописана в конфигах, меняешь ее тоже в конфигах.
Потому что репозиторий, как я уже сказал, это не сервис. Если ты считаешь иначе - твое право, переубеждать не буду. :)
Ты не прав. Репозиторий - это сервис. Не стоит путать абсолютную истину и свое личное мнение, с которым никто не согласен.
Автокомплит легко добавляется с помощью phpdoc, который используются в php повсеместно
То есть вместо дефолтного механизма ты предлагаешь юзать костыль, который по сути даже стандартом не является, п росто кастомное решение пхпшторма. Ну такое себе.
Насчет этого согласен, но при наличии кучи аргументов в контроллере этот плюс становится не таким весомым.
Ты знаешь, даже если там будет 100 аргументов - я предпочту именно такой вариант.
А где ты делаешь тогда валидацию и кеширование? Не в сервисном слое, надеюсь?
Кэширование? На стороне пхп? Зачем?
Валидатор обычно внедряется либо через конструктор, либо через аргументы экша. Тащить в контроллер всю фабрику это бээээ, все равно что весь di-контейнер затащить.