Сегодня мы поговорим о том, как развивается платформенная команда «Спортмастера». Речь пойдёт о подходе к организации фронтенд-приложений, который получил название 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 не обещает «серебряной пули», но даёт то, чего часто не хватает в реальной практике: ясность, гибкость и масштабируемость без лишней сложности.
Комментарии (18)

strannik_k
02.12.2025 21:29Выглядит лучше, чем FSD. Понравилось, что статья очень хорошо структурирована.
Не планируете как-то ещё продвигать методологию? Хотелось бы поскорее похоронить нынешний стандарт (FSD), который я считаю одним из худших подходов к организации файловой структуры проекта.
Касательно слоя Modules – радует, что наконец-то начинают возвращаться к тому, что было в прошлом - одному бизнес-слою вместо размазывания связанной бизнес-логики по 4-ём FSD-шным.
Вопрос насчёт папок modules, ui, api в этом слое – я правильно понимаю, что группирование файлов в модулях по подобным папкам не обязательно? На мой взгляд, в большинстве проектов это лишь удвоит вложенность, не принеся пользы. В проектах, в которых я участвовал и в которых был аналог слоя Modules, мы не заводили подобные папки (за исключением вложенной папки modules – аналогичные редко заводили, если скапливалось много вложенных папок-подмодулей) и проблем не возникло.

zede
02.12.2025 21:29Планируется объемная документация с примерами. К сожалению, пока документация к FEOD только в локальной базе знаний компании (поэтому же на докладе некоторым моментам меньше времени было посвящено), но сейчас идет работа по формированию версии не привязанной к специфике компании, планировалось успеть сделать ее к выходу статьи, но придется подождать. Также в проработке линтер, который будет следить за соблюдением стилей и автофиксом импортов (так как это наиболее назойливая проблема на практике)
Насчет модулей: я сторонник того, что каждый модуль может обладать своей спецификой, он может быть типовым с плоской структуру или более древовидным. Тут надо смотреть на потребности и сложность логики модуля. Главная стандартизация касается только правил импортов и публичного апи. В примерах был плоская структура в модулях, так как она просто привычна тем, кто только и работал раньше с плоской, как таковых же ограничений на нейминг, четкую структуру тут нет. Я вообще сторонник того, что каждый модуль должен обладать минимальным README c пояснением (кратким!) по основной сути модуля

nin-jin
02.12.2025 21:29Между собой common-сущности тоже не связаны.
Чтобы что? Зачем модулю uif8decoder ограничивать доступ к модулю buffer?
Модули «могут использоваться в Common, но импортировать их из Common нельзя.
То есть в Common должны находиться интерфейсы всех модулей из Modules? Чтобы что?
в Modules мы создали компоненты, а в Pages из них собрали страницу
У меня есть приложение, например, "электронная таблица". Я не могу вывести его в качестве одной из станиц? Почему?
Никто не может импортировать из App ... страница может тянуть зависимости только из App и модулей, но не наоборот
Вы бы хоть стрелочки рисовали, а то сами запутались уже.
если в компоненте содержится логика и предполагается переиспользование, это модуль.
В Computer Science наоборот: https://en.wikipedia.org/wiki/Component-based_software_engineering
Если вы импортируете что-то в обход индексного файла, вы нарушаете правило изоляции.
не надо использовать в Common индексные файлы. Ломается трешейкинг. Добавляется бюрократия.Кажется в модулях эта проблема будет куда серьёзней.

zede
02.12.2025 21:29Чтобы что? Зачем модулю uif8decoder ограничивать доступ к модулю buffer?
Отталкиваемся от самых слабых. Если не натыкать палок в колеса в common, то оно в короткие достаточно сроки превращается в свалку "не знал куда положить, кинул в туда" и в реальных проектах это наблюдалось далеко не 1 раз. При этом возникала достаточно большая запутанность между частями. Если нескольким частям нужно работать сообща и активно переиспользоваться, лучше рассмотреть вынос в модуль. Да, это несет определенное количество минусов, и действительно, есть случаи когда такое переиспользование уместно, но тут оно принесено в жертву.
То есть в Common должны находиться интерфейсы всех модулей из Modules? Чтобы что?
Там недостаточно подсвечен этот момент, но по своей сути, это опять же истекает из невозможности импортировать в common модули. Если возникает такая необходимость, то выворачиваем это наизнанку используя инверсию контроля. Поэтому не всего, а только самый необходимый минимум, также это долгая и неприятная процедура, опять же чтобы минимизировать возможность таких связей
У меня есть приложение, например, "электронная таблица". Я не могу вывести его в качестве одной из станиц? Почему?
На самом деле, это есть в проработке, что страницы могут формироваться внутри модулей и далее подключаться в App. В данной статье на это выделено только 1 приложение. Полноценная документация покрывает этот момент. (Разработка публичной версии документации в процессе)
Никто не может импортировать из App ... страница может тянуть зависимости только из App и модулей, но не наоборот
Тут оговорка или очепятка, вместо App явно подразумевался Common
Кажется в модулях эта проблема будет куда серьёзней.
И да и нет. Без особых плагинов сделать это лучше и явно не выходит. Я бы с удвольствием оперировал более крупными сегментами для описания ES-модуля нежели чем файл с импортами и экспортами, но все решения несут излишне большие издержки (абьюз неймспейсов, плагины с генерацией импортов и тд). Возможно в будущем удастся решить эту проблему более элегантно. Однако, модули часто более тесно связаны между собой, тогда как в Common связи ограничены, поэтому модули часто все равно бы тянули себя почти полностью, тогда как в Common сущности изолированы и нет причин для того, чтобы тянуть их все всегда

strannik_k
02.12.2025 21:29Если не натыкать палок в колеса в common, то оно в короткие достаточно сроки превращается в свалку "не знал куда положить, кинул в туда" и в реальных проектах это наблюдалось далеко не 1 раз.
Мне кажется из-за неприятного опыта в прошлом вы решили пойти очень сомнительным путём. Если модули выносить из common в modules, то слой modules может разрастить и стать запутанным, т.к. многие модули будут связаны. Если проект не на пару недель, то в common лучше сразу группировать утилиты по назначению, а также прочие файлы группировать в модули по назначению. В FSD так делается в shared/lib.
Как по мне, лучше порефачить свалку, написать правила для линтеров для ограничения импортов, чем усложнять проект, создавая обходные пути, чтобы воспользоваться функционалом из слоя выше.
zede
02.12.2025 21:29Полностью согласен на самом деле. Если бы мог придумать достаточную эвристику, чтобы удержать это от хаоса, с радостью бы ее использовал. Чисто в теории FEOD никак не запрещает модификацию, где использовался бы подход к shared аналогичный FSD, но опасность свалки высока, плюс клубки взаимосвязанных сущностей распутывать тоже приходилось (
Хотелось бы написать "старайтесь держать слабо связанными", скорее всего это более верное утверждение нежели чем полный запрет

js2me
02.12.2025 21:29страницы могут формироваться внутри модулей
В итоге это приведет к тому (же почему FSD начал продвигать pages-first подход), что, открывая
pages/users- всё содержимое этой страницы будет разбросано по всем другим слоям, что имхо, считаю проблемой

nin-jin
02.12.2025 21:29И вот у вас уже 5–6 связанных между собой сущностей, которые явно образуют единый смысловой блок. Это момент истины: пора признать, что это уже не Common, а полноценный самостоятельный модуль.
уровень модулей. здесь окажется всё то, что несёт бизнес-логикуБизнес-логика - это зависимости между сущностями? Вот это поворот!
Таким образом вы сохраните Common в чистоте и не превратите его в склад ненужных вещей, куда скидывается всё подряд «потому что не придумали, куда положить».
А есть какая-то ещё причина существования common помимо "хз куда это ещё засунуть в нашей красивой структуре из
57911 слоёв, которых на этот раз точно на долго хватит"?

brolnickij
02.12.2025 21:29большое спасибо, очень интересный подход, к тому же наконец-то узнал что такое "фрактальная архитектура"
было бы интересно поподробнее узнать о вашем подходе и механизмах работы с межмодульной / субмодульной коммуникацией, интересно узнать как вы вообще изолируете модули от "внешней среды"
---мы на довольно большом проекте, в свое время, тоже пришли к чему-то подобному, просто добавив несколько архитектурных приемов поверх FSD, не переизобретая архитектуру с самого нуля
наши приложения имеет три глобальных слоя, с однонаправленными зависимостями, которые придерживаются принципа Relaxed Layered System
-app/(Инфраструктурный слой)
-modules/(Бизнес слой)
-shared/(Переиспользуемый код)
направление зависимостей глобальных слоев каждая директория в
modules/разделена по принципу "изолированных предметных областей" + (привет DDD и его Strategic DDD), в каждом из которых живет FSD (из которого был исключен слойapp/)
каждый модуль содержит в себе FSD (без слоя app/)еще увидел, что вы остались на File-based Routing, что имеет ряд своих преимущество, но т.к. мы пошли по пути изолированных предметных областей и страницы у нас живут прямо в модулях, мы целиком и полностью перешли на ручное управление роутингом через
router.options.ts
механизм работы роутинга крч у нас получились аля микрофронтенды, запихнутые в модульный монолит, +- тож самое что и у вас

nin-jin
02.12.2025 21:29Вот сколько ни спрашиваю "зачем вы вообще пишете непереиспользуемый код?" - никто не может ничего вразуметельного ответить.

brolnickij
02.12.2025 21:29Вот сколько ни спрашиваю "зачем вы вообще пишете непереиспользуемый код?" - никто не может ничего вразуметельного ответить.
в каждом из модулей мы используем FSD 2.1, с page-first подходом, т.е. внутри модуля мы выносим на нижележащие слои только действительно то, что переиспользуется
а так, полностью согласен, нет никакого смысла дробить код на множество частей ради выдуманной "переиспользуемости", всегда нужно искать баланс, но и писать систему с кучей дублирования - тоже не вариант
мы же делим кодовую базу по предметным областям из-за очень толстого домена и к "переиспользуемости" это прямого отношения не имеет
nin-jin
02.12.2025 21:29Ах да, всё время забываю, что на вашем фреймворке писать переиспользуемый код - это боль и страдания.

brolnickij
02.12.2025 21:29Ах да, всё время забываю, что на вашем фреймворке писать переиспользуемый код - это боль и страдания.
тут должна была быть реклама $mol

zede
02.12.2025 21:29На самом деле то что у меня получилось, вообще не что-то уникальное. Очень много с кем ее обсуждал в моменте проектирования и почти всегда звучало "о мы пришли к нечто похожему". Но это не имело задокументированности нигде, вот эту проблему мы и решали. И все еще решаю в более открытом варианте.
Насчет файлового уротинга: и да и нет, FEOD не призывает никак использовать утилиты или плагины для файлового роутинга, но и не против этого. Вместо этого используем мимикрию под него, чтобы решить часть проблем. А там где файловый роутинг показывает себя плохо, то можно его и проигнорировать.Да, про микрофронтенды неплохо замечено, так как одна из целей такого подхода дать людям возможность делить зоны ответственности при этом минимально конфликтуя между собой. Те данный подход именно направлен на поптку отказаться от крайне дорогостоящего решения с микрофронтами, там где фичи именно технические микрофронтов не нужны (независимый деплой, внешний контроль за версиями и тп)

brolnickij
02.12.2025 21:29Но это не имело задокументированности нигде, вот эту проблему мы и решали.
согласен, когда-то давным-давно, когда я еще был зеленым, тоже столкнулся с такой же проблемой, когда ничего и нигде не было описано и все писали код "как знали, как умели, как чувствовали", и по итогу наш фронт превратилось в такую мусорку и свалку, похлеще всяких там замусоренных
shared/
по итогу мы тож в какой-то момент пришли к схеме "Docs as Code", т.е. стараться документировать используемые библиотеки, документировать подходы в написании тестов, как работать с ветками / коммитами и т.п., в долгую этот подход очень хорошо играет (естественно при наличии ко всему этому код-ревью), прямо как и тесты
благо в век ИИ-агентов все это дело стало чуть попроще поддерживать

brolnickij
02.12.2025 21:29И все еще решаю в более открытом варианте.
с нетерпением жду(ем), Денис, еще раз спс за труды :0
js2me
И эта архитектурная методология, которая накладывает тоже определённые ограничения, также как и FSD, будет вызывать много споров и вопросов.
Читая данную статью, у меня возник вопрос по поводу роутеров. Если импорты между pages и из app запрещены, тогда как пользоваться атомарным роутами во фронтенд приложениях?
Пример:
Данный роут, обычно я размещаю на уровне pages/products чтобы он лежал в том месте, к чему он относится. Соответственно, чтобы навигироваться то нужно этот роут импортировать, а по правилам FEOD нужно конфигурацию прутов выносить в global , или же в app/routing (но при этом нельзя оттуда импортировать)
zede
Серебряной пули нет)
Особенно в вопросах кода: всегда чем-то жертвуешь или получаешь серые пятна.
Если конкретно по вопросу. Тут конфигурация сочетается с необходимостью переиспользования на различных уровнях проекта. Действительно не самый приятный случай.
На вскидку приходит идея с разделением: определяем интерфейс на доступном уровне, например common / module или даже global если какой-то интерфейс перегружаем. А далее имплементируем его на уровне pages / app. Как и писалось в статье пригодится DI, те условный объект который и будет заполнен из app, а использоваться везде так как определен он в common.
Минус подхода: 2е определение ресурса, больше возни с имплементацией (того же DI базового). В случае разнесения по модулям все чуть проще: экспортируете вы только ключи доступа к атомарным роутам и "плагин" который встроится в app для регистрации самих роутов, далее базовые правила работы.
Ну и стоит понимать, что жесткие ограничения не навязываются прям, если технологически что-то ну слишком больно уносить наверх и поддержка этого будет стоить слишком дорого, то лучше вынести на более базовый слой, просто это явно указать, это лучше чем костылить или 1 логический блок разваливать по отдельным слоям, этого как раз мы и хотели избежать с модулями