mirror of
https://github.com/gromlab-ru/slm-design.git
synced 2026-08-22 07:30:16 +03:00
chore: sync
This commit is contained in:
@@ -10,11 +10,11 @@
|
||||
|
||||
### Ответственность
|
||||
|
||||
Связная часть приложения с одной причиной изменяться. Ответственность является самостоятельной, когда ей нужны собственный публичный API, зависимости, состояние или область жизни.
|
||||
Результат или поведение приложения, за которое отвечает один модуль-владелец. Ответственность является самостоятельной, когда ей нужны собственный публичный контракт, зависимости, состояние или область жизни, а не только внутренняя роль в работе другого модуля. Наличие у framework-сущности props, импортов, локального состояния или lifecycle-кода само по себе не создаёт самостоятельную ответственность.
|
||||
|
||||
### Владелец
|
||||
|
||||
Модуль, который определяет публичный API ответственности, её зависимости, состояние, область жизни и внутреннюю реализацию. Место выполнения кода не переносит владение.
|
||||
Модуль, который определяет публичный API ответственности, её зависимости, состояние, область жизни и внутреннюю реализацию. Каждый файл принадлежит ближайшей модульной границе и реализует ответственность этого модуля. Место выполнения кода или вид framework-сущности не переносит владение.
|
||||
|
||||
## Структурные сущности
|
||||
|
||||
@@ -34,13 +34,11 @@
|
||||
|
||||
Необязательная внутренняя часть одного модуля, группирующая его содержимое по назначению. Сегмент не является владельцем, публичным API или границей зависимостей.
|
||||
|
||||
### Компонент
|
||||
|
||||
Сущность фреймворка, реализующая часть интерфейса родительского модуля. Зависимости, состояние и жизненный цикл компонента принадлежат этому модулю и сами по себе не создают нового владельца. Компонент может иметь внутренний `index.ts`, который не является публичным фасетом SLM.
|
||||
|
||||
### Вложенный модуль
|
||||
|
||||
Обычный модуль, физически размещённый внутри родительского модуля. Он сам владеет отдельной ответственностью и имеет публичный API и границу зависимостей, но остаётся внутренней реализацией родителя для внешнего кода.
|
||||
Обычный модуль, физически размещённый внутри родительского модуля. Он владеет отдельно сформулированной связной частью ответственности родителя, имеет публичный API и собственную границу зависимостей. Родитель владеет общим результатом, а вложенный модуль — выделенной подответственностью; одна и та же ответственность не получает двух владельцев.
|
||||
|
||||
Для кода за пределами родительской границы вложенный модуль остаётся внутренней реализацией родителя. Внутри вложенного модуля снова действуют все правила обычного модуля, поэтому рекурсивная структурная вложенность создаётся только модульными границами.
|
||||
|
||||
## Публичная граница
|
||||
|
||||
@@ -62,7 +60,7 @@
|
||||
|
||||
Направленная статическая связь между архитектурными границами внутри одного SLM root. Обычный импорт, импорт типа (`import type`) и реэкспорт одинаково создают архитектурную зависимость.
|
||||
|
||||
Зависимость внутреннего файла, сегмента или компонента относится к ближайшему модулю-владельцу. Вложенный модуль начинает собственную границу зависимостей.
|
||||
Зависимость внутреннего файла или сегмента относится к ближайшему модулю-владельцу. Вложенный модуль начинает собственную границу зависимостей.
|
||||
|
||||
### Нормативная матрица слоёв
|
||||
|
||||
|
||||
@@ -28,14 +28,18 @@
|
||||
|
||||
На ревью проверяется смысл кода, который нельзя надёжно вывести из файловой системы:
|
||||
|
||||
- одна ли связная ответственность находится внутри модуля;
|
||||
- есть ли у каждой самостоятельной ответственности ровно один владелец;
|
||||
- одна ли связная ответственность находится внутри модульной границы;
|
||||
- есть ли у каждой самостоятельной ответственности ровно один ближайший владелец;
|
||||
- соответствует ли ответственность роли выбранного слоя;
|
||||
- не стали ли группа, сегмент или компонент скрытыми владельцами;
|
||||
- владеет ли вложенный модуль отдельно сформулированной подответственностью;
|
||||
- не стали ли группа или сегмент скрытыми владельцами;
|
||||
- реализует ли внутренний код ответственность ближайшего модуля;
|
||||
- не принимаются ли props, Context, локальное состояние или lifecycle-код за достаточное основание для новой модульной границы;
|
||||
- является ли главный файл в корне однозначной реализацией или сборкой ответственности;
|
||||
- не лежат ли прочие файлы реализации в корне вместо подходящих сегментов;
|
||||
- нужен ли каждый экспорт реальному внешнему потребителю;
|
||||
- не раскрывает ли публичный API изменяемые внутренние механизмы;
|
||||
- определены ли владелец, область жизни, число экземпляров и очистка каждого ресурса;
|
||||
- не переносится ли владение из-за места вызова, провайдера фреймворка или точки маршрута.
|
||||
- определены ли владелец, область жизни, число экземпляров и очистка каждого ресурса.
|
||||
|
||||
Окончательные смысловые требования имеют класс `R` в [реестре правил](../rules/registry.md).
|
||||
|
||||
@@ -43,14 +47,14 @@
|
||||
|
||||
Проект сопоставляет физические пути с SLM root, слоями, группами, модулями, вложенными модулями, сегментами, фасетами, точками входа `app` и ресурсами `shared`. Сопоставление задаётся локальной конфигурацией и не меняет смысл сущностей.
|
||||
|
||||
Наличие `index.ts` само по себе не объявляет модуль. Проверка отличает объявленные корни модулей и их публичные фасеты от локальных точек входа компонентов и других внутренних единиц.
|
||||
Наличие `index.ts` само по себе не объявляет модуль. Проверка отличает объявленные корни модулей и их публичные фасеты от локальных точек входа и других внутренних единиц.
|
||||
|
||||
Автоматически проверяются:
|
||||
|
||||
- допустимое направление импортов по матрице слоёв;
|
||||
- отдельная папка каждого модуля;
|
||||
- доступ к чужому модулю только через объявленные фасеты;
|
||||
- отсутствие циклов между модулями;
|
||||
- отсутствие циклов в свёрнутом модульном графе;
|
||||
- отсутствие прямого внешнего доступа к вложенным модулям;
|
||||
- допустимый транзитивный граф исполняемых импортов каждого фасета среды выполнения;
|
||||
- динамическое подключение `browser`-фасета с отключённым SSR.
|
||||
@@ -61,13 +65,26 @@
|
||||
|
||||
Для каждого внешнего импорта определяется:
|
||||
|
||||
1. Модуль-владелец исходного файла.
|
||||
2. Модуль-владелец целевого файла.
|
||||
1. Ближайший модуль-владелец исходного файла.
|
||||
2. Ближайший модуль-владелец целевого файла.
|
||||
3. Слои исходного и целевого владельцев.
|
||||
4. Публичный фасет, через который выполнен импорт.
|
||||
5. Отсутствие цикла после добавления связи.
|
||||
5. Отсутствие цикла после добавления межмодульного ребра.
|
||||
|
||||
Импорты типов (`import type`) и реэкспорты проверяются как архитектурные связи. Относительные импорты внутри одного модуля не пересекают модульную границу.
|
||||
Импорты типов (`import type`) и реэкспорты проверяются как архитектурные связи. Импорты файлов одного модуля сворачиваются и не создают межмодульного ребра. Вложенный модуль считается отдельным узлом.
|
||||
|
||||
File-level проверка не заменяет модульную: два модуля могут зависеть друг от друга через разные файлы без замкнутого пути между конкретными файлами. Полный алгоритм описан в разделе [Зависимости](../architecture/dependencies.md#запрет-циклов).
|
||||
|
||||
## Проверка внутренней структуры
|
||||
|
||||
Ревью или дополнительный project lint подтверждают:
|
||||
|
||||
- помимо опционального главного framework-файла, остальные компонентные единицы размещены на одном внутреннем уровне модуля;
|
||||
- их локальные каталоги не содержат другие компонентные единицы или вложенные модули;
|
||||
- runtime-дерево компонентов не используется как файловая иерархия;
|
||||
- рекурсивная структурная вложенность проходит только через вложенные модули;
|
||||
- в корне модуля находятся только фасеты и опциональный главный implementation- или assembly-файл;
|
||||
- остальная реализация организована сегментами.
|
||||
|
||||
## Проверка фасетов
|
||||
|
||||
@@ -78,6 +95,7 @@
|
||||
- `index` не достигает `client`, `browser` или `server`;
|
||||
- `client` не достигает `browser` или `server`;
|
||||
- `browser` и `server` не достигают друг друга;
|
||||
- `browser` экспортирует только browser-only или предназначенный для динамического подключения клиентский код;
|
||||
- `browser` доступен только через поддерживаемую динамическую границу без SSR;
|
||||
- специализированный фасет существует ради реального потребителя;
|
||||
- один исполняемый экспорт не дублируется между фасетами.
|
||||
@@ -88,11 +106,15 @@
|
||||
|
||||
Изменение соответствует SLM, когда одновременно выполнены условия:
|
||||
|
||||
- ответственность и единственный владелец определены;
|
||||
- ответственность и единственный ближайший владелец определены;
|
||||
- роль слоя соответствует ответственности;
|
||||
- публичный API минимален и используется всеми внешними потребителями;
|
||||
- зависимости разрешены и не образуют циклов;
|
||||
- зависимости разрешены и не образуют модульных циклов;
|
||||
- группа и сегменты не подменяют модульную границу;
|
||||
- вложенные модули владеют отдельными подответственностями;
|
||||
- внутренний код реализует ответственность ближайшего модуля;
|
||||
- framework-компоненты имеют одноуровневую файловую организацию с единственным допустимым исключением для главного файла в корне;
|
||||
- корень и сегменты соответствуют своим назначениям;
|
||||
- состояние и ресурсы имеют владельца и корректную область жизни;
|
||||
- физическая структура однозначно выражает принятое решение;
|
||||
- применимые автоматические проверки и архитектурное ревью пройдены.
|
||||
|
||||
Reference in New Issue
Block a user