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

Обсуждаем, как правильно строить приложения
Закрыто
Аватара пользователя
bupy7
Сообщения: 57
Зарегистрирован: 2014.03.10, 14:40
Контактная информация:

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

Сообщение bupy7 »

zelenin писал(а):http://habrahabr.ru/post/267125/
отличный перевод обширной статьи о hexagonal архитектуре
\m/^^
Очень плохие и совсем бесполезные расширения: http://github.com/bupy7
Аватара пользователя
mistbow
Сообщения: 64
Зарегистрирован: 2013.11.05, 20:26
Контактная информация:

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

Сообщение mistbow »

Фух! Осилил таки! ))
S c писал(а):
nepster писал(а):
Sam Dark писал(а):zf3 проще, ага :) Самопис — не вариант, если вы не готовы потратить на него пару-тройку лет чтобы получить нормальный набор фич и соответствующее популярным фреймворкам качество.

Александ, зря вы наверно так считаете. Сейчас все решается черзе независимые библиотеки, тоесть фактически берем MVC или что-то подобное, реализовываем поддержку composer и используем сторонние библиотеки без лишних 25 слоев абстракции.

Тот же RBAC, oauth2, mailer, спокойненько подключаем и работает практически на пряму.

Тут наверное главно не лажануть с архитектурой и писать как-то аккуратненько, по SOLID как полагается.

Что касается ZF, я его в глаза не видел, но судя по описанию и рекламы 3 версии, там как раз таки решили как говорят пришли к простате и независимым компонентам.

Yii2, конечно вещь кутая, но содержит много чего лишнего и ну прямо очень сильно подталкивает делать плохие вещи.
А я с вами соглашусь. Сам планирую для себя реализовать средненький проект без использования фреймворка. Добавлю сюда, что это незаменимый опыт. Именно на таких проектах и рождаются первоклассные архитекторы
Пописал с 2007 года проект для своей конторы самописом, но в конце концов все стало очень сложно в поддержке. Прикольно конечно, когда полностью свободен от всего, но потом начинает сильно доставать, что либо одного не хватает, либо другого, а все полностью переписывать уже сложновато. Проще фреймворком воспользоваться, где многие вещи уже сделаны грамотно и многими людьми выверены.))
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

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

Сообщение ElisDN »

mistbow писал(а):Проще фреймворком воспользоваться, где многие вещи уже сделаны грамотно и многими людьми выверены.))
Здесь в треде затык конкретно в этой грани: что делать, если в любимом фреймворке «многие» вещи сделаны грамотно, а остальная половина «немногих» вещей - не так грамотно, как хотелось бы. Вот здесь и приходится либо терпеть, либо костылить на том, что есть, либо половину фреймворка под себя переписывать, либо другой фреймворк выбирать, либо свой фреймворк собирать из компонентов.
Аватара пользователя
mistbow
Сообщения: 64
Зарегистрирован: 2013.11.05, 20:26
Контактная информация:

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

Сообщение mistbow »

Что-то я тут к языку Go присмотрелся) понравился он мне. Вот еще бы Yii2 на нем бы тоже хотелось бы иметь)
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

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

Сообщение zelenin »

mistbow писал(а):Что-то я тут к языку Go присмотрелся) понравился он мне. Вот еще бы Yii2 на нем бы тоже хотелось бы иметь)
qiang как раз на нем щас пилит)
Аватара пользователя
mistbow
Сообщения: 64
Зарегистрирован: 2013.11.05, 20:26
Контактная информация:

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

Сообщение mistbow »

zelenin писал(а):
mistbow писал(а):Что-то я тут к языку Go присмотрелся) понравился он мне. Вот еще бы Yii2 на нем бы тоже хотелось бы иметь)
qiang как раз на нем щас пилит)
А что пилит, если не секрет? ;)
Onotole
Сообщения: 1808
Зарегистрирован: 2012.12.24, 12:49

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

Сообщение Onotole »

mistbow писал(а):
zelenin писал(а):
mistbow писал(а):Что-то я тут к языку Go присмотрелся) понравился он мне. Вот еще бы Yii2 на нем бы тоже хотелось бы иметь)
qiang как раз на нем щас пилит)
А что пилит, если не секрет? ;)
Фреймворк
Аватара пользователя
mistbow
Сообщения: 64
Зарегистрирован: 2013.11.05, 20:26
Контактная информация:

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

Сообщение mistbow »

А где можно про эту работу почитать? ;)
Onotole
Сообщения: 1808
Зарегистрирован: 2012.12.24, 12:49

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

Сообщение Onotole »

mistbow писал(а):А где можно про эту работу почитать? ;)
https://github.com/go-ozzo
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

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

Сообщение samdark »

Набор либ по работе пилит...
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

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

Сообщение zelenin »

https://github.com/norzechowicz/mydrinks
отличное демо-приложение, реализующее принципы ddd и частично cqrs. На данный момент это лучшее и самое реальное, что есть на гитхабе.
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

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

Сообщение samdark »

И всё-равно оно не показывает, зачем... 90% кода там сложно реализует очень простые задачи.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

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

Сообщение zelenin »

Sam Dark писал(а):И всё-равно оно не показывает, зачем... 90% кода там сложно реализует очень простые задачи.
все верно. Приложение показывает КАК, а не ЗАЧЕМ. Показывать значение применения энтерпрайз-паттернов трудно на простом приложении, т.к. плюсы становятся видны только на крупном приложении. Поэтому пример полезен тем, кто понимает ЗАЧЕМ, но сомневается КАК.
m.khartanovych
Сообщения: 3
Зарегистрирован: 2016.04.08, 15:35

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

Сообщение m.khartanovych »

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

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

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

namespace frontend\modules\v1\actions\vehicle;


use frontend\modules\v1\actions\BaseAction;
use frontend\modules\v1\models\PromotedVehicle;
use frontend\modules\v1\models\Vehicle;
use frontend\modules\v1\models\vehicle\PopularCar;
use frontend\modules\v1\models\vehicle\VehicleQuery;
use frontend\modules\v1\Module;
use yii\data\ArrayDataProvider;

class PopularAction extends BaseAction
{
    /** @var array */
    private $result = [];

    public function run($limit = 5)
    {
        /** @var Vehicle $vehicle */
        $vehicle = new $this->modelClass([
            'scenario' => $this->scenario,
        ]);

        /** @var PromotedVehicle|array $promotedVehicles */
        $promotedVehicles = PromotedVehicle::find()
            ->with([
                'car' => function (VehicleQuery $query) use ($vehicle) {
                    return $query->active()->inUse();
                },
                'car.carModel',
                'car.carBrand',
            ])
            ->recently()
            ->last24Hours()
            ->limit($limit)
            ->all();

        if ($promotedVehicles) {
            foreach ($promotedVehicles as $promotedVehicle) {
                $this->result[] = $promotedVehicle;
            }
        }

        if (($cnt = count($promotedVehicles)) < $limit) {
            /** @var PopularCar|array $populars */
            $populars = $vehicle->find()
                ->with(['user', 'carModel', 'carBrand', 'commentCounter'])
                ->popular($vehicle->vehicle_type)
                ->language(Module::$langId)
                ->orderBy('rand()')
                ->limit((int)$limit - $cnt)
                ->all();

            if ($populars) {
                foreach ($populars as $popular) {
                    $this->result[] = $popular;
                }
            }
        }

        return new ArrayDataProvider([
            'allModels' => $this->result,
            'pagination' => false
        ]);
    }
}
Попробовал его порефакторить в соотвествии с прочитанным, а так же не сильно уходить от Yii стиля - если это можно так назвать.

Так же после прочтения многих статей на тему репозитория, есть чувство, что использовать этот паттерн в Yii просто нет необходимости, частично AR, Query и Model реализуют эту задачу, насколько правильно ? Вопрос второй, но если есть желание или необходимость использовать именно этот паттерн, то наверное имеет смысл выбрать Laravel или SF2 где этот подход является более приемлемым. Поправьте пожалуйста если это не так.

Поэтому я решил, что наверное существующую логику action'а имеет смысл выделить просто в сервис после этого action стал намного меньше

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

namespace frontend\modules\v1\actions\vehicle;


use frontend\modules\v1\actions\BaseAction;
use frontend\modules\v1\models\Vehicle;
use frontend\modules\v1\services\vehicle\popular\PopularVehicleService;
use yii\data\ArrayDataProvider;

/**
 * Class PopularAction
 * @package frontend\modules\v1\actions\vehicle
 */
class PopularAction extends BaseAction
{
    /** @var PopularVehicleService */
    private $popularVehicleService;

    public function run($limit = 5)
    {
        /** @var Vehicle $vehicle */
        $vehicle = new $this->modelClass([
            'scenario' => $this->scenario,
        ]);

        $this->popularVehicleService = new PopularVehicleService($vehicle, ['limit' => $limit]);

        return new ArrayDataProvider([
            'allModels' => $this->popularVehicleService->getVehicles(),
            'pagination' => false
        ]);
    }
}
а вся логика выборки переместилась в сервис

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

namespace frontend\modules\v1\services\vehicle\popular;

use frontend\modules\v1\models\PromotedVehicle;
use frontend\modules\v1\models\Vehicle;
use frontend\modules\v1\models\vehicle\VehicleQuery;
use frontend\modules\v1\Module;
use yii\base\Object;
use yii\helpers\ArrayHelper;

/**
 * Class PopularVehicleService
 * @package frontend\modules\v1\services\vehicle\popular
 */
class PopularVehicleService extends Object
{
    /** @var int */
    public $defaultLimit = 5;

    /** @var int */
    public $limit = 0;

    /** @var Vehicle */
    private $vehicle;

    /** @var array */
    private $vehicles = [];

    public function __construct(Vehicle $vehicle, $config = [])
    {
        $this->vehicle = $vehicle;

        parent::__construct($config);
    }

    public function getVehicles()
    {
        $this->fillVehicles($this->getPromotedVehicles());

        if ($this->notFull()) { // fill the array up to the limit by random vehicles
            $this->fillVehicles($this->getRandomPromotedVehicles());
        }

        return $this->vehicles;
    }

    /**
     * @return array|\yii\db\ActiveRecord[]
     */
    private function getPromotedVehicles()
    {
        $promoted = PromotedVehicle::find()
            ->joinWith([
                'car' => function (VehicleQuery $query) {
                    return $query->select(['id'])->active()->inUse();
                }
            ])
            ->recently()
            ->last24Hours()
            ->limit($this->getLimit())
            ->all();

        $promotedIds = ArrayHelper::getColumn($promoted, function ($element) {
            return $element['car_id'];
        });

        return $this->vehicle
            ->find()
            ->with(['user', 'carBrand', 'carModel', 'commentCounter', 'channel'])
            ->andWhere(['id' => $promotedIds])
            ->all();
    }

    /**
     * @return array|\yii\db\ActiveRecord[]
     */
    private function getRandomPromotedVehicles()
    {
        return $this->vehicle
            ->find()
            ->with(['user', 'carModel', 'carBrand', 'commentCounter', 'channel'])
            ->popular($this->vehicle->vehicle_type)
            ->language(Module::$langId)
            ->orderBy('rand()')
            ->limit($this->getRandLimit())
            ->all();
    }

    /**
     * @return int
     */
    private function getLimit()
    {
        return $this->limit ? $this->limit : $this->defaultLimit;
    }

    /**
     * @return int
     */
    private function getRandLimit()
    {
        return (int)($this->getLimit() - $this->countVehicles());
    }

    /**
     * @param $data
     */
    private function fillVehicles($data)
    {
        foreach ($data as $item) {
            $this->addVehicle($item);
        }
    }

    /**
     * @param $item
     */
    private function addVehicle($item)
    {
        $this->vehicles[] = $item;
    }

    /**
     * @return int
     */
    private function countVehicles()
    {
        return (int)count($this->vehicles);
    }

    /**
     * @return bool
     */
    private function notFull()
    {
        return $this->countVehicles() < $this->getLimit();
    }
}
Вся логика инкапсулирована в одном месте, структура понятна, но ощущение монолитности кода не покидает и есть сомнения в плане тестирования такого кода.

У меня есть желание вынести find'ы в другой класс, какой это может быть класс ? Репозиторий ? Но если следовать этому паттерну, то в первую очередь репозиторий - это интерфейс, а не класс в названии которого есть слово "Repository", что я имею ввиду

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

class SomeRepository {
    public static function getById($id){
        return Model::findOne($id);
    }
}
 
Подобный класс мало что имеет общего с репозиторием, это просто хелпер который имеет статический метод.

Но тогда вопрос, куда выделить все find'ы ? Или использовать их на прямую в сервисах или выделять в какой-то отдельный класс как пример выше ?

Просто если реализовать для этой цели репозиторий, он возможно будет избыточным и тогда не совсем ясна роль AR и Query... получиться что-то типа

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

interface PromotedVehicleRepositoryInterface
{
    public function getPromotedVehiclesIds($limit);
}

class PromotedVehicleRepository implements PromotedVehicleRepositoryInterface
{
    public function getPromotedVehiclesIds($limit)
    {
        $promoted = PromotedVehicle::find()
            ->joinWith([
                'car' => function (VehicleQuery $query) {
                    return $query->select(['id'])->active()->inUse();
                }
            ])
            ->recently()
            ->last24Hours()
            ->limit($limit)
            ->all();

        return $promotedIds = ArrayHelper::getColumn($promoted, function ($element) {
            return $element['car_id'];
        });
    }
}

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

interface VehicleRepositoryInterface
{
    public function getPromotedVehiclesByIds(array $ids, $limit);

    public function getPromotedRandomVehicles($limit);
}

class VehicleRepository implements VehicleRepositoryInterface
{

    public function getPromotedVehiclesByIds(array $ids, $limit)
    {
        return Vehicle::find()
            ->with(['user', 'carBrand', 'carModel', 'commentCounter', 'channel'])
            ->andWhere(['id' => $ids])// IN() condition
            ->all();
    }

    public function getPromotedRandomVehicles($limit)
    {
        return Vehicle::find()
            ->with(['user', 'carModel', 'carBrand', 'commentCounter', 'channel'])
            ->popular(Vehicle::TYPE_CAR)
            ->language(Module::$langId)
            ->orderBy('rand()')
            ->limit($limit)
            ->all();
    }

}
а в коде сервиса призойдут такие изменения

1. Избавляемся от методов, которые реализуют find'ы, а именно: getPromotedVehicles() и getRandomPromotedVehicles()
2. Метод getVehicles() приобретает след. вид

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

    public function getVehicles()
    {
        $vehicleRepo = new VehicleRepository();
        $promotedRepo = new PromotedVehicleRepository();

        $this->fillVehicles(
            $vehicleRepo->getPromotedVehiclesByIds(
                $promotedRepo->getPromotedVehiclesIds($this->getLimit()),
                $this->getLimit()
            )
        );

        if ($this->notFull()) { // fill the array up to the limit by random vehicles
            $this->fillVehicles($vehicleRepo->getPromotedRandomVehicles($this->getRandLimit()));
        }

        return $this->vehicles;
    }
 
Конечно, вид этого метода может быть лучше, рассматривайте это как пример...

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

p.s. немного запутался, а мысли смешаны, help! :)
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

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

Сообщение zelenin »

m.khartanovych писал(а):Так же после прочтения многих статей на тему репозитория, есть чувство, что использовать этот паттерн в Yii просто нет необходимости, частично AR, Query и Model реализуют эту задачу, насколько правильно ?
абсолютно неправильно. репозиторий - абстракция над хранилищем. AR - абстракции не дает.
m.khartanovych писал(а):Вопрос второй, но если есть желание или необходимость использовать именно этот паттерн, то наверное имеет смысл выбрать Laravel или SF2 где этот подход является более приемлемым. Поправьте пожалуйста если это не так.
этот подход там применяется, посколько в их сообществах заботятся об архитектуре. Какой-то специальной заточки под репозитории нигде нету и не должно быть. Соответственно использовать можно где угодно.

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

class PopularVehicleService extends Object
зря завязались на Object

Вся логика инкапсулирована в одном месте, структура понятна, но ощущение монолитности кода не покидает и есть сомнения в плане тестирования такого кода.

У меня есть желание вынести find'ы в другой класс, какой это может быть класс ? Репозиторий ? Но если следовать этому паттерну, то в первую очередь репозиторий - это интерфейс, а не класс в названии которого есть слово "Repository", что я имею ввиду

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

class SomeRepository {
    public static function getById($id){
        return Model::findOne($id);
    }
}
Подобный класс мало что имеет общего с репозиторием, это просто хелпер который имеет статический метод.

Но тогда вопрос, куда выделить все find'ы ? Или использовать их на прямую в сервисах или выделять в какой-то отдельный класс как пример выше ?

Просто если реализовать для этой цели репозиторий, он возможно будет избыточным и тогда не совсем ясна роль AR и Query... получиться что-то типа

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

interface PromotedVehicleRepositoryInterface
{
    public function getPromotedVehiclesIds($limit);
}

class PromotedVehicleRepository implements PromotedVehicleRepositoryInterface
{
    public function getPromotedVehiclesIds($limit)
    {
        $promoted = PromotedVehicle::find()
            ->joinWith([
                'car' => function (VehicleQuery $query) {
                    return $query->select(['id'])->active()->inUse();
                }
            ])
            ->recently()
            ->last24Hours()
            ->limit($limit)
            ->all();

        return $promotedIds = ArrayHelper::getColumn($promoted, function ($element) {
            return $element['car_id'];
        });
    }
}
 

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

interface VehicleRepositoryInterface
{
    public function getPromotedVehiclesByIds(array $ids, $limit);

    public function getPromotedRandomVehicles($limit);
}

class VehicleRepository implements VehicleRepositoryInterface
{

    public function getPromotedVehiclesByIds(array $ids, $limit)
    {
        return Vehicle::find()
            ->with(['user', 'carBrand', 'carModel', 'commentCounter', 'channel'])
            ->andWhere(['id' => $ids])// IN() condition
            ->all();
    }

    public function getPromotedRandomVehicles($limit)
    {
        return Vehicle::find()
            ->with(['user', 'carModel', 'carBrand', 'commentCounter', 'channel'])
            ->popular(Vehicle::TYPE_CAR)
            ->language(Module::$langId)
            ->orderBy('rand()')
            ->limit($limit)
            ->all();
    }

}
 
а в коде сервиса призойдут такие изменения

1. Избавляемся от методов, которые реализуют find'ы, а именно: getPromotedVehicles() и getRandomPromotedVehicles()
2. Метод getVehicles() приобретает след. вид

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

    public function getVehicles()
    {
        $vehicleRepo = new VehicleRepository();
        $promotedRepo = new PromotedVehicleRepository();

        $this->fillVehicles(
            $vehicleRepo->getPromotedVehiclesByIds(
                $promotedRepo->getPromotedVehiclesIds($this->getLimit()),
                $this->getLimit()
            )
        );

        if ($this->notFull()) { // fill the array up to the limit by random vehicles
            $this->fillVehicles($vehicleRepo->getPromotedRandomVehicles($this->getRandLimit()));
        }

        return $this->vehicles;
    }
Конечно, вид этого метода может быть лучше, рассматривайте это как пример...

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

p.s. немного запутался, а мысли смешаны, help! :)
вообще браво. отличную аналитику провели.

Репозиторий - абстракция над хранилищем. Соответственно мы везде закладываемся на интерфейс, и НЕ создаем экземпляр репозитория напрямую, иначе мы не сможем быстро подменить один репозиторий на другой.
Сервис+репозиторий - да, в вашем случае это похоже на репозиторий в репозиторие, но надо понимать что делает сервис и что репозитория. Репозиторий работает с хранилищем. Сервис осуществляет бизнес логику. В сервисе вы можете кроме сохранения через репозиторий сущности сохранить связанные сущности, отправить почту по факту сохранения, пересчитать какую-нибудь ставку, связанную с сохранением сущности (с помощью внедренного другого сервиса). В общем задача сервиса намного обширна. Если у вас пока нет таких бизнес требований - ничего.
И важный момент: используйте SOLID. Без этого у вас будет имитация архитектуры, которая не даст никаких плюсов, задумываемых ею. https://ru.wikipedia.org/wiki/%D0%9A%D0 ... 0.B8.D1.8F
То есть важно не просто репозиторий и сервис сделать, а понять зачем вы его сделали и чем он облегчит вам жизнь, кроме инкапсуляции какой-то логики.
m.khartanovych
Сообщения: 3
Зарегистрирован: 2016.04.08, 15:35

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

Сообщение m.khartanovych »

Спасибо за Ваш ответ.

Да, вы правы что AR не дает абстракции над хранилищем, но в Yii разве есть другой инструмент ?

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

Для меня, в контексте Yii, репозиторий нужен для того, что бы выделить туда find, save, update, возможно не правильно это называть репозиторием, можно дать другое название классу, но мне важно, что бы find, save, update были отделены от модели или query.

Сервис - тут все проще, обычно сервис затачивается под определенную задачу, в моем примере я создаю instance репозиторию внутри сервиса, но лучше было бы это инжектить в сервис и указывать не конкретный класс, а интерфейс

Вместо

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

// Метод сервиса
    public function getVehicles()
    {
        $vehicleRepo = new VehicleRepository();
        $promotedRepo = new PromotedVehicleRepository();
    ...
    }
 
я бы заменил на

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

class PopularVehicleService extends Object
{
    /** @var int */
    public $defaultLimit = 5;

    /** @var int */
    public $limit = 0;

    /** @var VehicleRepository */
    private $vehicleRepo;

    /** @var PromotedVehicleRepository */    
    private $promotedRepo

    /** @var array */
    private $vehicles = [];

    public function __construct(PromotedVehicleRepositoryInterface $promotedRepo, VehicleRepositoryInterface $vehicleRepo, $config = [])
    {
        $this->promotedRepo = $promotedRepo;
        $this->vehicleRepo = $vehicleRepo;

        parent::__construct($config);
    }
 
В таком случае, мы отталкиваемся от интерфейса, а не от реализации, реализация сокрыта в конкретном классе и сервису все равно какой это класс, главное что у нас есть контроль типа, своего рода контракт.

Сервису важно что бы были соблюдены контракты - интерфейсы и его задача выполнить логику: заполнить массив, сделать проверку, что-то посчитать, отдать результат.

Так же у меня нет задачи разбивать на мелкие модули ради мелких модулей. Я это стараюсь делать для того, что бы:
1. классы имели свою ответственность
2. что бы не приходилось по всем контроллерам менять например query цепочку, а сделать это в одном месте
3. тестирование
4. рефакторинг
5. поддержка

По 4-му и 5-му пункту - благо в Yii есть встроенный инструмент для генерации документации классов, мне кажется это полезным.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

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

Сообщение zelenin »

m.khartanovych писал(а):Да, вы правы что AR не дает абстракции над хранилищем, но в Yii разве есть другой инструмент ?
есть кверибилдер, есть прямые запросы в БД. В Симфони например вообще нет БД-слоя.
но не в этом суть. Смысл репозитория в том, что вы через единый интерфейс можете общаться с хранилищем, зная что оттуда получите. А что там внутри (AR, PDO, http-клиент, file_get_contents) вы знать не должны. Таким образом вы можете иметь админку с репозиторием на AR, а фронт с репозиторием на голых запросах.
m.khartanovych писал(а):Так уж получилось, что проект написан с использованием Yii фреймворка, мне не хочется внедрять что-то инородное, тратить на это время и деньги заказчика, но в то же время мне хочется поддерживать код в чистоте и порядке, но без фанатизма, так как ничего идеального не бывает.
простой сервисный слой - уже неплохо.
m.khartanovych писал(а):Для меня, в контексте Yii, репозиторий нужен для того, что бы выделить туда find, save, update, возможно не правильно это называть репозиторием, можно дать другое название классу, но мне важно, что бы find, save, update были отделены от модели или query.
думаю, это банально должен быть PostService с методами find, save, update.
m.khartanovych писал(а):Сервис - тут все проще, обычно сервис затачивается под определенную задачу, в моем примере я создаю instance репозиторию внутри сервиса, но лучше было бы это инжектить в сервис и указывать не конкретный класс, а интерфейс

Вместо

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

// Метод сервиса
    public function getVehicles()
    {
        $vehicleRepo = new VehicleRepository();
        $promotedRepo = new PromotedVehicleRepository();
    ...
    }
я бы заменил на

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

class PopularVehicleService extends Object
{
    /** @var int */
    public $defaultLimit = 5;

    /** @var int */
    public $limit = 0;

    /** @var VehicleRepository */
    private $vehicleRepo;

    /** @var PromotedVehicleRepository */    
    private $promotedRepo

    /** @var array */
    private $vehicles = [];

    public function __construct(PromotedVehicleRepositoryInterface $promotedRepo, VehicleRepositoryInterface $vehicleRepo, $config = [])
    {
        $this->promotedRepo = $promotedRepo;
        $this->vehicleRepo = $vehicleRepo;

        parent::__construct($config);
    }
В таком случае, мы отталкиваемся от интерфейса, а не от реализации, реализация сокрыта в конкретном классе и сервису все равно какой это класс, главное что у нас есть контроль типа, своего рода контракт.
все верно. контракт это и называется.
Закрыто