То есть у вас будут отдельно сущности домена, которые будут маппиться на объекты ActiveRecord, в которых не будет никакой логики кроме save/delete/…?
Не совсем, но от части да. Библиотека сейчас не в открытом доступе, поэтому в одном комментарии не получится нормально все описать.
Общие концепции таковы:
Разбить модель предметной области на ускоспециализированные классы: сущность, репозиторий, маппер данных(по сути расширение ActiveRecord — не хочу вдаваться в реализацию) и ряд вспомогательных классов средствами которых все будет взаимодействовать между собой
Сущность не должна ничего знать о БД — ее область ответственности это бизнесс логика
Репозиторий знает как получать данные и сохранять их но абстрагирован от источника данных
ActiveRecord используется только как источник данных и остальные классы не завязаны на него намертво
Это описание довольно упрощенное, но без ссылки на гитхаб с кодом, чтобы все можно было посмотреть и увидеть на примерах врядли у меня получится вкратце обьяснить достаточно хорошо, а пока что, извините, в открытом доступе библиотеку предоставить не могу.
Как раз о проблемах своей неправильной реализации я и рассказывал.
Я не спорю, Вы рассказали о своих проблема и донесли до читателей опыт полученый методом проб и ошибок, что есть хорошо так как кому-то это может сохранить много нервов.
В статье Вы аргументировали многое примерами реализаций, ссылками на другие статьи и собственным опытом, но мне иенно не очень понравился пункт в выводах:
Не пытайтесь играть с Repository в frameworks с ActiveRecord. Повторюсь: практически всегда это будет избыточно, за исключением тех вариантов, когда Вы действительно знаете, что делаете и отдаете себе полный отчет о последствиях.
Просто некоторые люди могут воспринять такой совет слишком радикально и далее работать по принципу «Я на Хабре прочитал, что играть с Repository в frameworks с ActiveRecord не стоит и поэтому буду дулать все по старинке». Я это из личного опыта говорю, т.к. в начале своей карьеры по неопытности допускал такие ошибки слушая подобные радикальные советы от более опытных коллег.
И после прочтения статьи сложилось ощущение, что она подталкивает отказу от применения шаблона Репозиторий только если он не используется в выбранном фраемворке по умолчанию.
Повторюсь, это мое личное мнение, возможно я просто не совсем правильно Вас понял.
По поводу примера:…
Если это не всевдо-код, то если пользователь подтянет парочку реляций за собой, особенно вложенных, то мы на массивах далеко не уедем.
Это вполне себе рабочий код — типичный вариант получения данных.
Я хотел тем примером немного другой вопрос осветить, извините если немного сбил с толку. Давайте немного подробнее разъясню свою мысль.
Вы говорили:
Да, благодаря интерфейсу я действительно смог легко подменить реализацию, однако формат возвращаемых данных изменился. Ранее это был экземляр класс с ActiveRecord, однако теперь мой репозиторий мог возвращать массив или коллекцию.
Так вот, я хотел сказать, что от изменение хранилища или его реализации не должен меняться формат возвращаемых данных. И в примере я хотел показать, что от смены типа хранилища логика не должна обязательно страдать.
Наверно стоит чуть изменить пример, чтобы изменения были видны более явно
Изначальный вариант кода у нас будет:
/**
* наша бизнес логика. В функцию мы передаем обьект, который ответственный за поиск пользователей.
*/
function doStuffWithUser($userFinder) {
$user = $userFinder->one(123); // получаем обьект пользователя
// do some stuff
$user = $userFinder->asArray()->one(123); // получам массив аттрибутов
}
// потом, допустим в контроллере, мы сделаем следующее
function actionIndex () {
$userFinder = User::find();
doStuffWithUser($userFinder);
}
После изменения хранилища с базы данных на файловую систему нам должно было быть достаточно сделать изменение только в контроллере(для конкретно этого примера):
function actionIndex () {
$userFinder = UserFromFile::find();
doStuffWithUser($userFinder);
}
User и UserFromFile у нас в данном случае выступают двумя реализациями репозитория(только не вдавайтесь в то, что мы меняем в коде User на UserFromFile, я привел эти имена для большей понятности того, что поменялось, а на практике $userFinder должен был быть получен из контейнера по интерфейсу, к примеру UserFinderInterface).
Это просто пример, чтобы показать что формат данных не должен меняться вне зависимости от реализации репозитория.
Тоесть Ваш новый репозиторий должен был возвращать ту же ActiveRecord и все было бы хорошо.
А описанная проблема это больше неправильная реализация поставленной задачи так как если мы меняем результат, который возвращает метод то должны моменять код во всех местах, гда этот метод используется и тут уже не имеет значения какой шаблон проектирования используется.
Надеюсь теперь будет понятнее=)
Тоесть Вы по сути пишите свою мини доктрину для Yii2?
Нет — ни в коем случае. В Yii2 уже есть ORM и вокруг ActiveRecord много чего центрировано. Я разрабатываю библиотеку, которая будет использовать уже сущесутвующий слой доступа к данным для реализации сущностей и репозиториев.
А там еще нужно обработчики для этих сущностей и пошло поехало, а сути уже есть Doctrine.
Лично мое мнение — Doctrine не лучший пример того как стоит реализовывать данныш шаблон, но давайте не будем углубляться в эту степь так как на эту тему можно долго вести дискуссию и в итоге не прийти к чему-то стоящему внимания.
Основной мой посыл — репозитории можно и нужно использовать, но, как и другие щаблоны проектирования, его нужно использовать с умом и не сувать куда не попадя.
Так что в:
Вы действительно знаете, что делаете и отдаете себе полный отчет о последствиях.
я с вами абсолютно согласен.
В принципе, проблема подмены ActiveRecord чем-то другим в основном может возникнуть когда проект стал на поддержку, а в этом случае просто выкинуть ActiveRecord из проекта и вставить ту же Doctrine врядли выйдет.
Как уже говорилось, ActiveRecord, доставляет много проблем в проектах со средней и более обьемной предметной областью.
Фаулер еще в книге «Шаблоны корпоративных приложений» об этом говорил и советовал применять вместо ActiveRecod, к примеру, DataMapper.
Но я не соглашусть с выражением:
Не пытайтесь играть с Repository в frameworks с ActiveRecord.
Собственно, я сейчас занимаюсь тем, что разрабатываю библиотеку для Yii2, для имплементации репозитория и сущностей и под капотом, для получения данных, я использую всеми гонимый ActiveRecord.
Вся загвоздка в том, чтобы четко разделить сушьность, репозиторий и прослойку получения данных.
Определив четкий интерфейс для прослойки получения данных, в дальнейшем, можно менять источник данных без изменения репозитория и сущности.
Собственно тут на помощь прихождит принцип единой ответственности — только лишь разделив ответственности по специализированным классам можно решить много проблем.
Что же касается проблемы с изменением источника данных:
И вот произошел момент, когда мне действительно нужно было подменить реализацию. Я приехал в офис с улыбкой и с мыслями: «Как я все легко подменю, просто создам другой класс и поменяю строку при биндинге».
то тут проблема не шаблона «Репозиторий», а проблема его реализации. При правильной реализации, подобных проблем не возникнет. Подобная проблема может возникнуть того, что репозиторий выполняет больше обязанностей чем положено.
И касательно формата данных:
Да, благодаря интерфейсу я действительно смог легко подменить реализацию, однако формат возвращаемых данных изменился.
не имеет значения какой шаблон проектирования используется, будь то Repository или ActiveRecod — формат возвращаемых данных не должен меняться даже при изменении реализации. Ну а если формат возвращаемых данных нужно изменять, потому что таково новое требование к системе в целом, то мы имеем обычную задачу, а не проблему.
Давайте разберемся на примере Yii2. Допустим мы получаем ActiveRecod в виде обьектов и массивов. Собственно в коде у нас это может выглядеть так:
/**
* наша бизнес логика. В функцию мы передаем обьект, который ответственный за поиск пользователей.
*/
function doStuffWithUser($userFinder) {
$user = $userFinder->one(123); // получаем обьект пользователя
// do some stuff
$user = $userFinder->asArray()->one(123); // получам массив аттрибутов
}
// потом, допустим в контроллере, мы сделаем следующее
$userFinder = User::find();
doStuffWithUser($userFinder);
И, естественно, код, который использует переменную "$user" знает какого она типа — обьект или массив.
Допустим, в один прекрасный день мы решим, что пользователей нам нужно хранить в файлах.
Для реализации такого подхода имеется официальное расширение https://github.com/yii2tech/filedb
Собственно нам нужно будет только изменить базовый класс для модели «User».
И, в данном случае, замена источника данных ни коим образом не повлияет на логику метода «doStuffWithUser» так как ActiveRecod, который работает с файловой базой данных так же будет возвращать обьекты или массивы той же структуры что и ActiveRecod работающий с базой данных MySQL/PostgreSQL и т.п.
Я это все к тому, что не стоит путать проблемы шаблона, проблемы реализации шаблона и то, как реализации шаблона используются — это все разные проблемы и решаются совершенно разными проблемами.
К примеру, использование неправильного шаблона решается применением другого шаблона, а вот использование плохой реализации — поиском более качественной альтернативы. Криворуккость некомпетентность разработчиков использующих реализации шаблонов решается уже индивидуально или же не решается вообще.
Не совсем, но от части да. Библиотека сейчас не в открытом доступе, поэтому в одном комментарии не получится нормально все описать.
Общие концепции таковы:
Это описание довольно упрощенное, но без ссылки на гитхаб с кодом, чтобы все можно было посмотреть и увидеть на примерах врядли у меня получится вкратце обьяснить достаточно хорошо, а пока что, извините, в открытом доступе библиотеку предоставить не могу.
Я не спорю, Вы рассказали о своих проблема и донесли до читателей опыт полученый методом проб и ошибок, что есть хорошо так как кому-то это может сохранить много нервов.
В статье Вы аргументировали многое примерами реализаций, ссылками на другие статьи и собственным опытом, но мне иенно не очень понравился пункт в выводах:
Просто некоторые люди могут воспринять такой совет слишком радикально и далее работать по принципу «Я на Хабре прочитал, что играть с Repository в frameworks с ActiveRecord не стоит и поэтому буду дулать все по старинке». Я это из личного опыта говорю, т.к. в начале своей карьеры по неопытности допускал такие ошибки слушая подобные радикальные советы от более опытных коллег.
И после прочтения статьи сложилось ощущение, что она подталкивает отказу от применения шаблона Репозиторий только если он не используется в выбранном фраемворке по умолчанию.
Повторюсь, это мое личное мнение, возможно я просто не совсем правильно Вас понял.
Это вполне себе рабочий код — типичный вариант получения данных.
Я хотел тем примером немного другой вопрос осветить, извините если немного сбил с толку. Давайте немного подробнее разъясню свою мысль.
Вы говорили:
Так вот, я хотел сказать, что от изменение хранилища или его реализации не должен меняться формат возвращаемых данных. И в примере я хотел показать, что от смены типа хранилища логика не должна обязательно страдать.
Наверно стоит чуть изменить пример, чтобы изменения были видны более явно
Изначальный вариант кода у нас будет:
После изменения хранилища с базы данных на файловую систему нам должно было быть достаточно сделать изменение только в контроллере(для конкретно этого примера):
User и UserFromFile у нас в данном случае выступают двумя реализациями репозитория(только не вдавайтесь в то, что мы меняем в коде User на UserFromFile, я привел эти имена для большей понятности того, что поменялось, а на практике $userFinder должен был быть получен из контейнера по интерфейсу, к примеру UserFinderInterface).
Это просто пример, чтобы показать что формат данных не должен меняться вне зависимости от реализации репозитория.
Тоесть Ваш новый репозиторий должен был возвращать ту же ActiveRecord и все было бы хорошо.
А описанная проблема это больше неправильная реализация поставленной задачи так как если мы меняем результат, который возвращает метод то должны моменять код во всех местах, гда этот метод используется и тут уже не имеет значения какой шаблон проектирования используется.
Надеюсь теперь будет понятнее=)
Нет — ни в коем случае. В Yii2 уже есть ORM и вокруг ActiveRecord много чего центрировано. Я разрабатываю библиотеку, которая будет использовать уже сущесутвующий слой доступа к данным для реализации сущностей и репозиториев.
Лично мое мнение — Doctrine не лучший пример того как стоит реализовывать данныш шаблон, но давайте не будем углубляться в эту степь так как на эту тему можно долго вести дискуссию и в итоге не прийти к чему-то стоящему внимания.
Основной мой посыл — репозитории можно и нужно использовать, но, как и другие щаблоны проектирования, его нужно использовать с умом и не сувать куда не попадя.
Так что в:
я с вами абсолютно согласен.
В принципе, проблема подмены ActiveRecord чем-то другим в основном может возникнуть когда проект стал на поддержку, а в этом случае просто выкинуть ActiveRecord из проекта и вставить ту же Doctrine врядли выйдет.
Как уже говорилось, ActiveRecord, доставляет много проблем в проектах со средней и более обьемной предметной областью.
Фаулер еще в книге «Шаблоны корпоративных приложений» об этом говорил и советовал применять вместо ActiveRecod, к примеру, DataMapper.
Но я не соглашусть с выражением:
Собственно, я сейчас занимаюсь тем, что разрабатываю библиотеку для Yii2, для имплементации репозитория и сущностей и под капотом, для получения данных, я использую всеми гонимый ActiveRecord.
Вся загвоздка в том, чтобы четко разделить сушьность, репозиторий и прослойку получения данных.
Определив четкий интерфейс для прослойки получения данных, в дальнейшем, можно менять источник данных без изменения репозитория и сущности.
Собственно тут на помощь прихождит принцип единой ответственности — только лишь разделив ответственности по специализированным классам можно решить много проблем.
Что же касается проблемы с изменением источника данных:
то тут проблема не шаблона «Репозиторий», а проблема его реализации. При правильной реализации, подобных проблем не возникнет. Подобная проблема может возникнуть того, что репозиторий выполняет больше обязанностей чем положено.
И касательно формата данных:
не имеет значения какой шаблон проектирования используется, будь то Repository или ActiveRecod — формат возвращаемых данных не должен меняться даже при изменении реализации. Ну а если формат возвращаемых данных нужно изменять, потому что таково новое требование к системе в целом, то мы имеем обычную задачу, а не проблему.
Давайте разберемся на примере Yii2. Допустим мы получаем ActiveRecod в виде обьектов и массивов. Собственно в коде у нас это может выглядеть так:
И, естественно, код, который использует переменную "$user" знает какого она типа — обьект или массив.
Допустим, в один прекрасный день мы решим, что пользователей нам нужно хранить в файлах.
Для реализации такого подхода имеется официальное расширение https://github.com/yii2tech/filedb
Собственно нам нужно будет только изменить базовый класс для модели «User».
И, в данном случае, замена источника данных ни коим образом не повлияет на логику метода «doStuffWithUser» так как ActiveRecod, который работает с файловой базой данных так же будет возвращать обьекты или массивы той же структуры что и ActiveRecod работающий с базой данных MySQL/PostgreSQL и т.п.
Я это все к тому, что не стоит путать проблемы шаблона, проблемы реализации шаблона и то, как реализации шаблона используются — это все разные проблемы и решаются совершенно разными проблемами.
К примеру, использование неправильного шаблона решается применением другого шаблона, а вот использование плохой реализации — поиском более качественной альтернативы.
Криворуккостьнекомпетентность разработчиков использующих реализации шаблонов решается уже индивидуально или же не решается вообще.