mirror of
https://github.com/gromlab-ru/slm-design.git
synced 2026-08-22 07:30:16 +03:00
168 lines
19 KiB
Markdown
168 lines
19 KiB
Markdown
# Проверка архитектуры
|
||
|
||
Проверка SLM подтверждает две разные стороны решения:
|
||
|
||
- смысловая проверка устанавливает ответственность, владельца и корректность границ;
|
||
- структурная проверка подтверждает, что решение правильно выражено путями, публичными фасетами и зависимостями.
|
||
|
||
Успешная сборка или корректно отображаемый интерфейс не доказывают архитектурную корректность.
|
||
|
||
## Карточка решения
|
||
|
||
Перед изменением структуры нужно ответить:
|
||
|
||
| Вопрос | Что зафиксировать |
|
||
|---|---|
|
||
| Ответственность | Какой результат или поведение изменяется как единое целое |
|
||
| Доменный сценарий | Какой модуль `domains` владеет предметным результатом и через какой API его используют внешние потребители, включая композиции |
|
||
| Доменный контракт | Какие входы, модели и результаты определены предметным смыслом независимо от источника данных |
|
||
| Доменные ошибки | Какие ожидаемые неуспешные исходы определяет домен и где проходит граница с programming defects |
|
||
| Граница источника | Какие внешние контракты получает домен и как они адаптируются к предметному смыслу |
|
||
| Владелец | Какой модуль определяет контракт и внутреннюю реализацию |
|
||
| Слой | Какой архитектурной роли соответствует ответственность |
|
||
| Потребители | Кому действительно нужен публичный API |
|
||
| Зависимости | Какие другие владельцы и возможности необходимы |
|
||
| Состояние | Кто определяет смысл и допустимые изменения данных |
|
||
| Жизненный цикл | Кто создаёт ресурсы, какова их область жизни и очистка |
|
||
| Физическая форма | Какими путями и фасетами представлено принятое решение |
|
||
|
||
Если ответственность или владелец не определены, проверка путей откладывается: одинаковая файловая структура может представлять разные архитектурные решения.
|
||
|
||
## Архитектурное ревью
|
||
|
||
На ревью проверяется смысл кода, который нельзя надёжно вывести из файловой системы:
|
||
|
||
- одна ли связная ответственность находится внутри модульной границы;
|
||
- есть ли у каждой самостоятельной ответственности ровно один ближайший владелец;
|
||
- принадлежит ли каждый доменный сценарий модулю слоя `domains`;
|
||
- находятся ли бизнес-правила, продуктовое состояние, смысл операций с предметными данными, предметные исходы и доменный UI у владельца сценария;
|
||
- только ли использует и компонует модуль `compositions` готовые публичные API доменов, не определяя и не дополняя их сценарии;
|
||
- не размещён ли сценарий временно в композиции только потому, что подходящий доменный модуль ещё не создан;
|
||
- не скрывает ли связывание нескольких доменных API новый порядок, условие, общий предметный результат или политику ошибок;
|
||
- обслуживает ли прямое использование `infra` собственную техническую потребность композиции, а не продуктовую операцию или доступ к предметным данным;
|
||
- соответствует ли ответственность роли выбранного слоя;
|
||
- владеет ли вложенный модуль отдельно сформулированной подответственностью;
|
||
- не стали ли группа или сегмент скрытыми владельцами;
|
||
- реализует ли внутренний код ответственность ближайшего модуля;
|
||
- не принимаются ли props, Context, локальное состояние или lifecycle-код за достаточное основание для новой модульной границы;
|
||
- является ли главный файл в корне однозначной реализацией или сборкой ответственности;
|
||
- не лежат ли прочие файлы реализации в корне вместо подходящих сегментов;
|
||
- нужен ли каждый экспорт реальному внешнему потребителю;
|
||
- не раскрывает ли публичный API изменяемые внутренние механизмы;
|
||
- определены ли владелец, область жизни, число экземпляров и очистка каждого ресурса.
|
||
|
||
Окончательные смысловые требования имеют класс `R` в [реестре правил](../rules/registry.md).
|
||
|
||
Вызовы `fetch`, HTTP-клиента, SDK, query client или storage внутри `compositions` являются сигналами для проверки, но не самостоятельным доказательством нарушения. Ревью устанавливает, обслуживает ли вызов техническую ответственность самой композиции или реализует доменный сценарий в обход его владельца.
|
||
|
||
## Проверка домена
|
||
|
||
Для каждого создаваемого или изменяемого домена дополнительно проверяется:
|
||
|
||
- остаётся ли домен одним специализированным модулем и узлом графа, а не неявным контейнером нескольких владельцев;
|
||
- объявлены ли входы, модели и результаты самим доменом до подключения источника;
|
||
- можно ли описать доменный контракт без упоминания endpoint, SDK, DTO или схемы внешнего сервиса;
|
||
- не выведен ли публичный тип через alias, наследование, `Pick`, `Omit`, `ReturnType` или другой source type;
|
||
- определены ли ожидаемые неуспешные исходы самим доменом;
|
||
- создаёт ли реализация только исходы, объявленные доменным контрактом;
|
||
- не определяют ли mapper, adapter или framework-код независимые ошибки параллельно декларации домена;
|
||
- зависит ли потребитель только от error contract текущего домена независимо от выбранной формы его представления;
|
||
- не выдаётся ли project policy о code, payload, union или casing за универсальное правило SLM;
|
||
- отсутствуют ли в публичной ошибке чужие error type, source code, message, transport status, raw payload и `cause`;
|
||
- адаптируются ли request и response источника выбранным внутренним механизмом;
|
||
- попадают ли в правила, состояние и доменный UI только значения доменного контракта;
|
||
- интерпретируется ли каждая ошибка источника и зависимого домена в терминах текущего сценария;
|
||
- нужен ли runtime-механизм идентификации реальным потребителям и совместим ли он с их средой выполнения;
|
||
- не экспортируется ли constructor, guard, parser или schema без доказанной потребности;
|
||
- не замаскирована ли programming defect под ожидаемую доменную ошибку.
|
||
|
||
Прямой импорт source type во внутренний код адаптации сам по себе допустим. Нарушением является его достижимость из публичного фасета, использование как доменной модели или состояния либо передача потребителю без преобразования.
|
||
|
||
Интеграционный код ревьюится только после определения доменного контракта и семантики ожидаемых ошибок. Запрос к реальному источнику не считается допустимой временной реализацией домена, если предметная граница ещё не объявлена.
|
||
|
||
## Автоматическая проверка
|
||
|
||
Проект сопоставляет физические пути с SLM root, слоями, группами, модулями, вложенными модулями, сегментами, фасетами, точками входа `app` и ресурсами `shared`. Сопоставление задаётся локальной конфигурацией и не меняет смысл сущностей.
|
||
|
||
Наличие `index.ts` само по себе не объявляет модуль. Проверка отличает объявленные корни модулей и их публичные фасеты от локальных точек входа и других внутренних единиц.
|
||
|
||
Автоматически проверяются:
|
||
|
||
- допустимое направление импортов по матрице слоёв;
|
||
- отдельная папка каждого модуля;
|
||
- доступ к чужому модулю только через объявленные фасеты;
|
||
- отсутствие циклов в свёрнутом модульном графе;
|
||
- отсутствие прямого внешнего доступа к вложенным модулям;
|
||
- допустимый транзитивный граф исполняемых импортов каждого фасета среды выполнения;
|
||
- динамическое подключение `browser`-фасета с отключённым SSR.
|
||
|
||
Каждое правило класса `A` должно полностью блокировать проверку при нарушении. SLM не требует конкретного lint-инструмента.
|
||
|
||
## Проверка зависимостей
|
||
|
||
Для каждого внешнего импорта определяется:
|
||
|
||
1. Ближайший модуль-владелец исходного файла.
|
||
2. Ближайший модуль-владелец целевого файла.
|
||
3. Слои исходного и целевого владельцев.
|
||
4. Публичный фасет, через который выполнен импорт.
|
||
5. Отсутствие цикла после добавления межмодульного ребра.
|
||
|
||
Импорты типов (`import type`) и реэкспорты проверяются как архитектурные связи. Импорты файлов одного модуля сворачиваются и не создают межмодульного ребра. Вложенный модуль считается отдельным узлом.
|
||
|
||
File-level проверка не заменяет модульную: два модуля могут зависеть друг от друга через разные файлы без замкнутого пути между конкретными файлами. Полный алгоритм описан в разделе [Зависимости](../architecture/dependencies.md#запрет-циклов).
|
||
|
||
## Проверка внутренней структуры
|
||
|
||
Ревью или дополнительный project lint подтверждают:
|
||
|
||
- помимо опционального главного framework-файла, остальные компонентные единицы размещены на одном внутреннем уровне модуля;
|
||
- их локальные каталоги не содержат другие компонентные единицы или вложенные модули;
|
||
- runtime-дерево компонентов не используется как файловая иерархия;
|
||
- рекурсивная структурная вложенность проходит только через вложенные модули;
|
||
- в корне модуля находятся только фасеты и опциональный главный implementation- или assembly-файл;
|
||
- остальная реализация организована сегментами.
|
||
|
||
## Проверка фасетов
|
||
|
||
Совместимость фасета определяется всем достижимым исполняемым кодом, а не только его собственным файлом.
|
||
|
||
Проверка подтверждает:
|
||
|
||
- `index` не достигает `client`, `browser` или `server`;
|
||
- `client` не достигает `browser` или `server`;
|
||
- `browser` и `server` не достигают друг друга;
|
||
- `browser` экспортирует только browser-only или предназначенный для динамического подключения клиентский код;
|
||
- `browser` доступен только через поддерживаемую динамическую границу без SSR;
|
||
- специализированный фасет существует ради реального потребителя;
|
||
- один исполняемый экспорт не дублируется между фасетами.
|
||
|
||
Импорт типа остаётся архитектурной зависимостью, но не добавляет исполняемый код в среду фасета.
|
||
|
||
## Критерий завершения
|
||
|
||
Изменение соответствует SLM, когда одновременно выполнены условия:
|
||
|
||
- ответственность и единственный ближайший владелец определены;
|
||
- каждый доменный сценарий целиком принадлежит модулю `domains` и используется композициями только через его публичный API;
|
||
- отсутствие готового доменного модуля не привело к временной реализации сценария в `compositions`;
|
||
- междоменная координация с собственным продуктовым результатом получила доменного владельца;
|
||
- каждый домен остаётся специализированным модулем и самостоятельно объявляет предметный контракт;
|
||
- публичный API домена не содержит DTO, source types или чужие error contracts;
|
||
- все внешние значения адаптированы к доменному контракту до использования в правилах, состоянии или доменном UI;
|
||
- ожидаемые неуспешные исходы определены текущим доменом независимо от способа их представления;
|
||
- реализация и интеграции используют только исходы, объявленные доменным контрактом;
|
||
- ошибки источников и зависимых доменов не пересекают публичную границу в исходной форме;
|
||
- runtime-идентификация предоставлена только при наличии реального потребителя и совместима с его средой;
|
||
- роль слоя соответствует ответственности;
|
||
- публичный API минимален и используется всеми внешними потребителями;
|
||
- зависимости разрешены и не образуют модульных циклов;
|
||
- группа и сегменты не подменяют модульную границу;
|
||
- вложенные модули владеют отдельными подответственностями;
|
||
- внутренний код реализует ответственность ближайшего модуля;
|
||
- framework-компоненты имеют одноуровневую файловую организацию с единственным допустимым исключением для главного файла в корне;
|
||
- корень и сегменты соответствуют своим назначениям;
|
||
- состояние и ресурсы имеют владельца и корректную область жизни;
|
||
- физическая структура однозначно выражает принятое решение;
|
||
- применимые автоматические проверки и архитектурное ревью пройдены.
|