А почему мы не пишем код в контроллерах?

в 15:13, , рубрики: .net, ASP, ASP.NET, C#, code, Совершенный код

Я думаю, многие из Вас слышали мнение о том что кода в контроллерах быть не должно, и потому контроллер с методами в одну строку считаются «Best Practice».Я в свою очередь сомневаюсь в том, что польза от этого так уж велика. Если у Вас возникали похожие мысли, прошу под кат.

image

Всем привет! Сразу хочу сказать что моё мнение не является истинной, и цель сего поста это высказать своё мнение, и услышать комментарии других. Все сказанное относится в реализации API, если у вас MVC с вьюшками, то этот кейс не сработает ибо в таком случае в контроллерах лучше писать уже только логику с View

Что не так?

Я точно уверен что многие из вас работают с типичным CRUD приложение с 3-х слойной архитектурой (больше слоев кстати нет, но об этом как то в следующий раз). И в этой архитектуре у вас есть слой работы с данными (дальше DA), слой бизнес логики (дальше BL), и слой вью (дальше VL).

BL может быть сделан по разному, я встречал 2 варианта:

  • Class — просто класс в котором есть куча зависимостей и методы. Каждый метод описывает какой-то бизнес флоу, к примеру передача денег, авторизация, регистрация. Этот класс использует более низкоуровневые вещи такие как IRepository для работы с бд, различные API клиенты для других сервисов и тому подобное, в общем на этом слое собирают все модули вместе и делают бизнес логику.
  • CQS — На каждый бизнес флоу создают DTO (CommandQuery) классы, это просто входящие параметры в наш обработчик. Этот способ становится более популярен так как лучше делится ответственность и этот обработчик не имеет так много зависимостей.

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

Рассмотрим пару реализаций в этом стиле, а потом я перенесу код в контроллеры и мы проанализируем что ж мы потеряли.

image

image

В таком виде мы реализовываем все методы и дальше просто их вызываем на уровне API:

image

Я надеюсь вы понимаете что код с Command -> Handler будет аналогичен, просто больше разделен.

И мне вечно не дает покоя, зачем я делаю эту дополнительную работу?

  • Чтобы переиспользовать код? — Нет, флоу перевода денег не подойдет для флоу начисления бонусов.

    К тому-же если вы даже найдете возможность совместить 2 фичи, вы рискуете сломав одну — автоматически сломать другую

  • Чтобы перенести вызов кода в другое место? — Возможно, но это бывает так часто? Да и переносить то не обязательно, кто Вам запретил резолвить экземпляр контролера закрытым под IUserService?
  • Тестирование? Контроллеры точно так же тестируются, а подняв TestServer вы практически напишите end2end тесты.

А теперь посмотрим на черную магию

Давайте уберем лишнего.

image

image

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

Я считаю что ASP NET отлично абстрагировал нас от работы с HTTP, у нас есть наилучшее место где мы оперируем нашими типами. Повторюсь, если у Вас есть вьюшки, тогда в контроллерах лучше писать код только для View, а в сервисах писать переиспользуемые методы для получения данных для View. Но в текущих реалиях все чаще у нас API + SPA.

Валидация?

ASP NET Core Pipeline очень хорошо тюнится и имеет массу решений, взгляните на FluentValidation, вы добавите валилдацию даже не меняя кода в контроллерах.

Хотите больше разделения?

Разделяйте интерфейс и если нужно реализацию тоже.

image

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

Подключение других каналов

Если мне скажут что у нас может появиться ещё один канал, к примеру через очередь, я просто могу получить экземпляр контроллера и использовать его в другом канале. Этот контроллер легко резолвится из DI. Кроме того ASP NET достаточно гибкий и некоторые каналы можно научить его обрабатывать самому, опять-таки модифицируя пайплайн.

image

Забавный факт

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

Итого

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

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

Я считаю что контроллеры это и есть те самые BL Service или CommandQueryHandler, и в своих проектах я практикую этот подход и контролеры делю очень хорошо, рекомендую и Вам попробовать.

Ответственность фреймворка мы ни как не увеличили, у него была задача:

  1. Принимать запросы
  2. Маппить их в модели
  3. Вызывать указанный нами код по каким то правилам
  4. Отдавать ответы

Она у него и осталась.

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

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

Автор: BashkaMen

Источник


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js