Сервисы и репозитории. Слоистая архитектура. Примеры.

Обсуждаем, как правильно строить приложения
Аватара пользователя
magicoder
Сообщения: 133
Зарегистрирован: 2015.12.16, 23:33
Контактная информация:

Re: Сервисы и репозитории. Слоистая архитектура. Примеры.

Сообщение magicoder »

Если есть такой код постраничной навигации в контроллере (из документации), то правильно ли будет вынести часть кода из экшена в сервис?

Код: Выделить всё

function actionIndex()
{
    $query = Article::find()->where(['status' => 1]);
    $countQuery = clone $query;
    $pages = new Pagination(['totalCount' => $countQuery->count()]);
    $models = $query->offset($pages->offset)
        ->limit($pages->limit)
        ->all();

    return $this->render('index', [
         'models' => $models,
         'pages' => $pages,
    ]);
}
Т.е. создать сервис на подобие

Код: Выделить всё

namespace ...
use ...

class MyPageService{

public static function buildPagination(){
$query = Article::find()->where(['status' => 1]);
    $countQuery = clone $query;
    $pages = new Pagination(['totalCount' => $countQuery->count()]);
    $models = $query->offset($pages->offset)
        ->limit($pages->limit)
        ->all();
}

}
Или лучше все работы с бд вынести в модель?
Разработка на yii2 или чистом php.| email: site4coder@gmail.com | skype: for-web1
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Сервисы и репозитории. Слоистая архитектура. Примеры.

Сообщение zelenin »

модель - это об одной бизнес-сущности. Никакой работы с БД там нет и быть не должно.

пагинация и БД - удел репозиториев.
Аватара пользователя
magicoder
Сообщения: 133
Зарегистрирован: 2015.12.16, 23:33
Контактная информация:

Re: Сервисы и репозитории. Слоистая архитектура. Примеры.

Сообщение magicoder »

Т.е. что-то типа:

Код: Выделить всё

namespace ...;
use ...;

final class DataRepository 
{

public static function buildPagination(){
$query = Article::find()->where(['status' => 1]);
    $countQuery = clone $query;
    $pages = new Pagination(['totalCount' => $countQuery->count()]);
    $models = $query->offset($pages->offset)
        ->limit($pages->limit)
        ->all();
}



}
 
Разработка на yii2 или чистом php.| email: site4coder@gmail.com | skype: for-web1
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Сервисы и репозитории. Слоистая архитектура. Примеры.

Сообщение zelenin »

с хабра притащил картинку
Изображение
если что, это ирония :-)
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Сервисы и репозитории. Слоистая архитектура. Примеры.

Сообщение zelenin »

magicoder писал(а):Т.е. что-то типа:

Код: Выделить всё

namespace ...;
use ...;

final class DataRepository 
{

public static function buildPagination(){
$query = Article::find()->where(['status' => 1]);
    $countQuery = clone $query;
    $pages = new Pagination(['totalCount' => $countQuery->count()]);
    $models = $query->offset($pages->offset)
        ->limit($pages->limit)
        ->all();
}



}
 
очень условно говоря да. AR выкидываем, убираем статику итд.

Код: Выделить всё

public function findPostsByCondition(Condition $condition) {
    $postsData = (new Query)
        ->limit($condition->getLimit())
        ->offset($condition->getOffset())
        ->all();
    return array_map(function($row) {
        return $this->toEntity($row);
    }, $postsData);
}
Контроллер:

Код: Выделить всё

$page = $request->get('page');
$limit = $request->get('limit');
$posts = $postService->getPosts($page, $limit);
 
Сервис:

Код: Выделить всё

function getPosts($page, $limit) {
    return $this->postRepo->findPostsByCondition(new Condition($page, $limit);
}
 
Аватара пользователя
magicoder
Сообщения: 133
Зарегистрирован: 2015.12.16, 23:33
Контактная информация:

Re: Сервисы и репозитории. Слоистая архитектура. Примеры.

Сообщение magicoder »

zelenin писал(а):с хабра притащил картинку
если что, это ирония :-)
Правильнее использовать pdo?
Разработка на yii2 или чистом php.| email: site4coder@gmail.com | skype: for-web1
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Сервисы и репозитории. Слоистая архитектура. Примеры.

Сообщение zelenin »

magicoder писал(а):
zelenin писал(а):с хабра притащил картинку
если что, это ирония :-)
Правильнее использовать pdo?
нет, ирония в том, что SRP - это единственная ответственность для класса, а AR - это куча всяких ответственностей в одном классе.
PDO или не PDO - дело второе.

UPD: код выше обновил.
Аватара пользователя
magicoder
Сообщения: 133
Зарегистрирован: 2015.12.16, 23:33
Контактная информация:

Re: Сервисы и репозитории. Слоистая архитектура. Примеры.

Сообщение magicoder »

Можно ли как-то оставить использование AR? Создать класс - адаптер, где выбирать, с помощью чего оперировать данными - pdo, AR или queryBilder? Это нужно для того, чтобы можно было потом переключиться на как на AR ,так и на другие способы работы с данными.
Разработка на yii2 или чистом php.| email: site4coder@gmail.com | skype: for-web1
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Сервисы и репозитории. Слоистая архитектура. Примеры.

Сообщение zelenin »

magicoder писал(а):Можно ли как-то оставить использование AR?
можно даже слоистую архитектуру не использовать.
magicoder писал(а):Создать класс - адаптер, где выбирать, с помощью чего оперировать данными - pdo, AR или queryBilder?
репозиторий и есть адаптер. В нем вы реализуете интерфейс из доменного слоя, используя любой способ получения данных, будь то пдо, рест, файлы или csv/xls. Благодаря интерфейсу, вы можете менять реализацию, не ломая код.
magicoder писал(а):Это нужно для того, чтобы можно было потом переключиться на как на AR ,так и на другие способы работы с данными.
переключайтесь между другими способами. Про AR забудьте - это огромнейшая протечка слоев друг между другом и невозможность изолировать одно от другого. Т.е. условно говоря вы помните, что модели надо получать через репозиторий ($repo->getPosts()), а ваш коллега напрямую через AR их получит (Post::findAll()).
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Сервисы и репозитории. Слоистая архитектура. Примеры.

Сообщение ElisDN »

magicoder писал(а):Правильнее использовать pdo?
Можно Query::find() или createCommand(), как в приведённом мной на первой странице репозитории. Без разницы.

Сама суть разделения - делать доменную модель и практически весь сайт отдельно от базы. Сущность реализуется в своём классе, не наследующиеся от ActiveRecord и прочего. Тогда где же это сохранять в базу? Как раз всё преобразование в базу и обратно при этом будет в репозиториях.

Для чего это и чем AR неудобен? AR - это один-в-один таблица из базы. Из одной базы и из одной таблицы. Для любого изменения базы придётся изменять AR-модель. То есть даже столбик в таблице переименовать нельзя без изменения модели и переписывания кода на всём сайте. В простых сайтах такое терпимо и этого хватает.

А если сайт посложнее, то надо придумать, как в одно поле массив или вложенный объект сохранить, или чтобы даты в полях хранились в виде объектов DateTime вместо строк, чтобы удобно было преобразовывать формат для различных виджетов-календарей. Это всё задачи не для слабонервных. Или, например, половину полей хранить в MySQL, а текст и закешированные комментарии подгружать из MongoDB или Redis... В AR для этого нужно будет целиком весь фрагмент выносить в подмодель и подвязывать через hasOne.

AR ничего экзотического изначально делать не умеет. А в случае наличия отдельного адаптера (репозитория) просто делаем в нём несколько MySQL и Redis запросов, десерелизуем массивы, оборачиваем и формируем свой результат именно таким, какой нужен:

Код: Выделить всё

$commentsFromRedis = ...;

return new Post(
    new PostId($data['id']),
    $data['title'],
    new \DateTime($data['createdAt'])],
    unserialize($data['attibutes']),
    $commentsFromRedis
); 
Никакой стандартный Post::findOne($id) с несколькими базами так делать не сумеет.

Теперь базы данных и таблицы в них можно менять и перестраивать как угодно, просто переписывая время от времени репозиторий.

И кстати, есть ещё один лайфхак. Сущность поста блога для выполнения различных операций может содержать связанные категории, метки, фотографии, текст, комментарии, автора, версии и т.п. А выборок на сайте может быть много:

- В виджете популярных статей нужны только заголовок и превью.
- В списке статей достаточно выводить превью, автора, категорию, главное фото и число комментариев.
- На странице статьи нужны целиком весь основной текст, фотографии, автор, категория, метки, сами комментарии...

Выбирать целиковые громоздкие сущности для этого всего может быть слишком напряжно. Поэтому для вывода на сайте можно сделать второй PostReadRepository чисто для выборок. И он из метода getPopularPosts($limit) вместо массивов оригинальных тяжёлых сущностей Post может возвращать массивы легковесных вмпомогательных DTO вроде $popular[] = new PopularPost($id, $title, $photo, $commentsCount) только с нужными полями. И для их извлечения из базы в репозитории уже можно легко формировать любые отдельные SQL-запросы.

В этом и суть. Примитивность AR и отсутствие настройки полей часто мешает делать то, что нам хочется. Вместо этого мы постоянно думаем таблицами из базы, постоянно терпим, что не можем сохранить в поле массив и не можем сделать вложенный объект Address внутри Company без hasOne и hasMany связей, так как поля в базе поддерживают только числа и строки; постоянно думаем, хранить время created_at в INTEGER, в DATETIME или в TIMESTAMP; постоянно думаем, поддерживает ли наша база JSON или на хостинге ещё его нет. Отсюда получаем поколение ActiveRecord-программистов, всю сознательную жизнь программирующих всё только в понятиях MySQL.
Аватара пользователя
slavcodev
Сообщения: 3134
Зарегистрирован: 2009.04.02, 21:42
Откуда: Valencia
Контактная информация:

Re: Сервисы и репозитории. Слоистая архитектура. Примеры.

Сообщение slavcodev »

Вот ведь парадокс, паттерн введенный Мартином Фаулером в книге "Patterns of Enterprise Application Architecture",
но почему-то пугающий других разработчиков, постоянно называющих его "анти-паттерном".

Может вы просто "не умеете их готовить"?
Жду Yii 3!
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Сервисы и репозитории. Слоистая архитектура. Примеры.

Сообщение zelenin »

slavcodev писал(а):Вот ведь парадокс, паттерн введенный Мартином Фаулером в книге "Patterns of Enterprise Application Architecture",
но почему-то пугающий других разработчиков, постоянно называющих его "анти-паттерном".

Может вы просто "не умеете их готовить"?
об AR речь?
Аватара пользователя
slavcodev
Сообщения: 3134
Зарегистрирован: 2009.04.02, 21:42
Откуда: Valencia
Контактная информация:

Re: Сервисы и репозитории. Слоистая архитектура. Примеры.

Сообщение slavcodev »

zelenin писал(а):об AR речь?
Да, Active Record.
Жду Yii 3!
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Сервисы и репозитории. Слоистая архитектура. Примеры.

Сообщение zelenin »

slavcodev писал(а):
zelenin писал(а):об AR речь?
Да, Active Record.
ну тут либо слой хранилища на AR либо на репозиториях. Иначе у нас есть два пути делать запросы в хранилище.
Фаулер как раз писал что это хороший выбор для простого домена, основанного на crud.
Опять же нарушение SRP, зависимость от реализации (в нашем случае yii2) , маппинг один к одному итд. Минусы и плюсы известны.
Что тут уметь готовить?
Аватара пользователя
slavcodev
Сообщения: 3134
Зарегистрирован: 2009.04.02, 21:42
Откуда: Valencia
Контактная информация:

Re: Сервисы и репозитории. Слоистая архитектура. Примеры.

Сообщение slavcodev »

Научится разделять доменную логику от персистной, научится применять интерфейсы.
И тогда не будет проблем с AR. AR это всего лишь один из подходов ОРМ.
Жду Yii 3!
Аватара пользователя
magicoder
Сообщения: 133
Зарегистрирован: 2015.12.16, 23:33
Контактная информация:

Re: Сервисы и репозитории. Слоистая архитектура. Примеры.

Сообщение magicoder »

slavcodev писал(а):Научится разделять доменную логику от персистной, научится применять интерфейсы.
И тогда не будет проблем с AR. AR это всего лишь один из подходов ОРМ.
А можно про это дело по подробнее, а то уже думал отказываться от AR .
Разработка на yii2 или чистом php.| email: site4coder@gmail.com | skype: for-web1
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Сервисы и репозитории. Слоистая архитектура. Примеры.

Сообщение zelenin »

slavcodev писал(а):Научится разделять доменную логику от персистной, научится применять интерфейсы.
И тогда не будет проблем с AR. AR это всего лишь один из подходов ОРМ.
это бла-бла к AR, не имеющее отношение. AR глобально доступен отовсюду. AR уже свою логику не разделяет, и ее нельзя изолировать.
Единственное, что можно придумтаь - это контракт между разработчиком и кодом, типа здесь используем AR как модель, а здесь как коннекшн. Это нерабочий контракт. Если есть AR-модель, то другой разработчик обязательно использует ее напрямую во вьюшке.
Аватара пользователя
slavcodev
Сообщения: 3134
Зарегистрирован: 2009.04.02, 21:42
Откуда: Valencia
Контактная информация:

Re: Сервисы и репозитории. Слоистая архитектура. Примеры.

Сообщение slavcodev »

Тот же разработчик может использовать все что угодно не там где нужно, например мапперы в сервисах или в контролере.
Проблема выдуманная.
Жду Yii 3!
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Сервисы и репозитории. Слоистая архитектура. Примеры.

Сообщение zelenin »

slavcodev писал(а):Тот же разработчик может использовать все что угодно не там где нужно, например мапперы в сервисах или в контролере
суть-то в изоляции. Маппер не надо изолировать - ты либо его специально под кейс инициализируешь либо нет. А AR у тебя всегда сущестувует - бери и пользуйся. Его нельзя изолировать за интерфейсами, он вездесущ, подталкивая разработчика быстрее заюзать, особенно знакомого с концепцией yii2.
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Сервисы и репозитории. Слоистая архитектура. Примеры.

Сообщение samdark »

PDO тоже доступен отовсюду, как и, например, query builder.
Закрыто