Handbook frontend. Луковая архитектура. Часть 1


Привет, Хабр. Меня зовут Виктор Жабин, я архитектор в Т1. Эта статья — начало большого руководства по созданию луковой архитектуры своими руками.
Архитектурные стили — это, по сути, устоявшиеся схемы организации компонентов в системе. В профессиональной среде они выполняют ту же роль, что и паттерны проектирования в коде: произносишь название — и коллега уже примерно представляет, о чём речь, какие преимущества и недостатки у такого подхода, как устроены данные и что ждать при развёртывании.
Например, когда говорят о луковой архитектуре, сразу становится понятно, как выглядит структура: ядро с бизнес‑логикой в центре, вокруг него слои‑адаптеры (интерфейсы, базы данных, внешние API). Сразу видно, с какими сложностями можно столкнуться и какие стратегии хранения данных здесь чаще всего применяются. Главный принцип — зависимости направлены внутрь, внешние слои зависят от внутренних, но не наоборот. То есть за каждым названием скрывается не просто картинка, а целый набор решений — и удачных, и не самых удачных.
Но если мы говорим о фронтенде, то луковый подход обретает свои особенности. Здесь слои чаще всего выглядят как UI‑компоненты (внешний слой), бизнес‑логика (внутренний слой) и API‑клиенты (внешний слой). И хотя топология остаётся той же — ядро в центре, адаптеры снаружи, — конкретные инструменты и практики сильно отличаются от бэкенда, а принцип изоляции бизнес‑логики от внешних технологий работает так же.
Хороший архитектор, даже работая исключительно на фронтенде, должен знать несколько базовых архитектурных стилей. Они служат той основой, на которую нанизываются любые более сложные или гибридные схемы. Без этого понимания легко переусложнить проект или, наоборот, потерять структуру там, где она нужна.
Примеры популярных архитектур больших серверных приложений:
Layered Architecture — с разделением на логические слои;
Tiered Architecture — с физическим разделением на уровни;
SOA — с интеграцией через сервисы;
Microservice Architecture — с независимыми сервисами.
У каждой свои достоинства и недостатки, но их изучение ничего вам не даст.
Архитектура — это ответ на вопрос «как организовать взаимодействие тысяч объектов внутри системы». И пока на своём опыте не почувствуешь всю сложность проблемы, ты не сможешь понять и всю многогранность решения.
Все системы построены по какой‑либо архитектуре, даже если это не декларируется. Освоив популярные подходы, вы научитесь быстрее ориентироваться в коде и попадать в точки изменений. Сразу же возникает вопрос: а как понять, где эти точки, и бывают ли такие, которые лучше обходить стороной? Да, бывают, и их надо уметь видеть.
Предположим, вы работаете над проектом, который развивают уже три года. Команда 30 человек. За это время накопилось около 300 тысяч строк кода, несколько сотен компонентов и примерно 50 модулей разного размера и назначения. Типичная ситуация: бизнес‑логика размазана по всему проекту. Где‑то она лежит в компонентах, где‑то — в сервисах, а иногда и вовсе спрятана в HTML‑шаблонах. Сможете ли вы с ходу определить, в какой именно файл нужно вносить правки?
И поверьте, это не страшилка, так устроено большинство проектов. А бывает и ещё плачевнее. Откуда берётся этот хаос? Классические причины:
множество разработчиков с разным видением идеальной структуры;
новым людям проще дописать своё, чем разбираться в старом;
софт живёт правками, а каждая правка тянет за собой следующую;
изначальная архитектура закладывалась под задачи, которые давно потеряли актуальность.

Но главное, что при любой архитектуре все работающие над проектом программисты обязаны иметь одно и то же представление о том, как он устроен. Без этого толку не будет.
Начнём мы с самого простого: с клиент‑серверной архитектуры.
Клиент‑серверная архитектура
C течением времени требовалось отделить одну систему от другой. Многие стили архитектуры позволяют эффективно это сделать.
Фундаментальный стиль в архитектуре разделяет техническую функциональность между фронтендом и бэкендом и называется двухуровневой, или клиент‑серверной архитектурой. Существует множество различных её вариантов в зависимости от эпохи и вычислительных возможностей.
Desktop + сервер базы данных
Ранняя архитектура персональных компьютеров поощряла разработчиков писать богатые настольные приложения в пользовательских интерфейсах, таких как Windows, вынося данные на отдельный сервер базы данных. Автономные серверы баз данных могли подключаться по стандартным сетевым протоколам. Благодаря этому логика представления могла находиться на рабочем столе, в то время как более интенсивное вычислительное действие (как по объёму, так и по сложности) происходило на более надёжных серверах баз данных.
Браузер + веб‑сервер
Как только появилась современная веб‑разработка, в моду вошёл веб‑браузер, подключённый к веб‑серверу (который, в свою очередь, был подключён к серверу баз данных).
Разделение обязанностей было аналогично настольному варианту, но с ещё более тонкими клиентами в качестве браузеров. Благодаря этому такая архитектура получила массовое распространение и внутри брандмауэров, и снаружи.
Хотя база данных отделена от веб‑сервера, архитекторы часто всё ещё считают это двухуровневой архитектурой, потому что веб‑серверы и серверы баз данных работают на одном классе машин в центре операций, а пользовательский интерфейс работает в браузере пользователя.
Трёхуровневая архитектура
Трёхуровневая архитектура (трёхзвенная, англ. three‑tier) — архитектурная модель программного комплекса, предполагающая наличие в нём трёх типов компонентов (уровней, звеньев): клиентских приложений (с которыми работают пользователи), серверов приложений (с которыми работают клиентские приложения) и серверов баз данных (с которыми работают серверы приложений).

Трёхуровневая архитектура, которая стала довольно популярной в конце 1990-х годов, подразумевала ещё больше слоёв разделения. Всё чаще использовать серверы приложений на Java и.NET, и компании начали создавать новые слои в топологии: уровень базы данных с использованием сервера базы данных промышленного уровня; уровень приложений, управляемый сервером приложений; интерфейс, закодированный в генерируемом HTML; и всё больше JavaScript, поскольку его возможности расширились.
Трёхуровневая архитектура использовала концепцию общей архитектуры посредника запросов объектов (CORBA) и распределённую компонентную объектную модель (DCOM). Они облегчали построение распределённых архитектур, сняв с разработчиков заботу о создании протоколов. Примерно так же, как сегодня мы не задумываемся о работе сетевых протоколов вроде TCP/IP — они просто работают.
Трёхуровневая концепция языка и долгосрочные последствия
Язык Java возник во времена, когда в моде были трёхуровневые вычисления. И предполагалось, что в будущем все системы будут иметь трёхуровневую архитектуру. Одна из общих с существовавшими языками проблем заключалась в сложности согласованного перемещения объектов по сети между системами. Разработчики Java решили встроить эту возможность в ядро языка, используя механизм под названием сериализация. Каждый Object в Java реализует интерфейс, который требует его для поддержки сериализации. Авторы посчитали, что поскольку трёхуровневая архитектура навсегда останется архитектурным стилем, то удобно будет внедрить его в язык. Как и всё модное, стиль пришёл и ушёл, но его остатки проявляются в Java по сей день, что очень расстраивает проектировщиков языков, желающих добавить современные функции: для обратной совместимости они должны поддерживать сериализацию, которую практически никто сегодня не использует.
Понимание долгосрочных последствий проектных решений всегда ускользало от нас как в программном обеспечении, так и в других инженерных дисциплинах. Вечный совет отдавать предпочтение простым конструкциям — это, во многом, защита от будущих последствий.
Цель архитектуры программного обеспечения

Цель архитектуры программного обеспечения — уменьшить человеческие трудозатраты на создание и сопровождение системы.
Роберт Сесил Мартин
Хорошая архитектура та, где изменения обходятся дёшево и не дорожают со временем. Плохая — где каждое обновление требует всё больше сил. Если проект пишут наспех, штат растёт только ради релизов, а за порядком в коде никто не следит, то крах неизбежен. Переработки тут не помогут. Вместо новых фич команда занимается одним: тушит пожары, ловит баги и перекладывает хаос из одной части системы в другую. Так и живём, в основном разгребаем, а по‑настоящему новое появляется крайне редко.
Самая большая ложь, в которую верят многие разработчики, — что грязный код поможет им быстро достичь результатов. В действительности он затормозит их движение в долгосрочной перспективе.
Роберт Сесил Мартин (из книги «Clean Code», пересказ ключевой идеи)
Признаки качественной архитектуры (мой взгляд)
Опытный разработчик всегда отличит хорошее решение от плохого, но объяснить, в чём разница, сможет не каждый. И я не исключение. Единого эталона нет, как нет и универсального определения. Но за годы работы я выработал для себя несколько ориентиров.
Главный из них: система должна быть понятной. Не красивой, не модной, а именно понятной. Когда я открываю проект, я хочу за пять минут понять, куда добавлять новый код, где искать существующий и как всё друг с другом связано. Если для этого нужно полчаса водить пальцем по экрану, то архитектура провалилась, даже если она написана по всем канонам.
Я перестал верить в универсальные списки качеств, когда понял, что идеальная архитектура это та, о которой не думаешь. Если я трачу время на выбор паттерна, а не на написание фичи, то я уже проиграл. Но если уж говорить о свойствах, то я оцениваю систему по шести пунктам. Только не как абстрактные термины, а как конкретные вопросы к себе.
1. Работоспособность (она же продуктивность)
Очевидно, что система должна делать то, для чего её создали, и делать это стабильно. Но странно, на практике это бывает далеко не всегда. Базовая функциональность часто хромает, потому что разработчики увлеклись «красивой архитектурой» и забыли про пользователя.
Мой тест прост: можно ли залогиниться, посмотреть данные и что‑то сохранить без ошибок? Если да, то мы на правильном пути. Если нет, то все остальные достоинства не имеют смысла. Я не требую идеального uptime, но базовые сценарии должны отрабатывать как часы.
2. Гибкость (адаптивность)
Если есть свойство, которое я ставлю выше работоспособности, то это гибкость. Потому что требования меняются всегда. Всегда. И чем безболезненнее мы можем вносить правки, тем дольше система проживёт.
Но есть нюанс: гибкость — это не про удобство. Это про деньги. Если на добавление кнопки в интерфейсе уходит две недели вместо двух часов, то архитектура не гибкая. И каждый такой «кирпич» в будущем стоит либо времени разработчика, либо нервов заказчика. Настоящая гибкость измеряется в человеко‑часах, а не в количестве паттернов.
Новички часто пытаются спроектировать идеальную структуру под сегодняшние задачи. Это ошибка. Нужно закладывать основу под то, что появится через год. При этом предугадать требования невозможно, там всегда сюрпризы. Поэтому я постоянно спрашиваю себя: «А что будет, если это решение окажется неверным? Сколько кода придётся переписывать?» Хороший признак — когда изменения в одной части не тянут за собой цепочку правок в других.
Роберт Мартин когда‑то сказал: качественная архитектура позволяет отложить принятие важнейших решений на потом. Я же в своих проектах понял, что это работает ровно до тех пор, пока ты не ошибаешься. А ошибаешься ты всегда. Поэтому мой вариант этой мысли звучит так: «Архитектура должна стоить столько же, сколько стоит ваша самая дорогая ошибка». И если цена ошибки переписать три класса, а не триста, то вы всё сделали правильно.
Один из популярных способов достичь гибкости — микросервисы. Но здесь легко перегнуть. Если для реализации крошечной фичи приходится править десяток сервисов, то это явный перебор. Гибкость не в количестве сервисов, а в том, насколько легко их менять по отдельности.
Альтернативный подход — плагинная архитектура. Она даёт похожую гибкость, но без распределённого ада. Новые возможности подключают как плагины, не трогая ядро — как в WordPress, где движок обновляют годами, а функциональность наращивают через плагины. И микросервисы, и плагины решают одну задачу: изолируют изменения.
3. Масштабируемость
Масштабируемость для меня это не про нагрузку. Это про команду. Может ли система расти, если к проекту подключаются новые люди? Архитектура должна позволять распределять работу так, чтобы разработчики не мешали друг другу. Казалось бы, очевидно, но практика показывает обратное. Есть знаменитая книга «Мифический человеко‑месяц», в которой описано, как добавление новых рук в опаздывающий проект чаще всего лишь затягивает его. Всё потому, что люди начинают спотыкаться о чужой код.
Мой тест: может ли новый стажёр добавить эндпоинт, не сломав оплату? Если да, то система живая. Если нет, то мы перемудрили.
4. Расширяемость (способность к развитию)
Расширяемость это возможность встраивать новые сущности, не ломая уже сложившуюся структуру. В начале я закладываю только самое необходимое (принцип YAGNI: “you ain't gonna need it”). Но при этом архитектуру нужно спроектировать так, чтобы добавление новых фич не превращалось в проблему. Наиболее вероятные изменения должны требовать минимум усилий.
Гибкость и расширяемость настолько критичны, что им посвящён «Принцип открытости‑закрытости» (Open‑Closed Principle), второй из SOLID. Он гласит: программные сущности должны быть открыты для расширения, но закрыты для изменения. Проще говоря, новое поведение должно добавляться, а не переписывать старое. Именно на этом построена плагинная архитектура.
Но я смотрю на это проще. Способность к развитию для меня — это не про SOLID. Это про страховку. Если завтра уйдёт ключевой разработчик, сможет ли система выжить? Хорошая архитектура та, которая позволяет компании не остановиться, если я попаду под автобус (Bus Factor). Это вопрос непрерывности бизнеса, а не красоты кода.
5. Тестируемость
Покрывать код тестами — хорошая практика. Это даёт уверенность, что интерфейс работает как задумано, и позволяет смело править код, мгновенно проверяя, не сломалось ли что‑то.
Но как только начинаешь писать тесты, быстро обнаруживаешь, что большая часть кода к этому не приспособлена: приватные методы, жёсткие связки, статические классы, глобальные переменные — всё это мешает.
Новичок спросит: «Зачем тесты, если код работает?» Профессионал ответит: «Зачем рабочий код, если его нельзя проверить?»
Однажды мы спроектировали систему с идеальной гибкостью, но забыли про тестируемость. Когда пришло время выпускать релиз, мы две недели не могли понять, почему падает эксплуатация, потому что у нас не было ни одного интеграционного теста. С тех пор для меня пригодность к тестированию это не пункт в списке, а дверь в прод. Без тестов система для меня не существует, какой бы гибкой она ни была.
Код, который легко тестировать, содержит меньше ошибок и ведёт себя предсказуемее. Но тесты полезны не только этим. Я давно пришёл к выводу: требование тестируемости само по себе является отличным ориентиром, который невольно подталкивает к правильной архитектуре.
6. Сопровождаемость
Редкий проект живёт в одиночестве. Команды меняются, это норма. Кто‑то уходит в поисках нового, кто‑то приходит на его место. За время жизни среднестатистического продукта через него проходит несколько составов разработчиков. И если проект существует уже несколько лет, то вполне вероятно, что большинство текущей команды не застало его первый коммит.
Здесь кроется главный вызов. Код уже написан, но работа не заканчивается, его нужно поддерживать: чинить баги, подстраивать под новые задачи, рефакторить по мере необходимости. И всем этим занимаются люди, которые чаще всего не имеют никакого отношения к исходному авторству.
Проект должен быть устроен так, чтобы новый человек мог в нём ориентироваться без долгих погружений и ритуальных танцев с бубном. Что для этого нужно?
Чтобы файлы и папки лежали там, где их ожидаешь найти, а не в случайном порядке.
Чтобы одна логика жила в одном месте, а не была размазана по трём модулям. Иначе любое изменение превращается в квест.
Понятный код — такой, который можно прочитать и осмыслить без второго кофе.
Хотя бы минимальная документация, пара абзацев про то, как запустить проект и куда смотреть в случае проблем. Этого уже достаточно, чтобы сэкономить новичку несколько часов.
Без самодельных велосипедов там, где отлично работают общеизвестные подходы. Потому что стандарты понятны всем, а авторская экзотика — только одному человеку.
У меня есть простой способ проверить сопровождаемость. Я мысленно ставлю себе вопрос: «Сможет ли разработчик, который первый раз видит этот код, найти нужное место и исправить баг за час?» Если ответ «да», то система в порядке. Если «нет», то нам есть над чем работать.
Сопровождаемость это не про технологии. Это про человеческий фактор. Про то, что твой код будут читать другие. И от того, насколько ты об этом позаботился, зависит, будут ли они тебя проклинать или благодарить.
Как справляться со сложностью
95% слов об архитектуре программного обеспечения тратят на восхваление преимуществ «модульности». И это мало говорит о том, как её достичь, если вообще говорит.
Гленфорд Дж. Майерс (1978)
Всё, что мы называем «хорошей архитектурой», на самом деле сводится к одному простому действию: разбиению сложного на простые куски. Без этого не работают ни гибкость, ни тестируемость, ни сопровождаемость. Это фундамент, на котором всё держится.
Почему? Потому что человеческий мозг не способен удерживать в голове тысячи деталей одновременно. Мы справляемся с большими задачами только одним способом: делим их на части, каждую из которых можно понять по отдельности. В программировании это называется декомпозицией, хотя, по сути, это всё тот же старый принцип «разделяй и властвуй».
Как это выглядит на практике? Представьте, что вы строите дом. Вы не пытаетесь возвести стены, провести электричество и залить фундамент одновременно. Вы делите работу на этапы, а каждый этап — на конкретные задачи. В архитектуре ПО то же самое:
сначала определяете крупные блоки системы, например, фронтенд, бэкенд, база данных;
каждый блок разбиваете на более мелкие части: сервисы, слои, модули;
внутри них — на классы, функции, методы;
и так до тех пор, пока каждый кусок не станет настолько простым, что его можно написать и понять без усилий.
И чем глубже и продуманнее это разбиение, тем проще потом жить.
Почему это работает? Когда система разбита на независимые части, вы получаете несколько важных преимуществ:
Масштабируемость. Хотите добавить новую функциональность? Просто добавляете новый модуль, не трогая старые.
Гибкость. Меняете один модуль, остальные не страдают.
Тестируемость. Каждый кусок можно проверить отдельно, без необходимости поднимать всю систему.
Заменяемость. Если модуль устарел или не подходит, то его можно заменить на другой без переписывания всего проекта.
Переиспользование. Удачно сделанный модуль можно применить в других проектах.
Сопровождаемость. В разбитой на части системе новому разработчику проще разобраться.
По сути, декомпозиция — это и есть главный архитектурный приём. Всё остальное — способы организовать эти части друг с другом и с внешним миром.
Мой взгляд
Когда я слышу слово «архитектура», я не думаю о красивых схемах или модных паттернах. Я думаю об одном: насколько хорошо я разбил систему на части. Потому что если разбиение правильное, то всё остальное — гибкость, тестируемость, сопровождаемость — возникает само собой. Если разбиение плохое, то никакие паттерны не спасут.
Мартин Фаулер однажды сказал, что архитектура — это те решения, которые трудно менять. Потому что именно на этапе деления закладывается, что будет легко менять, а что нет.
Поэтому мой главный совет: не гонитесь за красивыми терминами. Начните с простого, возьмите свою систему и честно ответьте на вопрос: «Можно ли её разбить на части так, чтобы каждая жила своей жизнью?» Если да, то вы на правильном пути. Если нет, то ищите, где перемудрили.
Знакомство с луковой архитектурой, эволюция многослойного подхода
Многослойная архитектура — один из самых популярных подходов, но её эволюцией стала луковая архитектура (Onion). Если классическая n‑уровневая модель часто делит код по техническим признакам (контроллеры, сервисы, репозитории), то луковая архитектура делит его по зонам влияния. В центре находится чистая бизнес‑логика (сущности и сценарии), которая ничего не знает о внешнем мире. А всё остальное — базы данных, API, UI — становится внешними слоями‑адаптерами.
Этот принцип перекликается с знакомым многим MVC, но с важным отличием: если в React или Angular вы кладёте API‑сервисы в один слой, а компоненты в другой, то в луковой архитектуре вы меняете направление зависимостей. Компоненты и сервисы для API теперь не «используют» бизнес‑логику, а наоборот реализуют интерфейсы, которые бизнес‑логика им диктует.
Неправильная логика приведения закона Конвея
Закон Конвея гласит, структура системы отражает структуру общения в команде. В луковой архитектуре мы обращаем это правило себе на пользу, чётко выделяем ядро и адаптеры, и команды выстраиваем соответствующим образом. Одни разработчики фокусируются на предметной области (core), другие на адаптерах (data и ui). Это позволяет каждому быть экспертом в своей зоне, а не универсальным «верстальщиком». В результате код остаётся чистым, а бизнес‑логика не расползается по всему проекту.
Если поместить архитектуру на последнее место, то разработка системы будет обходиться всё дороже, и в конце концов внесение изменений в такую систему или в отдельные её части станет практически невозможным.
Топология
В многослойной архитектуре компоненты выстраивают в логические горизонтальные уровни. У каждого своя чёткая роль в системе. Сколько именно слоёв делать и какие именно, строгих правил нет, это зависит от конкретного проекта. Но сложился стандартный набор, который встречается чаще всего:
интерфейс (UI): всё, что видит и с чем взаимодействует пользователь;
бизнес‑логика (core): правила, расчёты, принятие решений;
работа с данными (data): получение, сохранение, отправка информации.

Каждый слой в архитектуре решает свою конкретную задачу и не вникает в то, как устроены соседние.
Например, слой представления (компоненты интерфейса) не должен знать, откуда берутся данные и как они обрабатываются. Его работа — просто показать информацию в нужном виде. Всё, что ему нужно, это получить уже готовые данные и отрисовать их.
Слой бизнес‑логики (core), в свою очередь, не заботится о том, как данные выглядят на экране или откуда они пришли. Он получает их из хранилища или API, применяет нужные расчёты, преобразования и агрегации, а затем передаёт результат наверх, в представление.
Благодаря такому разделению бизнес‑правила живут своей жизнью, отдельно от интерфейса и источников данных. Если ты меняешь способ отображения информации или переписываешь работу с API, то бизнес‑логика остаётся нетронутой. И наоборот.
Это даёт простую, но важную возможность менять, дорабатывать и пересобирать только ту часть, которая действительно изменилась. Не нужно трогать всё остальное. А если что‑то пошло не так, то проблема локализована в одном слое, и её проще найти и исправить.
Закрытые и открытые слои в луковой архитектуре
В луковой архитектуре каждый слой может быть либо закрытым, либо открытым. И это важно, потому что определяет, как запросы и зависимости путешествуют по системе.
Закрытым называют слой, который не имеет доступа к пропуску через себя. В классическом понимании запрос, идущий сверху вниз, обязан пройти через все слои по порядку. Но в луковой архитектуре правило иное: внешний слой не может напрямую обращаться к внутреннему ядру в обход промежуточных слоёв. Запрос от интерфейса (самый внешний круг) сначала попадает в сценарии использования (прикладной слой), и только потом в доменные сущности (ядро). Никаких сокращённых путей к бизнес‑логике.
Зачем это нужно? Чтобы ядро оставалось изолированным от внешнего мира. Когда каждый внешний слой проходит через чёткие контракты (интерфейсы), изменения в одном месте не задевают остальные, при условии, что договорённости между слоями не меняются. Внешние адаптеры не знают, как устроено ядро, и не лезут в его внутренности. Это главный принцип луковой архитектуры: зависимости направлены внутрь, а не наружу.
Но чтобы такая изоляция работала, критически важно закрывать слои, через которые проходят основные запросы. Если разрешить интерфейсу (UI) обращаться напрямую к репозиториям или базам данных, минуя сценарии использования и домен, то любое изменение в хранилище ударит сразу по нескольким слоям. В результате приложение становится жёстко связанным, а слои — зависимыми друг от друга, что разрушает идею луковой архитектуры.
Изоляция даёт ещё одно полезное свойство: вы можете заменить один внешний слой на другой, не трогая ядро. Главное, чтобы контракты (порты) оставались неизменными, а логика делегировалась через чётко определённые интерфейсы. Например, вы можете заменить REST‑контроллер на GraphQL‑адаптер, и бизнес‑логика даже не заметит этого, если она продолжает получать те же данные через те же договорённости. Или заменить PostgreSQL на MongoDB — достаточно реализовать новый репозиторий, реализующий тот же интерфейс, который ожидает домен.
Таким образом, в луковой архитектуре закрытость слоёв работает не как жёсткая очередь прохождения, а как защитная оболочка: внешнее знает о внутреннем, но внутреннее не знает о внешнем. Это и есть та самая изоляция, которая делает код гибким, тестируемым и устойчивым к изменениям технологий.
Зачем выбирать этот подход?
Луковая архитектура — это эволюционная классика. И мы выбрали её осознанно. Для нас она даёт главное — простоту и предсказуемость за счёт чёткого разделения на ядро и периферию. Всё разложено по кругам: разработчики быстро понимают, где что искать, не нужно городить сложную инфраструктуру. Это дёшево в поддержке, привычно для команды и позволяет быстро стартовать без лишних раздумий. При этом у нас несколько распределённых команд, и каждая создаёт свои модули в рамках этой архитектуры. Команды работают параллельно, не мешая друг другу, потому что модули изолированы и имеют чёткие границы. Никто не наступает на пятки соседям, каждый отвечает за свою зону и не лезет в чужую.
Да, у этого подхода есть ограничения, как у любого инструмента. Но мы управляем этими рисками. В результате мы получаем предсказуемую, понятную и масштабируемую систему, которую могут развивать несколько команд параллельно. И пока этот подход приносит нам больше пользы, чем сложностей. Луковая архитектура — это инструмент, который мы умеем использовать. И в наших условиях он работает отлично.
Как мы к этому пришли
Раньше модули были монолитными: было сложно заводить и сопровождать проекты, обновлять модули в продукте, расширять и конфигурировать без ломки. Единого подхода не было, код раскладывали кто как умел. Страдали читаемость, проверки и тесты. Росли баги и техдолг. Тимлид тратил много времени на разбор «где что лежит».
На практике чаще путались не «между фичами приложения», а внутри одного модуля: куда сервис, куда HTTP, куда компонент. Нужна была схема, которая проще объясняется команде и которая жёстко отвечает на вопрос: «куда класть код внутри модуля, чтобы бизнес‑правила не зависели от внешних технологий».
Было:
Монолитный модуль.
Трудно менять и проверять.
Один человек «владеет» всем модулем.
Разный стиль в проектах.
Сложно тестировать.
Стало:
Чёткое ядро (core) и внешние адаптеры (data, ui).
Зависимости направлены внутрь — бизнес‑логика не знает про HTTP и БД.
Быстрее правки и проверка тимлидом.
Несколько разработчиков по слоям.
Единый стандарт.
Тесты по слоям (моки через токены и интерфейсы).
Конфиг при передаче в проектные команды (forRoot, providers).
Как устроены слои в луковой архитектуре
Мы назвали слои просто — примерно как MVC, чтобы порог входа был низким, но с чётким пониманием, кто от кого зависит:
core — ядро: сущности, правила, сценарии использования (use cases). Это центр, который ничего не знает о внешнем мире.
data — адаптеры к данным: API, хранение, маппинг. Реализует интерфейсы, которые определены в core.
ui — адаптеры к представлению: показ и действия пользователя. Зависит от core, но не наоборот.
bootstrap — сборка и DI: константы, конфигурация модуля, подключение адаптеров к ядру.
Ориентир — луковая архитектура (она же чистая архитектура Роберта Мартина): зависимости направлены внутрь, бизнес‑логика не знает про UI и HTTP. Отсюда core (правила) в центре, а data и ui (детали) снаружи. Термины entities, use cases, adapters в каждый модуль не тащим — суть та же, объяснять команде проще.
Ключевое отличие от классической многослойной архитектуры: у нас не просто слои «лежат друг на друге», а внешние круги зависят от внутренних, но не наоборот. Это значит, что при замене HTTP‑клиента или библиотеки UI мы меняем только внешний слой (data или ui), а core остаётся нетронутым.
Ограничения и как ими управляем
У подхода есть ограничения, как у любого инструмента. При росте проекта код внутри слоёв может усложняться, а изменения — задевать несколько слоёв. Мы управляем этими рисками так:
чётко определяем контракты между ядром и адаптерами;
не позволяем бизнес‑логике просачиваться в представление или в слой данных;
проектируем слои максимально независимыми — внешние слои можно заменять без правок в core;
автоматизируем тестирование, чтобы регрессии не становились проблемой.
Как луковая архитектура поможет в работе
Для руководителя продукта
Предсказуемость команды. Видно, кто и сколько времени тратит на разные типы задач и какие слои требуют больше внимания.
Управление рисками. Если на отдельных слоях чаще возникают баги, то время на них можно заранее закладывать в план.
Оценка новых фич. Понятнее, сколько займут масштабирование и добавление функциональности.
Меньше ошибок. Единый подход к разработке снижает число багов.
Быстрее разработка. Одна структура модулей сокращает время на написание, доработку и исправления.
Дешевле сопровождение. Изолированные слои и понятное масштабирование снижают стоимость поддержки.
Гибкое распределение задач. В команде могут работать разработчики разного уровня; обязанности делегируются точечно, без потери качества.
Для тимлида фронтенд‑команды
Code review быстрее и точнее. Слоистая структура помогает смотреть на важное, а не разбирать хаос «где что лежит».
Быстрый вход в любой модуль. Разработчик оперативно подключается к задаче даже в незнакомом модуле.
Делегирование проверок. Часть из них можно отдавать сильным разработчикам, растёт их уровень, разгружается тимлид.
Конфигурируемость. Модуль можно настроить при передаче в другую команду для стороннего использования.
Адаптация новичков (в том числе из другой команды). Быстрее вливаются за счёт единой структуры.
Для разработчика фронтенда
Слабая связанность слоёв модуля. Проще чинить и менять точечно.
Удобнее планировать работу. Задачи, естественно, режутся по слоям.
Проще локализовать баг. Понятно, где искать: в ui, core или data.
Тесты по приоритету. Покрывать важный или нестабильный слой, а не все сразу.
Быстрое погружение в единый подход к структуре любого модуля.
Быстрее изменения. Правки в нужном слое, без расползания по всему модулю.
Проще документация. Одна схема описания для разных модулей.
Несколько человек могут работать над одним модулем без «толкания локтями».
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.