
2024-05-10
Архитектура Symfony приложения - простейшая схема
2023-07-27
Сразу скажу - статья больше для новичков. Архитектура - это большой и во многом спорный момент всегда, но начинать с чего-то нужно, поэтому именно для новичков я пишу данную статью о том, как хорошо архитектурно начать писать в Symfony фреймворке.
Сам по себе фреймворк имеет некоторые наработки в этом направлении, но не всегда и не все им следуют, поэтому я предложу максимально близкую и на мой субъективный взгляд адекватную схему распределения функционала внутри фреймворка.
Итак... Для начала мы имеем Entity - их естественно мы храним отдельно так как и диктует нам фреймворк в папке Entity.
Далее мы должны определиться с контроллерами - они по-умолчанию находятся в папке Controllers - тут тоже всё хорошо, но далее начинается весёлое. Вроде бы на этот момент у нас есть точка входа ( контроллер ) и есть БД - надо их как-то увязать между собой и еще и бизнес-логику приложения где-то написать...
Что же нам предлагает по этому вопросу фреймворк?... Фреймворк нам предлагает довольно большое количество подходов, но в документации это все преподносится довольно сумбурно, поэтому многие начинающие путаются и часто пишут просто ерунду либо слитую всю в один класс либо разбросанную по рандомно названным классам.
Наша же цель немного систематизировать и разделить хотя бы первично приложение на слои.
1) Контроллеры - принятие данных, валидация, выдача данных на фронтэнд
2) Сервисы - бизнес-логика приложения
3) Репозитории - сложные запросы в БД реализуемые либо нативно либо через QueryBuilder
4) Entity - сущность отражающая таблицу в БД
5) Listener / Security / Interfaces / etc. - прочие сервисы встроенные во фреймворк или написанные самостоятельно
Мы делим приложение на вышеуказанные группы классов ( первично и примерно ). А теперь схематически располагаем их так, как они должны взаимодействовать друг с другом.
На схеме выше отражается чёрным цветом - то как должны общаться между собой классы. Красным - то как не стоит делать.
Теперь рассмотрим это более детально на примерах.
Зелёный сценарий SPA приложения. От фронтэнда нам поступает запрос в контроллер ( 1 ), контроллер принимает данные ( Request ) и валидирует их с помощью ValidatorInterface. На этот момент мы валидировали входящие данные от фронта ради безопасности нашего приложения. Эти действия принятие данных и валидация этих данных - это логически выполняет контроллер. Далее нам надо осуществить какую-то логику, которая не соотносится с логическими обязанностями контроллера.
Как только контроллер понимает, что данные валидны - ему нужно позвать соответствующего работника, который выполнит необходимую работу = вызвать соответствующий сервис и метод сервиса, передав туда данные, полученные с фронта ( например ).
Сервис внутри себя логически относится обычно к какой-либо одной бизнесовой сущности. Например если есть Entity/Article - то будет и src/Service/ArticleService, который будет содержать в себе бизнес-логику работы со статьями.
Наш контроллер хочет получить статью и прилежащие к ней сущности, например список статьей с тем же автором, в количестве 3 штук и не старше 3 месяцев. С этими хотелками он обращается в сервис, который в свою очередь обращается в репозиторий ( 3 ). Репозиторий составляет несколько запросов в БД, выполняет их, формирует данные и отдает их в сервис ( 4 ).
Далее сервис знает, что согласно бизнес-логике приложения при просмотре статьи надо увеличить ее количество просмотров, а так же увеличить количество показов статей, которые предлагаются к просмотру вместе с запрошенной статьей. В этот момент сервис взаимодействует, возможно несколько раз, с Entity/Article ( 5 и 6 ), а после этого обращается к стороннему сервису EntityManagerInterface для сохранения данных в БД методом flush() ( 7 и 8 ).
Сервис осознал, что всю бизнес-логику он выполнил - настал момент отдать данные контроллеру ( 9 ).
Контроллер получает данные от сервиса и единственное что делает - это проверяет валиден ли ответ. Если сервис вернул то, что ожидалось - то отдает фронту ответ 200 и данные. Если сервис вернул ошибку - то контроллер отдает фронту ответ 400 и текст ошибки.
Мы только что рассмотрели простейший примитивный способ разработки архитектуры приложения во фреймворке Symfony.
Теперь расскажу немного про красные стрелочки и что нельзя делать даже если очень хочется. Это не то, чтобы нельзя делать, но вам потом будет в лучшем случае стыдно за такой код, а в худшем случае он доставит вам кучу проблем. Все следующие пункты в основном происходят из первого принципа SOLID S - single responsibility principle - принцип единственной ответственности. То есть мы делим приложение на слои ответственности.
1) нельзя вызывать EntityManager в контроллере - потому что это сервис фреймворка, который работает с Entity, осуществляет поиск по БД и сохраняет данные в БД. Соответственно работе с ним - не место в контроллере.
2) нельзя из Repository отдавать наверх объекты типа QueryBuilder - потому что это объекты для работы с БД и кроме как в репозитории им больше нигде не место
3) не нужно писать простые запросы вида select * from user where id = 2; в репозитории, потому что они совершенно спокойно выполняются с использованием EntityManagerInterface в сервисе с использованием методов findBy, findOneBy, find. Методы репозитория должны быть действительно сложными, которые не получится изобразить с помощью EntityManagerInterface
4) не нужно располагать в Entity методы для работы с сущностью, даже если они очень простые и уж тем более стоит с большой осторожностью подходить к добавлению магических методов с логикой, которая меняет штатное поведение Entity
5) каждый класс должен логически работать только со своими объектами. Например для передачи в сервис данных - лучше сформировать массив или передать данные поштучно, а не передавать в метод сервиса Request класс, с которым логически должен работать только контроллер
Все вышеописанное является всего лишь рекомендацией, а не жесткими правилами, но всегда стоит помнить, что если вы пробуете интегрировать какие-либо собственные практики или архитектурные подходы - вы всегда должны быть готовы аргументированно их зищитить, особенно если вы находитесь в крупной и сильной команде разработки.
И еще один главный принцип в архитектурных спорах - никогда не бойтесь признавать свои ошибки, главное не оказаться правым, а прийти к истине. Всегда важно четко понимать что действительно является аргументом, основанном на фактах, а что является чьей-то просто хотелкой / привычкой / легаси и т.д.
Ура! Я наконец-то дописал статью как собирать собственные бандлы на Symfony 6!!!
Статья про EasyAdmin всё ещё в процессе )))
Не, ну мне же надо на чем-то тестировать твиттер локальный...
Я тут еще много полезного буду выкладывать, так что заходите обязательно почитать.
Сайтик пока что в разработке - это далеко не окончательная версия - по сути это то что удалось слепить за 8 часов.
Комментарии
Vel
2023-08-10 09:11:32OtezVikentiy
2023-10-16 23:14:29