OnLe.org – онлайн курсы по саморазвитию

Собираем здесь завершённые проекты, использующие Yii. Один проект — одна тема.
Ответить
cvl
Сообщения: 25
Зарегистрирован: 2015.03.02, 08:42
Контактная информация:

OnLe.org – онлайн курсы по саморазвитию

Сообщение cvl »

Вот, собственно: OnLe.org – онлайн курсы по саморазвитию

В конце мая перезапустил проект. Раньше он был на C# ASP.NET MVC, сейчас переписан на PHP Yii2.

Это не первый мой проект на Yii2, до него был Strateg.org. Его, кстати, тоже переписывал, только там переписывал с Laravel. Однако это первый мой проект с более-менее правильной, продуманной архитектурой. Для меня правильная архитектура – это когда соблюдается принцип единственной ответственности, когда есть слой доменной логики. Я расслаивал модель на доменную часть и часть, которая относится к работе с хранилищем (БД). Не скажу, что Yii2 сильно к этому приспособлен, но выкрутиться можно.

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

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

Re: OnLe.org – онлайн курсы по саморазвитию

Сообщение samdark »

Не скажу, что Yii2 сильно к этому приспособлен, но выкрутиться можно.
Нет ли случайно предложений на тему того, что можно поменять в фреймворке малой кровью, чтобы он был более приспособлен?
cvl
Сообщения: 25
Зарегистрирован: 2015.03.02, 08:42
Контактная информация:

Re: OnLe.org – онлайн курсы по саморазвитию

Сообщение cvl »

Sam Dark

Я не настолько хорошо знаю фреймворк. Да и вряд ли это можно сделать безболезненно. Там, как минимум, нужно перестраивать одну из базовых иерархий: ActiveRecord » BaseActiveRecord » Model. В ActiveRecord попадает все, что относится к валидации форм, правилам, а это неправильно (ИМХО).

Вот упрощенный пример моей доменной модели:

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

namespace app\models\domain;

class Project extends \yii\base\Model
{
    use _Project;
    use \app\utility\Replicate;

    public $isNewRecord = false;

    public function rules()
    {
        return [
            [['title', 'content', 'user_id'], 'required'],
            [['content'], 'string'],
            [['id', 'user_id'], 'integer'],
            [['title'], 'string', 'max' => 255]
        ];
    }

    public function attributeLabels()
    {
        return [
            'id' => 'ID',
            'title' => 'Title',
            'content' => 'Content',
            'user_id' => 'User ID',
        ];
    }
}
Ядро доменной модели вынесено в трейт:

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

namespace app\models\domain;

trait _Project {

    public $id;
    public $title;
    public $content;
    public $user_id;
}
 
Вот модель, относящаяся к хранилищу:

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

namespace app\models\storage;

class Project extends \yii\db\ActiveRecord
{
    public static function tableName()
    {
        return 'project';
    }

    public function getUser() : \yii\db\ActiveQuery
    {
        return $this->hasOne(User::className(), ['id' => 'user_id']);
    }
} 
Я знаю, что каждая из них должна заниматься своим делом и, следуя «высоким стандартам правильной архитектуры», я не буду обременять модели несвойственными задачами. Однако мне никто не мешает это делать. Более того, меня провоцируют на это. Провоцируют со страшной силой многочисленные примеры, провоцирует gii. Вот как тут устоять неопытному, неокрепшему неофиту? :)

В общем, самый надежный вариант – разорвать порочную цепочку наследования: BaseActiveRecord » Model. Но думается мне, что если это и произойдет, то не раньше Yii3.

Самый простой вариант – доработать gii, чтобы он генерировал (опционально) что-то похожее на то, что я привел. Но тут есть куча нюансов. Заодно нужно генерировать репозиторий (я через него работаю с созданием, изменением, чтением, удалением моделей). Еще репликатор нужен (в примере он \app\utility\Replicate), чтобы конвертировать storage модель в domain модель (и обратно).

Короче, настроить-то gii можно, но нужно все эти архитектурные заморочки подробно разжевывать. Если для более-менее опытных разработчиков это не станет проблемой (хотя их тоже еще нужно убедить в полезности такого подхода), но начинающим тяжело придется.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: OnLe.org – онлайн курсы по саморазвитию

Сообщение zelenin »

мы недавно общались с одним форумчанином в скайпе - он рассказывал как делал слоеную архитектуру на ларавеле, что от yii парадигмально отличается слабо. Собственно рассказал он что репозитории делает на базе AR, т.к. удобно. А вот это сразу нет, т.к. AR включает в себя все и вся, в т.ч. понятия из разных слоев - домен, хранение, валидация - и от этого а) не избавиться б) подсознательно подталкивает к грязи, т.к. одним AR можно уже все сделать, без репозиториев итд.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: OnLe.org – онлайн курсы по саморазвитию

Сообщение zelenin »

интересно посмотреть.
если интересно могу прокомментировать код. Можно в приватном репозитории наподобии гитхаба (предоставлю).
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: OnLe.org – онлайн курсы по саморазвитию

Сообщение zelenin »

а в самом фреймворке на самом деле функционально делать почти ничего не надо. Достаточно разделить на пакеты и дописать интерфейсов для связи. Плюс более явно сделать DI и service locator. плюс все эти пакеты склеить в одном, чтобы можно было работать в старом режиме. Ну и убрать компоненты, перейдя на сервисы из сервис-локейтора, что по сути одно и то же, но более явно показывает, что откуда берется - ни к чему лишняя сущность. Соответственно конфигурировать их не в конфиге приложения, а в конфиге SL.
cvl
Сообщения: 25
Зарегистрирован: 2015.03.02, 08:42
Контактная информация:

Re: OnLe.org – онлайн курсы по саморазвитию

Сообщение cvl »

zelenin

Здесь демонстрационный проект, писал для выступления на одном IT-мероприятии. В README.md инструкции по развертыванию проекта (нужно сделать две обязательные миграции, ну и, конечно, композер запустить).

От рабочего проекта есть два отличия, т.е. в рабочем проекте стоит сделать немного иначе.

1. Лучше обобщить и выделить базовый функционал типичного репозитария:

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

get(int $id);
create(Project $project) : int;
save(Project $project) : bool;
delete(int $id) : bool;
 
Сделать это стоит, чтобы не писать однотипный код для каждого из репозитариев.

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

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

namespace app\models\domain;

abstract class ProjectRepository implements BaseRepository
{
    /* @return Project|null */
    function get(int $id) {}
    function getProjectsUser(int $user_id, $active = null) : array {}
    function getProjectsTask(int $task_id) : array {}
    function getTasksProject(int $project_id, int $offset = null, int $limit = null) : array {}
    function getCountTasksProject(int $project_id) : int {}
}
 
Интерфейс BaseRepository тривиален:

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

namespace app\models\domain;

interface BaseRepository
{
    function get(int $id);
    function create($model) : int;
    function save($model) : bool;
    function delete(int $id) : bool;
}
 
Контроллеры работают с только классами доменного слоя (через DI идет внедрение конкретной реализации), про слой storage они ничего не знают.

Кусочек реализации ProjectRepository:

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

namespace app\models\storage;

use \app\models\domain as domain;

class ProjectRepository extends domain\ProjectRepository
{
    use BaseRepository;

    public function get(int $id)
    {
        /* @var $project Project */
        $project = Project::find()->where(['id' => $id])->with('user')->one();
        /* @var $projectDomain domain\Project */
        $projectDomain = domain\Project::createObject($project);
        $projectDomain->userName = $project->user->username;

        return $projectDomain;
    }
    ...
В трейте BaseRepository реализован get, но ProjectRepository я его переопределяю, т.к. здесь мне нужна специфичная реализация.
Последний раз редактировалось cvl 2016.06.07, 21:13, всего редактировалось 1 раз.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: OnLe.org – онлайн курсы по саморазвитию

Сообщение zelenin »

cvl писал(а):
Собственно рассказал он что репозитории делает на базе AR
Это он зря. Вот пример моего репозитария (из рабочего проекта):

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

namespace app\models\storage;

use \app\models\domain as domain;

class ProjectRepository extends domain\ProjectRepository
{
    use BaseRepository;

    public function get(int $id)
    {
        /* @var $project Project */
        $project = Project::find()->where(['id' => $id])->with('user')->one();
        /* @var $projectDomain domain\Project */
        $projectDomain = domain\Project::createObject($project);
        $projectDomain->userName = $project->user->username;

        return $projectDomain;
    }
    ...
вы не поняли - именно об этом я и говорил - у вас (ус отклеился) AR в репозитории. А в идеале должен быть только коннекшн к базе или некая обертка/маппер/клиент. AR просто вообще не трогать.
cvl
Сообщения: 25
Зарегистрирован: 2015.03.02, 08:42
Контактная информация:

Re: OnLe.org – онлайн курсы по саморазвитию

Сообщение cvl »

zelenin

Это репозитарий в слое storage. То, что он умеет работать с базой (через Query Builder) – это нормально. Так же как и в случае с моделями я выделяю два репозитария. Один на уровне доменной логики (в виде интерфейса или абстрактного класса), а другой на уровне работы с данными. В демонстрационном проекте это легко увидеть.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: OnLe.org – онлайн курсы по саморазвитию

Сообщение zelenin »

теперь по коду:
- репозиторий - абстракция над хранилищем (mysql, elastic, redis). Репозиторий ищет (get/find), удаляет (delete/remove) или сохраняет (store/save/persist) из хранилища, но не создает модель (create).
- доменный слой на основе AR, который включает в себя сам по себе несколько слоев, плохое решение. AR вообще не трогаем.
- видимо следует добавить сервисный слой, чтобы в контроллере не плодить логики.

В сэмпле кода мало, поэтому толком не прокомментировать.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: OnLe.org – онлайн курсы по саморазвитию

Сообщение zelenin »

cvl писал(а):Это репозитарий в слое storage. То, что он умеет работать с базой (через Query Builder) – это нормально.
репозиторий должен уметь работать с хранилищем (базой). Но без использования AR (у вас AR, а ну Query Builder). Мы же про SRP. Сегодня там AR, который "просто делает запросы" (зачем тогда там правила валидации?), а завтра вы прикручиваете к нему поведения. Выход простой: создаем простую POPO (plain old php object), с помощью QueryBuilder делаем запросы, в репозитории же их превращаем в модели.
Создав AR-модель, вы оставили лазейку другому разработчику сделать запрос в базу без репозитория. Это называется протечка слоя.
А AR-модель включает в себя доменный слой (модель, репозиторий), слой инфраструктурный (или хранилища - соединение с базой, маппинг таблицы), слой приложения (валидация).
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: OnLe.org – онлайн курсы по саморазвитию

Сообщение zelenin »

также например Replicate - кандидат для сервиса. Странно, что доменная модель в себя включает статические методы для преобразования в другие объекты, к тому же только в объекты с публичными полями, что является нарушением инкапсуляции. Один поддерживаемый тип объектов (к тому же тип, очень редко использующйися в классических слоеных приложениях) указывает на то, что это введено в угоду другому слою - протечка.
Этим должен заниматься специальный класс, какой-нибудь ObjectResolver с передаваемой в него стратегией (PublicFieldsStrategy, MappingStartegy).
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: OnLe.org – онлайн курсы по саморазвитию

Сообщение zelenin »

про нарушения psr-кодстайла я не упоминаю.
На самом деле это не слоеное приложение, а обычное приложение на yii, в котором добавлен репозиторий (правда сэмпл очень куц - непонятно в какую сторону двинулось приложение после).
cvl
Сообщения: 25
Зарегистрирован: 2015.03.02, 08:42
Контактная информация:

Re: OnLe.org – онлайн курсы по саморазвитию

Сообщение cvl »

zelenin
но не создает модель (create).
Мой создает :). Не вижу в этом никакой проблемы. И такой подход вполне распространен: "Паттерн 'Репозиторий' в ASP.NET".
доменный слой на основе AR, который включает в себя сам по себе несколько слоев, плохое решение. AR вообще не трогаем.
Это ошибочное утверждение. У меня нет доменного слоя на основе AR. В демке репозитарий доменного слоя – это чистый интерфейс.
видимо следует добавить сервисный слой, чтобы в контроллере не плодить логики.
Не совсем понял. Если мы говорим о доменной логике, то она и будет в доменном слое. Зачем нужен еще один сервисный слой?
cvl
Сообщения: 25
Зарегистрирован: 2015.03.02, 08:42
Контактная информация:

Re: OnLe.org – онлайн курсы по саморазвитию

Сообщение cvl »

zelenin

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

Re: OnLe.org – онлайн курсы по саморазвитию

Сообщение zelenin »

cvl писал(а):zelenin
но не создает модель (create).
Мой создает :). Не вижу в этом никакой проблемы. И такой подход вполне распространен: "Паттерн 'Репозиторий' в ASP.NET".
нет, вы не поняли. Ваш метод create не создает модель, а сохраняет на самом деле. Модель уже была создана вне репозитория. Это вопрос наименования.
cvl писал(а):
доменный слой на основе AR, который включает в себя сам по себе несколько слоев, плохое решение. AR вообще не трогаем.
Это ошибочное утверждение. У меня нет доменного слоя на основе AR. В демке репозитарий доменного слоя – это чистый интерфейс.
я чуть ошибся, что не меняет сути - в домене у вас Model с валидацией, в сторадже - AR с добавленным коннекшном.
cvl писал(а):
видимо следует добавить сервисный слой, чтобы в контроллере не плодить логики.
Не совсем понял. Если мы говорим о доменной логике, то она и будет в доменном слое. Зачем нужен еще один сервисный слой?
у вас кроме доменной логики будет еще инфраструктурная типа посчитать, отправить письма, создать новую модель если null (см. метод get репо). Ну то есть я не советую чего-то экстраординарного. В контроллере обычно сервисы принимают реквесты и внутри работают с доменом. Это либо сервисный слой слоеного приложения, либо application layer ddd.

$postService->createPost($request); - внутри провалидировали данные, добавили событие, отправили письмо, кинули задачу в очередь задач, создали объект модели, сохранили в репозитории. Это все не доменная логика. Всегда есть какой-то пул логики, который не относится непосредственно к модели, но инфраструктурно свзяан с ней на уровне приложения.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: OnLe.org – онлайн курсы по саморазвитию

Сообщение zelenin »

cvl писал(а):zelenin

За сим я откланяюсь. Не готов больше тратить на это время. У меня нет цели убеждать вас в чем либо. Если выложенное мною кому-то пригодиться, замечательно. Если нет, значит не судьба.
оп, что началось-то? я думал мы щас обсудим плюсы и минусы, оставив обсуждение для будущих поколений. Я ни в коем случае не критикую или что-то. Только указываю на недочеты. Слоеное приложение создается не просто как использование интересного паттерна, а для того, чтобы с помощью независимых слоев создать легко поддерживаемое, поддающееся рефаторингу, расширяемое приложение.
viewtopic.php?f=19&t=36725 - тут например была отличная попытка, тоже на yii.

Окей, если вы так воспринимаете, то оставим эту ветку как ветку демонстрации вашего завершенного проекта.
Ответить