Сегодня мы поговорим о том, как развивается платформенная команда «Спортмастера». Речь пойдёт о подходе к организации фронтенд-приложений, который получил название FEOD — Fractal Entity Oriental Design.

Архитектура vs. Структура. Почему это важно?

Первое, на чём стоит остановиться — это различие между архитектурой и структурой проекта. Эти понятия часто путают, хотя на деле они отвечают на разные вопросы.

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

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

Структура и архитектура не тождественны, но одна без другой работает плохо. Если в проекте хаос и файлы раскиданы где попало, то и продуманную архитектуру построить будет крайне сложно. Поэтому структура становится фундаментом для грамотной архитектуры. Можно сказать так: структура отвечает за организацию кода, а архитектура — за организацию логики.

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

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

Кроме того, наличие четких правил снимает множество спорных моментов. В документации всегда можно найти ответ на вопрос «а как правильно», и тогда код-ревью превращается не в столкновение субъективных мнений, а в процесс, где есть понятные ориентиры. Это снижает количество конфликтов, ускоряет работу и делает ее прозрачной.

Если смотреть шире — с точки зрения бизнеса — эффекты еще заметнее. Унифицированная структура позволяет безболезненно перемещать сотрудников между проектами: человек приходит на новый продукт и практически сразу включается в задачу, потому что всё организовано по тем же принципам. То же самое касается и запуска новых проектов: не нужно каждый раз изобретать велосипед, тратить время на обсуждения базовых вещей — есть готовый проверенный подход, который можно просто взять и применить.

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

В итоге мы получаем не только аккуратный код, но и предсказуемость: в скорости, в качестве, в стоимости разработки. Это то, что одинаково важно и инженерам, и менеджерам, и всей компании в целом.

Проблематика современных проектов

Когда мы говорим о структуре фронтенд-приложений, то сразу встаёт вопрос: как вообще всё это организовать? На практике нередко получается каша из компонентов и логики. Сначала создаёшь какой-то кусочек, он требует соединения с другим, потом кладёшь его в одно место, ещё часть — в другое, и в итоге уже непонятно, что где лежит и как всё это искать.

Отдельная боль — циклические импорты и контроль связей между модулями. Если проект строится на модульной архитектуре, очень быстро всплывает проблема: модули знают слишком много друг о друге. Получается не структура, а очередная каша, только уже из связей.

Добавим сюда ещё фактор входного порога. Например, гексагональная или «чистая» архитектура требует большого контроля. FSD (feature-sliced design) — пока разберёшься, как именно оперировать определениями и правилами, пройдёт немало времени. А на код-ревью такие архитектуры часто становятся источником споров — каждый по-своему трактует подходы и отстаивает «правильное» решение.

Всё это привело нас к мысли о том, что нужен усреднённый вариант. Архитектура должна быть достаточно гибкой и лояльной, чтобы с ней мог разобраться даже Junior-разработчик. При этом она не должна скатываться в плоскую структуру, где всё свалено в духе «компоненты сюда, стили туда».

Какие есть инструменты для решения проблем

Когда речь заходит о том, как организовать фронтенд-проект, первое, что приходит в голову — уже известн��е архитектурные подходы. Но у каждого из них есть своя специфика, сильные и слабые стороны. 

Atomic Design

Самый популярный подход, который часто упоминают в связке с дизайн-системами. У Atomic Design всё логично: атомы, молекулы, организмы — понятная иерархия. Если вам нужно эффективно взаимодействовать с дизайнером, то методология вполне рабочая.

Но проблема в том, что Atomic Design слабо отвечает на вопросы, связанные с бизнес-логикой. Где хранить эту логику? Как разделять её? Как быть с архитектурой приложения, когда речь идёт уже не только о кнопках и формах, а о сложных сценариях? В этих случаях концепция начинает распадаться.

Feature-Sliced Design (FSD)

Следующий шаг в развитии — Feature-Sliced Design. Этот подход задал более строгие правила: появились версии, уточнения, пересмотры принципов. FSD объяснил, как и что использовать, предложил жёсткое разделение на сущности, фичи, виджеты.

Однако вместе с этим появились и новые сложности. Во-первых, ментальная модель FSD достаточно тяжёлая. Во-вторых, команды тратят много времени на споры: что относить к Entity, что — к Feature, а что — к Widget. В итоге применение FSD на практике оказывается не таким простым, как кажется на бумаге.

Модульный подход

Есть и более простой вариант — модульная архитектура. Но у неё своя проблема: отсутствует единый стандарт. Каждый понимает модульность по-своему. Кто-то использует плоскую структуру, кто-то перемешивает всё подряд. Как результат — появляются неконтролируемые связи и хаос в кодовой базе.

Папки и плоские структуры

Ну и, конечно, знакомый всем «метод папок». Разработчику захотелось — он создал папку. Захотелось ещё — завёл ещё одну. Вроде бы удобно, но со временем проект превращается в хаотичное нагромождение, где ориентироваться становится всё труднее. 

Если подытожить:

  • Atomic Design — даёт визуально понятную схему, но почти не затрагивает разработку и бизнес-логику.

  • FSD — сильно формализует процесс, но его сложно применять и он порождает споры.

  • Модульность — звучит просто, но на деле не даёт внятных правил.

  • Папки — путь к хаосу.

Мы попробовали переосмыслить FSD: упростить его в некоторых местах, объединить идеи модульного подхода и гексагональной архитектуры. Так родился наш вариант — Fractal Entity Oriental Design (FEOD).

Что такое FEOD: основные концепции

FEOD (Fractal Entity Oriental Design) — это подход к организации фронтенд-приложений, который появился как попытка объединить лучшее из существующих архитектурных практик и при этом упростить жизнь разработчикам.

Мы взяли из Atomic Design гранулярное разделение. Из Feature-Sliced Design (FSD) — правила горизонтальных импортов и слоёв, а также чёткие правила организации зависимостей. Из модульного подхода — гибкость: возможность формировать собственные логические блоки в проекте. А из гексагональной архитектуры, которая уже стала стандартом на бэкенде, — изоляцию модулей и правила построения публичных API.

Всё это мы объединили, убрав максимально спорную терминологию, которая часто вызывает путаницу (особенно в случае FSD или Atomic). Наша цель была — упростить ментальную модель и дать разработчику конкретные критерии: что и куда класть. И чтобы проекты могли безболезненно расти — мы добавили принцип фрактальности. В итоге получилась интуитивная, гибкая и масштабируемая архитектура.

Ключевые концепции FEOD

1. Модульность

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

Модули помогают структурировать проект так, чтобы навигация по нему была очевид��ой, а изменения в одном месте не ломали весь код.

2. Фрактальность

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

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

3. Сущности

Третья концепция — сущности. Здесь важно не путать их с классическими сущностями из DDD (Domain-Driven Design), к которым привыкли бэкендеры. В контексте FEOD сущность — это просто указание на то, что у каждой части проекта есть роль или назначение. Это способ формализовать мысль: «каждый элемент в кодовой базе что-то значит и зачем-то нужен».

Уровни FEOD и правила импорта

FEOD — это не просто набор рекомендаций, а подход со своей архитектурной моделью. Внутри него мы выделяем несколько слоёв. 

Global (опциональный). Слой для всего, что должно быть доступно из любого места, но при этом не импортироваться. 

Common. Переиспользуемые сущности проекта (то, что многие привыкли называть Shared). Это общие утилиты, не привязанные к бизнес-логике.

Modules. Логические блоки приложения. Чаще всего они связаны с конкретной бизнес-логикой или представляют собой большое количество сцепленных между сущностей, которые уже организовали некий скоуп знаний.

Pages. Для приложений особенн�� важно, какие в них есть страницы, поэтомy Pages — отдельный слой. Здесь из готовых модулей собираются конкретные страницы.

App. Описывает, как наше приложение запускается и с какими параметрами это происходит.

Самое важное в FEOD — правило импортов между слоями.

  • Global нигде не импортируем. Он и называется Global, потому что всё, что мы туда положили, доступно отовсюду без импортов. 

  • Common — это общие утилиты. Они не завязаны на бизнес-логику, поэтому могут импортироваться откуда угодно. Между собой common-сущности тоже не связаны.

  • Modules — уже про бизнес-логику и крупные связки. Здесь возможны взаимодействия между модулями. Модули «могут использоваться в Common, но импортировать их из Common нельзя

  • Pages — следующий уровень. Здесь страницы просто используют готовые модули, чтобы собрать конечный экран. Представьте конструктор: в Modules мы создали компоненты, а в Pages из них собрали страницу.

  • App никто не импортирует. Это bootload приложения — место, где приложение запускается, где определяются его основные сущности и конфигурации. 

Разберём каждый слой отдельно.

Уровень APP

Именно здесь начинается жизнь всего фронтенд-приложения. Если вы заходите в новый проект, App-слой — одно из самых интересных мест, куда стоит заглянуть в первую очередь.

Что находится внутри App? Прежде всего — настройки и конфигурации. Все конфиги собирают��я именно здесь, настройки роутера, навигации, интеграции с внешними сервисами. Если приложению нужно передавать какие-то данные наружу, правильным решением будет хранить их в App.

Кроме того, в App живёт главный layout. Логично, что мы не хотим импортировать его в разных местах и дублировать, layout должен находиться только на верхнем уровне.

Принцип изоляции App: «Никто не может импортировать из App, но сам App может импортировать кого угодно».

Если же возникает ситуация, когда из App нужно что-то «достать» наружу, используется подход инверсии контроля. Мы хотим держать максимум настроек и конфигураций в одном месте, то есть все конфигурации важно попытаться удержать здесь. 

Структура App

Cтруктура App достаточно свободная. Никто не будет ругаться, если вы придумаете новую папку или, наоборот, откажетесь от какой-то стандартной. Смысл не в том, как именно разложены папки, а в том, для чего используется сам модуль App.

Пример структуры

Но повторюсь: это всего лишь пример, а не жёсткий стандарт.

Как прокинуть конфигурацию в модуль? Представим, у нас есть модуль аналитики. Мы можем в него из модуля App прокинуть данные. Таким образом, конфигурация осталась в App, но при этом запуск совершается из App, а вся логика осталась в модуле аналитики. 

Уровень Pages

Здесь мы стараемся удерживать ключевой принцип: URL пытаются повторить тот файл, в котором они располагаются. То есть по адресу страницы можно сразу понять, какой файл за неё отвечает.

Это сделано не случайно. Pages — достаточно самостоятельные сущности. Они находятся вне модулей, и у них есть строгие ограничения на импорты: страница может тянуть зависимости только из App и модулей, но не наоборот.

Хотя сами Pages существуют отдельно, никто не мешает модулям создавать собственные роуты. Здесь и работает принцип повторения URL — чтобы, видя адрес, можно было мгновенно найти файл, который за него отвечает.

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

Vue Router позволяет строить страницы из нескольких компонентов. В таких случаях можно создать два файла с одинаковым именем, чтобы аккуратно разделить логику. Это не обязательное правило, а скорее удобная опция.

Есть ещё одна возможность — приватные модули. Она опциональна, но иногда полезна. Представьте, у вас есть домашняя страница, и только для неё нужен один-единственный вспомогательный компонент. Вместо того чтобы тащить его в общий модуль, можно положить рядом в приватный.

Здесь важно не переборщить. Приватные модули — это скорее способ держать связанные сущности рядом, а не плодить отдельные каталоги ради одного компонента. Если идея кажется сомнительной, её можно вовсе запретить в конкретном проекте — это не принципиальное требование.

Уровень Modules

Следующий и, пожалуй, самый важный слой — это уровень модулей. Если вы знакомы с FSD, то здесь окажется всё, что связано с feature-модулями, entity, widget'ами — то есть всё то, что несёт бизнес-логику и описывает блоки, скоупы знаний.

Проще говоря: если в компоненте содержится логика и предполагается переиспользование, это модуль. Если же речь идёт о простой утилите (например, функции для форматирования даты или обработки данных), то это уже не модуль, а утилита, и ей место в Common.

Ключевая особенность модулей в FEOD — фрактальность. Каждый модуль может содержать свои подмодули. При этом они строго изолированы.

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

Глубина вложенности может быть любой. Допустим, у вас есть модуль, связанный с юзером. Внутри модуля можно создавать подмодули: например, отдельный подмодуль для работы с профилем или подмодуль для загрузки аватарок. Но доступ к ним возможен только через публичный API родитель��кого модуля.

В FEOD нет строгих ограничений по тому, как именно структурировать папки внутри модуля — команда может выбрать удобный способ. Но есть одно строгое правило: импорт возможен только из публичного API.

Если в подмодуле есть свой подмодуль, то напрямую в него импортировать тоже нельзя. Подмодуль — это такой же модуль, и у него есть свои границы. Всё, что не экспортировано, считается приватной частью. Это создаёт чёткие барьеры и гарантии, что команда сама определяет границы модуля и сокращает хаос на проекте.

Естественно, полностью избежать кросс-модульных связей невозможно. Опытные разработчики могут ограничивать их через техники вроде Dependency Injection, но навязывать всем DI было бы избыточно — особенно джунам. Однако если вы владеете такими техниками, вы можете их применять у себя на проекте, чтобы ограничить количество связей модулей между собой. Это будет только приветствоваться.

В общем, кросс-модульные связи разрешены, но с тем же правилом: импортировать можно только публичную часть модуля. Влезать внутрь подмодуля чужого модуля — нарушение.

Когда стоит создавать подмодуль

​​Подмодуль появляется, когда сущность или блок знаний становятся слишком большими. Если вы пишете WYSIWYG-редактор, внутри него появляется отдельная логика для работы с текстом, затем блок для загрузки и редактирования изображений. Каждая такая обособленная часть превращается в подмодуль.

Сигналы к созданию подмодуля:

  • модуль стал слишком большим,

  • в команде нужно разделить ответственность,

  • появилась необходимость писать документацию на отдельную часть.

Такой подход даёт несколько плюсов:

  • Чёткие границы. Количество связей между частями системы не растёт экспоненциально.

  • Лучше управляемость. Команда понимает, кто за что отвечает.

  • Упрощение тестирования. Изолированные модули проще покрывать тестами.

  • Документируемость. Подмодули можно сопровождать документацией, что упрощает вход в проект.

Уровень Common

Это последний важный слой в архитектуре FEOD, и его роль часто недооценивают. Он выступает своего рода «плоским слоем», в котором живут сущности, не привязанные к бизнес-логике. Чаще всего сюда попадают небольшие утилиты, универсальные компоненты или типы данных, которые удобно держать в одном файле.

Именно сюда складываются те самые «мелочи», которые трудно отнести к какому-то конкретному бизнес-процессу или блоку знаний.

Важный аспект, который хотелось бы подсветить, — не надо использовать в Common

индексные файлы. На практике это приносит больше вреда, чем пользы:

  • Ломается тришейкинг. Сборщик не понимает, какие именно элементы реально используются, и тянет в бандл лишнее.

  • Добавляется бюрократия. Каждый раз, когда кладёте новую сущность в Common приходится вручную прописывать её в индексном файле. Зачем? Для кого? В реальности пользы это не даёт.

Вам может не понравиться большое количество отдельных импортов вместо одного, однако в этом тоже есть смысл: вам не захочется превращать common в склад всего подряд, так как с этим становится просто неприятно работать, и у вас появляется естественное желание прибраться.

Поэтому наша рекомендация — никаких индексных файлов в Common.

Когда Common перестаёт быть Common

На старте всё кажется логичным: у вас есть папка Common (или Components, или как угодно её назовите), где лежат утилиты, универсальные UI-компоненты или композиционные функции. Всё работает, пока это действительно набор разрозненных сущностей.

Но в какой-то момент появляются тревожные сигналы. Например:

  • Вы сделали tooltip.

  • Потом появился tooltip-manager.

  • Чуть позже — composable, управляющий всей этой логикой.

И вот у вас уже 5–6 связанных между собой сущностей, которые явно образуют единый смысловой блок. Это момент истины: пора признать, что это уже не Common, а полноценный самостоятельный модуль.

Полезно воспринимать модули так, будто бы это отдельные npm-зависимости. Если вам хочется «спрятать» несколько сущностей и оформить их в единый набор, скорее всего, правильное решение — вынести их в модуль.

Таким образом вы сохраните Common в чистоте и не превратите его в склад ненужных вещей, куда скидывается всё подряд «потому что не придумали, куда положить».

Уровень Global

Как уже говорилось, он опционален. Но именно поэтому Global и специфичен: у него довольно узкая зона ответственности.

Зачем он нужен? На практике — для shim-ов библиотек. Например:

  • определить глобальные компоненты во Vue;

  • задать метаданные для ViewRouter-а;

  • подключить шимы для файлов или сборщика.

Все это удобно делать именно через Global.

В отличие от других уровней, отсюда ничего не импортируется напрямую. Но при этом всё, что вы определили в Global, становится доступным во всём приложении.

То есть, если вы используете какую-то глобальную переменную и хотите явно указать её «глобальность», то как раз здесь она и должна быть описана.

Важно ��онимать, что Global — это не Common.

  • Common предполагает импорт: вы берете сущности оттуда и явно подключаете их в коде.

  • Global — это слой, который «растворяется» в проекте. Его определения доступны везде, хотя нигде не видно прямого импорта.

Представьте ситуацию: вы открываете проект и натыкаетесь на сущность, которая используется в коде, но нигде не импортируется. В таком случае почти наверняка эта сущность описана в Global.

Именно поэтому Global — это точка, где собирается всё то, что должно быть доступно «из воздуха»: shim-ы, глобальные настройки и переменные, которые задают общий контекст работы приложения.

Вам понравился FEOD. Что дальше?

Давайте попробуем представить ситуацию, что вам понравился FEOD и вам захотелось на него перейти. Как это сделать?

Шаг 1. Выделяем App

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

Фиксируем эту часть, сохраняем — и двигаемся дальше.

Шаг 2. Views или Pages

Обычно у нас уже есть какая-то базовая структура — например, папка Views. Хотите — оставляйте. Хотите — называйте её Pages. Это не принципиально.

Главное — вместо бесконечных попыток изобрести название для каждой страницы, используйте описание URL. Вы можете использовать любую удобную вам конвенцию для файлового роутинга или даже ситуативно игнорировать его (если возникает такая потребность). Тут нет навязывания подключения библиотеки с файловым роутингом  — оставляйте все под своим контролем. Однако если он вам понадобится, то перейти на него будет тоже не сложно.

Шаг 3. Модули 

Мы начинаем разносить логику по папкам и блокам. Здесь часто помогает сама структура команды: каждый разработчик обычно отвечает за свою часть. Он и знает: вот эти файлы — его зона ответственности, а остальные туда не лезут.

Такой разработчик может организовать свою область в виде отдельного блока, и это уже становится модулем.

Важно понимать: модуль — это не личная папка, куда можно свалить всё подряд. Это самостоятельная, чётко очерченная часть проекта.

Шаг 4. Common

И, наконец, всё, что остаётся у вас «в плоской структуре» — отправляется в Common.

Это те вещи, у которых нет бизнес-логики, которые не относятся ни к одному модулю напрямую. Общие компоненты, утилиты — всё это живёт здесь.

Расширения FEOD для продвинутых проектов

Есть несколько моментов, которые стоит разобрать отдельно, потому что именно они открывают путь к более сложным и продвинутым сценариям использования.

FEOD изначально спроектирован так, чтобы поддерживать SSR. Для работы с ним есть готовый пример, и, что важно, ключевые отличия кроются в организации приложения.
Вместо единого App мы можем вынести отдельный уровень Server. Туда попадают части, связанные исключительно с сервером и недоступные нигде больше.

В реальности это выражается всего в двух файлах:

  • EntryServer.ts

  • EntryClient.ts

И на этом всё. Такая структура упрощает разделение обязанностей и делает приложение более прозрачным.

Второй момент касается внедрения зависимостей. В FEOD акцент сделан на отказ от кросс-модульных взаимодействий в пользу Dependency Injection. Для этого используется инструмент SDI.

Подход делает зависимости явными и управляемыми. Это особенно заметно, когда проект разрастается, и поддержка структуры становится критически важной.

Если в вашей команде много бэкенд-разработчиков и они привыкли мыслить в терминах DDD, FEOD легко подстраивается под этот подход. В модулях можно выделить дополнительные сущности — Entities и Adapters.

Для тех, кто знаком с DDD, назначение этих частей очевидно. Они помогают еще чётче определять входы и выходы модуля, обеспечивая строгую структуру и понятные границы. Такой способ интеграции FEOD особенно полезен для проектов, где требуется дисциплинированная архитектура.

И наконец, более открытый вопрос: можно ли использовать FEOD за пределами фронтенд-приложений? Например, в разработке инструментов для линтинга, UI-библиотек или любых других пакетов, которые сами по себе не являются frontend-приложениями.

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

Выводы

FEOD — это попытка найти баланс между строгими архитектурными методологиями и реальными нуждами команд. Он объединяет сильные стороны Atomic Design, FSD, модульного и гексагонального подходов, но избавлен от перегруженной терминологии и лишних правил. Благодаря модульности, чётким слоям и принципу фрактальности, FEOD одинаково хорошо работает как в небольших проектах, так и в масштабных системах, которые со временем обрастают функциональностью.

Для разработчиков это единый язык и прозрачные правила взаимодействия, снижающие количество споров и упрощающие вход в проект. Для бизнеса — предсказуемость в скорости запуска, стоимости поддержки и управляемости роста. FEOD не обещает «серебряной пули», но даёт то, чего часто не хватает в реальной практике: ясность, гибкость и масштабируемость без лишней сложности.