К написанию этой статьи меня подтолкнуло изучение архитектурных подходов для Vue.js-проектов, а вдохновила - детально описанная методология Feature-Sliced Design.
К сожалению, PHP-сообществу не хватает подобных развернутых рекомендаций, да и вообще, каких-то общепризнанных стандартных подходов в структуре проекта.
Моя статья - это попытка обобщить изученную мною за много лет информацию и сформировать универсальную структуру проекта, основанную на принципах Clean Architecture и модульного монолита.
В статье будет много ссылок на другие статьи, книги или видео. Я специально даю ссылки, а не пытаюсь заново всё объяснить, т.к., к сожалению, не возможно уместить всю информацию в одну статью. Да и по факту, это будет просто пересказ другими словами.
Но настоятельно рекомендую последовательно! ознакомиться со всеми материалами.
Только после этого, в вашей голове начнут формироваться правильные нейронные связи.
Всё по классике: отрицание, гнев, торг, принятие :)))
Побыстрее или подумать?
Любой из PHP-фреймворков имеет рекомендации по структуре папок, что и где надо создавать: Laravel, Symfony, Yii, CakePHP, CodeIgniter, Laminas, Spiral.
И это правильно, это очень сильно облегчает жизнь, обеспечивая низкий порог вхождения и быстрый старт разработки, особенно, джунам и мидлам.
Так почему не устраивает разделение кода по типу файлов?
Всё дело в Low Coupling и High Cohesion и нескольких других критериях, подробно описанных в статье Как организовать структуру приложения.
Конечно, базовую структуру легко переопределить под свои нужды. И вот здесь начинается самое сложное (Как запустить MVP и не превратить его в технический долг):
с одной стороны - каждый проект уникален и не понятно как применять стандарты, особенно на начальном этапе,
с другой стороны - качество архитектуры кода будет сильно зависеть от уровня разработчика,
а с третьей стороны - за прошедший год написана "тонна" кода по базовым правилам, кто заплатит за переделку?
Если в команде собраны эксперты, то они и так знают как надо правильно организовать структуру проекта. Но если в проекте большинство джунов и мидлов, то постоянно возникают вопросы:
Где создать этот файл?
Где разместить этот метод?
Где граница ответственности?
и тп
И этот поток вопросов надо контролировать и ограничивать стандартами внутри проекта или команды. Иначе уже через год проект превратиться в легаси из говнокода и лапшекода.
В комментах к статье Как организовать структуру приложения прозвучало:
Структура папок - это просто инструмент, который помогает организовать код.
Как карта местности. Она очень важна для навигации, но без качественного содержимого внутри теряет всякий смысл.
Без понимания бизнес-процессов и здравого смысла даже идеальная структура папок превращается в свалку кода.
Это важный момент. Нам программистам, всегда проще ориентироваться в четкой иерархической структуре, чем иметь два десятка файлов в одной директории. Но, чтобы получилось что-то хорошее, сначала надо осмыслить:
Какие Модули/Bounded Context есть в проекте?
Как тесно они между собой связаны?
Есть ли планы по-дальнейшему развитию?
И уже после ответа на эти вопросы надо начинать "рисовать карту местности".
А точно нужен весь этот overhead?
Однозначного ответа на этот вопрос нет, как и «серебряной пули»!
Выбор структуры проекта это не вопрос моды.
Это стратегическое решение, которое должно базироваться на анализе конкретных бизнес‑требований, доступных ресурсов и сроков выполнения проекта!
Есть только путь в попытке найти баланс между трудозатратами, качеством и скоростью.

Но однозначно могу сказать, качественный и легко поддерживаемый проект не получится создать без разделения кода на зоны ответственности и написания тестов.
Зоны ответственности - это: "контроллеры", "модели", "валидация", "шаблоны" и множество других понятий, которые нужны только, чтобы по определенным правилам разделять написанный код.
А тесты, это не только проверка функциональности и помощник при рефакторинге, но и лакмусовая бумажка, насколько правильно была выделена зона ответственности. Ведь хороший тест должен проверять результат только одного действия. Даже если это End-To-End тест.
Я не буду подробно останавливаться на том как правильно строить архитектуру проекта и что такое хороший тест. Об этом уже неоднократно писали:
В своей практике я пользуюсь парой "формальных" правил, когда не стоит "заморачиваться" и менять/усложнять базовые правила выбранного фреймворка:
Когда у вас простой проект (кол-во контроллеров и моделей, условно!, 8-10 штук),
Когда проект не собирается развиваться и усложняться (сайт-визитка, лендинг, блог и тп).
А следует задуматься над структурой кода, когда проект:
Имеет сложную логику (содержит десятки модулей),
Планируется долгосрочная поддержка и развитие функционала.
Может уже есть готовые подходы?
Да, попытки были и не раз:
DDD, Hexagonal, Onion, Clean, CQRS… как я собрал всё это вместе,
Архитектура в Laravel. Как сделать код понятным и масштабируемым,
Как мы быстро распилили монолит, когда командам стало тесно,
Практический пример декомпозиции монолитного PHP приложения.
Но, к сожалению, это больше теоретические рассуждения: как сделать лучше, а не проработанные подходы, принципы, рекомендации или методологии. Нисколько не хочу принижать значимость этих работ. Я очень благодарен авторам за их труд. Все эти статьи помогли мне осознать, на что именно надо обращать внимание при создании архитектуры проекта.
Отдельно хочу упомянуть статью Упакуйте свой код правильно, где очень подробно рассказано о структуре папок и есть ссылки на репозитории с примерами, но всё же что-то не то. Эта статья рассчитана на опытного разработчика, который знает разные подходы, но немного сомневается как правильнее сделать или не хватает аргументов, чтобы убедить коллег в принятии решения.
Я же хочу подготовить набор подсказок и терминов или даже "формальных" правил, которые были бы понятны всем, а не только опытным ребятам. И этот набор был бы единообразен для разных команд и проектов, чтобы все могли говорить на "одном" языке.
Контроль соблюдения стандарта
Еще один важный момент это не только принять и описать какие-то правила в команде, но и осуществлять контроль по их соблюдению. Это ярко проявляется, когда в команду приходит новый сотрудник. Сначала ментор ему всё объясняет, показывает в WIKI где и что описано, а потом следит на Code Review, чтобы все эти правила соблюдались. Когда текучка небольшая, то команда быстро усваивает и запоминает все правила, но даже в таком варианте бывают случайные ошибки. Оптимальным вариантом для уменьшения "человеческого фактора" - является автоматизация таких проверок.
К счастью, у нас для контроля есть тесты. Но не обычные, а архитектурные. Инструментов много:
Pest Architecture Testing - надстройка над PHPUnit Application Architecture Test,
А вот хороших статей на эту тему мало:
Применение статических анализаторов архитектуры на примере гексагональной архитектуры,
Чистая архитектура на PHP. Как её измерять и контролировать? и PHPCleanArchitecture — Что нового?,
Чистая структура
Новое — это хорошо забытое старое
Сейчас все говорят о DDD, Hexagonal Architecture, но давайте вернемся к истокам - модульности:
Сложность и модулярность две стороны одной медали. Влад Хононов,
Эволюция архитектурных паттернов в бэкенд-разработке: от MVC к микросервисам,
Почему именно модульность? Потому что всё остальное - это частные случаи модуля, попытки формализовать правила для определения зоны ответственности кода.
В каждом фреймворке уже существует возможность разделять по модулям: Package в Laravel, Bundle в Symfony, Module в Yii 2.0, Module в CodeIgniter, Plugin в CakePHP, Module в Laminas.
Но, к сожалению, нет разделения на архитектурные слои. Так же отмечу, что фреймворки позиционируют свои "модули" как нечто самостоятельное для переиспользования в разных проектах, но мы всё равно можем использовать этот функционал для разделения кода на бизнес модули и внутри каждого модуля поделить код на слои.
За годы практики у меня сформировался боле-менее универсальный подход и я назвал его: "Чистая структура" (The Clean Structure). Фактически это та же группировка по типу файлов, только обернутая в модульность и разделенная по архитектурным слоям с комментариями: что и где.
Основные термины и понятия
Как я писал выше: очень важно, чтобы понимать друг друга, говорить единообразно. Приведу немного терминов:
Модуль - максимально самодостаточная часть продукта отвечающая за определенный функционал. Каждый Модуль может содержать "публичные" (доменные) интерфейсы, сущности или события, определяющие границы его использования - API. Теоретически, модуль можно вынести в самостоятельную composer-библиотеку.
UseCase - "простая" реализация варианта поведения пользователя (что именно хочет сделать пользователь).
Interactor - более глобальный уровень для реализации поведения пользователя.
DTO - упрощенно, это "типизированный ассоциативный массив".
-
Event - специализированный DTO, сообщающий другому модулю о возникшем событии и делящийся на:
Уведомления - Прошедшее событие (синхронное или асинхронное, много получателей).
Запросы - Получение данных (только синхронное, только один получатель).
Команды - Создание, Изменение и Удаление данных (только синхронное, много получателей).
Материалы для самостоятельного изучения:
Универсальная структура папок и набор "формальных" правил: что и за что отвечает, и в какой папке создается файл
-
/src
Core - Ядро приложения, то от чего зависит работа большинства модулей, и код можно вынести в самостоятельную composer-библиотеку. Какие-то общепроектные события, исключения или обертки над фреймворком и vendor. Структура аналогична модулю, см ниже.
-
/%Название Группы модулей% - Группировка схожих по смыслу модулей, если необходимо.
-
/%Название Модуля%
-
/Application - Реализация любой бизнес-логики.
/Command - Изменение данных во внешних системах. Например, БД.
/Dto - Объекты для передачи из UseCase в Command, Query, Query и обратно.
/Factory - Фабричные классы для создания Dto, Response, ValueObject.
-
/Responder - Всё что требует специальной генерации контента вместо json для ответа пользователю.
/Template - email-шаблоны, Excel/Word-шаблоны, сообщения в Telegram.
%Название.php% - Какой-то генератор ответа либо выбор стратегии для генерации ответа. Например, CreateTaxExcelReport.php.
-
/Service - Самодостаточный функционал.
%Название.php% - Какая-то уникальная реализация логики. Например, калькулятор ABC-анализа.
/Query - Получение данных из внешних систем. Например, БД.
/UseCase - Варианты использования приложения, те обработка действий, которые выполняет пользователь.
-
/Domain - Предметная область. Обычно все эти объекты доступны во вне модуля, как публичное API модуля (Контракт).
/Dto - Объекты для передачи из Infrastructure и Presentation в Application и обратно.
/Event - Дополнительные действия/уведомления, возникающие в ходе выполнения UseCase.
/Exception - Исключительные сообщения во вне об ошибке в Application.
/Entity - Dto для описания структуры таблицы, используется в Command для создания записи в БД или требуется для ORM.
/Request - "Входящие" Dto в Controller и Console, часто со встроенной валидацией данных..
/Response - "Исходящие" Dto в Controller.
/Validation - Бизнес требования по валидации Dto.
/ValueObject - Узкоспециализированный DTO с валидацией, часто со встроенной валидацией данных.
%Интерфейс.php% - API (контракт) для взаимодействия между модулями или для инверсии зависимости между слоями модуля.
-
/Infrastructure - Здесь собран функционал для реализации подхода Anti-corruption layer и Framework Agnostic.
/Adapter - Реализация интерфейсов из Domain для взаимодействия из Application с другими модулями/vendor.
/Repository - Упрощенный вариант CQS/CQRS для доступа к данным
/%Название внешней библиотеки% - Адаптеры, упрощающие взаимодействие с библиотеками из vendor. Пример, SerializerDecorator.
-
/Presentation - Часть фреймворка для обеспечения запуска и завершения приложения, или точки входа и выхода в модуле.
-
/Config - Конфигурационные файлы.
Обертка над получением значений из env (https://laravel.su/docs/12.x/configuration#konfiguraciia-okruzeniia, https://laravel.su/docs/12.x/packages#resursy).
Управление зависимостями. Реализация паттернов "Dependency injection" и "Service Container" https://laravel.su/docs/12.x/container и https://laravel.su/docs/12.x/providers.
Роутинг (https://laravel.su/docs/12.x/routing)
/Console - Крон-скрипты, Демоны и другие команды, вызываемые из консоли (https://laravel.su/docs/12.x/artisan#napisanie-komand).
-
/Http - Запросы по http-протоколу (REST API, SOAP API или обычные HTML-страницы).
/Controller - https://laravel.su/docs/12.x/controllers.
/Middleware - https://laravel.su/docs/12.x/middleware.
/View - Html-шаблоны.
/Listener - Подписчики на события (https://laravel.su/docs/12.x/events#opredelenie-slusatelei).
-
-
-
-
/tests
/Architecture - Архитектурные тесты.
/Stub - Все что требуется для выполнения тестов. Например, универсальный fake-jwt.
-
/Suite - Набор тестов для нашего приложения.
-
/%Название Группы Модулей%
-
/%Название Модуля%
...
-
-
TestCase.php - Абстрактный родитель-обертка над фреймворком.
Предложенный вариант, это "максимальная" сложность одного модуля.
В реальной жизни, не требуется создавать все папки сразу.
Примеры реализации
Я создал демо-проект, чтобы на почти реальных примерах показать универсальность предложенной структуры папок.
1. Простая HTML-страница
Яркий пример, как с помощью фреймворка создать быстро WelcomePage
2. Например, есть задача: проверять, что ресурс доступен (аля ping, только через HTTP)
Т.е. каждую секунду будет HTTP запрос. Для реализации этого функционала будет достаточно создать один файл: Presentation/Http/Controller/PingController
3. А теперь давайте подумаем, какая будет структура для задачи "Проверки доступности и работоспособности DB в проекте"
К примеру, такая проверка нужна 1 раз в минуту. Тут уже потребуется более сложная логика и для Laravel будет вот такая структура:
Application/Command/CheckDbWriter.php
Application/Query/CheckDbReader.php
Application/UseCase/DbHealthUseCase.php
Domain/Entity/HealthCheck.php
Presentation/Config/HealthCheckServiceProvider
Presentation/Listener/HealthChecker.php
На первый взгляд, кажется, что вся это вложенность - прям очень сильный оверхед, почему не создать 5 файлов в одной папке? А через два-три месяца, может потребоваться проверять доступность для Redis, RabbitMQ или ClickHouse.
В итоге, будут десятки файлов в одной папке и захочется их сгруппировать.
Как вариант, можно разделить код через создание группы модулей:
-
/DbChecker
DbCheckerProvider.php
DbCheckerListener.php
DbHealthUseCase.php
CheckDbReader.php
CheckDbWriter.php
HealthCheckEntity.php
/RedisChecker
/ClickHouseChecker
К сожалению, при такой группировке начинают размываться границы между архитектурными слоями внутри модуля. В данном примере это не критично. НО:
Если будет не опытный разраб, реализация бизнес логики может оказаться в контроллере, вплоть до запросов к БД. И это придется отслеживать на уровне Code Review.
Если модуль усложняется, допустим это вывод аналитической информации со сложными фильтрами, экспортом и тп, то выделение подмодулей становиться крайне сложным. Может получиться очень много взаимосвязей между модулями и вместо упрощения будет сильная связанность.
Я бы сделал так:
Application/Command/CheckDbWriter.php
Application/Command/RedisWriter.php
Application/Command/ClickHouseWriter.php
Application/Query/CheckDbReader.php
Application/Query/RedisReader.php
Application/Query/ClickHouseReader.php
Application/UseCase/DbHealthUseCase.php
Application/UseCase/RedisHealthUseCase.php
Application/UseCase/ClickHouseHealthUseCase.php
Domain/Entity/HealthCheck.php
Presentation/Config/HealthCheckServiceProvider
Presentation/Listener/HealthChecker.php
Зависимости и контроль
Напомню общий принцип инверсии зависимостей "Чистой архитектуры"

Почему надо контролировать зависимости между слоями и какие способы взаимодействия между модулями (контекстами) существуют хорошо освещено в статье Связывая Контексты: Руководство по Эффективному Взаимодействию.
Для себя я сформулировал следующие упрощенные правила.
Не усложняй и так всё сложно!
-
Между слоями существуют следующие зависимости:
Presentation, тк это часть фреймворка, может использовать Infrastructure, Application и Domain (и свой и чужой),
Infrastructure может использовать только Application и Domain (и свой и чужой),
Application может использовать Domain (желательно, только свой),
Domain может использовать только Domain (и свой и чужой),
Для внедрения реализации из слоя Infrastructure в Application потребуется создать интерфейс в Domain.
UseCase - не может использовать другой UseCase, тк теряется "простота". Но может агрегировать в себя Query и Command,
Interactor - не должен использовать другой Interactor, тк, скорее всего, не правильно декомпозированы варианты использования. Но может агрегировать в себя Query, Command и UseCase,
Модуль может использовать другие модули используя подход "Anticorruption layer": взаимодействие происходит через Domain интерфейсы другого модуля.
Еще один вариант организовать взаимодействие между модулями - доменные события.DTO применяется для типизации входных и выходных данных в public и protected методах, private методы могу принимать и возвращать ассоциативный массив.
Обязательность интерфейсов
В рамках одного модуля не обязательно создавать Интерфейс, если существует только одна реализация. Но всегда помним о зависимостях между слоями.
Интерфейс создается всегда, если надо что-то вызвать из другого модуля.
Тесты
Тесты пишутся всегда! Даже когда их некогда писать.
Один из способов проверить всё и сразу - это End-To-End тесты. Такой вид теста охватывает почти весь код, но очень сложен в разработке и поддержке.
End-To-End тесты лучше всего подходят для проверки связанной работоспособности всех кусочков системы и проверяют, в основном, положительные кейсы.
Unit и Integration тесты лучше использовать для более детальной проверки, особенно негативных случаев.
PS
Приглашаю всех желающих к обсуждению и созданию методологии или набора методик/практик для стандартизации структуры проекта.
Давайте создадим коллекцию примеров правильной структуры проекта для разных бизнес-задач с использованием популярных фреймворков.
Ещё немного полезных ссылок
Комментарии (30)
Glembus
14.05.2025 07:15Красиво. Но есть нюанс. Тот кто инвестирует хочет быстрее получить отдачу и готов мириться порой с говнокодом если он будет работать да еще и сделан быстро. Потом оикрываешь джиоу а там такой огромный пулл задач по тех долгу. Это реалии... На исправление или улучьшение рабочего кода тратиться нехотят, потому как он уже приности доход с инвестиции.
vandy Автор
14.05.2025 07:15Да, есть такой нюанс, я об этом писал в статье:
Структура проекта - это стратегическое решение, которое должно базироваться на анализе конкретных бизнес‑требований, доступных ресурсов и сроков выполнения проекта!
Я знаю, что есть компании, которые экономят на всем. Тут можно только пытаться объяснять чем это грозит, искать компромиссы и убеждать выделять больше ресурсов.
Это как с зубами: если ты их не чистишь, то сколько не лечи, все равно, в конечном итоге будет удаление.
magistrral
14.05.2025 07:15Респект за материал и подачу - сразу видно вложенные усилия, потраченные на пост. Особенно приятно, что есть референсы в виде ссылок.
Фидбек по сути\смыслу - если бы я знал, что меня ждет проект с таким подходом - я бы туда точно не пошел. Когнитивная нагрузка дикая (код чаще читают, чем пишут).
Оверинжиниринг на лицо (потому что декларируется "уникально правильный" подход под все кейсы, а не гибкая разработка под ситуацию). Складывается ощущение, что для вас цель написания кода - это не решение проблем и ведение бизнеса, а маниакальное стремление к субъективному перфекционизму. Пока вы разрабатываете продукт, конкуренты уже захватят рынок и вам не останется на нем места.
Bounded context, разделение слоев и зон ответственности в коде - это важно. Но это важно только для поддержки и расширения. Если у вас есть гарантия, что код будет одноразовый и ни для чего другого использоваться не будет - вы все равно будете разбивать функции вроде хелсчека на 6 классов или все же нужно решать в зависимости от контекста?vandy Автор
14.05.2025 07:15Хм, для меня структурирование кода только уменьшает нагрузку при чтении нового кода. А если эта структура идет из проекта в проект, из модуля в модуль, то всё упрощается в разы.
Согласен, не все готовы работать по определенным стандартам - это разрыв шаблона, это ограничение творческой жилки и тп. Я сам через это проходил, но опыт на разных проектах, чтение умных статей и просмотр выступлений помогли сформировать понимание, что стандартизация - это хорошо.Оверинжиниринг будет ярко выражен на маленьких и простых проектах. В остальных случаях этот подход будет оправдан. Я писал в статье:
Нет «серебряной пули», есть только путь в попытке найти баланс между трудозатратами, качеством и скоростью. И в каждому случае - он свой.
И там же привел пару правил, которые для себя выработал для определения сложности.Когда-то моя команда создавала несколько микросервисов, и там мы точно не задумывались над структурой папок. Там на один сервис было не более 20 файлов. Но проекту это не помогло, его закрыли. В сроки и бюджет не уложились, и скорость разработки тут была совершенно не причем.
Часто заказчик хочет всё и вчера, но если подойти с умом, то сначала надо сделать MVP, который запустить на рынок и начать зарабатывать, и параллельно с этим допиливать новый функционал. И это всё можно делать с первых строк кода хорошо.
Естественно не надо сразу проектировать коня в вакууме, делаем как умеем и только то что надо сейчас + готовим точки для расширения на будущее.Хелсчек я привел, как пример, своей логики рассуждения. Если у меня сервис не планирует развиваться, например, учет прохода пациентов в клинику, то можно его сделать быстро и по простому.
НО если выбран определенный подход, то он должен быть везде по всему коду и к этим правилам должны быть тесты. В противном случае будет хаос и через год-два, даже автор кода будет с трудом там разбираться.magistrral
14.05.2025 07:15В целом не люблю использовать аналогии, но тут подходит - для хорошего UX стараются минимизировать количество переходов\кликов пользователя до получения нужного ему результата. Вот если представить, что вам для действия, которое на других сайтах занимает 2 клика, потребуется сделать 6 - как может когнитивная нагрузка быть меньше?
Представьте, что вы знаете эту архитектуру, но видите проект впервые. Чтобы понять, как конкретно работает хелсчек роут, вам придется "загрузить" в голову логику из 6 отдельных классов и построить представление работы этого кода. Не представляю, как это может оказывать меньшую когнитивную нагрузку, чем если бы, по вашему говоря, человек "заговнокодил" всю логику в 10-15 строк в контроллере. В этом кейсе человек, незнакомый с проектом\кодом, получит всю нужную ему информацию за 2 перехода - поиск нужного роута и переход в экшн контроллера. Офк, хелсчек это просто пример) Основная проблема здесь в том что, вероятнее всего, на своих проектах вы будете, в том числе с помощью инструментов CI, форсить "чистую архитектуру" и code coverage, тем самым снижая гибкость разработки
vandy Автор
14.05.2025 07:15У меня нет цели вас переубедить, со временем, понимание само придет. Но размещая всю логику в контроллере, вы заставляете его знать больше чем требует его зона ответственности.
Всё зависит от ваших знаний и умений.
magistrral
14.05.2025 07:15Я вашу стадию уже проходил)) У нас была модульная архитектура еще тогда, когда nwidart\internachi\им подобных пакетов и в помине не было) Это сейчас она пилится проще простого, а в те времена нам приходилось точечно оверрайдить логику лары :) Так что да, будем надеяться, что и у вас понимание придет :)
vandy Автор
14.05.2025 07:15Очень интересен ваш опыт.
Если не секрет: у вас сколько человек в команде разработки и какие приняты правила по организации кода?
m03r
Дядюшка Боб писал, что фреймворк — это деталь (которую должно быть легко заменить), поэтому я с подозрением отношусь ко всем шаблонам вида «чистая архитектура для...». Кстати, с этой точки зрения и «проверка доступности» — такая же деталь, которая, наверное, имеет значение только для балансировки трафика. Ведь если приложение действительно недоступно, то об этом пользователи узнают при любом запросе.
Но в связи с первым хочу задать вопрос: насколько эта структура поменяется, например, при смене фреймворка с Laravel на Symfony? Придётся ли переписывать что-то, кроме «клея» между бизнес-логикой и фреймворком? И, наверное, самый важный вопрос: как такая структура защищает от «прикипания» к конкретному фреймворку?
TsarS
В данном случае явно придется. Ибо, например, в доменном слое, в Entity используется Laravel:
vandy Автор
В идеальном мире framework agnostic надо было бы создать свой адаптер на
Illuminate,
в этом есть минус - это дополнительное время на разработку и тестирование. Но так же это и плюс - при смене фреймворка либо самого либо его версии надо будет исправить только адаптер.В общем, надо искать баланс и где-то идти на уступки всей этой чистой идеологии.
TsarS
Я примерно так и пишу (для себя, не для коммерческой разработки). Условно пишу на чистом PHP (ну кроме всяких библиотек вроде phpunit, uuid и assert-ов для тестов), а потом уже когда упираюсь в инфраструктурном слое в new Class(new Class2 (new Class3)) и так далее, начинаю привязывать какой нибудь нефрейморковский DI, потом нейфреймоврковские шины, а потом уже убираю и привязываю, например, Symfony и делаю уже адаптеры к его Messenger и его DI. В большинстве случаев "тот" чистый php проект можно таскать как угодно между фреймворками. Повторюсь, это не для коммерческой разработки.
vandy Автор
Я пробовал применить это к Симфони, всё точно так же и некоторые моменты даже проще чем в Ларе.
В целом, защищает от "прикипания", особенно если на все важные места создавать адаптеры и декораторы.
vandy Автор
Не только для балансировки, не всегда пользователи сразу сигнализируют об ошибке. Часто просто уходят с негативом.
И даже когда они придут и скажут про ошибку, всегда приятнее им ответить: "наша система мониторинга уже сообщила о проблеме, мы сейчас с ней разбираемся и скоро всё исправим."
gun_dose
А вы не путаете фреймворк с библиотекой? Фреймворк по определению должен задавать структуру проекта. Замена фреймворка на проекте - это то же самое, что замена шасси в самосвале.
vandy Автор
Фреймворк - это просто инструмент. Его можно заменить, тут больше вопрос целесообразности этих действий.
Замена шасси в самосвале не будет выглядеть невозможной, если самосвал спроектирован модульно. Например, вы делаете кабину и к ней есть набор адаптеров для разных вид шасси, у кузова тоже есть свои адаптеры для крепления и т.д.
gun_dose
Видимо, пример с самосвалом не очень удачный. Либо вы его неправильно поняли. Фреймворк - это шасси, а не кабина. Можно проектировать универсальные кабины под любые шасси. Можно проектировать шасси, подходящие под разные транспортные средства. Но если рассматривать самосвал, как самостоятельное целостное транспортное средство, в нём можно поменять колёса, кабину, кузов, но смена шасси запрещена по закону, т.к. это приведёт к изменению VIN-номера. Опять же, кабину, кузов, двигатель можно снять с самосвала, но снять шасси нельзя, можно только полностью разобрать самосвал.
На практике понятие "framework agnostic" можно применить только к отдельным программным компонентам. Если же речь идёт о целом приложении, то никто и никогда не проектирует "framework agnostic" приложения. И более того, никто никогда не ставит задачу сменить фреймворк приложения.
vandy Автор
Пример очень удачный :)))
Да, фреймворк - это шасси. Бизнес ставит вам задачу сделать кузов. Вы смотрите на рынок выбираете самое популярное шасси, НО чтобы не завязываться на одного производителя делаете кузов не под конкретное шасси, а через адаптер.
Через год приходят санкции и запчасти на шасси заканчиваются. Понимая, что бизнес может встать, вы покупаете другое шасси и делаете для него адаптер. И планово переносите кузов на новое шасси. Так что шасси можно менять :))
В статье давал ссылку на Framework Agnostic длиной в 12 лет, там целый проект так живет.
gun_dose
Я тут вижу некорректное использование термина framework agnostic. Этот термин можно применять к библиотекам. Например geophp - это framework agnostic библиотека, т.к. ей для работы не нужен никакой фреймворк. Другие же библиотеки могут требовать для работы определённый фреймворк, поэтому они не являются framework agnostic. Но применять этот термин к целым приложениям вообще некорректно, потому что приложение может использовать фреймворк, а может не использовать. И если твоё приложение не использует никакой фреймворк, то это просто приложение, не использующее фреймворк, а не framework agnostic.
Нет ничего плохого в том, чтобы писать приложения без фреймворка. Но автор первого в этой ветке комментария утверждает, что фреймворк можно заменить, что является абсолютно некорректным утверждением. Фреймворк - это по определению та часть, которую заменить невозможно. Поэтому если ты боишься этой зависимости, то просто не используй никакой фреймворк.
vandy Автор
Подходы "Framework agnostic" и "Anti-Corruption Layer" помогают изолировать бизнес логику от внешних зависимостей.
Если код написан правильно, те разделен на модули и слои, то вынести кусочек приложения в самостоятельную библиотеку не составит труда. В итоге приложение будет включать в себя множество ваших библиотек с бизнес логикой и через адаптеры связывать их с конкретным фреймворком.
Это прям идеал, к которому надо стремится, но очень дорогой и трудоемкий идеал.
gun_dose
Модульность - это, безусловно, must have. Но это опять же перенос библиотек, а не замена фреймворка. Это как перевозка мебели с одной квартиры на другую. Ты можешь перевезти диван, но ты не можешь просто поменять под диваном квартиру.
vandy Автор
Мне кажется, у вас не правильно расставлены зависимости.
Фреймворк, как шасси, как и квартира - это средство реализации ваших желаний.
А кузов или диван - это ваши потребности, созданные под ваши нужды. Мы можете взять свой любимый, удобный и мягкий диван и с ним переехать в любое место, тк для него нужен просто интерфейс взаимодействия с полом - ножки. Даже если пол кривой - под ножку можно что-то подсунуть (адаптер). Дивану даже пол не нужен - его можно подвесить, те берем новый адаптер.
gun_dose
Нет, это у вас и у автора проблемы с пониманием целого и частного. Если я хочу возить песок по дорогам общего пользования, мне нужен не кузов, а целый самосвал. И с кузовом, и с шасси, и с кабиной, и с VIN-номером, и с госномером. А если я захочу возить песок в этом же кузове, но с другим шасси, мне нужно будет полностью пересобрать самосвал, и у него уже будет совсем другой VIN, и он будет считаться другим транспортным средством.
Точно так же вы можете вынести всю бизнес-логику в модули, сделать их независимыми от чего угодно. Но бизнес не может пользоваться модулем. Чтобы модуль заработал, он должен быть частью целого приложения. И вот если у тебя сегодня приложение работает на симфони, а завтра ты хочешь переехать на ларавель, то тебе надо на ларавели собрать новое приложение и поставить туда твой модуль. Послезавтра ты хочешь отказаться от фреймворка, ты пишешь приложение без фреймворка и ставишь туда свой модуль. И все три раза это будут три разных приложения. То есть это перенос бизнес-логики в другое приложение, но никак не замега фреймворка.
vandy Автор
Это и есть смена легкая фреймворка, достаточно только поправить связывающий слой. Бизнес логика не будет меняться.
Приложение - это средство для достижения цели, фреймворк - это инструмент. А бизнес логика - это самое ценное, то на чем компания зарабатывает деньги.
Бизнесу важно не с каким VIN будет перевозка песка, а сколько песка можно будет перевезти.
Бизнесу все равно на ваше шасси, одно это транспортное средство или разные.
Бизнесу ОЧЕНЬ важно, чтобы самосвал легко чинился и легко модернизировался под новые перевозки.
gun_dose
А вы попробуйте продать бизнесу самосвал с плохо зарекомендовавшим себя двигателем, вот и посмотрим.
А вообще интересно, как лихо многие говорят о "лёгкой смене фреймворка". Сколько раз в жизни вам приходилось его менять, и сколько раз это оказалось легко?
vandy Автор
Даже обновление мажорной версии фреймворка выливается в "смену", настолько сильные бывают изменения.
И это было больно, тк изначально не захотели делать правильно, а делали побыстрее.
gun_dose
Чтобы не наломать дров при обновлении мажорной версии, нужно при обновлениях минорных внимательно следить за всеми deprecations и change records. Ах ну да, change records ведь пишут авторы фреймворка, а старина Боб так пренебрежительно о них высказался. Но мне интересно, неужели вы не видите подвоха, что мнение одного автора, написавшего теоретическую книгу, вдруг становится важнее, чем мнение множества авторов программного продукта, который отлично работает в сотнях тысячах или даже миллионах приложений? Боб сказал, надо делать не так, как хотят авторы фреймворка, значит авторы фреймворка дураки? Мнение одного, пусть умного, человека против мнения коллектива людей, доказавших свою правоту на практике?
Ну так а кто делает правильно? Есть примеры, кроме примеров банального отказа от фреймворков?
Приведу пару цитат из книги Мартина:
Тут он поясняет, что это самое "расстояние вытянутой руки" должно быть заполнено прокси-классами. Ну или адаптерами. И вот тут самое время спуститься с небес на землю. В реальном проекте эти самые адаптеры и прокси классы будут составлять порядка 90% написанного вами кода. И эти 90% будут на 99,(9)% определять стабильность и производительность вашего приложения. И это будет самый сложный для понимания код, потому что там вам придётся использовать и внедрение зависимостей, и инверсию зависимостей, и опираться одновременно на API фреймворка и API вашей бизнес-логики.
И что получим на выходе? 10% чистого кода, одобряемого дядюшкой Бобом. И 90% грязных вонючих адаптеров для работы с богомерзким фреймворком, авторы которого дураки. И в итоге смена фреймворка выльется в переписывание этих самых 90% кода. Но при этом 10% кода, содержащего бизнес-логику, перенесутся без изменений.
Так вот, к чему я? В реальных приложениях бизнес-логика - это ничтожно малая часть функционала приложения. Бизнес-логику вообще можно писать на языках разметки, а не языках программирования (например, YAML). Но качество приложения почти на 100% зависит от качества вспомогательного кода - того самого фреймворка и адаптеров для работы с ним. И это то, чем практика отличается от теории.
m03r
Так целая глава называется
Оглавление «Чистой архитектуры»
gun_dose
Осуждаю
gun_dose
Посмотрел. Там, оказывается, есть не только оглавление. В самой главе автор почти в каждом абзаце демонстрирует своё несогласие с подходом, предлагаемым авторами фреймворков. Именно потому, что по задумке авторов фреймворка это не деталь, а именно то, что пытается диктовать архитектуру. И он предлагает использовать фреймворки по-своему. Поэтому заголовок этой главы правильно трактовать так: используй фреймворк так, чтобы это была деталь.
Вы же истолковали это как "фреймворк является деталью". Но ведь эта книга - это набор советов, а не набор определений. Более того, это набор идеализированных советов. Он там сам пишет, что в класс Main неизбежно попадут зависимости фреймворка. И это самый главный абзац в той главе. Тут же дело в том, что Main - это точка входа. То место, которым приложение соприкасается с окружающим миром. И у любой бизнес-логики этих точек много. Например, точка, где юзер жмёт кнопочку на форме. А чтобы показать кнопку, нужен как минимум роутер, контроллер, и шаблонизатор. И тут можно ещё задаться вопросом, входит ли, скажем, защита от XSS в нашу бизнес-логику? Или мы можем взять её из фреймворка. И тут мы с практически 100% вероятностью придём к тому, что 99% приложения - это "деталь". А твоя чистая архитектура - это те 100 строчек кода, в которые уместилась вся бизнес-логика