Каким должен быть правильный контроллер?

Время от времени каждый из нас смотрит чужой код, чужие проекты, и порой приходится думать почему автор поступил именно так, а не иначе.
Особенно остро для меня сейчас встал вопрос логики в контроллерах, особенно для простых CRUD операций.
в Yii2 обычно весь CRUD в одном контроллере с разными названиями методов. Иногда копаясь в чужих проектах, вижу что каждое действие выносят в отдельный контроллер, и в итоге ради CRUD к одной модели создаётся 4 контроллера. Аналогичную картину видел и в Laravel - либо генерируют resource, либо также отдельными файлами, либо более хитрым способом - в одном контроллере, но различают действия не по названиям методом, а по типу пришедшего запроса (POST/GET/DELETE).
Видя такое разнообразие, хочется все таки узнать, а как все таки правильнее будет?
Правильнее не просто для себя, а также для всех, кто после меня придет на проект.
  • Вопрос задан
  • 6311 просмотров
Решения вопроса 3
@D3lphi
Контроллер должен быть тонким. Именно это и есть "правильно". Сами по себе контроллеры берут на себя, например, валидацию входных данных, построение http ответа и тд. Все логика должна выносится в сервисы. Чтобы вы понимали, сервис - это просто класс, зачастую, без хранения состояния и с единственным методом, который содержит бизнес логику. Из контроллера будет вызываться метод этого сервиса, тот будет возвращать результат. Контроллер будет преобразовывать этот результат в нужный формат, дабы отдать его пользователь в нужном виде. Сервисы можно не использовать в небольших приложениях. Согласитесь, может быть излишним создавать класс ради того, чтобы, грубо говоря, сложить 2 и 2. В документации Laravel почти все пишется в контроллерах по причине упрощения код. Дабы было проще понять саму суть.
Ответ написан
sanek_os9
@sanek_os9
Работаю с Laravel, Vue, Vuetify, AWS Amazon, Linux
Нету единого правильного способа реализации MVC, поэтому в Laravel, Yii2 и других фреймворках и проектах реализовано правильно, но по своему. Вам же стоит использовать не какой-то "правильный" подход реализации, а более удобен и понятен вам, не зацикливайтесь сильно о других людях которые придут на ваше место потому что всем не угодить, некого универсального подхода нету, достаточно тщательно комментировать свой код и писать код так что бы его легко было масштабировать и вносить любые другие правки.
По поводу холиварах о том какой должна быть модель, выдержка из статьи:
В описании оригинальной реализации MVC в Smalltalk упоминается о пассивной и активной модели. Пассивная модель не осведомлена о существовании представления, контроллера, и даже о своем участии в MVC-триаде. Контроллер отслеживает изменения модели и оповещает представление. При этом либо контроллер передает представлению информацию об изменениях, либо представление самостоятельно выбирает данные из модели. Более изящным решением является активная модель. Активность модели проявляется в ее праве самостоятельно оповестить представление об изменении своего состояния. Чтобы не нарушить основное требование MVC о независимости модели от представления и контроллера, механизм оповещения реализуется на основе шаблона проектирования Observer.

А так же сама статья рекомендуемая к прочтению: rsdn.org/article/patterns/generic-mvc.xml
Ответ написан
Комментировать
onqu
@onqu
weasy
Никаким. Его не должно быть. Вообще. Только сразу камнями не закидываете.

Разъясню.

1. Контроллеры в большинстве случаев не имеют состояний, а объект без состояния лишь дополнительное пространство имен для функций.

2. Реализации
В небольших приложениях в контроллеры наваливают бизнес логику, чтобы не тратить время на построение архитектуры. Оно себя оправдывает. Да мы лишаем себя возможности модульных тестов, но функциональные остаются рабочими. И это является противоречием назначению контроллера.
class RobotsControlller
{
    public function inactiveRobots()
    {
        $robots = Robots::find()->where(['active' => false])->all();
        foreach ($robots as $robot) {
            $robot->active = true;
            $robot->save();
        }

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


В проектах средней величины уже пытаются выносить бизнес логику в сервисы. Сервисный слой это и есть модель из множества классов. В контроллере вызывается один-два из этих сервисов и данные передаются в представление. И все бы хорошо, связующее звено для сервисов..., но это грамотно завуалированная бизнес логика, которую отчетливо видно лишь в крупных проектах.
class RobotsControlller
{
    public function inactiveRobots()
    {
        $container = $this->getContainer();
        $robotsService = $container->getRobotsService();
        $robots = $robotsService->getInactiveRobots();

        $stationService = $container->getStationService();
        $stationService->acivateRobots($robots);

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


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

3. Как же все-таки правильно.
Отказываться от контроллеров и пилить свой велосипед на текущий момент слишком затратно по времени на всех проектах меньше больших. Мой ответ будет прост - использовать здравый смысл, сохранять семантику и не забывать об удобстве. Называете вещи своими именами и придерживайтесь однородности. Если строите api на типах resource (get | delete | put | post) то придерживайтесь этого стиля, не мешайте со стандартными экшенами. Разобравшись в одном будет проще понять работу всего остального.

4. Примеры из жизни
Рассмотрите любые примеры из реальной жизни. Например, процедуру покупки продуктов в супермаркете или покупки билета в метро. Контроллером является кассир, который принимает входящие данные (товары и деньги) и отдает представление (чек). Но со временем роль кассира начинает выполнять автомат, покупатель сам пробивает товары или покупает билет на проезд. Контроллер поменялся. Через еще некоторое время билет покупать уже не требуется, есть просто телефон с NFC, а в супермаркете просто получаем товары по заранее отправленному с этого же телефона заказу.

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

p.s. можно начинать кидать камни)
Ответ написан
Пригласить эксперта
Ответы на вопрос 4
Deroy
@Deroy
Senior Developer, Software Architect
Отвечу с позиции опыта на больших и средних проектах:

Как говорят другие ответчики - D3lphi и Александр Шаповал , контроллер должен содержать минимум логики, и нет "единого правильного способа" его реализовать.

Однако, исходя из своей практики, для обеспечения таких качеств проекта как сопровождаемость, тестируемость и изменяемость, я поступаю следующим образом:

Контроллер, каким бы он не был и как бы не организовывался - содержит только обработку входных данных (валидация и преобразование) и преобразование выходных данных в нужный формат

Логика и оркестрация которая в 80% случаев и является предметом спора о том как стоить контроллер (речь всех этих вызовах сервисов, управления потоками данных между ними, последовательности действий и пр), находятся в отдельном классе согласно паттерну Unit of Work - что позволяет легко покрыть все что связано с логикой вменяемыми юниттестами без танцев с бубном.

Т.е. структура в итоге такая:

{view} <--> {controller} <---> {unit_of_work} -->>> {{{..lots of services...}}}

по сути класс семейства UnitOfWork - это полный сценарий обработки входных и выходных данных , с оркестрацией потоков данных и порядком действий, возможно какой то мелкой вспомогательной логикой, эдакий фронтенд над сервисами, заточенный под каждое конкретное действие извне (сам паттерн предполагает что класс создается на каждый отдельных случай и почти один в один описывает UseCase логику как она пришла от бизнеса (заказчика/аналитиков/моей головы/etc).

Сами классы семейства UnitOfWork (имеется ввиду в одном приложении) могут иметь собственные иерархии наследования и композии - в угоду бизнес логики и дабы не дублировать код - главный критерий - это полная независимость бизнес и сервис слоев от контроллера. - что и является на мой взгляд "каноничным" вариантом реализации композии M и C в MVC.

У меня, как у человека который не пишет фронты в принципе (имею ввиду сам UX/UI), но постоянно занимается сервисами, рестом, библиотеками - этот подход выработался по одной просто причине:
взаимодействие с командой, я предоставляю "библиотеку"(не в классическом понятии этого слова) полностью абстрагированную от любых фронтов, оттестированную, полностью функциональную - а люди пишут к ней шаблонные обвязки где только валидация данных и рендер нужного формата под HTTP/Sockets/AMQP/etc типа всяких там CRUD/REST или RPC
Ответ написан
Комментировать
действия не по названиям методом, а по типу пришедшего запроса (POST/GET/DELETE).

Почитайте учебник по HTTP и REST
в чужих проектах [...] ради CRUD к одной модели создаётся 4 контроллера

Не читайте давнокод.
Ответ написан
Комментировать
Он должен ни фига не делать :) Только вернуть быстрый ответ
Ответ написан
Комментировать
На самом деле контроллер связующее звено.
Он должен быть тонким, но не более чем нужно.

И стоит рассказать в данном контексте о модели. Она может быть простой и смешиваться с энвироментом в виде эктив рекордс. Или сложной. Иметь сверху обёртку в виде реализации сервисов, связывающих её с внешним миром, например, репозиториями, внешними приходящими данными и т.д.
В контроллер стоит вынести приём данных из внешних источников, работу с правами, реакцию на события (логирование, отправка почты и т.д.). Причём реакция - это не обязательно исполнение действий напрямую, но так же постановка их в очередь для обработчика.

И вот от сложности самой модели и её обёртки зависит тонкость контроллера.
Чем проще реализация модели, тем толще контроллер.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы