Разделение приложения на слои

Обсуждаем, как правильно строить приложения
nepster
Сообщения: 838
Зарегистрирован: 2013.01.02, 03:35

Re: Разделение приложения на слои

Сообщение nepster »

xoma писал(а):Свои 5 копеек по приведенному примеру кода:

1 Избавьтесь от $model = new ArticleForm(); и особенно $service = new ArticleService(); в контроллерах.
Di и конструктор контроллера вам помогут в этом.

2 ArticleRepository: у каждого репозитория (в идеале) должен быть интерфейс который он реализует. ArtcileRepositoryInterface или что-то типа того. Потом делаете конкретную реализацию: ArticleArRepository или ArticleDAORepositroy и т.д. Методы репозитория не статичтны, репозиторий инжектится к контроллер как и в п.1 через конструктор.

3 Удаление и создание статьи должно идти через репозиторий (у вас через сервис), т.к. именно репозиторий абстрагирует хранилище.
Можно (имхо) продублировать это в сервис (но удалять все равно через репозиторий) если удаление или создание статьи сопровождается дополнительным функционалом: генерация события, отправка уведомления и т.д.


Чтобы прочувствовать на практике это все хотябы частично есть очень простой совет - перепишите ваш пример только забудьте про ActiveRecord от Yii2, возьмите Doctrine2, а контроллеры, вьюхи и все прочее оставьте от Уии.
Смотрите, ArticleRepository это не совсем паттерн Repository, это скорее хранилище для методов выборки. Тоесть когда проект больше обычного, то очень много различных вариаций выборок, и по моей логике их лучше выкинуть в отдельный файл.

ArticleService, это тоже самое отделньый файл для логики сохранения данных. Через пару часов я попробую переписать пример.

По поводу DI, а вы можете предоставить крисивый пример (особенно если несколько моделей форм) ?
Аватара пользователя
xoma
Сообщения: 641
Зарегистрирован: 2009.04.02, 15:24
Откуда: Ногинск
Контактная информация:

Re: Разделение приложения на слои

Сообщение xoma »

nepster писал(а):
Смотрите, ArticleRepository это не совсем паттерн Repository, это скорее хранилище для методов выборки. Тоесть когда проект больше обычного, то очень много различных вариаций выборок, и по моей логике их лучше выкинуть в отдельный файл.

ArticleService, это тоже самое отделньый файл для логики сохранения данных. Через пару часов я попробую переписать пример.

По поводу DI, а вы можете предоставить крисивый пример (особенно если несколько моделей форм) ?
Repository и занимается тем, что извлекает объекты из хранилища и запихивает их туда.
В вашем ArticleService нет никакой доп. логики при работе с данными. Для чего он ? Просто сохранить объект в БД может репозиторий.
По Di отличный пример вот тут http://www.yiiframework.com/doc-2.0/gui ... ical-usage как раз про контроллер и сервисы. Если параметров несколько - инжектите их все через конструктор (пока в Уии2 нельзя инжектить через метод-экшн, увы)
nepster
Сообщения: 838
Зарегистрирован: 2013.01.02, 03:35

Re: Разделение приложения на слои

Сообщение nepster »

xoma писал(а):
nepster писал(а):
Смотрите, ArticleRepository это не совсем паттерн Repository, это скорее хранилище для методов выборки. Тоесть когда проект больше обычного, то очень много различных вариаций выборок, и по моей логике их лучше выкинуть в отдельный файл.

ArticleService, это тоже самое отделньый файл для логики сохранения данных. Через пару часов я попробую переписать пример.

По поводу DI, а вы можете предоставить крисивый пример (особенно если несколько моделей форм) ?
Repository и занимается тем, что извлекает объекты из хранилища и запихивает их туда.
В вашем ArticleService нет никакой доп. логики при работе с данными. Для чего он ? Просто сохранить объект в БД может репозиторий.
По Di отличный пример вот тут http://www.yiiframework.com/doc-2.0/gui ... ical-usage как раз про контроллер и сервисы. Если параметров несколько - инжектите их все через конструктор (пока в Уии2 нельзя инжектить через метод-экшн, увы)
Хорошо большое спасибо. Я попробую через пару часов сделать пример. Если будет время прокомментируйте.

П.С. я уже пробовал работать с DI, но что-то код, который получался на выходе мне не очень нравился. Пока использую DI только, что бы перебивать конфиги расширений.
Аватара пользователя
xoma
Сообщения: 641
Зарегистрирован: 2009.04.02, 15:24
Откуда: Ногинск
Контактная информация:

Re: Разделение приложения на слои

Сообщение xoma »

Вот тут еще полезное http://www.slideshare.net/symfoniacs/en ... e-50640471
Особенно 14 слайд. И не смотрите, что там речь идет о Symfony =)
nepster
Сообщения: 838
Зарегистрирован: 2013.01.02, 03:35

Re: Разделение приложения на слои

Сообщение nepster »

Версия №2


Контроллер

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

    /**
     * Создать
     * @return string
     */
    public function actionCreate()
    {
        $model = new ArticleForm(['scenario' => 'user-create']);

        if (Yii::$app->request->isPost) {
            $model->load(Yii::$app->request->post());
            if ($model->validate()) {
                if ($model->createArticle()) {
                    Yii::$app->session->setFlash('success', 'Данные успешно сохранены');
                } else {
                    Yii::$app->session->setFlash('danger', 'Возникла ошибка');
                }
                return $this->refresh();
            }
        }

        return $this->render('create', [
            'model' => $model,
        ]);
    }

    /**
     * Обновить
     * @param $id
     * @return string
     * @throws NotFoundHttpException
     */
    public function actionUpdate($id)
    {
        /** @var ArticleForm $model */
        $model = new ArticleForm();
        $model = $model->repository->findById($id);

        if (!$model) {
            throw new NotFoundHttpException("Страница под номером '{$id}' не найдена");
        }

        $model->setScenario('user-update');

        if (Yii::$app->request->isPost) {
            $model->load(Yii::$app->request->post());
            if ($model->validate()) {
                if ($model->updateArticle()) {
                    Yii::$app->session->setFlash('success', 'Данные успешно сохранены');
                } else {
                    Yii::$app->session->setFlash('danger', 'Возникла ошибка');
                }
                return $this->refresh();
            }
        }

        return $this->render('create', [
            'model' => $model,
        ]);
    }

    /**
     * Удалить
     * @param $id
     * @return string
     * @throws NotFoundHttpException
     */
    public function actionDelete($id)
    {
        /** @var ArticleForm $model */
        $model = new ArticleForm();
        $model = $model->repository->findById($id);

        if (!$model) {
            throw new NotFoundHttpException("Страница под номером '{$id}' не найдена");
        }

        if ($model->deleteArticle()) {
            Yii::$app->session->setFlash('success', 'Данные успешно удалены');
        } else {
            Yii::$app->session->setFlash('danger', 'Возникла ошибка');
        }

        return $this->redirect('index');
    }
 

Основная модель

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

class Article extends \yii\db\ActiveRecord
{
    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return '{{%articles}}';
    }

    /**
     * @return ArticleQuery
     */
    public static function find()
    {
        return new ArticleQuery(get_called_class());
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
            'id' => '№',
            'slug' => 'Слаг',
            'title' => 'Заголовок',
            'short_text' => 'Краткое описание',
            'full_text' => 'Полное описание',
            'meta_title' => 'Заголовок (Title)',
            'meta_description' => 'Описание (Description)',
            'meta_keywords' => 'Ключевые слова (Keywords)',
            'visible' => 'Отображение',
            'time_create' => 'Дата создания',
            'time_update' => 'Дата редактирования',
        ];
    }

    /**
     * @return ArticleRepository
     */
    public function getRepository()
    {
        $model = new ArticleRepository($this);
        return $model;
    }
}
 


Репозиторий

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

class ArticleRepository
{
    /**
     * @var Article
     */
    protected $model;

    /**
     * @param Article $model
     */
    public function __construct(Article $model)
    {
        $this->model = $model;
    }

    /**
     * Получить запись по идентификатору
     * @param $id
     * @param int $visible
     * @return array|null|\yii\db\ActiveRecord
     */
    public function findById($id, $visible = 1)
    {
        $model = $this->model;
        return $model::find()
            ->where(['id' => $id])
            ->visible($visible)
            ->one();
    }
} 

Кастомные квери

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

class ArticleQuery extends ActiveQuery
{
    /**
     * @param int $state
     * @return $this
     */
    public function visible($state = 1)
    {
        $this->andWhere([Article::tableName() . '.visible' => $state]);
        return $this;
    }
}
 

Форма

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

<?php

namespace app\modules\articles\models\frontend;

use app\modules\articles\models\Article;
use Yii;

/**
 * Форма сущности "Article"
 * @package app\modules\articles\models\frontend
 */
class ArticleForm extends \app\modules\articles\models\Article
{
    /**
     * @inheritdoc
     */
    public function behaviors()
    {
        return [
            'TimestampBehavior' => [
                'class' => \yii\behaviors\TimestampBehavior::className(),
                'attributes' => [
                    \yii\db\ActiveRecord::EVENT_BEFORE_INSERT => 'time_create',
                    \yii\db\ActiveRecord::EVENT_BEFORE_UPDATE => 'time_update',
                ],
            ]
        ];
    }

    /**
     * @inheritdoc
     */
    public function transactions()
    {
        return [
            'user-create' => self::OP_ALL,
            'user-update' => self::OP_ALL,
        ];
    }

    /**
     * @inheritdoc
     */
    public function scenarios()
    {
        return [
            'user-create' => ['title', 'short_text', 'full_text'],
            'user-update' => ['title', 'short_text', 'full_text'],
        ];
    }

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['title', 'short_text', 'full_text'], 'required'],

            [['title', 'short_text', 'full_text'], 'filter', 'filter' => 'trim'],
            [['title', 'short_text', 'full_text'], 'string'],

            [['title'], 'string', 'max' => 100],
        ];
    }

    /**
     * @inheritdoc
     */
    public function beforeValidate()
    {
        if (parent::beforeValidate()) {

            // Логика

            return true;
        }

        return false;
    }

    /**
     * @inheritdoc
     */
    public function beforeSave($insert)
    {
        if (parent::beforeSave($insert)) {


            $this->meta_title = $this->title;
            $this->meta_description = $this->title;
            $this->meta_keywords = $this->title;

            return true;
        }

        return false;
    }

    /**
     * Создать
     * @inheritdoc
     */
    public function createArticle()
    {
        $this->slug = md5(uniqid(true));
        $this->visible = 1;

        return $this->save(false);
    }

    /**
     * Обновить
     * @return bool
     */
    public function updateArticle()
    {
        return $this->save(false);
    }

    /**
     * Удалить
     * @return bool
     */
    public function deleteArticle()
    {
        return $this->delete();
    }

} 


Теперь попробую расписать логику

Контроллер
DI - почему я не хочу использовать DI и передавать классы в конструкторе, все потому, что их может быть несколько, поэтому нужно создавать дополителньые переменные и мусорить в конструкторе, куда удобнее просто импортировать нэймспейс.
+ класс будет отрабатывать только тогда, когда действительно это нужно.


Основная модель
Содержит только методы AR и геттер на репозиторий


Репозиторий
Это не совсем паттерн репозиторий (его не обязательно наследовать от интерфейса), он нужен скорее для того, чтобы вынести все выборки в отдельный класс и не мешать их с прочим делом.


Кастомные квери
Ну тут все понятно, не какой магии.


Форма
Для каждой формы предлагаю создавать класс (например ArticleForm), который будет содержать сю логику работы с формой.
Он должен наследоваться от основной модели.
Аргументы:
- transactions()
- свойства
- избавляемся от лишней сущности (сервисы) и делаем удобное сохранение через $this. Я разбирал эти сервисы и понял, что в простых и средних вариантах, а именно: получить данные - обработать - записать в базу, нет никакого смысла создавать еще 1 слой. Оставим сервис для более сложных задач.


Результат:
Все более менее структурировано, на 1 сущность можент быть около 2 форм (frontend и backend).
Вся логика (получить данные - обработать - сохранить) реализована в файле формы.

Модель не содержит 100500 строк кода, а работат только с дефолтными данными AR

В репозиторий инкапсулированы все выборки, котоыре нам нужны.

Кастомные квери помогают избавится от дублей в запросах.

В общем правила игры yii2 приняты и получаем удобную архитектуру.

Вопросы:
Вопрос пока только 1. Что делать с геттреами реляций ?

Есть задачи, когда например какой-то метод для фронтенда не такой как для бэкенда, поэтому реляции переопределены, а выборка идет через static (позднее статическое связывание), например метод выборки лежит в common, а получаем модель из frontend. Если это инкапсулировать в репозиторий, будет вопрос как это будет работать.

Собственно вопрос актуален: куда девать геттры на реляции, оставлять в основной моделе ?
Аватара пользователя
xoma
Сообщения: 641
Зарегистрирован: 2009.04.02, 15:24
Откуда: Ногинск
Контактная информация:

Re: Разделение приложения на слои

Сообщение xoma »

Зачем все это ? Вы пытаетесь переизобрести существующие решения на свой лад ? Тогда вам тут никто не поможет, так как никто не сможет понять чтоже вы делаете =) Модель ничего не должна знать про репозиторий, как и про БД вообще. Модель - это энтити, она вообще не знает куда она сохраняется и откуда получается. Представьте что модель это просто класс, который ни от чего не наследуется. Как и куда сохранять модель - знает только репозиторий, он же знает как и откуда доставать модели. Почитайте основы ДДД, вам много станет понятно. Ну и сделаейте пример с доктриной.
Аватара пользователя
xoma
Сообщения: 641
Зарегистрирован: 2009.04.02, 15:24
Откуда: Ногинск
Контактная информация:

Re: Разделение приложения на слои

Сообщение xoma »

И да, сценарии в моделях - это тихое зло, старайтесь их избегать.
nepster
Сообщения: 838
Зарегистрирован: 2013.01.02, 03:35

Re: Разделение приложения на слои

Сообщение nepster »

xoma писал(а):Зачем все это ? Вы пытаетесь переизобрести существующие решения на свой лад ? Тогда вам тут никто не поможет, так как никто не сможет понять чтоже вы делаете =) Модель ничего не должна знать про репозиторий, как и про БД вообще. Модель - это энтити, она вообще не знает куда она сохраняется и откуда получается. Представьте что модель это просто класс, который ни от чего не наследуется. Как и куда сохранять модель - знает только репозиторий, он же знает как и откуда доставать модели. Почитайте основы ДДД, вам много станет понятно. Ну и сделаейте пример с доктриной.
Я когда пробовал симфони, в первый же день наткнулся на баг в доктрине (что то там с COUNT), 4 дня решал одну простую задачу, обиделся и теперь у меня психологическая травма в отношении доктрины.

Что касается модели, я помню что там в симфони, модель это просто набор геттеров и сеттеров, все остальное в сервисах. Но у нас тут совсем другая архитектура. Я не пытаюсь изобрести что-то новое, я просто хочу шаблон удобного подхода к разработке именно на yii2.
Сейчас 90% сообщества все лепят в одну модель, в результате 15000 строк кода и трогать это опасно и страшно.

Основная задача все структурировать таким образом, что бы все было удобно расширяемо и красиво.

Что касается, того что модель знает про репозиторий, ну так удобнее выборку делать.
xoma писал(а):И да, сценарии в моделях - это тихое зло, старайтесь их избегать.
Ну зачем вы так? Это одна модель для 1 формы, мало вероятно, что у вас будет больше 4 сценариев.
nepster
Сообщения: 838
Зарегистрирован: 2013.01.02, 03:35

Re: Разделение приложения на слои

Сообщение nepster »

Почитайте основы ДДД, вам много станет понятно.
Читал, мне пока туда рановато. Я застрял на TDD и BDD. Уже как освою полностью тестирование, будем двигаться в DDD.
Аватара пользователя
xoma
Сообщения: 641
Зарегистрирован: 2009.04.02, 15:24
Откуда: Ногинск
Контактная информация:

Re: Разделение приложения на слои

Сообщение xoma »

Symfony тут вообше не имеет отношения.
Подход используемый в Symfony и Doctrine можно эмулировать и на Yii2, да, это костыльно, да это не так красиво и хорошо, но в 100 раз удобнее и легче в сопровождении. Ребята из Laravel эмулируют и ничего +)

Репозитории в Laravel:
http://culttt.com/2014/09/08/benefits-u ... ositories/
http://culttt.com/2014/03/17/eloquent-t ... positories

Эти два подхода 1 в 1 портируются в Yii2.
Для чего изобретать что-то новое, которое будет известно и удобно только Вам если уже есть тонны рецептов и рекомендаций ?
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Разделение приложения на слои

Сообщение zelenin »

ну имхо ты все-таки не понимаешь зачем все это нужно и пытаешься сделать "не как у всех", из-за этого получается какая-то мешанина из общеизвестных паттернов, yii-шной начинки и непонятного чего-то.

Репозиторий - паттерн, абстрагирующий от хранилища. Если у тебя это сборище методов для выборки, то ты сделал надстройку над ActiveQuery, и не называй это репозиторием, чтобы тебе каждый раз не указывали на это.

Если ты создаешь объект репозитория, то не надо называть его моделью - это же не модель. Аналогично с формами.

Соглашусь с xoma.
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Разделение приложения на слои

Сообщение samdark »

nepster, не то.

Весь смысл слоёв — избавиться от зависимостей. У вас не вышло. Зависимости никуда не делись.
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Разделение приложения на слои

Сообщение samdark »

xoma и zelenin дело говорят. Другой вопрос — нужно ли это вам сейчас...
lynicidn
Сообщения: 2222
Зарегистрирован: 2014.05.24, 15:12

Re: Разделение приложения на слои

Сообщение lynicidn »

Sam Dark писал(а):xoma и zelenin дело говорят. Другой вопрос — нужно ли это вам сейчас...
олололо, Александр, я этот вопрос поднял месяца 3 назад на pepper-cube, и там эту суть понял только creocoder :P вы же все остальные сказали, что это не нужно и чуть ли что из уии пытаемся сделать симфони :roll:
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Разделение приложения на слои

Сообщение zelenin »

lynicidn писал(а):
Sam Dark писал(а):xoma и zelenin дело говорят. Другой вопрос — нужно ли это вам сейчас...
олололо, Александр, я этот вопрос поднял месяца 3 назад на pepper-cube, и там эту суть понял только creocoder :P вы же все остальные сказали, что это не нужно и чуть ли что из уии пытаемся сделать симфони :roll:
так суть в том, что yii (прости господи) для нубов в целом, поэтому как бы не пыжился, yii останется таким как есть, а абстрактный nepster перейдет со своей структурой на любой продукт, основанный на симфони-компонентах.
lynicidn
Сообщения: 2222
Зарегистрирован: 2014.05.24, 15:12

Re: Разделение приложения на слои

Сообщение lynicidn »

я целиком за разделение, по мессаге выше можно понять, что я заваривал эту кашу
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Разделение приложения на слои

Сообщение zelenin »

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

Re: Разделение приложения на слои

Сообщение samdark »

Я бы не сказал, что Yii для нубов. Да, если нужен ынтырпрайз во всей его красе без каких либо поблажек и строго по книгам — лучше сразу взять другую базу. Но дело в том, что реально нужен он в редких случаях и чаще всего мешает конторе сильно экономить без каких-либо последствий. Инструменты выбирать надо правильно.
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Разделение приложения на слои

Сообщение samdark »

При этом слоить можно замечательно и в Yii. Это особо ничем не отличается от слоения в других фреймворках. И нет, для этого не обязательно использовать Doctrine.

При этом надо понимать, зачем это делается, как это делается и, главное — зачем оно вам.
nepster
Сообщения: 838
Зарегистрирован: 2013.01.02, 03:35

Re: Разделение приложения на слои

Сообщение nepster »

Вы наверно не совсем понимаете что я пытаюсь сделать.
Я не пытаюсь лепить велосипед или делать симфони, я просто пытаюсь взять yii2 и выработать удобный подход к разработке.

Какие есть проблемы на сегодняшний день:
1) В лучшем случае все лепят код в одну модель. В результате чего получается 1 модель с 25 сценариями и 5000 строк кода, это поддерживать крайне тяжело. Причем это в лучшем случае, в худжем все это будет раскидано по вьюшкам и контроллерам.

2) Когда нарастает кодовая база и нужно что-то добавить или изменить, то это превращается в сущий ад, так как например если у вас есть выборка из базы например всех статей, а потом понадобилось учитывать статус, то если эта выборка не инкапсуляция в метод, нужно искать по всему проекту где это находится и лепить с коп везде в лучшем случае.

3) Плюс ко всему в юи есть очень тупняковые штуки, которые нужно использоваться очень аккуратно. Я когда допилю проект, закину обзор с какими проблемами я столкнулся при разработке.

4) В документации почти нет ничего про бест практик в реальных проектах. Все ограничивается простенькими примерчиками, как любят говорить, на уровне "брожика".


От сюда следует:
1) Нужно сделать красивый подход в отношении структуры принимая правила игры yii2 (привет zelenin =)).
2) Где-то я смотрел видео выступления Александра Макарова по теме yii2, где была замечательная фраза: "Yii2 - это другая ниша (в контексте сравнения с симфони)"
3) Что касается зависимостей, ну не возможно от них избавиться на все 100%, а те кто считают, что возможно, значит не решали обезбашенные задачи (привет сетевому маркетингу и mlm матрицам).
4) Попытка сделать супер независимый код, выйдет в избыток абстрактности, что в итоге будет буксовать приложение, а потом все будут плакать, что php говно (кто хочет поспорить, пожалуйста кидайте ссылки на свои гит аккаунты, я с радостью).
5) Когда жмут сроки или вселенская лень, никто не знает про TDD, BDD, SOLID, DDD и прочие сокращения. Я еще не разу не видел ни один проект, который бы был сделал идеально по всем стандартам, библиотеки да, хелперы, виджеты - да. Но чем больше проект тем больше в нем говна.

ФормМодел и СирчМодел описываются в документации по yii2.
Мне бы еще не помешало разработать концепцию как красиво отделять выборку и возможно стороннюю логику.
Закрыто