mirror of
https://github.com/gromlab-ru/slm-design.git
synced 2026-08-22 07:30:16 +03:00
139 lines
11 KiB
Markdown
139 lines
11 KiB
Markdown
# Зависимости
|
||
|
||
Зависимость связывает архитектурных владельцев. Исходный файл создаёт ребро от своего ближайшего модуля к ближайшему модулю импортируемого файла.
|
||
|
||
## Модульный граф
|
||
|
||
Узлами архитектурного графа являются модули, включая вложенные. Группы, сегменты, framework-компоненты, hooks, stores и другие файлы реализации отдельных узлов не создают.
|
||
|
||
Для каждой связи определяются:
|
||
|
||
1. Ближайший модуль-владелец исходного файла.
|
||
2. Ближайший модуль-владелец целевого файла.
|
||
3. Слои исходного и целевого владельцев.
|
||
4. Публичный фасет, через который пересечена граница.
|
||
|
||
Обычный импорт, `import type` и реэкспорт одинаково создают архитектурное ребро. Связь файлов внутри одного модуля остаётся внутренней реализацией и не создаёт межмодульную зависимость.
|
||
|
||
Вложенный модуль начинает новый узел. Импорт из родительского модуля во вложенный или обратно проверяется как обычная межмодульная связь.
|
||
|
||
## Публичная граница
|
||
|
||
При пересечении модульной границы используется только объявленный публичный фасет целевого модуля:
|
||
|
||
```ts
|
||
// Допустимо
|
||
import { Button } from '@/ui/button'
|
||
|
||
// Недопустимый глубокий импорт
|
||
import { Button } from '@/ui/button/button'
|
||
```
|
||
|
||
Разрешённое направление слоя или отсутствие цикла не делает глубокий импорт допустимым.
|
||
|
||
## Направление между слоями
|
||
|
||
Матрица определяет, от каких слоёв может зависеть исходный слой:
|
||
|
||
| Исходный слой | Допустимые целевые слои |
|
||
|---|---|
|
||
| `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`.
|
||
|
||
Разрешённое направление не переносит владение. Если модуль `domains` использует `infra`, предметный сценарий остаётся ответственностью доменного модуля, а техническая возможность — ответственностью инфраструктурного.
|
||
|
||
## Допустимость связи и владение
|
||
|
||
Матрица отвечает только на вопрос, может ли один слой зависеть от другого. Она не разрешает исходному модулю реализовывать ответственность, которая по своей роли принадлежит целевому или другому слою.
|
||
|
||
`compositions` может зависеть от `domains` и `infra`, но использует эти направления по-разному:
|
||
|
||
- доменный сценарий доступен композиции только как готовый публичный API доменного модуля;
|
||
- инфраструктурный API может обслуживать собственную техническую потребность композиции, например тему, локализацию или доставку метрики показа страницы;
|
||
- инфраструктурный HTTP-клиент, SDK или storage не используются композицией для реализации продуктовой операции, загрузки предметных данных или определения доменного исхода.
|
||
|
||
```ts
|
||
// Допустимо: композиция использует готовый доменный UI.
|
||
import { OrdersList } from '@/domains/orders/client'
|
||
|
||
// Допустимо: техническая возможность обслуживает саму композицию.
|
||
import { useTheme } from '@/infra/theme/client'
|
||
|
||
// Направление импорта допустимо, но ответственность выбрана неверно.
|
||
import { http } from '@/infra/http'
|
||
|
||
await http.get('/orders')
|
||
```
|
||
|
||
В последнем примере запрос получает предметные данные и участвует в доменном сценарии. Его смысл, параметры, продуктовые исходы и вызов принадлежат доменному модулю, который открывает композиции готовый API.
|
||
|
||
Разрешённая зависимость `domains` от `infra` также не объединяет их контракты. Домен может использовать HTTP-транспорт, SDK или storage через публичный API инфраструктурного модуля, но DTO и ошибки источника остаются только во внутреннем интеграционном коде домена. До попадания в правила, состояние, доменный UI или публичный результат данные адаптируются к доменному контракту, а ошибка источника интерпретируется в терминах текущего сценария. Полные требования описаны в разделе [Домены](./domains.md#граница-внешних-данных).
|
||
|
||
Аналогично, технической доставкой метрик владеет `infra`, но смысл события определяется модулем-владельцем наблюдаемого поведения. Разрешённый вызов telemetry API из композиции не позволяет ей объявлять события доменного сценария от своего имени.
|
||
|
||
Композиция может размещать и связывать несколько доменных API. Если эта связь задаёт обязательный порядок, продуктовые условия, общий предметный результат или политику ошибок между доменами, она является отдельным доменным сценарием, а не внутренней логикой композиции.
|
||
|
||
## Зависимости внутри слоя
|
||
|
||
Модули одного слоя могут зависеть друг от друга в любом направлении при одновременном выполнении двух условий:
|
||
|
||
1. Целевой модуль используется только через публичный API.
|
||
2. Общий модульный граф остаётся ацикличным.
|
||
|
||
Принадлежность модулей одной или разным [группам](./groups.md) не влияет на разрешение связи. Группа не имеет API и не является промежуточным узлом импорта.
|
||
|
||
SLM не задаёт отдельные same-layer матрицы для `pages`, `layouts`, `widgets`, доменов, инфраструктуры, UI или shared. Если проекту нужна более строгая локальная политика, она является дополнительным проектным ограничением, а не общим правилом SLM.
|
||
|
||
## Запрет циклов
|
||
|
||
Общий граф модулей внутри одного SLM root остаётся ацикличным. Запрет действует для модулей одного слоя, разных разрешённых слоёв и вложенных модулей.
|
||
|
||
Проверки только файлового графа недостаточно. Например:
|
||
|
||
```text
|
||
module-a/file-1.ts → module-b/file-1.ts
|
||
module-b/file-2.ts → module-a/file-2.ts
|
||
```
|
||
|
||
Между конкретными файлами может не существовать замкнутого пути, но после сопоставления файлов владельцам возникает архитектурный цикл:
|
||
|
||
```text
|
||
module-a ↔ module-b
|
||
```
|
||
|
||
Lint-проверка модульных циклов должна:
|
||
|
||
1. Сопоставить каждый файл ближайшему модулю-владельцу.
|
||
2. Свернуть внутренние импорты файлов одного модуля.
|
||
3. Добавить межмодульные рёбра для импортов типов, исполняемого кода и реэкспортов.
|
||
4. Считать каждый вложенный модуль отдельным узлом.
|
||
5. Блокировать любое сильносвязное множество из нескольких модулей.
|
||
|
||
Стандартная file-level проверка циклов может использоваться дополнительно, но не заменяет проверку модульного графа.
|
||
|
||
## Проверка связи
|
||
|
||
Для каждого нового или изменённого импорта проверяются три условия:
|
||
|
||
1. Направление разрешено матрицей слоёв.
|
||
2. Целевая модульная граница пересечена через публичный фасет.
|
||
3. После добавления ребра модульный граф остаётся ацикличным.
|
||
|
||
## Связанные правила
|
||
|
||
- [`SLM-LAYER-A002`](../rules/registry.md#slm-layer-a002)
|
||
- [`SLM-DOMAIN-R022`](../rules/registry.md#slm-domain-r022)
|
||
- [`SLM-DOMAIN-R023`](../rules/registry.md#slm-domain-r023)
|
||
- [`SLM-DOMAIN-R024`](../rules/registry.md#slm-domain-r024)
|
||
- [`SLM-DOMAIN-R025`](../rules/registry.md#slm-domain-r025)
|
||
- [`SLM-DOMAIN-R026`](../rules/registry.md#slm-domain-r026)
|
||
- [`SLM-MODULE-A004`](../rules/registry.md#slm-module-a004)
|
||
- [`SLM-DEPENDENCY-A005`](../rules/registry.md#slm-dependency-a005)
|
||
- [`SLM-NESTED_MODULE-A010`](../rules/registry.md#slm-nested_module-a010)
|