# SLM Design Scoped Layered Module Design — модульная архитектура фронтенд-приложений. Код организован по слоям ответственности, а модуль содержит всё, что ему нужно: компоненты, хуки, сторы, типы, стили. ## Преимущества ### Вертикальная организация домена Бизнес-домен не разбивается по техническим слоям — сценарии, сущности, типы и UI живут в одном модуле. Это сокращает время навигации и упрощает сопровождение: все изменения домена локализованы. ### Разделение ответственности без перегрузки слоёв Сервисы приложения (`infrastructure/`), UI-кит (`ui/`) и общие ресурсы (`shared/`) — три разных слоя с разной природой. Ни один слой не превращается в свалку разнородного кода. ### Горизонтальная инкапсуляция Вложенные модули (`parts/`) и направление зависимостей позволяют нескольким разработчикам работать над одной областью приложения параллельно, не затрагивая код друг друга. ### Колокация по умолчанию Код начинает жизнь рядом с местом использования и поднимается в общие слои только при реальной потребности. Глобальные слои не засоряются преждевременными абстракциями. ### Явное разделение каркаса и контента Каркас группы маршрутов (`layouts/`) и контент конкретной страницы (`screens/`) — независимые слои с собственной ответственностью. ### Масштабирование через группировку При росте проекта слои не теряют структуру — модули группируются по естественным признакам: бизнес-домены по субдоменам, страницы по разделам, UI-компоненты по уровню абстракции (примитивы и композиции). ### Dependency Injection без фреймворков Cross-domain зависимости в бизнес-слое реализуются через фабрики — модуль декларирует что ему нужно, а точка использования предоставляет зависимости. Домены изолированы без DI-контейнеров, провайдеров и шин событий. ## Происхождение SLM Design вырос на основе: - **Feature-Sliced Design** — слоистая структура, публичный API модуля, направление зависимостей - **Vertical Slice Architecture** — модуль как вертикальный срез, содержащий всё необходимое - **Screaming Architecture** — структура проекта «кричит» о назначении: открыл `business/auth` — видишь авторизацию - **Colocation Principle** — код живёт рядом с местом использования ## Пример структуры проекта ```text src/ ├── app/ │ ├── layouts/ │ ├── main/ │ └── dashboard/ │ ├── screens/ │ ├── home/ │ ├── products/ │ ├── product-detail/ │ └── about/ │ ├── widgets/ │ ├── page-heading/ │ ├── hero-section/ │ └── promo-banner/ │ ├── business/ │ ├── auth/ │ ├── catalog/ │ ├── orders/ │ └── chat/ │ ├── infrastructure/ │ ├── theme/ │ ├── i18n/ │ ├── backend-api/ │ └── logger/ │ ├── ui/ │ ├── button/ │ ├── input/ │ ├── modal/ │ ├── toast/ │ └── dropdown/ │ └── shared/ ├── lib/ ├── types/ └── styles/ ``` ## Принципы - **Домен — единое целое.** Всё, что относится к домену, живёт в одном модуле. - **Колокация.** Код рождается рядом с местом использования и поднимается только при необходимости. - **Зависимости однонаправлены.** Импорты только сверху вниз, только через публичный API. - **Архитектура — каркас, не клетка.** Правила фиксируют направление зависимостей и структуру модуля, остальное определяет команда. ## Слои Раздел описывает слои SLM: что такое слой, какие бывают, как между ними направлены зависимости и какие правила действуют на каждом. ### Определение **Слой — уровень организации кода внутри `src/`. Каждый слой отвечает за свою область (каркас страницы, бизнес-логика, UI-кит) и задаёт правила для кода внутри: направление импортов, именование, допустимые связи между модулями.** ### Группы слоёв Слои делятся на три группы: | Группа | Слои | Описание | |--------|------|----------| | Композиция | `app`, `layouts`, `screens`, `widgets` | Собирают интерфейс из готовых блоков: маршруты, каркасы, страницы | | Ядро | `business`, `infrastructure`, `ui` | Реализация продукта: бизнес-домены, техсервисы, UI-кит | | Фундамент | `shared` | Общие ресурсы: утилиты, хелперы, стили, конфиги | ### Направление зависимостей Любой импорт между модулями — только через публичный API. ``` app → [ layouts | screens ] → widgets → business → infrastructure → ui → shared ``` - `layouts` и `screens` — параллельные слои, не импортируют друг друга - Модули одного слоя в группе «Композиция» изолированы друг от друга - Модули одного слоя `infrastructure` и `ui` могут импортировать друг друга через публичный API - Модули `business` — cross-domain зависимости по коду через фабрику, `import type` напрямую - Импорт типов (`import type`) в «Ядре» разрешён в обоих направлениях ### Слой App Точка входа приложения. Отвечает за запуск, роутинг и композицию маршрутов из layout и screen. В отличие от остальных слоёв, `app/` не содержит модулей SLM. Здесь живут только инфраструктурные файлы, которые не могут быть никаким другим слоем: файлы фреймворка роутинга, точка запуска и код инициализации. #### Требования - Не содержит модулей SLM — только файлы фреймворка, роутинг, инициализация - Содержит: файлы маршрутов, bootstrap, обработку ошибок верхнего уровня (404, error boundary), подключение глобальных стилей и ассетов - Провайдеры и гарды — только подключает готовые из нижних слоёв, не реализует - Не содержит бизнес-логику, UI-компоненты, хуки, сторы, сервисы - Никем не импортируется ### Слой Layouts Каркас страницы: общие элементы, одинаковые для группы маршрутов (header, footer, sidebar). ```text src/layouts/ ├── main/ ├── dashboard/ └── auth/ ``` #### Требования - Содержит только модули - Не содержит бизнес-логику - Контекстно-зависимые блоки принимает через пропсы от `app`, не импортирует напрямую ### Слой Screens Контент конкретной страницы: собирает её из модулей нижних слоёв. ```text src/screens/ ├── home/ ├── products/ ├── product-detail/ ├── about/ └── contacts/ ``` Когда количество страниц затрудняет навигацию — вводится группировка по разделам. Группа — папка для организации, не модуль (без `index.ts`). ```text src/screens/ ├── shop/ │ ├── home/ │ ├── products/ │ ├── product-detail/ │ └── cart/ ├── account/ │ ├── profile/ │ ├── settings/ │ └── order-history/ └── info/ ├── about/ ├── contacts/ └── faq/ ``` #### Требования - Содержит только модули - Не содержит бизнес-логику - Локальные одноразовые секции живут внутри screen-модуля, не выносятся в `widgets`/`business` ### Слой Widgets Составной блок интерфейса, который компонует модули ядра, но не принадлежит конкретному бизнес-домену. Widget появляется когда блок используется в нескольких screens или layouts. Если блок принадлежит домену — он живёт в `business/{area}/`, даже если переиспользуется. Если блок нужен только в одном месте — это `screens/{name}/parts/` или `layouts/{name}/parts/`, а не widget. ```text src/widgets/ ├── page-heading/ ├── hero-section/ ├── onboarding-checklist/ ├── promo-banner/ └── error-boundary/ ``` #### Требования - Не принадлежит конкретному бизнес-домену. Если блок доменный — он живёт в `business/` - Используется в нескольких screens или layouts ### Слой Business Бизнес-домены приложения: auth, catalog, orders, checkout, chat. Каждый домен — отдельный модуль со своими типами, логикой, UI и сервисами. Слой входит в группу «Ядро». Импортирует `infrastructure/`, `ui/`, `shared/`. Cross-domain зависимости по коду реализуются через фабрику. `import type` между доменами разрешён напрямую. Business объединяет то, что в FSD разделено на `features` и `entities`: пользовательские сценарии и бизнес-сущности живут вместе, внутри одного домена. Внутри домена сегменты разделяют ответственность: `types/` — доменная модель, `hooks/` и `services/` — сценарии и логика, `mappers/` — трансформация данных, `parts/` — составные блоки. ```text src/business/ ├── auth/ ├── catalog/ ├── orders/ ├── checkout/ └── chat/ ``` Когда количество доменов затрудняет навигацию — вводится группировка по субдоменам. Группа — папка для организации, не модуль (без `index.ts`). ```text src/business/ ├── commerce/ │ ├── catalog/ │ ├── cart/ │ ├── orders/ │ └── checkout/ └── communication/ ├── chat/ └── notifications/ ``` #### Требования - Один модуль = один бизнес-домен - Циклические зависимости между доменами запрещены - Импорт кода между доменами — через фабрику. `import type` — напрямую - Доменные типы (`User`, `Product`) живут здесь, не в `shared/` ### Слой Infrastructure Техсервисы приложения: theme, i18n, API-адаптеры, logger, realtime. Каждый сервис — отдельный модуль. Слой входит в группу «Ядро». Импортирует `infrastructure/`, `ui/`, `shared/`. Отличие от `shared/`: infrastructure — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги). ```text src/infrastructure/ ├── theme/ ├── i18n/ ├── backend-api/ ├── maps-api/ ├── logger/ ├── feature-flags/ └── realtime/ ``` #### Требования - Один модуль = один техсервис - Импортирует `infrastructure/`, `ui/`, `shared/` ### Слой UI UI-кит без бизнес-логики: button, carousel, toast, modal. Слой входит в группу «Ядро». Импортирует `ui/` и `shared/`. Компоненты строятся друг на друге: `button` использует `icon`, `carousel` использует `button`. ```text src/ui/ ├── button/ ├── input/ ├── icon/ ├── carousel/ ├── modal/ ├── toast/ ├── dropdown/ ├── tabs/ └── tooltip/ ``` Когда количество компонентов затрудняет навигацию — вводится группировка на примитивы и композиции. Примитивы (`button`, `icon`, `input`) не импортируют композиции. Композиции (`carousel`, `modal`, `dropdown`) строятся на примитивах. ```text src/ui/ ├── primitives/ │ ├── button/ │ ├── input/ │ ├── icon/ │ └── badge/ └── composites/ ├── carousel/ ├── modal/ ├── dropdown/ ├── tabs/ └── tooltip/ ``` #### Требования - Не содержит бизнес-логику - Импортирует только `ui/` и `shared/` ### Слой Shared Общие ресурсы: утилиты, хелперы, стили, конфиги. Не знает о бизнес-домене. Слой входит в группу «Фундамент» — ни о ком не знает, никого не импортирует. Отличие от `infrastructure/`: infrastructure — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги). Отличие от `ui/`: UI-компоненты (button, carousel, modal) живут в слое `ui/`, а не здесь. ```text src/shared/ ├── lib/ ├── types/ ├── styles/ └── sprites/ ``` #### Требования - Не имеет runtime-состояния ## Модули Раздел описывает модули SLM: что такое модуль, из чего он состоит и как взаимодействует с остальным кодом. ### Определение **Модуль — универсальный строительный блок архитектуры. Живёт на слое и содержит всё необходимое для своей работы: компоненты, хуки, сторы, сервисы, типы, стили. Набор содержимого не фиксирован — включаются только нужные части.** ### Модуль vs компонент **Компонент** — один `.tsx` файл. Не имеет своих сегментов, использует сегменты родительского модуля. Живёт в корне или `ui/` сегменте модуля. **Модуль** — папка, которая может содержать корневой компонент, сегменты (`hooks/`, `types/`, `styles/`, `ui/`, `parts/` и т.д.) и публичный API (`index.ts`). ```text auth/ ├── ui/ │ ├── auth-guard.tsx │ └── logout-button.tsx ├── parts/ │ ├── login-form/ │ ├── registration-form/ │ └── restore-form/ ├── hooks/ ├── stores/ ├── types/ ├── auth.tsx # корневой компонент (опционален) └── index.ts ``` ### Структура Модуль состоит из сегментов. Ни один сегмент не обязателен — модуль может состоять даже из одного `index.ts` с реэкспортом типов. ```text {module-name}/ ├── {module-name}.tsx # корневой компонент (опционален) ├── ui/ # компоненты модуля (только .tsx) ├── parts/ # вложенные модули (со своими сегментами) ├── hooks/ # хуки ├── stores/ # сторы состояния ├── services/ # внешние источники данных ├── mappers/ # трансформация данных между форматами ├── types/ # типы ├── styles/ # стили ├── lib/ # утилиты модуля ├── config/ # константы └── index.ts # публичный API ``` Подробное описание каждого сегмента — в разделе [Сегменты](/reference/segments). ### Публичный API Модуль экспортирует наружу только то, что нужно другим. Всё остальное — внутреннее. ```ts // business/auth/index.ts export type { User, Session } from './types/user.types' export { useAuth } from './hooks/use-auth.hook' export { AuthGuard } from './ui/auth-guard' ``` Импорт в обход `index.ts` запрещён: ```ts // Плохо import { validateToken } from '@/business/auth/lib/tokens' // Хорошо import { useAuth } from '@/business/auth' ``` ### Фабрика Если модуль зависит от кода другого бизнес-домена — он экспортирует фабрику. Фабрика декларирует необходимые зависимости и возвращает API модуля. Точка использования (screen, widget, layout) предоставляет зависимости при вызове. Модуль без cross-domain зависимостей экспортирует API напрямую. Типы всегда экспортируются напрямую — `import type` не является runtime-зависимостью. #### Модуль без зависимостей — прямой экспорт: ```ts // business/auth/index.ts export { useAuth } from './hooks/use-auth' export { useCurrentUser } from './hooks/use-current-user' export type { User, Session } from './types' ``` #### Модуль с зависимостями — фабрика: ```ts // business/chat/types/deps.ts import type { User } from '@/business/auth' export interface ChatDeps { useCurrentUser: () => User | null } ``` ```ts // business/chat/index.ts import type { ChatDeps } from './types/deps' export function chatFactory(deps: ChatDeps) { return { useMessages: (roomId: string) => { const user = deps.useCurrentUser() // ... }, useSendMessage: (roomId: string) => { const user = deps.useCurrentUser() return (text: string) => { /* ... */ } }, useChatRooms: () => { const user = deps.useCurrentUser() // ... }, ChatBadge: ({ count }: { count: number }) => { /* ... */ }, } } export type { Message, ChatRoom } from './types' export type { ChatDeps } from './types/deps' ``` #### Использование на странице: ```tsx // screens/support/support.tsx import { useCurrentUser } from '@/business/auth' import { chatFactory } from '@/business/chat' const chat = chatFactory({ useCurrentUser }) export function SupportScreen() { const { useMessages, useSendMessage, ChatBadge } = chat const messages = useMessages('support') const sendMessage = useSendMessage('support') return (