Чистая архитектура решения, тесты без моков и как я к этому пришел

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


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


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


К примеру, часто вижу N-Слойную архитектуру, есть слой работы с данными (DA), есть слой с бизнес логикой (BL), который работает используя DA и возможно ещё какие-то сервисы, а так же есть слой вьюшки\API в котором принимается запрос, обрабатывается используя BL. Вроде удобно, но посмотрев на код вижу такую ситуацию:


  • [DA] вытягивает\записывает\меняет данные, пусть даже сложный запрос — OK
  • [BL] 80% вызывает 1 метод и прокидывает результат выше — Зачем этот пустой слой?
  • [View] 80% Вызывает 1 метод BL прокидывает результат выше — Зачем этот пустой слой?

Кроме этого, модно оборачивать в интерфейсы чтоб потом замокать и тестить — вау, просто вау!


  • А зачем мокать?
  • Ну, чтоб выпилить сайд эффекты на время тестов.
  • То-есть протестим без сайд-эффктов, а в прод с ними?
    ...

Это основанная вещь которая мне не нравилась в этой архитектуре, так как чтоб решить задачу по типу: "Вывести список лайков пользователя" это большой процесс, а на деле 1 запрос в БД и возможно маппинг.


Примерное решение

1) [DA] Добавить запрос в DA
2) [BL] Пробросить ответ DA
3) [View] Пробросить результат BA, может промаппить


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


В другом месте я видел реализацию API с подходом CQRS.


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


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


И наконец к моему проекту


Как я говорил, я рефакторил свой проект несколько раз, в тот момент у меня была "депрессия программиста", я просто был не доволен своим кодом, и рефакторил его, снова и снова, в итоге начал смотреть видео про архитектуру приложения, чтоб увидеть как делают другие. Наткнулся на доклады Антона Молдована про DDD и функциональное программирование, и подумал: "Вот оно, мне нужен F#!".


Потратив пару дней на F# я понял что в принципе тоже-самое сделаю на C# и не хуже. В видео показывали:


  • Вот код C#, он говно
  • Вот F# классный, меньше написал — супер.

Но прикол в том что решение на F# реализовали по другому, и против этого показывали плохую реализацию на C#. Главный принцип был в том, что BL это не штука которая вызывает DA, сервисы и делает всю работу, а это чистая функция.


Конечно F# хорош, мне понравились некие фичи но, как и C# это всего лишь инструмент, который можно использовать по-разному.


И я снова вернулся к C# и начал творить.


Создал я такие проекты в решении:


  1. API
  2. Core
  3. Services
  4. Tests

Так же я использовал фичи C# 8, особенно nullable refence type, её применение покажу.
Коротко о задачах слоев, которые я им дал.


API
1) Получение запросов, модели запросов + валидация, ограничения


Подробнее

image


2) Вызов функций из Core и Services


Подробнее

image


Тут мы видим простой, читабельный код, я думаю каждый поймет что тут написано.
Наблюдается четкий шаблон
1) Достать данные
2) Обработать, изменить и тд — Именно эту часть нужно тестировать.
3) Сохранить.


3) Маппинг, если нужен
4) Обработка ошибок (логирование + человеческий ответ)


Подробнее

В этом классе собраны все возможные ошибки приложения, на которые реагирует exception handler


image


image


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


Есть у меня AppError.Bug эта ошибка на не понятный случай.


У меня есть CallBack от другого сервиса, в нем будет userId в моей системе, и если я не найду юзера с этим ID значит либо с юзером что-то случилось, либо вообще не понятно, такая ошибка летит мне как CRITICAL, по идее не должна возникать, но если возникнет, то требует моё вмешательство.


image


Core, самое интересное


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


Если функции нужен баланс юзера, то МЫ достаем баланс, и передаем в функцию, а НЕ пихаем сервис юзеров в BL.


1) Основные действия сущностей


Подробнее

image
image


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


image
image


Не менее важной темой считаю хорошее построение моделей сущностей.


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


1) Добавление\удаление валюты. Эта задача сразу означает для нас не только написание нового кода, а и миграция, с наполнением\удалением всех существующих пользователей и это самый просто вариант. Не дай бог, чтоб добавить новую валюту пришлось бы делать кнопку для юзера, которую он нажмет и инициирует создание нового кошелька по какому-то бизнес процессу. В итоге нужно было всего enum расширить для новой валюты, а написали ещё фичу по созданию кошельков по кнопке, ещё задачу фронту кинули.


2) В коде постоянные FirstOrDefault(s=> s.Currency == currency) и проверка на null


Моё решение

image


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


Services


Этот слой предоставляет мне удобные иструмены для работы с различными сервисами.
В своем проекте я использую MongoDB и для удобной работы с ней, обернул колекции в такой себе репозиторий.


Подробнее

Сам репозиторий
image


Монга блокирует документ на момент работы с ним, соответсвенно это поможет нам с решение проблем в конкуренции запросов. А ещё в монге есть методы для поиска сущности + действию над ней, например: "Найти юзера с id и добавить к его текущему балансу 10"


А теперь про фичу C# 8.


image


image


Сигнатура метода мне говорит, что может вернутся User, а может Null, соответственно когда я вижу User? Я сразу получаю предупреждение компилятора, и делаю проверку на null.


image


Когда метод возвращает User я уверенно с ним работаю.


image


Ещё хочу обратить внимание на то, что нет try catch потому как исключения могут быть только от "странных ситуаций" и не верных данных, которые сюда доходить не должны так как есть валидация. В слое API тоже нет try catch, есть только один глобальный exception handler.


Есть только один метод который бросит Exception это метод Update.
В нем реализована защита от потери данных при многопоточном режиме.
image


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


image


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


А вот сама модель юзера, тут четко видно что реферал у юзера не обязательный, а со всем остальным можно работать не думая о null.


image


И наконец Tests


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


Подробнее

Я скачал nuget FSCheck который генерит рандомно входящие данные и позволяет проводить много разных кейсов.


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


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


image


А вот и сами тесты


image


image


image


После каких-то изменения я запускаю тесты, через 1-2 секунду вижу что все в порядке.
Так же в планах написать E2E тесты, дабы проверять всю API из вне и быть уверенным что она работает так как нужно, от запроса, до ответа.


Фишки


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

Каждый мой запрос легируется, при возникновении бага, я нахожу requestId и могу легко воспроизвести баг, повторив запрос, ведь у моего API нет состояния, и каждый запрос зависит только от параметров запроса.


image


Подведем итог.


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

Поделиться публикацией

Похожие публикации

AdBlock похитил этот баннер, но баннеры не зубы — отрастут

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

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

    0

    Шикарно. Если кто узнает академическое название подхода, буду признателен.
    Сам пришел к похожему подходу, но для нативного кода. Есть вертикаль IO — файлы, сеть, иные поставщики неструктурированных данных, есть вертикаль бизнес-логики(BL) — как вы описываете преимущественно чистые функции, выполняющие необходимые преобразования на уже структурированными и провалидированными сущностями. Есть вертикаль собственно приложения или библиотеки, которая тривиальным линейным кодом использует IO и BL. Иногда для унификации или упрощения вырастает отдельная как можно более тонкая вертикаль преобразования неструктурированных данных в структурированные — тоже тестировать легко и непринужденно через предподготовленные наборы данных.

      +4
      Скиньте ссылку на репозиторий. Сложно смотреть в картинки/скриншоты кода.

      Покажите реализацию сущности db. Которая умеет Users..
      Это ORM? За конкуренцию в запросах вы сами следите?

      Что с идемпотентностью?
        –2
        Обновил раздел Service, показал БД.
        Весь проект не уверен что смогу показать, это не прототип, а реальное приложение.
          +4
          посмотрел Database. Получается у вас Сервисы жестко зависят от реализации Database.
          Если вам скажут перейти на Postgres, к примеру, — вам придется переписать весь Services. DDD который вы читали и применяете — говорит что так делать нельзя, как вы реализовали.

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

          Непонятно почему самоцель отказаться от маппинга?

          Тесты тоже странные. Зачем-то пришлось писать билдеры — лишняя работа. Я еще понимаю, интеграционные тесты — там билдеры состояний для кейсов. Но здесь это явно лишнее.
          Еще стиль тестов — вы внутри теста делаете ребус — угадай какое состояние — угадай какое оно будет в конце теста. зачем??? всякие byte.MaxValue. Чтобы программист тренировал память на константы?
        +14

        Видите ли, CQRS (как и DDD в принципе) придумали умные дяденьки и тетеньки для приложений других масштабов. Если:


        • вы работаете над приложением один;
        • вся бизнес-логика сводится к краду над парочкой моделей;
        • "[BL] 80% вызывает 1 метод и прокидывает результат выше",

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


        А вот когда:


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

        вот тогда вам внезапно начинают нравится Domain Services, Aggregate Roots, Events и прочие штуки. Не говоря уж об интерфейсах и всяких там солидах.


        А пока что ваши рассуждения выглядят как-то так: "Что за люди придумали экскаваторы? Сложно, куча абстракций, топливо залей, за рычаги подергай… Я вон взял лопату, грядку у бабушки на огороде вскопал, и нормально". При этом и лопата, и экскаватор – отличные инструменты. Только задачи они решают разные...


        А в приведенных вами кусках кода – там действительно не DDD надо впихивать. Там бы сначала решить проблемы попроще, например, с инкапсуляцией. Вот если вам надо withdraw у пользователя сделать – что мне как программисту мешает просто написать user.Wallet[currency] -= amount? Как я должен узнать, что мне надо использовать расширение user.Withdraw()? А еще, я так понимаю, перед этим я должен убедиться, что user.HasOnBalance()? И где этот код должен быть – в контроллере? Так у вас тогда логика просто размажется по контроллерам, и поддержка превратится в ад. Ну и так далее.

          –4
          Я рассказывал про код приложения где использовалась N-layer, это был проект который писало 7 разрабов.

          А пока что ваши рассуждения выглядят как-то так: «Что за люди придумали экскаваторы? Сложно, куча абстракций, топливо залей, за рычаги подергай…

          Я не говорил что абстракции — сложно, я говорю что их часто не оправдано много. А 4+ вложеностей абстакций с паарой реализаций, очень удобно дебажить, ведь так? Тыкаешь F12, оба посмотрел на список реализаций, ой, а теперь нужно понять какую из них заюзает runTime? а там, за выбор реализации по Id отвечает фабрика, с фаричным методом в придачу, и не забываем что у нас абстрактный репозиторий, с unit of work который обернул репозиторий EF (Db context). Время занимает просто понять как сработает код, не то чтобы ошибку искать. А че? зато заменяемо.

            +2
            Я не говорил что абстракции — сложно, я говорю что их часто не оправдано много. А 4+ вложеностей абстакций с паарой реализаций, очень удобно дебажить, ведь так?

            А какая разница? F11 все равно приведет в ту, которая сейчас используется.

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

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

              +2
              Я рассказывал про код приложения где использовалась N-layer, это был проект который писало 7 разрабов.

              Ну это само по себе не показатель, тут возможны несколько вариантов, например:


              1. Разработчики были не очень хорошие, и они нагородили неправильных абстракций.
              2. Разработчики были нормальные, просто они игрались с разными подходами.
              3. Разработчики были нормальные, и они все сделали правильно, но вам не хватило опыта (времени, желания etc) осознать, что это была необходимая сложность.

              Но вполне вероятно, что эта была смесь из вышеперечисленного.


              А 4+ вложеностей абстакций с паарой реализаций, очень удобно дебажить, ведь так?

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


              я говорю что их часто не оправдано много.

              Таки да. Но вот если бы статья была в духе: "Не надо в простых проектах городить сложные абстракции. Можно сделать просто и понятно – вот так, например" – и показали бы поддерживаемый и легко читаемый код, то было бы просто отлично. Но я этого в статье, к сожалению, не увидел.

              0
              На счет инкапсуляции, согласен, нужно подумать как зыкрыть set и не потерять в удобстве Core
              +1
              Если функции нужен баланс юзера, то МЫ достаем баланс, и передаем в функцию, а НЕ пихаем сервис юзеров в BL.

              Круто. Один простой вопрос: если у вас поменялись требования на валидацию (операции или сущности), и этой валидации нужно на один источник данных больше, в скольких местах придется поменять код?


              Скажем (пусть это и не валидация, зато прямо в коде пример есть), операции user.MakeToken() понадобился CRNG. Что происходит дальше?


              В этом классе собраны все возможные ошибки приложения, на которые реагирует exception handler

              … а не на уровне фронта у вас ошибок не бывает?


              (кстати, счастливой отладки вам при таком "логгировании")


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

              Это, кстати, неправда.

                0
                Недавно была задача добавить ReferId в User, а дальше начилсять рефералу бонус после неких действтий. Задача решилась минут за 15. Добавил свойство в модель, расширил метод makeToken чтоб закинуть в JWT referId для быстрого доступа, в нужных местах достал id и по Id начилил бонус.
                  0

                  Это не то, что я спросил.


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

                    –4
                    В самой функции + 2 места использования, регистрация и вход.
                      +7

                      Бинго. Во всех местах использования, а если те — тоже "чистые функции", то во всех местах их использования, и так дальше по цепочке. Хотя, казалось бы, изменение-то локальное, на внешний контракт влиять не должно, и вот это вот всё. А как же принцип единой ответственности (точнее, единственной причины изменения)? А как же инкапсуляция?

                        –3
                        MakeToken() это метод на уровне апишки, это не Core, в этой задаче домен даже не будет изменен
                          +3

                          Во-первых, какая разница, все равно проблемы остались.


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

                          0

                          А чистоте это не мешает. Это же MonadReader environment, просто добавляете туда ещё одно поле.

                            0

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

                              0

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

                                +1

                                И?..

                    –1
                    … а не на уровне фронта у вас ошибок не бывает?

                    Обратите внимание что я возвращаю не только текст, а и код ошибки.
                    Текст нужен разрабу, код для справочника ошибок. Фронт по коду достает из словаря (eng ru) текст и дает внятный ответ ошибки
                      +2

                      А толку? Как вам это поможет разобраться с конкретной ошибкой в беке? Стектрейс, оригинальная ошибка — это все вам не нужно?

                        0
                        Стектрейс присылается в логи
                          0

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

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

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

                        0
                        1) Без миграции, в массиве у пользователя не будет новой currency — гарантированный exception
                        2) Выглядит грамоздко
                        3) Хотелось чтоб ошибки были собственного типа (AppError) для удобной обработки.
                        0
                        Ну что тут можно сказать, все когда-то были на этом этапе). Ваш код нелох сам по себе, но вы же понимаете, что вся критика тут только лишь потому, что вы это преподносите как идеал, к которому все должны стреимться.
                        но файлов было так много, что просто кошмар

                        Так а простите, куда вы будете девать свои файлы, когда проект разрастётся?
                        Я вынес методы как методы расширения дабы класс не раздувался, а функционал можно группировать по фичам

                        Ага, а дальше будет этап когда фичи начнут раздуваться и в каждый метод вынесете в отдельный класс и придёте к CQRS.
                        Вы немного лукавите предоставив только тут одну простую сущность «пользователь». Но вот представьте, например, что вашему пользователю придётся играть в три игры рулетку, бандита и блекджек. Потом выяснится, что в рулетку играют в 5 раз чаще чем в блекджек, а в бандита раз в день и то не факт. Потом захочется показывать аналитику по всем трём играм. Потом захочется видеть аналитику по всем пользователям и тд.
                        Жду ещё одну статью как вы всё это без моков масштабируете
                          +1
                          Как я и говорил тестировать нужно только логику...

                          Ну, допустим, что так.


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

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

                            +2
                            Моки всего лишь перенесутся с одного уровня, на другой. То есть вы упрощаете тесты в одном месте, но усложняете в другом.
                              +2
                              Вы взяли простой пример, чуть сложнее «Hello World», и пытаетесь показать, что для «Hello World» фабрики не нужны (KISS). Но даже к такому простому коду много вопросов, к примеру, самый первый метод контроллера:
                              if (user != null) throw ...
                              тестировать уже не нужно?
                              Далее, это веб -> параллелизм, что часто забывают начинающие разработчики, и управление им невозможно спихнуть на кого-то еще. Где у вас расположиться транзакция? Но предположим, вы полагаетесь на уникальный индекс, зачем тогда эта проверка?
                              В целом, похвальная попытка, но когда вы испытаете боль от изменений в вашей реализации, на что вам пытаются намекнуть в комментариях, у вас появится лучшее понимание назначения DDD, SOLID, dependency inversion и пр.
                                0
                                Я рассказал про решение параллелизма.
                                Проверка нужна в случае если id нет, очевидно же
                                  0
                                  Можете указать, я не могу найти.
                                    0
                                    В разделе Service, скриншот метода Update
                                      0
                                      Вы, видимо, меня не поняли. Я все время говорил про метод Register и, соответственно, Create.
                                      И вместо даты я бы посоветовал простой counter — проще и надежнее.
                                  0
                                  А про тестирование всего кода я тоже написал, сейчас в разработке e2e тесты
                                    +1
                                    Я вижу тестирование того, что вы называете Core. Проверка находится в методе контролера, вы ее тестируете?
                                  +1

                                  А вы в курсе, что если вызвать параллельно несколько раз метод play, то у вас база придет в неконсистентное состояние? У вас баланс сохраненный в wallet начнет отличаться от суммы транзакций.

                                    0
                                    Нет, такого не будет. В методе обновления проверяется версия файла (Дата изменения). Если она не совпадет, то изменения не будет, запрос выдаст ошибку. Почитайте код Update.
                                    Это тестировалось в 50 потоков.
                                      0

                                      Код приведёте?

                                        +1

                                        Пропустил этот метод. Есть уверенность что UtcNow всегда будет разным? А если будет больше одного сервера, на котором работает приложение?


                                        Довольно странно выглядит такой вариант “optimistic concurrency”, там где она не нужна. Добавление транзакции в список можно делать параллельно.

                                          0
                                          В принципе дату можно замени гуидом
                                            0
                                            Даже если будет 10 серверов, база одна, и запрос FindAndReplace дает мне гарантию как транзакции. Если у метода не выйдет найти и обновить эту сущность, вернет null
                                              +1

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

                                        +2

                                        В PoEAA такое структурирование кода называется Transaction Script. И очень зря вы использовали монгу, так как транзакции она как раз не поддерживает. В этом случае вам как раз-таки стоило бы использовать её функции, типа "найти запись и увеличить значение на 10", чтобы код работал корректно. Но тогда архитектура получилась бы не такой.


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


                                        Часть, связанная с вынесением обработки ошибок и логирования отдельно называется Aspect-Oriented Programming, сокращенно AOP. Это когда фреймворк, в данном случае ASP.NET Core берет на себя работ по вставке вызовов "водопроводного" кода, отчищая от него бизнес-логику. Чтобы в итоге бизнес-логика не обращалась к логгерам, обработке ошибок итд.


                                        Это все известно уже лет двадцать, активно применяется более десяти.


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

                                          0

                                          Кстати.
                                          Покажите еще метод, который вам вернет сколько заработала ваша система за период. Он скорее всего у вас есть.

                                            0
                                            image
                                            Пока что такая
                                              0

                                              А вот это часть какого слоя, кстати? Это же бизнес-логика, или нет?

                                                0
                                                Это находится в сервисе, так как решается одним запросом.
                                                  0

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

                                                    0
                                                    Хорошее замечание, не подумал об этом, метод писал для себя.
                                                    Исправил
                                                    image
                                                    image
                                                      0

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


                                                      Ну и да, у вас юзер всегда при загрузке из БД приносит все свои транзакции?

                                                        0
                                                        Второй в Core.
                                                        Всегда.
                                                          0
                                                          Второй в Core.

                                                          А что такое Core, и чем это отличается от бизнес-логики?


                                                          Всегда.

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

                                                            –5
                                                            Я б не поднимал тему производительности.
                                                            У вас все равно она хуже, чем моя.
                                                            image
                                                            Можете сами убедиться
                                                            crypto-games.space
                                                              +1
                                                              У вас все равно она хуже, чем моя.

                                                              Это весьма бессмысленное утверждение.


                                                              Но не суть. Что такое Core, и чем это отличается от бизнес-логики?

                                                                0
                                                                Это проект с BL, и моделями приложения, короче домен
                                                                  0

                                                                  Ну то есть из двух приведенных выше функций обе лежат в слое бизнес-логики?

                                                                    0

                                                                    Одна в сервисах, одна в бл

                                                                      +3

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


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

                                                    +1

                                                    Такая же бизнес-логика, не хуже других.
                                                    Вы по сути сделали то, о чем писали в начале статьи.
                                                    У вас несколько слоёв, большая часть из них вызывает функции низлежащего слоя и прикидывает результат наверх. Нет Единого места где сосредоточена логика нужная бизнесу. Она и в сервисах, и в экстеншенах, и в контроллере, и в методе update.

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

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

                                              Самое читаемое