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

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

    image

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

    Что не так?


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

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

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

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

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

    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 или Command\QueryHandler, и в своих проектах я практикую этот подход и контролеры делю очень хорошо, рекомендую и Вам попробовать.

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

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

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

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

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

    Подробнее
    Реклама

    Комментарии 265

      +1
      «Я думаю многие из Вас слышали мнение о том что кода в контроллерах быть не должно, и потому контроллер с методами в одну строку считаются «Best Practice».Я в свою очередь сомневаюсь в том, что польза от этого так уж велика.»
      Зря сомневаетесь, была бы возможность я бы вам показал код пары контроллеров крупных сайтов рунета, вы бы подофигели и не задавли бы глупых вопросов почему в контроллере кода должно быть меньше.
      По той простой причине, что если его оттуда не разносить по другим местам (маррерам, сервисам, билдерам) вы очень скоро перестанете понимать что там происходит
        +19
        Я не писал в статье о том, что мы должны отказаться от сервисов, мапперов и прочего.
        Я видел контроллеры с большим и кол-вом кода, разделив его на несколько контроллеров, проблема решается.
        Спрятав говно в другое место, и обернув его для вызова в одну строку, ситуация ни как не изменится.

        И статья описывает ситуации где силы вкладывают в то что б в контроллере было не больше чем 1 строка в каждом методе — тоесть делать и контроллера тупой враппер.
          +3
          А как вы создав контроллер узнаете сколько в нем будет строк?
          Если там есть какой то сервис, то должен быть наверное и маппер, уже одной строки не будет. А со временем получается что в контроллер инжектится уже штук 5-10 этих самых сервисов. Если не разносить это все, это будет ад.
          Например у вас есть интерет магазин, вам нужно обработать заказ, возьмем не микросервисы, а старый класс. MVC. Вам нужно взять юзера, создать заказ, добавить в него товары, отправить это все обратно, отправить нотификейшн по e-mail и sms. Вы предлагаете все эти действия реализовать в контроллере или искуственно тут что-то поделить на несколько контроллеров?
            +2
            может будет много строк, а может и не будет. Это гадание на кофейной гуще. Если обработчик укладывается в простейшую декларативную логику, то вынос его в сервисы просто потому что так надо это лишняя работа. Я думаю автор об этом. Нет никакой проблемы, чтобы простые действия сначала писать в контроллерах, а потом вернувшись к ним за масштабными усовершенствованиями вынести их в сервис. Все должно происходить по требованию, а не ради фапа на идеальную архитектуру.
          +1
          Спрятав говно в другое место, и обернув его для вызова в одну строку, ситуация ни как не изменится.
          Ну, начнем с того, что если писать говно то его хоть как размазывай, а суть не изменится.

          Я видел контроллеры с большим и кол-вом кода, разделив его на несколько контроллеров, проблема решается.
          Аналогично, могу сказать что такой подход совершенно не решает проблемы. Не то что видел такое, я с таким работаю в данный конкретный момент. Один метод может занимать по четыре сотни строк кода и это при учете что внутри вызывается пара десятков других сервисов. Что уже используется валидация через FluentValidator. Что логирование и обработка ошибок централировано вынесена в filters. Что используется мапер.

          Я в свою очередь сомневаюсь в том, что польза от этого так уж велика
          Мы как инженеры работаем со сложностью от которой невозможно избавиться. Ей можно пытаться манипулировать. Можно написать один метод на тысячу строк, а можно структурировать код на уровни. Вынести логику работы с базой, выделить бизнес логику, подготовку данных для отображение и закрыть все это абстракциями. Поддерживать общность и консистенцию кода, слабую связность между реализациями различных уровней. Что бы каждый кусок был прост в понимании и редактировании, но разобрав как работает один контроллер было легко понять как работают остальные.

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

          Действительно. Если проект будет жить месяц, если его не планируется поддерживать и развивать то конечно проще и целесообразнее описать весь код в контроллере и не париться. Но если планируется расширение то очень скоро вы придете к тому что разница между реализациями отдельных контроллеров начнет душить вашу архитекруту. Потому что не будет единства, не будет возможности легко вносить общность в код. Понимать каждый контролер придется с нуля потому что каждый член команды пишет по своему и смотря с какой ноги встал сегодня утром. И поверьте мне, люди чаще всего встают не с той ноги.

          Личный опыт в подобных вопросах это все равно что «Синдром выжившего». И до тех пор пока вы не сталкнетесь с бизнес логикой уровня чуть выше чем «за что купил — за то продал» вряд ли удастся осознать зачем напридумали всяких там шаблонов, SOLID и написали несколько толстых книжек такие товариши как Макконел, Фаулер, Мартин.

          Еще раз подчеркну: Много простого — хорошо! Сложно, но Мало — очень очень плохо! В первом случае каждый конкретный момент вы будете работать с емкой, конкретной функциональностью отдельного уровня абстрации. Во втором случае в любой момент вы будете иметь честь по локоть возиться в том самом, что вы так настойчиво не хотели скрыть за абстракциями.
          +1

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

            +1

            Ну вот в данных примерах контроллер не тупо переадресует, а, средствами фреймворка, преобразует http-запросы во что-то более-менее нейтральное, независимое от протокола.

              0

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

          +3
          Потому что если сложность вашего приложения превосходит туду приложение, то ваши контроллеры превращаются в помойку.
            +7
            эээм, вы не поняли что я говрю
            Чем код с первой реализации лучше второй?
            Я показал наглядный пример, была одна строка, под не логика
            В чем выигрыш что ты спрятал все под одной строкой? (в контролере userService.Do....)
            +16
            Меня тоже терзали эти мысли, но больше не терзают. Намучался называется…
            У контролера и сервиса с бизнес логикой разные задачи. Задача контролера заключается в обработки запроса. Не важно это http или grpc. Для этого у него есть все необходимые данные. Тело запроса, параметры, куки и все остальное. Он их анализурует и решает что делать дальше. Сервис же обрабатывает бизнес задачу. Для этого у него есть свои инструменты. Это две разные абстракции. Смешивание двух абстракций в одном методе точно увеличивает его размер(нужно писать логику для двух сразу), что увеличивает вероятность что-то недопонять когда ты к нему вернешся. Если же используются отдельные сервисы, то будет фигурировать как минимум один метод с логичным названием, что существено упростит понимание и поиск это места в будущем(что наверно даже болие важно).

            Сам все больше склоняюсь к использованию библиотек по типу MediatR. С ними искать и писать можно еще более точечно и весь контекст для выполнения задачи находится в одном месте. При этом исключается возможность случайно создать контролер с 10 зависимостями в конструкторе, что происходит почти всегда в особо больших проектах.
              0
              Рад что кто-то понял меня :)
              Я к этому в статье и подводил, писать контроллеры на подобии хендлеров, то-есть тоже точечные
              С медиатором контроллер выглядит
              _mediator.Send(...)
              Команда в основном биндится из тела запроса
                0
                Ну вот, товарищ выше написал про MediatoR и я сразу полез смотреть что это, выглядит интересно. У вас же в примерах мешанина, включая репозитории в контроллерах.
                Напишите статью про MediatoR, вот это б интересней было
                  0
                  таких полно, и во всех этих статьях контроллеры с одной строчкой кода
                  mediator.Send — опять таки простая прокся к нашей настоящей логике
                    0
                    Ну я не припомню чет, да и лишний раз не будет напомнить. В большинстве проектов не особо парятся над разделением, поэтому еще одна статья про медиатор не помешает мне кажется, если есть желание написать, я б с удоваольствием почитал
                +1
                При этом исключается возможность случайно создать контролер с 10 зависимостями в конструкторе, что происходит почти всегда в особо больших проектах.

                Становится ли от этого легче? Теперь вместо 10 зависимостей у контроллера 1 зависимость медиатр и по конструктору нельзя даже предположить, что делает и чего не делает контроллер. Напоминает service locator, не находите?
                  0
                  Предположим, что вот вам нужно починить багу. Вы нашли роут с которым не все в порядке. Открываем контролер и видим следующий конструктор.
                      public SomeController(
                          ISearchResultManager searchResultManager, 
                          IStorageManager storageManager, 
                          IMediaManager mediaManager, 
                          ISearchResultsExportManager searchResultsExportManager, 
                          IElasticSearchManager elasticSearchManager, 
                          IElasticSearchService elasticSearchService, 
                          ISearchHistoryService searchHistoryService, 
                          IAgentManager agentManager, 
                          IElasticSearchOptionsCreator elasticSearchOptionsCreator, 
                          ISearchResultCreator searchResultCreator, 
                          IWorkInProgressManager workInProgressManager, 
                          ICommonManager commonManager)
                      {
                          this.searchResultManager = searchResultManager;
                          this.storageManager = storageManager;
                          this.mediaManager = mediaManager;
                          this.searchResultsExportManager = searchResultsExportManager;
                          this.elasticSearchService = elasticSearchService;
                          this.searchHistoryService = searchHistoryService;
                          this.agentManager = agentManager;
                          this.elasticSearchOptionsCreator = elasticSearchOptionsCreator;
                          this.searchResultCreator = searchResultCreator;
                          this.workInProgressManager = workInProgressManager;
                          this.commonManager = commonManager;
                          this.elasticSearchManager = elasticSearchManager;
                      }
                  


                  Вам понятно что делает следующий контроллер? Я бы сказал что он может делать что угодно. Ладно, посмотрим сам обработчик нужного роута, там одна строка:
                  searchResultManager.GetSearchesByAgentId(agentId);
                  

                  Ага значит проблема в searchResultManager. И тут у меня возникает два вопроса. Первый, зачем было инициализировать все остальные зависимости? Все ресурсы на их инициализацию были потрачены в пустую. Второй, как мне помогло знание того, что этот контролер требует 12 зависимостей? Как по мне, оно только ввело в заблуждение.

                  С медиатором ты знаешь что нужно смотреть сам вызов медиатора и если там нет ничего http специфичного, то в обработчике будет одна строка.
                    +3
                    Вам понятно что делает следующий контроллер? Я бы сказал что он может делать что угодно.

                    Ну так он и правда делает что угодно, вы совершенно правильно подумали. Его давно пора декомпоновать.

                    • НЛО прилетело и опубликовало эту надпись здесь
                        0
                        Ваше предложение? Убить всех джунов, «не совсем професионалов с большим опытом работы» и индусов?
                        • НЛО прилетело и опубликовало эту надпись здесь
                          0
                          MediatR позволяет локализовать проблему. Пускай у нас будет один роут с 6+ зависимостями, а не 10 роутов с 10+, только из-за того, что они случайно оказались в одном контролере.
                          0
                          Первый, зачем было инициализировать все остальные зависимости? Все ресурсы на их инициализацию были потрачены в пустую

                          Если дело именно в оптимизации инициализации — я бы рассматривал это в последнюю очередь. Красивый и производительный код по моему опыту часто противоречат друг другу. Если сильно хотите — эти зависимости можно сделать ленивыми.
                          Второй, как мне помогло знание того, что этот контролер требует 12 зависимостей? Как по мне, оно только ввело в заблуждение.

                          Ну, у меня логика простая — явное лучше неявного. Соответственно я вижу 10 зависимостей и понимаю:
                          1. Возможно, контроллер плоховато написан и его стоит переписать.
                          2. Да, у него 10 зависимостей, но у него нет 11 и 12.
                          В случае с медиатр вы видите красивое IMediatr, контроллер до сих пор делает кучу вещей, только это абсолютно не очевидно.
                          Про Service Locator:
                          Не совсем понял что имеется виду. Можете уточнить?

                          Есть такой паттерн, который предполагает что вместо того, чтобы брать зависимость от сервиса, вы берёте зависимость от «локатора», который умеет эту зависимость доставать. В итоге с точки зрения конструкторов (наиболее частый способ DI в моей практике) мы имеет IServiceLocator везде, но зато в каждом методе мы можем волшебно забрать себе хоть 10 сервисов.
                          Подробнее можно глянуть, например, тут.
                            –2
                            Тут не будет никакого IServiceLocator. Все зависимости буду зарезовлены через DI во внутрь конструктора. Вот пример обработчика MediatorR:
                            public class Import : IRequestHandler<ImportRequest>
                            {
                                public Import(ISomeRepositoty repository, ISomeService service)
                                {
                                    // ...
                                }
                            
                                public async Task<Unit> Handle(ImportRequest request, CancellationToken cancellationToken)
                                {
                                    // ...
                                }
                            }
                            
                              +1

                              Вот только сам MediatorR в качестве сервис локатора и выступает.

                                0
                                Почему тогда нельзя сказать что контролер выступает в качестве сервис локатора при обычном подходе?
                                  0

                                  Во-первых, потому что все его зависимости статически декларированы (ну. обычно). Во-вторых, потому что его вызывает не пользовательский код, а ASP.NET.

                              0
                              В случае с медиатр вы видите красивое IMediatr, контроллер до сих пор делает кучу вещей, только это абсолютно не очевидно. 
                              

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

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

                                  0
                                  В случае с медиатр все очевидно, контролер не делает ничего связаного с бизнес логикой

                                  Этого можно добиться и без него. Достаточно обернуть ваш хендлер из примера выше в абстракцию и брать зависимость в контроллере на неё, а не на ISomeRepositoty и ISomeService. Таким образом, контроллер будет явно в своём конструкторе говорить, сколько бизнес-операций он совершает. Если много — дробим.
                                  На моей практике контроллеры с 5+ операциями встречались относительно редко, а если и встречались — эта проблема решается не тем, что мы просто скрыли весь код за медиатором.
                                  Не холивора ради, я искренне не понимаю, почему скрыв код за неявной фабрикой хендлеров мы считаем, что он стал лучше. Сложность класса-то от этого не поменялась, он как делал Х вещей, так и делает их, только теперь об этом не узнать.
                              0
                              Напоминает service locator, не находите?

                              Не совсем понял что имеется виду. Можете уточнить?
                            +6

                            Не буду ничего объяснять (это сто раз сделали до меня более умные люди), а просто приведу контрпример.


                            Поступила задача, продолжая поддерживать REST-ish API, реализовать GraphQL API (пример). Как будете делать?

                              0
                              Добавлю GraphQLMiddleware и с небольшими изменениями моделей будут так же валидно обрабатывать запрос
                                +2

                                Изменять модели под протокол клиента? Ох.


                                Да и конвертация из одного протокола в концептуально другой без крайней необходимости (адаптер к closed source) — это, как бы, попахивает. Уже вижу костыли.

                                  +2

                                  тут приходят третий, четвертый и пятый интеграторы и последовательно заявляют: вебсокеты, TCP, UDP, GRPC, да хоть через AMPQ интегровать будем.

                                    0
                                    Ну сокеты уже реализованы и поставив один нагет мы запросто получаем «контролеры» для сокетов
                                      0
                                      Вы имеете в виду из-за Project Bedrock можно реализовать такие же «контроллеры» поверх любого транспорта?

                                      Если бы количество транспортов не было бы так сильно ограничено и сложность реализации своего транспорта была не так велика, то я бы с удовольствием использовал такое решение.
                                –1

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

                                  0
                                  А типичная реализация UserService так же имеет кучу лишних методов.
                                  Флоу регистраци
                                  Флоу авторизаци и тд
                                    +3

                                    Подобные "сервисы" — это тоже антипаттерн.

                                      0
                                      Но ведь они есть, не везде используют CQS подход
                                        –2

                                        И без CQS можно. Главное — отказаться от анемичных моделей.


                                        А что где "есть" — я уж в курсе, я многое повидал. Хорошее редко увидишь.

                                          0
                                          Лично мне нравятся анемичные модели, чтобы entity не выглядел как зверь с тысячью зависимостей
                                            +1

                                            Если тысяча зависимостей, то, скорее всего, нарушается SRP

                                              –2
                                              Значит, по SRP, Entity должна быть анемичной. Она должна уметь только храниться в базе и всё, не должна иметь методов для обработки бизнес-процесса перемещения денег с одного аккаунта на другой, отправки оповещений по SMS и E-Mail, и т.п.
                                                +1

                                                ActiveRecord, да, нарушает SRP by design. DataMapper — не требует от Entity уметь храниться, это отвественность ORM.


                                                Перемещение денег с одного аккаунта на другой — ответственность Entity навскидку. Отправка оповещений — скорее нет, максимум — генерация доменного события "деньги перемещены", а уж сервисы приложения, слушая это событие, вызывают инфраструктурные сервисы оповещения.

                                      0

                                      Да, UserService довольно сложен. Вообще AspNet Identity не самая прозрачная библиотека. Спасает только то, что есть код из примеров.
                                      Я правда не понимаю, зачем оправдывать усложнение кода другим сложным кодом. От того что в nuget на пакете написано microsoft легче читать его не становится.

                                      0
                                      А когда в контроллере куча вспомогательного кода, и он перемешан из разных сценариев, оно просто не помещается в голове и я начинаю думать о том, что не контролирую происходящее.
                                      А в чем проблема вынести часть логики в сервис, когда если в контроллере накопится куча вспомогательного кода?
                                        0
                                        Автор статьи хочет писать код в контроллерах. Вынесение кода в сервис или в хэндлер будет противоречить его основной идее.
                                        Я то как раз за тонкий контроллер. И за инкапсуляцию логики в отдельных сервисах\обработчиках в зависимости от ситуации.
                                          0
                                          Повторюсь, если у Вас есть вьюшки, тогда в контроллерах лучше писать код только для View, а в сервисах писать переиспользуемые методы для получения данных для View. Но в текущих реалиях все чаще у нас API + SPA.
                                          Автор статьи хочет писать код в контроллерах. Вынесение кода в сервис или в хэндлер будет противоречить его основной идее.
                                          Автор не против вынесения кода в сервис. Идея не в том, чтобы весь код фигачить в контроллерах. Идея в том, чтобы выделять сервисы только при необходисти, а не по умолчанию.
                                            0

                                            А если по умолчанию считать, что http надо отделять от BL? Что код, который знает что-то об http не должен находиться в одном классе с BL?

                                              +1

                                              Код в примерах не разу не работал с http, даже есши бы нужно было бы работать с сессией я бы выделил бы какой-то ISessionStoeage и работал бы через него, дабы не обращаться к HttpContext или еще каким-то зависимостям фреймворка

                                              • НЛО прилетело и опубликовало эту надпись здесь
                                                  +1

                                                  … непонятно тогда, почему этот код все еще контроллер.

                                                    0

                                                    В .Net Http.post("login") не для работы с http используется?

                                                  0

                                                  Да, именно это хотел донести, спасибо! :)

                                                    0
                                                    Идея в том, чтобы выделять сервисы только при необходимости, а не по умолчанию.
                                                    Это хорошо, когда у проекта 1-2 разработчика. А если их 5, и у каждого своё мнение насчёт «необходимости». Тут лучше ввести правила, как code style, чётко им следовать, и не надо будет ломать голову: тут уже надо вводить сервис, или пока не надо (а это тоже время)
                                                      0
                                                      Да, именно это хотел донести, спасибо! :)
                                                +6
                                                Я занимался сопровождением проекта, создатель которого выбрал как раз такой подход к разработке. Распухание контроллеров это первая проблема, но не единсвтенная. В какой-то момент от нас потребовали добавить поддержку очередей, но в коде «сервисов» оказалось много зависимостей от HttpContext, и обрабатывая сообщение из очереди они часто падали в не предсказуемых местах.
                                                  +3
                                                  Почти все комментарии твердят о распухании контроллеров, а почему не кого не заботит что они оставили худой контроллер, и распухает BL?
                                                    +2

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


                                                    Рекомендую ознакомиться с подходами:


                                                    • Hexagonal Architecture,
                                                    • Domain Driven Development,
                                                    • CQRS.
                                                      +8
                                                      Мой комментарий не про распухание класса, а про то что у контроллеров есть особенности которых не должно быть у сервисов. Использование HttContext, правила авторизации, маршрутизации и тд. А чистые сервисы могут иметь фишки которых не будет в контроллере, например быть обощенными.
                                                        +3

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


                                                        Если по теме — то ваш подход может быть вполне оправдан для проектов, время жизни которых заведомо меньше времени устаревания фреймворка. В иных случаях может оказаться, что бизнес-логику написали в коде windows forms компонентов (потому что зачем усложнять, выделяя BL?), и теперь дешевле все выкинуть и начать сначала. Аналогично, через 10 лет такую же брезгливость будет вызывать бизнес-логика в контроллера, прибитых гвоздями к допотопному ASP. Net Core 3.1.

                                                        +9
                                                        кстати да, плюсую, когда мы в Job.ru начали пилить мобильное приложение, то вдруг выяснилось что внутри половины сервисов есть зависимости от HttpContext. И пришлось тратить кучу времени что бы это разнести, так что подход автора считаю не верным, даже вот по этой причине. А он нам именно мешанину предлагает
                                                        • НЛО прилетело и опубликовало эту надпись здесь
                                                          –2

                                                          Хорошая статья! Спасибо!
                                                          Бэст практисес в бизнес реалиях в большинстве случаев не работают.
                                                          В наших реалиях говнокод делает деньги. Особенно для энтерпрайз галёр. И такой подход тут — в самый раз. Для mvp некоторых тоже подойдёт. Только нужно покрывать тестами всё, чтобы можно было быстро отрефачить. :)

                                                          • НЛО прилетело и опубликовало эту надпись здесь
                                                              0
                                                              Контроллер нужен для того, чтобы преобразовывать HTTP-запрос в POCO и вызывать метод сервиса/команду, а затем возвращать результат в виде HTTP-ответа


                                                              Это он все делает сам, и код мы этот даже не видим. Мы уже достаточно хорошо абстрагировались от самого HTTP, но в принципе как я сказал, если у вас есть код для работы с вьюшками, или как вы сказали с http кодами и тд, то конечно стоит отделить контроллер и логику.
                                                              Я пишу такие API где принимаю модели и возвращаю модели, любые исключение ловит ExceptionHandler, то-есть все либо пройдет хорошо, либо вылетит в 500 ошибкой.
                                                              Если тип исключения домменный, то есть мой, я вывожу его пользователю
                                                              • НЛО прилетело и опубликовало эту надпись здесь
                                                                  0
                                                                  Я никогда так API не пишу.
                                                                  Всегда принимаю модель, возвращаю модель.
                                                                  По умолчанию всегда 200\201 код, исключения ловит exceptionHandler который может поставить другой код в зависимости от типа ошибки.
                                                                  Все такие вещи настраиваю немного выше, ведь у меня есть пайплайн
                                                                  • НЛО прилетело и опубликовало эту надпись здесь
                                                                      0
                                                                      так это конфигурацией билда настраивается и конфигами лога, нет?
                                                                      • НЛО прилетело и опубликовало эту надпись здесь
                                                                      0
                                                                      А как настроили swagger чтобы он знал про коды в обработчиках исключений?
                                                                  0
                                                                  И еще на счет gRPC все что поменяется это класс наследник, вместо ControllerBase будет ServiceBase котоый генерится на основании proto файла
                                                                  +2
                                                                  Кажется, что в микросервисах такой подход предпочтителен
                                                                    +1
                                                                    И мне, но почему-то хотят чтоб все были как один, и большие сервисы с кучей оберток, и маленькие
                                                                      +1
                                                                      Потому что микромонолиты
                                                                    +12
                                                                    Привет! Спасибо за статью. Очень смело.

                                                                    Вы доказываете, что механическое перетаскивание кода из класса А в класс Б не приносит пользы. И Вы правы!

                                                                    Потому что в Вашем коде нет никакого BL. И все слои вместе взятые — это одно сплошное императивное редактирование ячеек БД через HTTP запросы. (Как и в большинстве проектов, которые я видел).

                                                                    Я сам противник суеверного разбиения кода на слои. Но советую Вам попробовать выделить реальный BL, и посмотреть на эффект.

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

                                                                    // я про эти строчки
                                                                    fromUser.Balance -= amount;
                                                                    toUser.Balance += amount;
                                                                    


                                                                    Смысл не в том, чтобы назвать слои А, Б, В и менять местами код. Смысл в том, чтобы как можно бОльшая часть задачи решалась на высоком уровне абстракции, удобном для понимания. Но если эта абстракция отсутствует, то как Вы и говорите, толку в перетаскивании кода мало.

                                                                      +4
                                                                      > механическое перетаскивание кода из класса А в класс Б не приносит пользы

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

                                                                      А с другой стороны — все-таки это правильное разделение и нужно найти какую-то конкретную грань когда есть смысл писать БЛ в контроллере, а когда в сервисе
                                                                        0

                                                                        Скорее, БЛ никогда не нужно писать в контроллерах, просто нужно найти грань, где БЛ, а где нет.

                                                                          0
                                                                          Интересная мысль — я с ней согласен, но где проходит эта грань? Например редактирование справочников это БЛ?
                                                                            +1
                                                                            Как по мне, найти грань очень просто. Если это REST API, то это все, что может работать без веб сервера. Это та часть приложения что может жить в сфечисеком вакуме. Ты только дай ей данные и она сделает с ними все необходимое.

                                                                            Исходя из этого, редактирование справочников это точно бизнес логика. REST API получает запрос, преобзовывает его согласно принятым протоколам, отдает на откуп бизнес логике.
                                                                              0
                                                                              > REST API получает запрос, преобзовывает его согласно принятым протоколам, отдает на откуп бизнес логике.
                                                                              Практически все это делается автоматически фреймворком и мы возвращаемся к ситуации в начале статьи. Вот как вариант habr.com/ru/post/505708/#comment_21713234
                                                                                +2

                                                                                Не автоматически, а путём добавления http и подобных аннотаций к коду сервиса, не так ли? В таких приложениях необходимость работы с http возникает с самого начала ведь.


                                                                                Пока контроллер какой-то асбтрактный, для тестов чисто, что ли, то, да, не имеет смысла выносить код в отдельный сервис. Но как только добавили http аннотацию, значит по этим правилам пора выносить в сервис его бизнес-логику, а абстрактный контроллер превратить в http-контроллер, с ответственностью распарсить запрос, делегировать выполнение сервису и сформировать ответ. руками это делать или аннотациями — деталь реализации контроллёра.

                                                                        +2

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

                                                                          –2
                                                                          Потому что в Вашем коде нет никакого BL. И все слои вместе взятые — это одно сплошное императивное редактирование ячеек БД через HTTP запросы. (Как и в большинстве проектов, которые я видел).

                                                                          я тут недавно прям начал углубляться в ООП, книжки читаю, чтобы прям мышление поменять, так вот, я как бы сторонник идеи data-first т.е. сначала данные, разрабатывая что-то, думаю сначала о данных, а потом уже о том, как что-то с этими данными будет работать. Так вот, в одной из книг, я увидел такое описание что такое класс, что такое объект.
                                                                          Там привели аналогию с базой данных. Что класс — это таблица, со всеми полями. А объект, это строка данных в этой таблице. И я сейчас еще больше БД воспринимаю как часть бизнес логики. а не просто хранилище данных. Да, логика работы в коде, но данные, как данные класса, они в базе.
                                                                            +1
                                                                            Не стоит так воспринимать БД. Аналогия для понимания классов и объектов может и удобная, но работа с БД кардинально отличается от работы с обычными объектами: вытащить строку из БД гораздо накладнее, чем создать новый объект в памяти. Работу с БД лучше воспринимать как работу с внешним сервисом со специфическим интерфейсом (чем она технически и является) и инкапсулировать ее в отдельный сервис/репозиторий, тем более что «завтра» эти прямые запросы к БД и правда могут превратиться в запрос внешнего сервиса.
                                                                              +1

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

                                                                                0
                                                                                может быть, все же зависит от контекста, если это веб-проект, и все объекты все равно создаются из данных, которые лежат в базе данных. То что база может поменяться, сегодня это MySQL, завтра PostgreSQL послезавтра это какой нибудь Mongo или Ellastic да, согласен полностью, это вопрос к создателю объекта из базы он будет создавать объект или из внешнего сервиса или из кеша. в целом конечно согласен.
                                                                              0
                                                                              Не могли бы вы дать ссылку на пример подобной реализации, либо самому написать статью) После вашего комментария открылись глаза, прошерстил большую часть примеров «Правильной реализации веб-приложений на asp.net», и везде сервисы лишь являются переносом кода из контроллера в отдельную библиотеку, в которой используется вся доступная логика приложения.
                                                                                +2

                                                                                … вы, я так понимаю, ни PoEAA, ни DDD не читали?

                                                                              +2
                                                                              Ну, написали вы весь код в контроллере, класс
                                                                              Ну, а теперь напишите на него тесты, замокайте HttpContext — насколько я помню, он не реализует интерфейся, вот эта вся боль, вам оно надо?
                                                                              Я понимаю что вы сейчас попытаетесь отмазаться: «Это другое» — это то самое, возьмите студента, который только начал писать, дайте ему вашу статью, и завтра после утреннего кофе он начнет писать код в контроллере, потом все это пройдет код ревью, так как другой студент, постарше, не любит писать тесты, и вот, говнокод готов, прошу на прод

                                                                              Кайф
                                                                                0

                                                                                Чисто теоретически, для любителей писать бизнес логику в контроллере, есть ICurrentHttpContextAccessor.
                                                                                https://docs.microsoft.com/ru-ru/aspnet/core/fundamentals/http-context?view=aspnetcore-3.1

                                                                                  0

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

                                                                                    0
                                                                                    Справедливости ради, автор, рассматривает и тестирование
                                                                                    Тестирование? Контроллеры точно так же тестируются, а подняв TestServer вы практически напишите end2end тесты.
                                                                                    .
                                                                                    Вы ведь ходили по ссылке и смотрели на WebApplicationFactory, не так ли?
                                                                                    +1

                                                                                    а как сообщество шарперов относится к ситуации когда и контролер и сервис являются врапперами и для бизнес логики вызывают хранимые процедуры, которые и содержат всю бизнес логику?
                                                                                    я так и делаю, например, хотя мои проекты небольшие, не хайлоад, но данных много

                                                                                      +1

                                                                                      А как вы контроль версий делаете с хранимками? И как тестируете?

                                                                                        +1

                                                                                        храним модель базы в .DAC файле, которая сама делает миграции базы в ci/cd без потери данных.
                                                                                        https://docs.microsoft.com/en-us/sql/relational-databases/data-tier-applications/data-tier-applications?view=sql-server-ver15


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


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

                                                                                          +2
                                                                                          но там тяжело что-то зафакапить

                                                                                          Т.е. unit тестов нет?
                                                                                        0

                                                                                        Я считаю что подход уже не актуален, и мы шарперы вообще не любим вылазить за сам C#, потому всегда используем возможность не писать код на чем то другом. Пример попытки WebForms, Blazor, .razor файлы

                                                                                          +4

                                                                                          За сообщество не знаю, а лично я плохо отношусь. Потому что (а) хранимки — это не мой профиль и (б) я столько с ними наелся проблем с версионированием, развертыванием и тестированием, что больше не хочу.

                                                                                            +3

                                                                                            Я работал в фирме где так был написан проект, дебажить такие хранимки на 3 экрана это боль.

                                                                                              +2
                                                                                              хранимые процедуры, которые и содержат всю бизнес логику?

                                                                                              Подскажите, пожалуйста, что вы понимаете под бизнес логикой. Как правило, в это понятие входит не только взаимодействие с базой и, лично по моему опыту, вызывать сторонние сервисы в разы проще из кода приложения, особенно учитывая, что вам хотелось бы иметь там retry policy, логирование, метрики и прочее.
                                                                                              Встречал у коллег подход, где хранимки использовались исключительно чтобы экономить на траффике и не слать весь запрос по сети, но это скорее исключение из правил.
                                                                                              +2

                                                                                              Да, при написании простых CRUDов таких мысли приходят в голову. Но как только возникает необходимость работы с HTTP — контроллер перестаёт быть простым "почтальоном" и у него появляется своя ответственность, очевидно отделяемая от ответственности сервиса.


                                                                                              Например, такое случается как только возникает необходимость работать с файлами. При загрузке файла на сервер нужно достать из заголовков Content-Type и, возможно, имя файла; определить charset если файл текстовый. При скачивании файла надо заполнить этот самый Content-Type и не забыть про Content-Disposition.

                                                                                                –1

                                                                                                Если вы гиузите файл то в параметрах метода появится модель IFormFile, и в ней уже будет и файл, и имя, и тип

                                                                                                  +2

                                                                                                  Это всё равно часть HTTP/ Для нормального разделения отвественности передавать её из контроллера дальше — не комильфо.

                                                                                                    +4

                                                                                                    Это если "ловить" html-форму. Но бывает ведь ещё и вариант прямой передачи файла, без формы, когда содержимое файла без изысков лежит в Request.Body. А ещё иногда оказывается файл проще закодировать в base64 и передать как часть JSON или XML.


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

                                                                                                      0
                                                                                                      Согласен
                                                                                                    +3
                                                                                                    Но как только возникает необходимость работы с HTTP — контроллер перестаёт быть простым «почтальоном» и у него появляется своя ответственность, очевидно отделяемая от ответственности сервиса.
                                                                                                    Ваш комментарий отлично согласуется со статьей.
                                                                                                    Как только возникает необходимость работы с HTTP — отделяем сервис. До того — пользуемся только контроллером.
                                                                                                    Как только контроллер перегружается логикой — отделяем сервис. До того — пользуемся только контроллером.
                                                                                                    Как только появляется переиспользуемый код — отделяем сервис. До того — пользуемся только контроллером.
                                                                                                    Как только появляется требование GraphQL — отделяем сервис. До того — пользуемся только контроллером.

                                                                                                    Если мы абсолютно уверены, что один из критериев выше выполнится в ближайшие полгода, можно сразу пилить сервисы. Если же у нас сугубо внутреннее CRUD-API на 3 сущности, можно спокойно жить на контроллерах.
                                                                                                      +5

                                                                                                      Да, я тоже когда-то так думал. Но потом выяснилось, что мои коллеги не телепаты, и даже README не всегда читают, а потому наличие бизнес-логики в контроллере воспринимается ими как "на этом проекте принято писать логику в контроллере", соответственно момента отделения сервиса никогда не наступает. А ещё я сам пару раз оказался тем самым коллегой, который не понял гениальных умолчаний...


                                                                                                      Вот и приходится в начале каждого нового проекта играть в "архитектурную угадайку", понадобится — не понадобится :-)

                                                                                                        +1

                                                                                                        А кодревью у вас там есть?

                                                                                                          +1

                                                                                                          На активных проектах — есть. А вот на саппорте, когда на проекте половина разработчика...

                                                                                                        0

                                                                                                        А [Http:post(login)] или как там — это не работа с http?

                                                                                                          +1
                                                                                                          Последний проект делаем по данному принципу (уже год как в проде). Проект живет, постоянно добавляем страны, с кучей локальной логики. И… все нормально)
                                                                                                          По моим прикидкам у нас более половины контроллеров вообще не менялись с момента написания. Кода там мало и он понятный. Да и наши сервисы отличаются от контроллеров наличием пары аннотаций на методах\исключениях. Вынести сервис из контроллера — просто. Рефакторинг работает.

                                                                                                          Не вижу причин вводить дополнительный уровень сервиса, если он не нужен и не будет отдельно использоваться. Радуют фразы: «а вдруг новый протокол понадобится». Всего не предусмотреть. В нашем проекте гораздо веротнее, что придется переписывать пару сервисов на GO или Kotlin.
                                                                                                        +2
                                                                                                        Маленькие контроллеры хороши тем, что когда кому-то надо посмотреть список методов, выставленных наружу, с их роутами и параметрами, гораздо легче прочитать 100-строчный файл, чем продираться через 5000-строчный.
                                                                                                          +1

                                                                                                          Для этой цели замечательно подходит Swagger UI.

                                                                                                            +1
                                                                                                            для решения этой проблемы используем интерфейсы)
                                                                                                              0
                                                                                                              На них не видно роутов.
                                                                                                                0

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

                                                                                                                  0
                                                                                                                  Потому что атрибуты [Route], [HttpPost] должны быть применены к экземпляру.
                                                                                                                  Если их навесить на интерфейс, они работать не будут?
                                                                                                                    +1

                                                                                                                    В принципе, можно сделать и так, чтобы они заработали. Но придётся написать свои реализации .AddControllers() и .MapControllers(), потому что стандартные через интерфейсы и правда не работают.

                                                                                                                0
                                                                                                                гораздо легче прочитать 100-строчный файл, чем продираться через 5000-строчный.

                                                                                                                Разве современные ide не умеют в сворачивание тела? С трудом представляю себе, чтобы я хотел посмотреть на список методов контроллера и доблестно скроллил через код этих методов.
                                                                                                                  0

                                                                                                                  Умеют, но каждый сворачивать скорее всего придётся вручную и отдельно. Скроллить — быстрее.

                                                                                                                    –1
                                                                                                                    CTRL + M + O
                                                                                                                    CTRL + M + L
                                                                                                                    +1
                                                                                                                    Проблема, что в классе с контроллером окажутся не только методы, торчащие наружу, а ещё и вспомогательные методы, созданные при декомпозиции контроллера на кусочки, всякие константы и вспомогательные DTO. Их тела будут свёрнуты, но их декларации будут мешаться.
                                                                                                                  +1
                                                                                                                  Если есть понимание, что проект дальше определенного состояния не пойдет и ему будет достаточно написать код в контроллерах или хранимках и без тестов — то никто не запрещает этого делать. Даже наоборот, не будет лишней сложности. Или, например, будут использованы внутренние инструменты СУБД, которые для данной задачи будут подходить лучше.

                                                                                                                  Но главная проблема в изменчивости требований. С опытом начинаешь понимать, что есть неиллюзорный шанс однажды все переписать. Очень не хочется этого делать. Ни мне, как программисту, ни бизнесу. Отсюда все эти — «На одну кнопку две недели? Как??!!!». И поэтому подкладываешь соломку заранее. Где-то по простому — сразу не пишешь код в контроллерах или хранимках, потому что это дешево. Где-то оставляешь задел на вероятное будущее, которое может и не наступить. А заранее это делать дорого, да и есть более важные задачи. Все эти решения принимаются на основе опыта, видимого потенциала конкретного проекта и деадлайнов.

                                                                                                                  Пожалуй единственная категория проектов, где было все (или почти все) заранее известно — это для гос. учреждений по водопаду. На моей практике — такие проекты никогда серьезно не развивались. Их либо выкидывали совсем, либо заменяли другими. Я там мог сложить бизнес логику в хранимки и 5-10 лет это никого бы не парило.
                                                                                                                    +5
                                                                                                                    Если мне скажут что у нас может появиться ещё один канал, к примеру через очередь, я просто могу получить экземпляр контроллера и использовать его в другом канале. Этот контроллер легко резолвится из DI.
                                                                                                                    Восемь костылей из десяти.

                                                                                                                    Этот подход работает, но работает он грязно. Вместо того, чтобы иметь изолированную бизнес-логику и несколько параллельно существующих адаптеров для ее использования (http api, rmq, ...), вы прибили логику гвоздями к одному из них и теперь он будет тянуться везде — в другие адаптеры, в тесты. Тем самым связность системы и сложность ее понимания возрастает. Зачем модулю RMQ вообще знать что-то про HTTP API. Когда выйдет новая версия ASP.NET Core и вы захотите на нее перейти — вам придется обновлять и перетестировать не только http-модуль, а вообще все приложение целиком.

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

                                                                                                                      Просто в тот момент, когда вы вынесли из контроллера все, что связано с HTTP (биндинг в обе стороны, низкоуровневую валидацию, ответы с заголовками и прочим счастьем, роутинг и так далее, далее, далее), он перестал быть UI-контроллером, вот и все.

                                                                                                                        0
                                                                                                                        Если мне скажут что у нас может появиться ещё один канал, к примеру через очередь, я просто могу получить экземпляр контроллера и использовать его в другом канале. Этот контроллер легко резолвится из DI.

                                                                                                                        Как бы вам сказать… ваш контроллер все еще напрямую зависит от Microsoft.AspNetCore. А откуда у меня этот пакет в бэкенд-сервисе, который крутится себе тихо в демоне и очередь разбирает?

                                                                                                                          0

                                                                                                                          столько примеров кода и не одной схемы, хоть бы в итог добавили

                                                                                                                            0
                                                                                                                            Немного оффтопика:
                                                                                                                            Меня немного смущает то, что существование юзера в коде проверяется по имени и!!! паролю. Т.е. два одинаковых юзера с разными паролями — это ок? Наверное проверка существования должна быть только по имени.
                                                                                                                              0

                                                                                                                              (убрал старый комментарий, вы правы)


                                                                                                                              Я думаю, что это просто ошибка копипасты из логина.

                                                                                                                                +1
                                                                                                                                Кстати, спасибо за статью.
                                                                                                                                Мне очень нравятся статьи с рассуждениями а не только с безопеляционными утверждениями.
                                                                                                                                  0

                                                                                                                                  … мне кажется, вы ошиблись с адресатом благодарности.

                                                                                                                                    0
                                                                                                                                    Упс! Автору респект!
                                                                                                                                    0
                                                                                                                                    Спасибо
                                                                                                                                0
                                                                                                                                Автор статьи предлагает вынести логику из сервисов бизнес логики в контроллеры, но почему бы не пойти дальше, зачем тогда использовать эту ненужную прослойку под названием репозиторий и вообще зачем ORM можно же напрямую SQL запросы в базу писать или (как уже в комментариях было отмечено) хранимки использовать? Да и этот бесполезный IEmailService — кому он нужен? Пишите все в контроллере. Да и кому вообще нужна тестируемость, программисты должны код без ошибок писать, к чему еще думать про какие-то там тесты пусть QA баги ищет.

                                                                                                                                [Это был сарказм]

                                                                                                                                Ну а если по делу то цель создание сервисов бизнес логики равно как и репозиториев это разделение ответственности. Вы ведь упоминали в статье про трехслойную архитектуру (кстати слоев вполне может быть больше чем три, взять хотя бы тот же DDD) суть разделение по слоям не в следовании каким-то каргокультам внушенным нам рептилоидами а в разделении ответственности. Контроллеры отвечают за HTTP (ну или другой протокол обмена сообщениями), репозитории за взаимодействие с базой данных… Реализация IEmailService например за отправку почты. Я обычно бизнеслогику засовываю в .Net Standard либы чтоб не было соблазна «платформо-зависимый код» в них писать.

                                                                                                                                Слышали наверняка что слои верхнего уровня не должны знать о реализации нижнего уровня? Кто знает что там у него под капотом у реализации IEmailService, может быть он запускает механическую ногу, которая пинает спящего возле офиса бомжа, который несет вашему юзеру бумажку с распечатанным сообщением. В любом случае автор ведь использует интерфейс сервиса вместо того чтобы на прямую реализацию вставить туда где она используется будь то сервисы или контроллеры (т.е. подсознательно понимает что разделение ответственности не такая плохая идея).

                                                                                                                                Собственно к чему я это все тут написал — писать код в котроллере технически возможно, равно как и писать запросы к базе на прямую, или пинать бомжа напрямую без использования механической ноги. Но правильно ли это? Если этот проект планируется развивать и поддерживать многие годы то думаю писать все в контроллере плохая идею. Времена идут, все меняется, сегодня вы реквесты/респонсы через ASP.NET обрабатываете, завтра вместо контроллеров решите например Azure Functions с их HttpTriggers использовать после завтра например в Лямбды все завернете или еще чего другое придумается (тут уже упоминали Graph QL)… Подлодку делят на отсеки потому что если в одном из них пробоина, можно просто перекрыть перегородки и подлодка не потонет. Так же и тут, слои нужны для того чтобы в случае изменения реализации одного из них все остальные слои не пришлось переделывать, или вообще весь проект переписывать.

                                                                                                                                P.S. Ничего не имею против бомжей и не призываю их пинать. Считаю что этим людям просто где-то в жизни не повезло и надо помогать вернуться в социум.
                                                                                                                                  +1

                                                                                                                                  Лет 7 был бомжом, работая программистом :)

                                                                                                                                    0
                                                                                                                                    зачем тогда использовать эту ненужную прослойку под названием репозиторий

                                                                                                                                    тем более, что DbSet это и есть репозиторий — по крайней мере «крякает и плавает как утка» ©
                                                                                                                                    +1

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


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

                                                                                                                                      0

                                                                                                                                      Ну, тут на самом деле, есть интересный вопрос: а как переписать? Предположим, для интереса, что сервис, который он вызывает, используется и в других местах, поэтому заинлайнить код сервиса в контроллер не выйдет. Значит, надо избавляться от контроллера… но как, если фреймворк требует такой сущности?

                                                                                                                                        –1
                                                                                                                                        использовать другую архитектуру, там где не надо ручками хардкодить и привязывать каждый контроллер к каждому сервису. например Postgrest
                                                                                                                                          +3
                                                                                                                                          использовать другую архитектуру

                                                                                                                                          Это если вы с нуля архитектуру выбираете. А если программист просто добавил еще один контроллер в систему, где архитектура до него была выбрана?

                                                                                                                                          0

                                                                                                                                          Если сервис вызывается более чем в одном месте, то очевидно, что для сокращения кода, упрощения отладки и внесения изменений надо вызывать сервис.
                                                                                                                                          Но в приведенном примере как раз сервис вызывается в одном месте.


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

                                                                                                                                            +2
                                                                                                                                            вызываются ровно в одном месте, поэтому создавать подобный код не нужно

                                                                                                                                            Может быть не нужно быть таким категоричным?
                                                                                                                                            Разные случаи бывают.
                                                                                                                                            Например, пускай код и вызывается в одном месте, но он не влезает в 4 монитора. Что — оставлять в контроллере или все-таки попытаться перенести в другие классы?
                                                                                                                                            Да много примеров таких есть.
                                                                                                                                            По моему опыту, когда пишутся первые строчки, программист на 99% не знает будут они последними или нет. Так что лучше с самого начала держать все под контролем и следовать принципу SRP — контроллер отвечает за преобразование запросов в Dto и вызывает сторонние методы, преобразует exception в соответствующие коды возврата.
                                                                                                                                            Когда в будущем мне понадобится переиспользовать бизнес логику (например в новом транспорте — консьюмере очереди), я просто вызову соответствующие бизнес сервисы и не буду трогать контроллеры (т.к. добавление нового транспорта не должно затрагивать существующие). Тем более, очень часто, нужно сделать быстро и нет времени на рефакторинг и разделение «лапшекода».
                                                                                                                                              +1

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


                                                                                                                                              Причем заранее создавать методы не обязательно, IDE сейчас хорошо умеют в Extract Method рефакторинг.


                                                                                                                                              Есл программист не знает будущую структуру программы, то ему категорически не надо создавать дополнительные сервисы. Когда в будущем понадобится переписывать, то чем меньше кода будет, тем проще будет переписывать.

                                                                                                                                            • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                                –3

                                                                                                                                                Я наверное пошатну ваше мироздание,"технический долг" не существует. Это красивая фраза чтобы программисты оправдывали бесполезное раздувание кода.


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


                                                                                                                                                И самое главное, ваше оправдание "технического долга" приводит к затртам реальных денег потому что программисты больше работают.

                                                                                                                                                  0
                                                                                                                                                  приводит к затртам реальных денег потому что программисты больше работают

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

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


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


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

                                                                                                                                                      0
                                                                                                                                                      Чем меньше кода, тем проще его поменять

                                                                                                                                                      Ну я могу ответить следующим — чем меньше грануляция участков кода, тем проще его переиспользовать. И задача инженера, на мой взгляд, как раз и в нахождении «золотой середины» не в падаясь в крайности.
                                                                                                                                                        0

                                                                                                                                                        "проще переиспользовать" это не метрика.
                                                                                                                                                        Если код функций вызывается в нескольких местах, то это сокращает объем кода. Если функция вызывается в одном месте, то неважно насколько просто её переиспользовать. на объем кода и плотность ошибок это не влияет.

                                                                                                                                                          +1
                                                                                                                                                          это не метрика.

                                                                                                                                                          Для меня — метрика и еще какая:
                                                                                                                                                          Количество рефакторинга при добавлении новых фич, если у меня код нормально гранулирован, я не выделяю новые функции из существующих, а использую те блоки, которые я до этого создал.
                                                                                                                                                          Т.е. при идеальном коммите у меня будет Lines Changed:0 Lines Added:[количество строк для новой фичи].

                                                                                                                                                            0

                                                                                                                                                            А тепрь давайте посмотрим на коммитлог вашего релаьного проекта ;)
                                                                                                                                                            В мире розовых единорогов чего только не бывает.

                                                                                                                                                              0
                                                                                                                                                              коммитлог вашего релаьного проекта

                                                                                                                                                              Шутку оценил:)
                                                                                                                                                              Но я все же о том, что
                                                                                                                                                              задача инженера, на мой взгляд, как раз и в нахождении «золотой середины», без впадения в крайности.
                                                                                                                                                                0

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


                                                                                                                                                                Поэтому существует принцип LEAN — делать как можно меньше работы до того как появилось подтверждение её нужности.

                                                                                                                                                                  0
                                                                                                                                                                  предугадать где эта середина

                                                                                                                                                                  В этом помогает «опыт».
                                                                                                                                                                  LEAN — делать как можно меньше работы

                                                                                                                                                                  Ага, а потом, когда нужно сделать какую либо работу «еще вчера» но требуется очень много времени, а его нет и городятся разного рода костыли и копипастинг.
                                                                                                                                                                  А то и хуже — бизнесу говорят, что приложение превратилось в «кусок» и что-то делать новое, просто легче переписать.
                                                                                                                                                                    0

                                                                                                                                                                    Я не видел ни одного случая, чтобы разработчик угадал куда проект будет развиваться, кроме случаев когда он сам себе product manager.


                                                                                                                                                                    Если нужно вчера, а еще очень много работы, то это значит что вы потратили время не на то, что нужно было делать или у вас очень нужная программа.


                                                                                                                                                                    Про переписыване говорят слабаки, которые не умеют программировать. посмотрите на опыт microsoft с "переписыванием IE".


                                                                                                                                                                    Напомню: группа программистов в редмонде сказала что IE (на тот момент версии 9 или 10) преватися в «кусок» и надо переписать.


                                                                                                                                                                    В это время другая группа выпустила вполне пристойно (для IE) работающий IE11, который не только умел в достаточно современные на тот момент стандарты, а еще и умел эмулировать баги старых версий, чтобы говносайты корпоративные приложения продолжали работать в новой винде.


                                                                                                                                                                    Родившийся параллельно edge пытался походить на Chrome поддержкой передовых, еще не до конца оформившихся, стандартов, но страдал от детских болезней и некоторых унаследованных, так как даже при "полно переписывании" умельцы тянули куски кода из старого IE.


                                                                                                                                                                    Спустя несколько лет Edge сдох, а MS выкатил новую версию на базе Chromium. Так что переписываие любого большого проекта это путь в никуда. Рано или поздно менеджеры поймут, что проще переписывающих уволить, а устаревший продукт заменить УЖЕ СУЩЕСТВУЮЩИМ современным.

                                                                                                                                                                    • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                                                        +3
                                                                                                                                                                        Я не видел ни одного случая, чтобы разработчик угадал куда проект будет развиваться

                                                                                                                                                                        А я не видел не одного случая, когда проект больше трех лет пишется с принципом «делай все проще» не превращается в сплошной копипаст и классы на не одну тысячу строк.
                                                                                                                                                        • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                                            0

                                                                                                                                                            Можно пример?

                                                                                                                                                            • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                                                +1

                                                                                                                                                                Вы серьезно сравниваете длину кода на разных языках?

                                                                                                                                                                • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                                                    +1

                                                                                                                                                                    Так как можно написать на TS нельзя написать на JS. В этом и есть разница. разные языки — разные возможности — разные плотности ошибок.
                                                                                                                                                                    Я даже больше скажу, если JS файл прогнать через TS компилятор можно много ошибок отловить.
                                                                                                                                                                    Вообще магия, код вроде не поменялся, а плотность ошибок упала.


                                                                                                                                                                    Поэтому нет смысл на разных языках саравнивать код.

                                                                                                                                                                    • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                                                        0

                                                                                                                                                                        То есть все-таки можем создать сколько захотим?
                                                                                                                                                                        Не смущает что править придется в двух местах если будет не 9, а 10 мест?


                                                                                                                                                                        А самое главное нет смысла так раздувать код ведь количество мест все равно определяется числом в одном месте (константой).


                                                                                                                                                                        Если число мест — не константа, то вы еще 100500 ошибок совершите из-за преобразования типов и получении значения извне.

                                                                                                                                                                        • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                                                          0

                                                                                                                                                                          А чем простая проверка предусловия if (count in [2, 6, 9]) throw new Error(`count must be in [2, 6, 9], ${count} given`) не угодила? С ним вы так же не сможете создать неверноый стол.

                                                                                                                                                                          • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                                                              0

                                                                                                                                                                              То есть пишем больше кода, чтобы об ошибке узнать раньше?

                                                                                                                                                                              • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                                                                  0

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

                                                                                                                                                                                  • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                                        0
                                                                                                                                                        Можно, но при этом вы 100% залезете в технический долг, о котором даже не будете подозревать.

                                                                                                                                                        Неа, в описанных условиях никакого долга нет.


                                                                                                                                                        что делать, если сервис, реализуемый контроллером, должен быть Singleton либо Transient?

                                                                                                                                                        … какой-такой сервис? Написано же: не надо никакого сервиса, потому что код вызывается в одном месте.

                                                                                                                                                        • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                                            0
                                                                                                                                                            Напомню, что в статье автор предлагает, с одной стороны, имплементировать интерфейс контроллером, и резолвить его из контейнера

                                                                                                                                                            Автор предлагает фигню, прямо скажем, и об этом я тут уже где-то писал.


                                                                                                                                                            т. е. я не могу запустить его по таймеру, по событию, либо из другого, например, мобильного интерфейса. Не могу (в общем, опять же, случае) запустить из Blazor'а, если использую SignalR, так как Scope SignalR не на один запрос.

                                                                                                                                                            Не можете. Потому что это не надо.


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

                                                                                                                                                            Бесплатных подходов вообще нет, вам в любом случае придется что-то тратить. В данном случае вы не тратите на то, что вам не нужно.

                                                                                                                                                            • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                                        +1
                                                                                                                                                        У вас и функции, наверно, по 300-500 строк без логического разделения на приватные подфункции?

                                                                                                                                                        Разделение на более мелкие сущности придумали не только для того, чтобы легче тестироваться и соблюдать DRY, его придумали еще и для того чтобы отделять «мух от котлет». Тут вот мы обрабатываем http запрос, тут мы его валидируем, тут работаем с очищенными данными, а тут — формируем ответ. И если уж так вышло, что что-то из этого выполнено на аннотациях, что-то автоматически выполняется до вызова контроллера, что-то после и на бизнес-логику остается только 1 строка — штош поделать, придется смириться с тем, что у нас экшн контроллера состоит из одной строки.

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

                                                                                                                                                        А в общем случае, если отойти от примера с 1 строкой, я думаю вы со мной спорить не будете, что легче работать с функциями по 10-30 строк и классами по 300-500 строк с не большим количеством методов, чем с монстрами. Естественно, увлекаться разбиением сущностей тоже не стоит, создавая по сервису не каждый чих — всё хорошо в меру
                                                                                                                                                          0

                                                                                                                                                          Я не говорил что надо писать код без разделения на функции. Это вы уже сами придумали. Я говорил про дополнительные сущности (классы) в коде и создание "водопроводного кода".


                                                                                                                                                          Разделение кода на функции это хорошо. Создание классов, которые занимаются в основном вызовом других классов — плохо.


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

                                                                                                                                                            0

                                                                                                                                                            Да какой-же водопроводный класс контроллера получается, если он преобразует http-запрос в dto? Или вы аннотации принципиально кодом не считаете?

                                                                                                                                                              0

                                                                                                                                                              HTTP запрос в DTO преобразует ASP.NET.
                                                                                                                                                              Или вопрос в чем-то другом?

                                                                                                                                                                +2

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

                                                                                                                                                                  –1

                                                                                                                                                                  Аннотации не обязательные. ASP.NET может и без них разобраться.

                                                                                                                                                                0
                                                                                                                                                                Да какой-же водопроводный класс контроллера получается, если он преобразует http-запрос в dto?

                                                                                                                                                                Вот собственно этот и получается. Когда экшн в контроллере сводится к Action(HttpRequest request) => request |> bind<ActionInput> |> _service.Action |> bind<HttpResponse> — это типичная сантехника, от которой лучше (именно что "лучше", а не "обязательно надо", потому что далеко не всегда надо или возможно) избавиться.

                                                                                                                                                                  0

                                                                                                                                                                  Если я правильно понял, то вы о случае когда объект http запроса передаётся в сервис? Я же не о нём, а том, когда этот объект, преобразуется (руками или аннотациями фреймворка — не суть) в какой-то DTO, который об http не знает ничего, а уж передаётся в сервис

                                                                                                                                                                    0

                                                                                                                                                                    Нет. Там же явно bind написан, вот оно, ваше преобразование.

                                                                                                                                                                      +1

                                                                                                                                                                      Вот если бы значки с непонятным смыслом (ассоциации с Хаскелем) словами написали, то было бы понятнее, возможно. bind в программировании для меня значит что-то к чему-то привязать, например, конкретное значение к плейсхолдеру в SQL запросе, или значение к переменной в замыкании.


                                                                                                                                                                      Но, допустим, понял. Так чем плохо разбивать процесс преобразования данных на чётко выделенные этапы:


                                                                                                                                                                      1. принимаем http-запрос параметром в контроллере
                                                                                                                                                                      2. преобразуем в методе контроллера нужные понятия http в понятия какого-то сервиса, реализующего прикладную логику и ничего об http не знающего
                                                                                                                                                                      3. вызываем этот сервис
                                                                                                                                                                      4. его результат преобразуем в http ответ
                                                                                                                                                                      5. возвращаем ответ

                                                                                                                                                                      Чёткое разделение ответственностей между контроллером и сервисом. Простота тестирования и контроллера, и сервиса по отдельности. Посадить писать сервис можно человека, ничего об http не знающего. Посадить писать контроллер можно человека ничего о сервисе, кроме, грубо говоря, его интерфейса, не знающего. В чём минусы такого подхода, кроме того, что один класс лишний надо написать?

                                                                                                                                                                        +2
                                                                                                                                                                        bind в программировании для меня значит что-то к чему-то привязать, например, конкретное значение к плейсхолдеру в SQL запросе, или значение к переменной в замыкании.

                                                                                                                                                                        … а в ASP.NET (Core), о котором речь в посте, model binding — это процесс привязки значений из HTTP request к конкретным типам параметров action. Я их просто вынес из аннотаций и магии в контроллер.


                                                                                                                                                                        Так чем плохо разбивать процесс преобразования данных на чётко выделенные этапы
                                                                                                                                                                        [...]
                                                                                                                                                                        В чём минусы такого подхода

                                                                                                                                                                        Я просто не о том подходе, о котором вы. Плохо не разбивать процесс преобразования, а писать рутинный код. Собственно, это было очевидно даже разработчикам ASP.NET WebAPI, поэтому, на самом деле, тот код, который я выше показал, можно написать вот так: Action(ActionInput request) => _service.Action(request) (преобразование типов на входе и выходе делается автоматически). В итоге, получаются вот такие контроллеры:


                                                                                                                                                                        ActionA(A input) => _service.ActionA(input);
                                                                                                                                                                        ActionB(B input) => _service.ActionB(input);
                                                                                                                                                                        ActionC(C input) => _service.ActionC(input);

                                                                                                                                                                        … что, лично мне, кажется скучным, рутинным, и, что самое главное, ненужным.


                                                                                                                                                                        Главное в этот момент не подумать, что надо втянуть сервис в контроллер. Надо выкинуть контроллер:


                                                                                                                                                                        webApi.RegisterProxyController<Service>()
                                                                                                                                                                          0
                                                                                                                                                                          Вот это меня отдельно возмущает. Из-за того, что «плохо писать БЛ в контроллере» напишем тоже самое в сервисе. То есть дело просто в названии класса? Название важная штука, но дает ли такой подход какую-то осмысленную выгоду?
                                                                                                                                                                            +2

                                                                                                                                                                            Нет, дело не в названии класса. Дело в том, что контроллер — это определенный набор обязанностей и, чаще всего, зависимостей. Сервис же, если он правильно написан, не имеет ни обязанностей, связанных с контроллером (например, роутинга), ни его зависимостей (например, HTTP-библиотек), и поэтому может легко переиспользоваться.

                                                                                                                                                                              +1

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

                                                                                                                                                                                0

                                                                                                                                                                                Контроллер, по сути, это лишь специфичный сервис, знающий, в данном контексте, об HTTP. Вопрос стоит так: делаем два сервиса, один из которых содержит БЛ, а второй работу с HTTP и вызов второго или делаем один сервис, в котором HTTP и БЛ вместе, максимум БЛ в приватный метод вынесена.

                                                                                                                                                                                0
                                                                                                                                                                                Главное в этот момент не подумать, что надо втянуть сервис в контроллер. Надо выкинуть контроллер:

                                                                                                                                                                                Классная идея! Даже немного завидую дотнетчикам, если так можно автоматически делать.

                                                                                                                                                                                  0

                                                                                                                                                                                  Печеньки у нас тоже есть.

                                                                                                                                                              0

                                                                                                                                                              Ну как ничего? Вот в коде изначальном в посте он преобразует http запросы в DTO какие-то и передаёт их в сервис. По сути http-адаптер для сервиса, которму нет никакого дела до http

                                                                                                                                                                +1

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

                                                                                                                                                                • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                                                                                    0

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


                                                                                                                                                                    Если вам нужно написать код на одну страницу, то надо написать код на страницу. Можно написать в одном месте, можно в другом, можно побить на функции, можно побить на несколько классов, которые вызывают друг друга. В любом случае чем меньше будет в совокупности классов и строк кода, тем код будет лучше.