Comments 14
Да, согласен. Сначала сторонники этих слоеных архитектур говорят, ну и что, что в 2 раза больше работы, зато... А потом приходят топ-менеджеры "А вот в компании X код пишет ИИ на 10% быстрее, давайте вас всех уволим"
Несмотря на то что согласен почти со всем, дальше это обычно приводит к тому что "упрощенная более понятная архитектура без овер инжиниринга" требует, например, добавить код логгирования api 3rd party вызовов всего лишь в 60-70 мест вместо одного. И по времени это существенно дешевле чем "заметить тут очевидное решение и выделить API client gateway".
Т.е. при устойчивом и корректном росте приложения:
"строгая" архитектура переусложнена на старте (но вы сами же пишете что сейчас IDE съедают эту сложность), но при этом остается предсказуемой для внесения изменений
тогда как "упрощенная" архитектура может быть более понятной сейчас, но впоследствии цена внесения изменений только растёт
Хорошо что все собранно в одной статье - я и сам местами стал приходить к тем же выводам.
С другой стороны - если сначала делать просто, то потом дольше переделывать на более сложный подходящий вариант.
И еще - как только мы начнем думать о том как все это тестировать, то все эти пункты могут быть переработаны. Например интеграционное тестирование. Можно тестировать начиная с контроллеров (это когда поднимают экземпляр сервисами дергают его по http). А можно поднимать только бизнес слой + бд и тогда однострочные контроллеры - благо.
Все это выглядит как лечение симптомов а не причины. Причем лечение симптомов закономерно приводящее к другим проблемам. Начнем с того, что упрощения автора во многих случаях просто делают пресловутую гексагональную архитектуру бесполезной. К примеру, он предлагает делать прямые вызовы репозиториев, при этом не задумываясь зачем вообще репозитории чаще всего спрятаны и к каким проблемам это может привести. Немножко подумаем и поймем что репозиторий можно отдавать только на чтение. Почему? Потому что если мы позволяем модифицировать данные в любом месте проекта то весь смысл слоеной архитектуры, теряется.
Следующий момент который автор не осознает - это аспекты промышленной разработки. В реальности, когда над проектом работают несколько человек то важнейший аспект это простота и единообразие подхода. К сожалению или счастью, средний разработчик не искушен понимание архитектурных изысков и уместности и давать ему возможность делать на своей усмотрение - плохая затея. Проект очень скоро будет представлять из себя клубок запутанного кода. Каждый разработчик как уникальная снежинка начнет делать по своему. Намного лучше тупо писать эти интерфейсы везде к примеру и где-то писать больше кода, чем заставлять среднего разраба вставать перед выбором писать или нет а другого разраба пытаться понять чем руководствовался первый.
Другой намного более важный вопрос которым стоит задаться: гексагональная архитектура - это вообще корректный подход к разработке плюсы которого перевешиваю минусы? Если немного отстранится от хайпа и карго культов, мы имеет сильно овер-инженерный подход, который даже "по учебнику" получается не очень и предлагается интерпретировать все правила вольным для каждого разработчика образом походу теряя те преимущества которые данный подход обещал. Может быть немного притормозить и оглянутся по сторонам?
Каждый раз, когда читаю очередную похожую статью, возникает ощущение, что наше понимание того, что является хорошим или плохим кодом, постепенно движется куда-то не в ту сторону. Упрощение ради самого упрощения стало идеей фикс.
Безусловно, в статье есть и дельные мысли. Но не покидает чувство попытки любые правила назвать "старыми привычками" и преподносить антипаттерны как полезные рекомендации. Те же толстые контроллеры, за которые раньше били по рукам новичков, теперь вдруг стали хорошим подходом.
Концентрическая архитектура имеет другое устоявшееся название - DDD Lite. Вполне себе практическая штука, когда команда перестает вывозить когнитивную нагрузку. Есть сомнения в переходе на два слоя, но четыре, как в классическом DDD, это черезчур.
Эти упрощения хороши, когда все разработчики в компании опытные, умеют в абстракции и отлично понимают, что такое хорошо, и что такое плохо. Когда же на одного архитектора приходится десяток проектов, в каждом из которых джуны, не понимающие архитектурных принципов, так как ещё просто не успели шишек набить, и мидлы с лозунгом Вовки "и так сойдёт", в кодовых базах мгновенно образуется лапша. К тому же, строго формализованную архитектуру легче автоматически валидировать.
Надо просто посчитать сколько стоит то или иное решение.
Выгодно изолировать - изолируем.
Нет - нет.
Всегда идет выбор между тем - "оверинжинирить" или положить в тех.долг. И тут основной вопрос - что дороже.
Например, про интерфейсы частый посыл - когда появится вторая реализация, тогда и сделаем. В такой трактовке кажется действительно дешевле положить это в тех.долг, т.к. неизвестно, когда это потребуется, и исправление выглядит совсем дешевым. Но интерфейсы это не только про наследование и разные имплементации. Интерфейсы еще позволяют описать API некоего блока кода и защититься от использования внутрянки этого кода снаружи. Вот если в таком случае не выставить заранее интерфейс, то потом, когда у вас появится еще один "потребитель" (а это частая история, в отличии от другой имплементации), никто не вспомнит про выделение интерфейса и система начнет превращаться в лапшу (те самые cohesion and coupling нарушаются). И тогда тех.долг становится очень дорогим (порой проще с нуля все переписать, чем распутать все переплетения в системе).
Аналогично и с другими примерами статьи. Надо оценивать стоимость оверинжиниринга и стоимость тех.долга. Тот же однострочный метод с вызовом другого метода ("middle man"), что дешевле - сделать его сейчас (и иметь лишний переход при дебаге и в стектрейсах), или рефакторить когда потребуется добавить дополнительную логику в него с учетом особенностей использования всеми его потребителями. Каждый раз стоит об этом задуматься: а не делать на автомате то, что написали в книжке, или в статье из интернета.
Чаще всего, многие вещи в книжках как раз написаны для того, чтобы при написании больших сложных систем сделать чуть больше работы, но зато обеспечить себе более дешевое развитие и изменение этой системы. Если у вас вся сложность системы заранее разбита на понятные блоки в виде простых микросервисов, то вы этот "оверинжиниринг" уже проделали заранее и нет смысла его еще и в код тащить (хотя в этом случае, я бы вообще задумался - а зачем вам там java)
ООП это тоже оверинжиниринг по сравнению с процедурным программированием (бейсик, паскаль). Но ООП победило в соревновании с ними. Хотя в последнее время на хабре были статьи, что ООП это худшее что могло случиться с программированием.
Используя инструменты для рефакторинга, включенные в современные IDE (например, IntelliJ), вы можете извлечь интерфейс из существующего класса и заменить референсы на класс новым интерфейсом во всей кодовой базе за несколько секунд и почти ничем не рискуя.
Вот вам яркий пример привязки к реализации. Описанной опции нет в VS Code, Fleet, nvim (даже с NvChad).
Другими словами, вся идея, описанная в статье, развалится, если у меня нет IDE IntelliJ.
Написано всё правильно,
кроме отдельных случаев:
а) если это большое монолитное приложение
б) если много джунов, которым надо ограничить возможность всё испортить.
Все эти архитектуры написаны для больших монолитов, микросервисов в то время не было.
Если доменная сложность вашей задачи достаточно низкая (только CRUD), или главный вызов, исходящий от вашего приложения, НЕ заключается в сложности его бизнес-правил, тогда (…) вам может больше подойти вертикальная нарезка, анемичная модель, CQRS или другой тип архитектуры.
…А вы опять делаете то, что за вас уже сделано сотни тысяч раз. ИИ справится лучше.
Оверинжиниринг в луковичной и гексагональной архитектурах