Перейти к содержимому

Основные концепции FDA

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

Фрактальность — это одинаковая структура на всех уровнях: компонент → субдомен → домен → приложение.

Домены

Практически это работает так. Корзина — домен cart. Список товаров в корзине — субдомен cart/items/, и внутри него та же структура, что и у самого домена: своя модель, свой контроллер, свои типы. Ещё глубже — карточка товара, компонент в cart/items/ui/. На каждом уровне вы находите те же роли файлов в тех же местах.

Зачем это нужно:

  • Навигация без карты. Открыв любой модуль FDA-проекта, вы уже знаете, где модель, а где разметка. Не нужно читать весь проект, чтобы найти одно место.
  • Порог входа ниже. Новому разработчику достаточно показать структуру одного домена — остальные устроены так же.
  • Масштабирование без деградации. Проект растёт в ширину (новые домены), и вглубь по мере нарастания функционала. Сложность логики одного модуля имеет лимит и при превышении бьется на субдомены.
ТерминЧто этоПример
ДоменВерхний уровень бизнес-логики, соответствует странице или разделу приложенияcart, auth, dashboard
МодульЕдиница функциональности; модуль — тоже доменcart
СубдоменМодуль внутри модуля; _sub не имеет своего URL (роута), sub — имеет. Также является доменомcart/_checkout, cart/items

Подробнее о доменах и субдоменах — в разделе 04.

Весь код проекта делится на две категории, и это первое решение, которое вы принимаете для любого нового файла:

КатегорияПутьЧто тамКак меняется
Инфраструктураsrc/lib/*Код без бизнес-логики: компоненты-примитивы, клиенты внешних сервисов, утилитыРедко и мелкими правками
Бизнес-логикаsrc/routes/*Домены и всё, что относится к задачам бизнесаПостоянно, целыми фичами

Правило выбора простое. Задайте вопрос: «что делает этот код?»

  • Отвечает на вопрос бизнеса — «оформить заказ», «показать корзину», «разрешить доступ» — это бизнес-логика, ей место в домене.
  • Работает в любом проекте без изменений, атомарный компонент, клиент Redis, валидатор email — это инфраструктура, ей место в lib/.
src/
├── lib/ # инфраструктура: меняется редко
│ ├── ui/ # Button, Input, Modal — без бизнес-логики
│ └── server/ # клиенты внешних сервисов
└── routes/ # бизнес-логика: меняется часто
├── cart/ # домен «корзина»
└── auth/ # домен «авторизация»

Внутри каждого модуля данные проходят один и тот же путь. Пять слоёв, направление строго сверху вниз:

Слои

СлойФайлыОтвечает заНе знает про
Repolib/server/repo/*Подключение к внешним источникам: база, кэш, секретыБизнес-правила домена
Modelmodel.server.ts, schema.tsОперации с данными и правила домена: «в корзине не больше 50 товаров»HTTP, интерфейс, состояние UI
RPCrpc.ts, +server.ts, client.tsОбработку запросов: парсинг, вызов модели, формат ответаКак хранятся данные
Controllercontroller.ts, stores.ts, state.tsРеактивное состояние и реакцию на действия пользователяПрямой доступ к источникам данных
UI+page.svelte, ui/Отображение и вводЗапросы, расчёты, хранение

Слои — это не только роли, но и направление зависимостей:

  • Импорты смотрят только вниз. UI использует контроллер, контроллер — RPC и клиентские вызовы, RPC — модель, модель — repo. Обратных импортов не существует.
  • UI никогда не импортирует model.server.ts. Модель живёт на сервере и тянет за собой repo с подключением к базе — такой импорт утянет серверный код в клиентский бандл.
  • Model не знает про HTTP. Модель вызывают из load-функции, из RPC или из фонового скрипта — результат одинаков.
  • Repo — единственное место, где живёт подключение к источнику данных. Поменяете базу — правите один слой, остальной домен не замечает подмены.

Как это выглядит в живом коде — на примере одного клика по кнопке «удалить товар» в корзине:

Путь клика «удалить товар»

Полный разбор домена cart по файлам — в разделе 08, где тот же код разбирается по шагам.

Каждый файл в домене имеет одну роль и один набор экспортов: types.ts экспортирует только типы, model.server.ts — только операции с данными, policy.ts — только правила доступа.

Это правило экономит время в трёх местах:

  • Поиск. Нужна валидация формы? Она не в types.ts и не в разметке — она в templates.ts или в модели. Поиск по ролям, а не по всему коду.
  • Ревью. Нарушение контракта видно без чтения диффа целиком: логика в constants.ts бросается в глаза.
  • Генерация. AI-агентам контракты дают каркас: агент знает, какой файл создавать и что в него писать, — и не перемешивает слои.

Контракты каждого файла с примерами кода — в разделе 05.

Слои и контракты не привязаны к мета-фреймворку. Меняются имена файлов и место папки по конвенции роутинга — роли остаются теми же:

КонцепцияSvelteKitNext.jsNuxt
Страница+page.sveltepage.tsxindex.vue
Layout+layout.sveltelayout.tsxlayout.vue
Server load+page.server.tspage.server.tsxsetup.ts
Server actions+page.server.tsactions.tsserver/
API-роуты+server.tsroute.ts/server/api/
RPCrpc.tsrpc.tsrpc.ts
Modelmodel.server.tsmodel.tsmodel.ts

Обратите внимание на последние две строки: RPC-слой и модель во всех фреймворках называются одинаково. Это ядро FDA — то, что переносится между стеками без изменений.

→ Структура проекта — как выглядит дерево каталогов и куда кладётся новый код

→ Контракты файлов — роль и экспорты каждого файла