mirror of
https://github.com/gromlab-ru/slm-design.git
synced 2026-08-22 07:30:16 +03:00
sync
This commit is contained in:
84
docs/architecture/README.md
Normal file
84
docs/architecture/README.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Архитектура SLM
|
||||
|
||||
SLM описывает владение ответственностями внутри одного фронтенд-приложения. Слой определяет роль кода, группа классифицирует модули, модуль владеет ответственностью, а сегмент организует реализацию владельца.
|
||||
|
||||
## Владение как основа
|
||||
|
||||
**Ответственность** — связная часть приложения с одной причиной изменяться. Она становится самостоятельной, когда ей нужны собственный публичный контракт, зависимости, состояние или область жизни.
|
||||
|
||||
У каждой самостоятельной ответственности есть ровно один владелец. В SLM владельцем является модуль. Он определяет:
|
||||
|
||||
- какие возможности доступны внешним потребителям;
|
||||
- от каких других возможностей зависит ответственность;
|
||||
- кому принадлежат данные и изменяемое состояние;
|
||||
- когда создаются и уничтожаются долгоживущие ресурсы;
|
||||
- как устроена внутренняя реализация.
|
||||
|
||||
Место выполнения кода не меняет владельца. Компонент, провайдер, маршрут или точка запуска могут технически вызывать код ответственности, но не получают владение ею автоматически.
|
||||
|
||||
## Структурная модель
|
||||
|
||||
```text
|
||||
SLM root
|
||||
└── слой
|
||||
├── модуль
|
||||
│ └── сегмент
|
||||
└── группа
|
||||
├── модуль
|
||||
└── группа
|
||||
└── модуль
|
||||
└── сегмент
|
||||
```
|
||||
|
||||
| Сущность | Назначение | Владеет ответственностью |
|
||||
|---|---|---|
|
||||
| Слой | Классифицирует код по архитектурной роли | Нет |
|
||||
| Группа | Классифицирует модули внутри слоя | Нет |
|
||||
| Модуль | Реализует одну самостоятельную ответственность | Да |
|
||||
| Сегмент | Организует внутренности одного модуля | Нет |
|
||||
|
||||
Слой и группа находятся снаружи модульной границы. Сегмент находится внутри неё. Компоненты, хуки, сервисы, хранилища и другие детали реализации принадлежат ближайшему модулю-владельцу, если сами не образуют вложенный модуль.
|
||||
|
||||
## Порядок проектирования
|
||||
|
||||
Архитектурное решение принимается от смысла к структуре:
|
||||
|
||||
1. Описать результат или поведение, за которое должен отвечать код.
|
||||
2. Определить одну причину изменения этой ответственности.
|
||||
3. Найти связанные данные, поведение, состояние и жизненный цикл.
|
||||
4. Назначить модуль владельцем и определить его внешних потребителей.
|
||||
5. Выбрать [слой](./layers.md) по роли ответственности.
|
||||
6. Спроектировать публичный API и допустимые зависимости [модуля](./modules.md).
|
||||
7. При необходимости организовать реализацию [сегментами](./segments.md).
|
||||
8. Только после этого выбрать физические пути и имена файлов.
|
||||
|
||||
Если ответственность или владелец не определены, файловая структура не может исправить архитектурную неопределённость.
|
||||
|
||||
## Логическая и физическая границы
|
||||
|
||||
Модуль не определяется наличием папки, `index.ts` или нескольких файлов. Его определяет самостоятельная ответственность и владение её контрактом, зависимостями, состоянием и жизненным циклом.
|
||||
|
||||
После принятия решения модуль получает отдельную папку и публичные точки входа. Это обязательное физическое представление модульной границы, необходимое потребителям и автоматическим проверкам.
|
||||
|
||||
Верны обе формулировки:
|
||||
|
||||
- самостоятельная ответственность требует модульной границы;
|
||||
- отдельная папка сама по себе не доказывает наличие модуля.
|
||||
|
||||
Пути сопоставляются со слоями, группами, модулями и сегментами в стайлгайде или конфигурации конкретного проекта. Имя пути не меняет нормативный смысл сущности.
|
||||
|
||||
## Область применения
|
||||
|
||||
SLM применяется внутри **SLM root** — границы структурной архитектуры одного приложения. Это может быть `src/` или другая область, установленная проектом.
|
||||
|
||||
Архитектура определяет:
|
||||
|
||||
- роли слоёв и допустимые направления зависимостей;
|
||||
- владельцев самостоятельных ответственностей;
|
||||
- публичные границы модулей;
|
||||
- назначение групп и сегментов;
|
||||
- владение состоянием и жизненным циклом ресурсов.
|
||||
|
||||
SLM не задаёт обязательный поток данных, полный файловый стайлгайд, фиксированный набор сегментов, правила монорепозиториев или обязательную внутреннюю форму каждого модуля.
|
||||
|
||||
Нормативный смысл терминов находится в [терминологии](../reference/terminology.md). Точные блокирующие требования объявлены только в [реестре правил](../rules/registry.md).
|
||||
120
docs/architecture/layers.md
Normal file
120
docs/architecture/layers.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# Слои
|
||||
|
||||
Слой классифицирует код по архитектурной роли и задаёт допустимые направления зависимостей. Слой не владеет ответственностью: владельцем остаётся модуль, размещённый в этом слое.
|
||||
|
||||
Слой выбирается после определения ответственности. Похожее имя папки или наличие зависимости от конкретной библиотеки не являются основанием для выбора слоя.
|
||||
|
||||
## Роли слоёв
|
||||
|
||||
SLM определяет шесть ролей:
|
||||
|
||||
| Слой | Роль |
|
||||
|---|---|
|
||||
| `app` | Связь приложения с фреймворком: запуск, маршруты, преобразование внешних входных данных и подключение готовых публичных API |
|
||||
| `compositions` | Сборка продуктового интерфейса: страницы, макеты, экраны, виджеты и другие композиции |
|
||||
| `domains` | Предметные модели, правила, сценарии и продуктовое состояние |
|
||||
| `infra` | Технические сервисы и возможности приложения без собственной предметной модели |
|
||||
| `ui` | Универсальные интерфейсные модули без зависимости от конкретной продуктовой композиции |
|
||||
| `shared` | Детерминированный фундамент без знания о продукте, изменяемого состояния и ввода-вывода |
|
||||
|
||||
Отсутствующая роль не требует пустой папки. Проект создаёт слой только тогда, когда в нём появляется соответствующая ответственность.
|
||||
|
||||
### App
|
||||
|
||||
`app` содержит точки связи с фреймворком: запуск, файлы маршрутов и преобразование внешних входных данных. Они подключают готовые публичные API других слоёв, но не присваивают их ответственность.
|
||||
|
||||
Точки входа `app` являются специальным немодульным исключением. Страница, макет, провайдер или другой самостоятельный продуктовый владелец реализуется в подходящем модуле и только подключается из `app`.
|
||||
|
||||
### Compositions
|
||||
|
||||
`compositions` содержит владельцев продуктовых композиций: страниц, макетов, экранов, виджетов, результатов маршрутов и интерфейса, объединяющего несколько ответственностей.
|
||||
|
||||
Конкретная организация слоя определяется продуктом и фреймворком. Названия `pages`, `layouts`, `screens` и `widgets` могут использоваться как группы, но не являются дополнительными слоями.
|
||||
|
||||
### Domains
|
||||
|
||||
`domains` содержит модули-владельцы предметных ответственностей: моделей, правил, сценариев и продуктового состояния.
|
||||
|
||||
Доменный модуль является обычным SLM-модулем. Ему не требуется отдельная архитектурная форма только потому, что он находится в `domains`.
|
||||
|
||||
### Infra
|
||||
|
||||
`infra` содержит технические возможности приложения: аналитику, локализацию, тему, телеметрию, интеграции с платформой и другие сервисы без собственной предметной модели.
|
||||
|
||||
Технический способ выполнения предметного сценария не переносит владение сценарием из `domains` в `infra`.
|
||||
|
||||
### UI
|
||||
|
||||
`ui` содержит универсальные интерфейсные модули, которые не знают о конкретной странице, маршруте или продуктовой композиции.
|
||||
|
||||
### Shared
|
||||
|
||||
`shared` содержит детерминированный фундамент, не зависящий от продукта и не имеющий ввода-вывода, изменяемого состояния или жизненного цикла.
|
||||
|
||||
В `shared` могут находиться обычные модули и небольшие немодульные ресурсы: чистые функции, общие типы, стили, декларативная конфигурация и статические файлы.
|
||||
|
||||
## Направление зависимостей
|
||||
|
||||
Матрица определяет, от каких слоёв может зависеть исходный слой:
|
||||
|
||||
| Исходный слой | Допустимые целевые слои |
|
||||
|---|---|
|
||||
| `app` | `app`, `compositions`, `domains`, `infra`, `ui`, `shared` |
|
||||
| `compositions` | `compositions`, `domains`, `infra`, `ui`, `shared` |
|
||||
| `domains` | `domains`, `infra`, `ui`, `shared` |
|
||||
| `infra` | `infra`, `ui`, `shared` |
|
||||
| `ui` | `ui`, `shared` |
|
||||
| `shared` | `shared` |
|
||||
|
||||
Разрешённая зависимость может пропускать промежуточные слои. Например, `compositions` может напрямую использовать модуль `ui`, не создавая посредника в `domains` или `infra`.
|
||||
|
||||
Обычный импорт, импорт типа (`import type`) и реэкспорт одинаково участвуют в архитектурном графе. Для связи между модулями дополнительно действуют их [публичные границы и запрет циклов](./modules.md#зависимости-между-модулями).
|
||||
|
||||
Разрешённое направление не переносит владение. Если модуль `domains` использует `infra`, предметный сценарий остаётся ответственностью доменного модуля, а техническая возможность — ответственностью инфраструктурного.
|
||||
|
||||
`infra` может использовать `ui`, когда технической возможности нужно собственное визуальное представление: CAPTCHA, uploader, карта или инструмент разработчика. `ui` не использует `infra`; необходимые технические возможности универсальный UI получает через входной контракт.
|
||||
|
||||
## Группировка модулей
|
||||
|
||||
Группа классифицирует модули внутри одного слоя или другой группы. Она нужна, когда плоский список модулей перестаёт быть понятным.
|
||||
|
||||
```text
|
||||
compositions/
|
||||
├── pages/ # Группа
|
||||
│ ├── catalog/ # Модуль
|
||||
│ └── profile/ # Модуль
|
||||
├── layouts/ # Группа
|
||||
│ └── main/ # Модуль
|
||||
└── widgets/ # Группа
|
||||
└── cart-summary/ # Модуль
|
||||
```
|
||||
|
||||
Группа:
|
||||
|
||||
- содержит только модули и вложенные группы;
|
||||
- не владеет ответственностью или реализацией;
|
||||
- не имеет состояния и жизненного цикла;
|
||||
- не предоставляет публичный API;
|
||||
- не является узлом графа зависимостей;
|
||||
- не реэкспортирует содержащиеся в ней модули.
|
||||
|
||||
Модуль может находиться непосредственно в слое. Группа вводится только ради реальной классификации, а её названия и глубину определяет проект.
|
||||
|
||||
Группа организует несколько владельцев внутри слоя. [Сегмент](./segments.md) организует код внутри одного владельца.
|
||||
|
||||
## Немодульные исключения
|
||||
|
||||
Внутри SLM root код по умолчанию принадлежит модулю. Исключения ограничены двумя случаями:
|
||||
|
||||
- точка входа `app` непосредственно связывает приложение с фреймворком;
|
||||
- ресурс `shared` является небольшой самостоятельной детерминированной единицей без внутренней границы.
|
||||
|
||||
Если ресурсу `shared` нужны несколько файлов реализации, собственные архитектурные зависимости, изменяемое состояние, ввод-вывод или жизненный цикл, ему требуется модуль-владелец.
|
||||
|
||||
## Связанные правила
|
||||
|
||||
- [`SLM-LAYER-R001`](../rules/registry.md#slm-layer-r001)
|
||||
- [`SLM-LAYER-A002`](../rules/registry.md#slm-layer-a002)
|
||||
- [`SLM-LAYER-R003`](../rules/registry.md#slm-layer-r003)
|
||||
- [`SLM-GROUP-R007`](../rules/registry.md#slm-group-r007)
|
||||
- [`SLM-MODULE-R011`](../rules/registry.md#slm-module-r011)
|
||||
176
docs/architecture/modules.md
Normal file
176
docs/architecture/modules.md
Normal file
@@ -0,0 +1,176 @@
|
||||
# Модули
|
||||
|
||||
Модуль является владельцем самостоятельной ответственности. Публичный API, зависимости, состояние, жизненный цикл и внутренняя реализация ответственности относятся к модулю независимо от того, в каком файле или сущности фреймворка выполняется код.
|
||||
|
||||
## Ответственность и владелец
|
||||
|
||||
Перед созданием модуля ответственность формулируется без упоминания желаемой папки, файла или библиотеки.
|
||||
|
||||
Самостоятельность ответственности определяется вопросами:
|
||||
|
||||
- есть ли у неё отдельная причина изменяться;
|
||||
- нужен ли внешним потребителям собственный контракт;
|
||||
- есть ли у неё архитектурные зависимости;
|
||||
- владеет ли она данными или изменяемым состоянием;
|
||||
- нужна ли ей собственная область жизни;
|
||||
- можно ли назвать её независимо от внутренней реализации.
|
||||
|
||||
Если ответственность самостоятельна, она получает ровно один модуль-владелец. Если несколько частей изменяются по несвязанным причинам, им нужны разные владельцы.
|
||||
|
||||
Размер не определяет модуль. Модуль может состоять из одного файла реализации, а большая папка может оставаться частью другого владельца.
|
||||
|
||||
## Граница владения
|
||||
|
||||
Модуль определяет:
|
||||
|
||||
- публичные возможности ответственности;
|
||||
- допустимые внешние зависимости;
|
||||
- модели и правила, принадлежащие ответственности;
|
||||
- состояние и источник истины;
|
||||
- создание и очистку долгоживущих ресурсов;
|
||||
- устройство внутренней реализации.
|
||||
|
||||
Компонент, хук, провайдер, хранилище, сервис или маршрут могут выполнять часть этой работы технически. Владение остаётся у ближайшего модуля, пока часть кода не получает самостоятельную ответственность и собственную модульную границу.
|
||||
|
||||
## Публичный API
|
||||
|
||||
У модуля один логический публичный API. Он является контрактом между владельцем и внешними потребителями, а не перечнем всех внутренних файлов.
|
||||
|
||||
Публичный API:
|
||||
|
||||
- открывает только возможности, необходимые реальным внешним потребителям;
|
||||
- скрывает детали реализации и изменяемые внутренние механизмы;
|
||||
- не раскрывает внутренние сегменты;
|
||||
- представлен объявленными публичными фасетами;
|
||||
- является единственным способом доступа к модулю извне.
|
||||
|
||||
Внутри своей границы модуль может обращаться к собственным файлам напрямую. Требование публичного API действует при пересечении модульной границы.
|
||||
|
||||
### Фасеты
|
||||
|
||||
Фасет — публичная точка входа, открывающая часть единого API для определённой среды выполнения.
|
||||
|
||||
| Фасет | Назначение |
|
||||
|---|---|
|
||||
| `index` | Универсальные типы и исполняемый код, совместимый с серверным рендерингом, включая RSC, и клиентским выполнением |
|
||||
| `client` | Клиентская граница фреймворка, участвующая в серверной предварительной отрисовке и выполняющаяся при гидратации и в браузере |
|
||||
| `browser` | Код только для браузера, подключаемый динамически через границу с отключённым SSR |
|
||||
| `server` | Код только для сервера, недоступный универсальной и клиентской среде выполнения |
|
||||
|
||||
`index` обязателен. Остальные фасеты создаются только при наличии реального потребителя и несовместимых требований к среде.
|
||||
|
||||
Ограничение среды распространяется на все исполняемые импорты и реэкспорты фасета, включая транзитивные. Имя файла, директива `use client`, tree shaking или проверка `typeof window` сами по себе не доказывают совместимость.
|
||||
|
||||
Один исполняемый экспорт размещается в минимально подходящем фасете и не дублируется между ними. Универсальный фасет не реэкспортирует специализированные фасеты.
|
||||
|
||||
```text
|
||||
auth/
|
||||
├── index.ts # Обязательный универсальный фасет
|
||||
├── client.ts # При необходимости
|
||||
├── browser.ts # При необходимости
|
||||
├── server.ts # При необходимости
|
||||
└── ... # Внутренняя реализация
|
||||
```
|
||||
|
||||
Эта файловая форма представляет уже определённую публичную границу. Наличие `index.ts` само по себе не создаёт модуль.
|
||||
|
||||
## Зависимости между модулями
|
||||
|
||||
Зависимость связывает владельца исходного кода с владельцем импортируемого кода. Импорт внутреннего файла, сегмента или компонента относится к ближайшему модулю-владельцу. Вложенный модуль начинает собственную границу зависимостей.
|
||||
|
||||
При пересечении модульной границы код использует только публичный фасет целевого модуля:
|
||||
|
||||
```ts
|
||||
// Допустимо
|
||||
import { Button } from '@/ui/button'
|
||||
|
||||
// Недопустимый глубокий импорт
|
||||
import { Button } from '@/ui/button/button'
|
||||
```
|
||||
|
||||
Для каждой связи выполняются три условия:
|
||||
|
||||
1. Направление разрешено [матрицей слоёв](./layers.md#направление-зависимостей).
|
||||
2. Целевой модуль используется только через публичный API.
|
||||
3. Общий граф модулей остаётся ацикличным.
|
||||
|
||||
Модули одного слоя могут зависеть друг от друга, если соблюдены публичные границы и не возникает цикл. SLM не требует обязательного посредника или отдельного механизма dependency injection для любой межмодульной связи.
|
||||
|
||||
## Компоненты
|
||||
|
||||
Компонент является сущностью фреймворка, реализующей часть интерфейса родительского модуля. Он не образует самостоятельного владельца только из-за наличия отдельного файла, каталога, props, локального состояния, доступа к данным или кода жизненного цикла.
|
||||
|
||||
Зависимости, состояние и ресурсы компонента относятся к родительскому модулю. Если UI-часть получает самостоятельную ответственность, публичный API, собственные зависимости или область жизни, ей требуется модульная граница.
|
||||
|
||||
Модуль может состоять из одного корневого компонента. В этом случае компонент реализует интерфейс, а модуль остаётся владельцем ответственности.
|
||||
|
||||
Компонент может быть оформлен отдельным каталогом и иметь локальный `index.ts`:
|
||||
|
||||
```text
|
||||
button-submit/
|
||||
├── button-submit.tsx
|
||||
├── styles/
|
||||
│ └── button-submit.module.css
|
||||
├── types/
|
||||
│ └── button-submit.types.ts
|
||||
└── index.ts
|
||||
```
|
||||
|
||||
Такой `index.ts` является внутренней точкой входа компонента. Он упрощает импорты внутри модуля, но не создаёт SLM public API, нового владельца или узел графа зависимостей.
|
||||
|
||||
| `index.ts` компонента | Публичный фасет модуля |
|
||||
|---|---|
|
||||
| Действует внутри ближайшего модуля | Действует при пересечении модульной границы |
|
||||
| Экспортирует локальную реализацию и типы | Экспортирует контракт владельца |
|
||||
| Не создаёт архитектурную границу | Представляет архитектурную границу |
|
||||
| Не делает компонент модулем | Принадлежит уже определённому модулю |
|
||||
|
||||
Если компонент входит в публичный контракт владельца, корневой фасет модуля явно реэкспортирует его локальную точку входа. Внешний код по-прежнему импортирует модуль, а не внутренний путь компонента.
|
||||
|
||||
## Вложенные модули
|
||||
|
||||
Вложенный модуль — самостоятельный владелец, физически размещённый внутри родительского модуля. Он имеет собственные ответственность, публичный API и границу зависимостей и подчиняется общим правилам модулей.
|
||||
|
||||
Код родительского модуля использует вложенный модуль через его публичный API. Код за пределами родительской границы не импортирует вложенный модуль напрямую и получает необходимые возможности через API родителя.
|
||||
|
||||
Если вложенный модуль становится нужен нескольким внешним владельцам, его можно перенести в минимальную общую область. Сам факт повторного использования внутри родителя переноса не требует.
|
||||
|
||||
## Состояние и жизненный цикл
|
||||
|
||||
Состояние принадлежит модулю, ответственность которого определяет его смысл, допустимые изменения и область жизни. Место хранения состояния не переносит владение в файл хранилища, провайдер или контекст фреймворка.
|
||||
|
||||
Для каждого долгоживущего ресурса модуль-владелец определяет:
|
||||
|
||||
- место создания;
|
||||
- момент запуска;
|
||||
- область жизни;
|
||||
- допустимое число экземпляров;
|
||||
- способ остановки, отмены или освобождения.
|
||||
|
||||
Подписка, обработчик событий, таймер, наблюдатель, запрос или соединение не остаются активными после завершения своей области жизни. Очистку может технически выполнить компонент, провайдер или фреймворк, но ответственность за корректную границу остаётся у модуля.
|
||||
|
||||
Размещение экземпляра на уровне файла не доказывает область жизни всего приложения. Точка входа `app` может запустить ресурс через публичный API модуля, но не становится его владельцем.
|
||||
|
||||
## Внутренняя организация
|
||||
|
||||
После определения ответственности и публичной границы модуль может организовать реализацию [сегментами](./segments.md), компонентами и вложенными модулями. SLM не требует полного каркаса и не задаёт обязательный набор внутренних папок.
|
||||
|
||||
Каждый модуль физически размещается в отдельной папке. Это делает логическую границу наблюдаемой для потребителей и автоматических проверок, но не заменяет решение о владельце.
|
||||
|
||||
Наличие `index.ts` не используется как единственный признак модуля. Модульную границу определяют ответственность, владелец, публичные потребители и сопоставление путей, принятое проектом.
|
||||
|
||||
## Связанные правила
|
||||
|
||||
- [`SLM-MODULE-A004`](../rules/registry.md#slm-module-a004)
|
||||
- [`SLM-MODULE-A014`](../rules/registry.md#slm-module-a014)
|
||||
- [`SLM-MODULE-R006`](../rules/registry.md#slm-module-r006)
|
||||
- [`SLM-MODULE-R011`](../rules/registry.md#slm-module-r011)
|
||||
- [`SLM-MODULE-R012`](../rules/registry.md#slm-module-r012)
|
||||
- [`SLM-DEPENDENCY-A005`](../rules/registry.md#slm-dependency-a005)
|
||||
- [`SLM-COMPONENT-R009`](../rules/registry.md#slm-component-r009)
|
||||
- [`SLM-NESTED_MODULE-A010`](../rules/registry.md#slm-nested_module-a010)
|
||||
- [`SLM-LIFECYCLE-R013`](../rules/registry.md#slm-lifecycle-r013)
|
||||
- [`SLM-ENVIRONMENT-R016`](../rules/registry.md#slm-environment-r016)
|
||||
- [`SLM-ENVIRONMENT-R017`](../rules/registry.md#slm-environment-r017)
|
||||
- [`SLM-ENVIRONMENT-R018`](../rules/registry.md#slm-environment-r018)
|
||||
- [`SLM-ENVIRONMENT-R019`](../rules/registry.md#slm-environment-r019)
|
||||
92
docs/architecture/segments.md
Normal file
92
docs/architecture/segments.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# Сегменты
|
||||
|
||||
Сегмент организует внутреннее содержимое одного модуля по назначению. Он помогает ориентироваться в реализации владельца, но не создаёт новую ответственность или архитектурную границу.
|
||||
|
||||
## Место в модели
|
||||
|
||||
Сегмент появляется только внутри уже определённого модуля:
|
||||
|
||||
```text
|
||||
Слой → [Группа*] → Модуль → [Сегмент*]
|
||||
```
|
||||
|
||||
Группа классифицирует несколько модулей внутри слоя. Сегмент классифицирует код одного модуля. Ни группа, ни сегмент не являются владельцами.
|
||||
|
||||
Все файлы, компоненты, состояние, зависимости и код жизненного цикла сегмента принадлежат родительскому модулю. Исключением является только вложенный модуль, который начинает собственную границу владения.
|
||||
|
||||
## Назначение
|
||||
|
||||
Сегмент используется, когда группировка внутренних файлов по назначению упрощает навигацию. Набор и названия сегментов определяет проект.
|
||||
|
||||
Возможная структура:
|
||||
|
||||
```text
|
||||
profile/
|
||||
├── index.ts
|
||||
├── profile.tsx
|
||||
├── hooks/ # Возможный сегмент
|
||||
├── services/ # Возможный сегмент
|
||||
├── stores/ # Возможный сегмент
|
||||
├── types/ # Возможный сегмент
|
||||
└── ui/ # Возможный сегмент
|
||||
```
|
||||
|
||||
Ни один из показанных сегментов не обязателен. Маленький модуль может хранить реализацию в корне без дополнительных каталогов.
|
||||
|
||||
Сегмент:
|
||||
|
||||
- не имеет самостоятельной ответственности;
|
||||
- не предоставляет публичный API;
|
||||
- не владеет состоянием или жизненным циклом;
|
||||
- не является узлом графа зависимостей;
|
||||
- не импортируется внешним кодом как отдельная архитектурная сущность.
|
||||
|
||||
Локальный `index.ts` может использоваться во внутренней единице сегмента, например в каталоге компонента. Он не превращает эту единицу или сегмент в модульную границу.
|
||||
|
||||
## Компоненты и вложенные модули
|
||||
|
||||
Сегмент может содержать компоненты и вспомогательные файлы родительского модуля. Компонент вправе иметь локальные `styles/`, `types/`, `tests/` и внутренний `index.ts`; всё это остаётся реализацией ближайшего модуля.
|
||||
|
||||
```text
|
||||
header/ # Модуль
|
||||
└── components/ # Сегмент
|
||||
└── button-submit/ # Компонент
|
||||
├── button-submit.tsx
|
||||
├── styles/
|
||||
│ └── button-submit.module.css
|
||||
├── types/
|
||||
│ └── button-submit.types.ts
|
||||
└── index.ts # Внутренняя точка входа
|
||||
```
|
||||
|
||||
Внешний код не импортирует `components/button-submit`. Если компонент нужен снаружи, модуль-владелец реэкспортирует его через собственный публичный фасет.
|
||||
|
||||
Сегмент также может содержать вложенные модули. В отличие от остальных файлов сегмента, каждый вложенный модуль сам владеет отдельной ответственностью и имеет публичный API и границу зависимостей.
|
||||
|
||||
```text
|
||||
landing/ # Родительский модуль
|
||||
└── parts/ # Сегмент
|
||||
└── hero/ # Вложенный модуль
|
||||
├── hero.tsx
|
||||
└── index.ts
|
||||
```
|
||||
|
||||
Имя `parts` является примером, а не обязательным соглашением SLM.
|
||||
|
||||
## Выбор границы
|
||||
|
||||
| Ситуация | Решение |
|
||||
|---|---|
|
||||
| Код относится к существующему владельцу и группируется только по назначению | Сегмент |
|
||||
| Части нужна собственная ответственность, API, зависимости, состояние или область жизни | Модуль |
|
||||
| Несколько модулей слоя нужно классифицировать для навигации | Группа |
|
||||
| Нескольким файлам не нужна отдельная группировка | Оставить в корне модуля |
|
||||
|
||||
Размер каталога и количество файлов не определяют выбор между сегментом и модулем.
|
||||
|
||||
## Связанные правила
|
||||
|
||||
- [`SLM-SEGMENT-R008`](../rules/registry.md#slm-segment-r008)
|
||||
- [`SLM-MODULE-A004`](../rules/registry.md#slm-module-a004)
|
||||
- [`SLM-COMPONENT-R009`](../rules/registry.md#slm-component-r009)
|
||||
- [`SLM-NESTED_MODULE-A010`](../rules/registry.md#slm-nested_module-a010)
|
||||
Reference in New Issue
Block a user