Как стать автором
Обновить

The Clean Structure — Универсальная структура PHP-проекта на примере Laravel

Уровень сложностиСредний
Время на прочтение12 мин
Количество просмотров6.5K
Всего голосов 6: ↑6 и ↓0+8
Комментарии30

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

Дядюшка Боб писал, что фреймворк — это деталь (которую должно быть легко заменить), поэтому я с подозрением отношусь ко всем шаблонам вида «чистая архитектура для...». Кстати, с этой точки зрения и «проверка доступности» — такая же деталь, которая, наверное, имеет значение только для балансировки трафика. Ведь если приложение действительно недоступно, то об этом пользователи узнают при любом запросе.

Но в связи с первым хочу задать вопрос: насколько эта структура поменяется, например, при смене фреймворка с Laravel на Symfony? Придётся ли переписывать что-то, кроме «клея» между бизнес-логикой и фреймворком? И, наверное, самый важный вопрос: как такая структура защищает от «прикипания» к конкретному фреймворку?

В данном случае явно придется. Ибо, например, в доменном слое, в Entity используется Laravel:

use Illuminate\Foundation\Auth\User as Authenticatable;
 use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
 {

....

}

В идеальном мире framework agnostic надо было бы создать свой адаптер на Illuminate, в этом есть минус - это дополнительное время на разработку и тестирование. Но так же это и плюс - при смене фреймворка либо самого либо его версии надо будет исправить только адаптер.
В общем, надо искать баланс и где-то идти на уступки всей этой чистой идеологии.

Я примерно так и пишу (для себя, не для коммерческой разработки). Условно пишу на чистом PHP (ну кроме всяких библиотек вроде phpunit, uuid и assert-ов для тестов), а потом уже когда упираюсь в инфраструктурном слое в new Class(new Class2 (new Class3)) и так далее, начинаю привязывать какой нибудь нефрейморковский DI, потом нейфреймоврковские шины, а потом уже убираю и привязываю, например, Symfony и делаю уже адаптеры к его Messenger и его DI. В большинстве случаев "тот" чистый php проект можно таскать как угодно между фреймворками. Повторюсь, это не для коммерческой разработки.

насколько эта структура поменяется, например, при смене фреймворка с Laravel на Symfony? Придётся ли переписывать что-то, кроме «клея» между бизнес-логикой и фреймворком? И, наверное, самый важный вопрос: как такая структура защищает от «прикипания» к конкретному фреймворку?

Я пробовал применить это к Симфони, всё точно так же и некоторые моменты даже проще чем в Ларе.
В целом, защищает от "прикипания", особенно если на все важные места создавать адаптеры и декораторы.

«проверка доступности» — такая же деталь, которая, наверное, имеет значение только для балансировки трафика.

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

А вы не путаете фреймворк с библиотекой? Фреймворк по определению должен задавать структуру проекта. Замена фреймворка на проекте - это то же самое, что замена шасси в самосвале.

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

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

Видимо, пример с самосвалом не очень удачный. Либо вы его неправильно поняли. Фреймворк - это шасси, а не кабина. Можно проектировать универсальные кабины под любые шасси. Можно проектировать шасси, подходящие под разные транспортные средства. Но если рассматривать самосвал, как самостоятельное целостное транспортное средство, в нём можно поменять колёса, кабину, кузов, но смена шасси запрещена по закону, т.к. это приведёт к изменению VIN-номера. Опять же, кабину, кузов, двигатель можно снять с самосвала, но снять шасси нельзя, можно только полностью разобрать самосвал.

На практике понятие "framework agnostic" можно применить только к отдельным программным компонентам. Если же речь идёт о целом приложении, то никто и никогда не проектирует "framework agnostic" приложения. И более того, никто никогда не ставит задачу сменить фреймворк приложения.

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

На практике понятие "framework agnostic" можно применить только к отдельным программным компонентам.

В статье давал ссылку на Framework Agnostic длиной в 12 лет, там целый проект так живет.

Я тут вижу некорректное использование термина framework agnostic. Этот термин можно применять к библиотекам. Например geophp - это framework agnostic библиотека, т.к. ей для работы не нужен никакой фреймворк. Другие же библиотеки могут требовать для работы определённый фреймворк, поэтому они не являются framework agnostic. Но применять этот термин к целым приложениям вообще некорректно, потому что приложение может использовать фреймворк, а может не использовать. И если твоё приложение не использует никакой фреймворк, то это просто приложение, не использующее фреймворк, а не framework agnostic.

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

Подходы "Framework agnostic" и "Anti-Corruption Layer" помогают изолировать бизнес логику от внешних зависимостей.
Если код написан правильно, те разделен на модули и слои, то вынести кусочек приложения в самостоятельную библиотеку не составит труда. В итоге приложение будет включать в себя множество ваших библиотек с бизнес логикой и через адаптеры связывать их с конкретным фреймворком.

Это прям идеал, к которому надо стремится, но очень дорогой и трудоемкий идеал.

Модульность - это, безусловно, must have. Но это опять же перенос библиотек, а не замена фреймворка. Это как перевозка мебели с одной квартиры на другую. Ты можешь перевезти диван, но ты не можешь просто поменять под диваном квартиру.

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

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

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

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

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

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

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

Бизнесу важно не с каким VIN будет перевозка песка, а сколько песка можно будет перевезти.
Бизнесу все равно на ваше шасси, одно это транспортное средство или разные.
Бизнесу ОЧЕНЬ важно, чтобы самосвал легко чинился и легко модернизировался под новые перевозки.

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

А вообще интересно, как лихо многие говорят о "лёгкой смене фреймворка". Сколько раз в жизни вам приходилось его менять, и сколько раз это оказалось легко?

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

Чтобы не наломать дров при обновлении мажорной версии, нужно при обновлениях минорных внимательно следить за всеми deprecations и change records. Ах ну да, change records ведь пишут авторы фреймворка, а старина Боб так пренебрежительно о них высказался. Но мне интересно, неужели вы не видите подвоха, что мнение одного автора, написавшего теоретическую книгу, вдруг становится важнее, чем мнение множества авторов программного продукта, который отлично работает в сотнях тысячах или даже миллионах приложений? Боб сказал, надо делать не так, как хотят авторы фреймворка, значит авторы фреймворка дураки? Мнение одного, пусть умного, человека против мнения коллектива людей, доказавших свою правоту на практике?

не захотели делать правильно

Ну так а кто делает правильно? Есть примеры, кроме примеров банального отказа от фреймворков?

Приведу пару цитат из книги Мартина:

О, вы можете использовать фреймворк — просто не привязывайтесь к нему. Держите его на расстоянии вытянутой руки.

Если фреймворк предложит вам породить свои бизнес-объекты от его базовых классов, скажите «нет»! Определите прокси-классы и держите их в компонентах, являющихся плагинами для ваших бизнес-правил.

Тут он поясняет, что это самое "расстояние вытянутой руки" должно быть заполнено прокси-классами. Ну или адаптерами. И вот тут самое время спуститься с небес на землю. В реальном проекте эти самые адаптеры и прокси классы будут составлять порядка 90% написанного вами кода. И эти 90% будут на 99,(9)% определять стабильность и производительность вашего приложения. И это будет самый сложный для понимания код, потому что там вам придётся использовать и внедрение зависимостей, и инверсию зависимостей, и опираться одновременно на API фреймворка и API вашей бизнес-логики.

И что получим на выходе? 10% чистого кода, одобряемого дядюшкой Бобом. И 90% грязных вонючих адаптеров для работы с богомерзким фреймворком, авторы которого дураки. И в итоге смена фреймворка выльется в переписывание этих самых 90% кода. Но при этом 10% кода, содержащего бизнес-логику, перенесутся без изменений.

Так вот, к чему я? В реальных приложениях бизнес-логика - это ничтожно малая часть функционала приложения. Бизнес-логику вообще можно писать на языках разметки, а не языках программирования (например, YAML). Но качество приложения почти на 100% зависит от качества вспомогательного кода - того самого фреймворка и адаптеров для работы с ним. И это то, чем практика отличается от теории.

Так целая глава называется

Оглавление «Чистой архитектуры»

Осуждаю

Посмотрел. Там, оказывается, есть не только оглавление. В самой главе автор почти в каждом абзаце демонстрирует своё несогласие с подходом, предлагаемым авторами фреймворков. Именно потому, что по задумке авторов фреймворка это не деталь, а именно то, что пытается диктовать архитектуру. И он предлагает использовать фреймворки по-своему. Поэтому заголовок этой главы правильно трактовать так: используй фреймворк так, чтобы это была деталь.

Вы же истолковали это как "фреймворк является деталью". Но ведь эта книга - это набор советов, а не набор определений. Более того, это набор идеализированных советов. Он там сам пишет, что в класс Main неизбежно попадут зависимости фреймворка. И это самый главный абзац в той главе. Тут же дело в том, что Main - это точка входа. То место, которым приложение соприкасается с окружающим миром. И у любой бизнес-логики этих точек много. Например, точка, где юзер жмёт кнопочку на форме. А чтобы показать кнопку, нужен как минимум роутер, контроллер, и шаблонизатор. И тут можно ещё задаться вопросом, входит ли, скажем, защита от XSS в нашу бизнес-логику? Или мы можем взять её из фреймворка. И тут мы с практически 100% вероятностью придём к тому, что 99% приложения - это "деталь". А твоя чистая архитектура - это те 100 строчек кода, в которые уместилась вся бизнес-логика

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

Да, есть такой нюанс, я об этом писал в статье:

Структура проекта - это стратегическое решение, которое должно базироваться на анализе конкретных бизнес‑требований, доступных ресурсов и сроков выполнения проекта!

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

Респект за материал и подачу - сразу видно вложенные усилия, потраченные на пост. Особенно приятно, что есть референсы в виде ссылок.

Фидбек по сути\смыслу - если бы я знал, что меня ждет проект с таким подходом - я бы туда точно не пошел. Когнитивная нагрузка дикая (код чаще читают, чем пишут).
Оверинжиниринг на лицо (потому что декларируется "уникально правильный" подход под все кейсы, а не гибкая разработка под ситуацию). Складывается ощущение, что для вас цель написания кода - это не решение проблем и ведение бизнеса, а маниакальное стремление к субъективному перфекционизму. Пока вы разрабатываете продукт, конкуренты уже захватят рынок и вам не останется на нем места.
Bounded context, разделение слоев и зон ответственности в коде - это важно. Но это важно только для поддержки и расширения. Если у вас есть гарантия, что код будет одноразовый и ни для чего другого использоваться не будет - вы все равно будете разбивать функции вроде хелсчека на 6 классов или все же нужно решать в зависимости от контекста?

Хм, для меня структурирование кода только уменьшает нагрузку при чтении нового кода. А если эта структура идет из проекта в проект, из модуля в модуль, то всё упрощается в разы.
Согласен, не все готовы работать по определенным стандартам - это разрыв шаблона, это ограничение творческой жилки и тп. Я сам через это проходил, но опыт на разных проектах, чтение умных статей и просмотр выступлений помогли сформировать понимание, что стандартизация - это хорошо.

Оверинжиниринг будет ярко выражен на маленьких и простых проектах. В остальных случаях этот подход будет оправдан. Я писал в статье:Нет «серебряной пули», есть только путь в попытке найти баланс между трудозатратами, качеством и скоростью. И в каждому случае - он свой. И там же привел пару правил, которые для себя выработал для определения сложности.

Когда-то моя команда создавала несколько микросервисов, и там мы точно не задумывались над структурой папок. Там на один сервис было не более 20 файлов. Но проекту это не помогло, его закрыли. В сроки и бюджет не уложились, и скорость разработки тут была совершенно не причем.
Часто заказчик хочет всё и вчера, но если подойти с умом, то сначала надо сделать MVP, который запустить на рынок и начать зарабатывать, и параллельно с этим допиливать новый функционал. И это всё можно делать с первых строк кода хорошо.
Естественно не надо сразу проектировать коня в вакууме, делаем как умеем и только то что надо сейчас + готовим точки для расширения на будущее.

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

В целом не люблю использовать аналогии, но тут подходит - для хорошего UX стараются минимизировать количество переходов\кликов пользователя до получения нужного ему результата. Вот если представить, что вам для действия, которое на других сайтах занимает 2 клика, потребуется сделать 6 - как может когнитивная нагрузка быть меньше?

Представьте, что вы знаете эту архитектуру, но видите проект впервые. Чтобы понять, как конкретно работает хелсчек роут, вам придется "загрузить" в голову логику из 6 отдельных классов и построить представление работы этого кода. Не представляю, как это может оказывать меньшую когнитивную нагрузку, чем если бы, по вашему говоря, человек "заговнокодил" всю логику в 10-15 строк в контроллере. В этом кейсе человек, незнакомый с проектом\кодом, получит всю нужную ему информацию за 2 перехода - поиск нужного роута и переход в экшн контроллера. Офк, хелсчек это просто пример) Основная проблема здесь в том что, вероятнее всего, на своих проектах вы будете, в том числе с помощью инструментов CI, форсить "чистую архитектуру" и code coverage, тем самым снижая гибкость разработки

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

Всё зависит от ваших знаний и умений.

Я вашу стадию уже проходил)) У нас была модульная архитектура еще тогда, когда nwidart\internachi\им подобных пакетов и в помине не было) Это сейчас она пилится проще простого, а в те времена нам приходилось точечно оверрайдить логику лары :) Так что да, будем надеяться, что и у вас понимание придет :)

Очень интересен ваш опыт.
Если не секрет: у вас сколько человек в команде разработки и какие приняты правила по организации кода?

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации