chore: границы доменов

This commit is contained in:
2026-08-10 14:42:29 +03:00
parent 1ba664f445
commit dd0448c91d
45 changed files with 452 additions and 3398 deletions

View File

@@ -12,6 +12,16 @@
Результат или поведение приложения, за которое отвечает один модуль-владелец. Ответственность является самостоятельной, когда ей нужны собственный публичный контракт, зависимости, состояние или область жизни, а не только внутренняя роль в работе другого модуля. Наличие у framework-сущности props, импортов, локального состояния или lifecycle-кода само по себе не создаёт самостоятельную ответственность.
### Доменный сценарий
Продуктово значимое поведение, сформулированное в предметных терминах и приводящее к предметному результату. Его владелец определяет модели, правила, переходы, продуктовое состояние, смысл операций с продуктовыми данными, допустимые исходы и доменный UI. Количество потребителей, текущая страница и технический механизм выполнения не меняют принадлежность сценария.
Техническая возможность, которую сценарий получает через публичный API другого модуля, сохраняет собственного владельца. Например, `infra` может владеть HTTP-транспортом или доставкой телеметрии, но смысл продуктовой операции и доменного события остаётся у доменного сценария.
### Доменный UI
UI-код, чьи данные, действия, состояния или исходы выражены в терминах одного домена и представляют либо запускают его сценарий. Доменный UI является частью реализации доменной ответственности даже тогда, когда используется только одной страницей. Универсальные визуальные элементы принадлежат `ui`, а размещение и связывание готовых доменных API в страницу или экран принадлежит `compositions`.
### Владелец
Модуль, который определяет публичный API ответственности, её зависимости, состояние, область жизни и внутреннюю реализацию. Каждый файл принадлежит ближайшей модульной границе и реализует ответственность этого модуля. Место выполнения кода или вид framework-сущности не переносит владение.
@@ -30,6 +40,10 @@
Минимальная самостоятельная архитектурная единица SLM. Модуль владеет одной связной ответственностью, имеет публичный API и физически размещается в отдельной папке.
### Домен
Специализированный модуль слоя `domains`, владеющий одной связной предметной ответственностью и её сценариями. Домен самостоятельно определяет публичный доменный контракт, ожидаемые неуспешные исходы, продуктовое состояние, доменный UI и адаптацию внешних данных и ошибок. Он остаётся обычным узлом модульного графа, подчиняется всем правилам модулей и не создаёт дополнительный контейнерный уровень.
### Сегмент
Необязательная внутренняя часть одного модуля, группирующая его содержимое по назначению. Сегмент не является владельцем, публичным API или границей зависимостей.
@@ -46,6 +60,10 @@
Единый логический контракт внешнего доступа к модулю. Он скрывает внутреннюю реализацию и физически представлен обязательным фасетом `index` и только необходимыми фасетами `client`, `browser` и `server`.
### Доменный контракт
Принадлежащая домену предметная форма его публичного API: принимаемые значения, возвращаемые модели и результаты, события, доступные потребителям состояния и ожидаемые неуспешные исходы. Доменный контракт определяется смыслом сценариев и не выводится из DTO, схемы, SDK или типов источника данных.
### Фасет
Объявленная публичная точка входа модуля, открывающая часть его единого API для определённой среды выполнения. Импорт фасета не является глубоким импортом; любой другой внешний путь внутрь модуля остаётся внутренним.
@@ -54,6 +72,38 @@
Импорт или реэкспорт внутреннего пути чужого модуля, который не объявлен его публичным фасетом.
## Граница источника данных
### Контракт источника
Техническая форма обмена с внешним сервисом, SDK, storage или другим источником данных. К ней относятся request и response DTO, source-specific enum, nullable semantics, статусы, payload и типы ошибок. Контракт источника не является доменным контрактом даже при полном структурном совпадении.
### DTO
Значение или тип контракта источника, предназначенный для передачи данных через техническую границу. DTO допускается во внутреннем интеграционном коде домена, но не используется как публичная модель, продуктовое состояние или значение доменного UI.
### Mapper
Один из возможных внутренних механизмов адаптации источника: функция или связный набор функций, преобразующий контракт источника в доменный контракт либо доменное значение в контракт запроса. Адаптация принадлежит доменному владельцу, но SLM не требует использовать mapper как конкретный паттерн, имя или файловую единицу.
## Доменные ошибки
### Доменная ошибка
Ожидаемый неуспешный исход доменного сценария, смысл и публичный контракт которого определены текущим доменом. Способ представления и передачи такого исхода, включая exception, `Result`, union или другую форму, SLM не устанавливает.
Доменная ошибка не является технической ошибкой источника. Чужой тип ошибки, source code, message, transport status, raw payload, `cause` и диагностические данные не входят в доменный контракт в исходной форме.
Реализация домена создаёт только исходы, объявленные доменным контрактом. Интеграционный или framework-код использует эту декларацию и не становится отдельным владельцем ошибок.
### Runtime-идентификация доменной ошибки
Публичная capability, позволяющая реальному потребителю отличить доменную ошибку от другого runtime-значения. Она может быть реализована constructor-ом, marker-ом, guard-ом, parser-ом, schema или иным способом, подходящим среде потребителя. SLM не требует такую capability для каждого домена и не устанавливает её форму.
### Неожиданный дефект
Неуспешное выполнение, которое не объявлено ожидаемым исходом доменного сценария и обрабатывается согласно общей политике приложения. Способ доставки и диагностики дефекта SLM не устанавливает, но техническая ошибка чужого источника не становится частью публичного API домена в исходной форме.
## Зависимости
### Зависимость

View File

@@ -14,6 +14,10 @@
| Вопрос | Что зафиксировать |
|---|---|
| Ответственность | Какой результат или поведение изменяется как единое целое |
| Доменный сценарий | Какой модуль `domains` владеет предметным результатом и через какой API его используют внешние потребители, включая композиции |
| Доменный контракт | Какие входы, модели и результаты определены предметным смыслом независимо от источника данных |
| Доменные ошибки | Какие ожидаемые неуспешные исходы определяет домен и где проходит граница с programming defects |
| Граница источника | Какие внешние контракты получает домен и как они адаптируются к предметному смыслу |
| Владелец | Какой модуль определяет контракт и внутреннюю реализацию |
| Слой | Какой архитектурной роли соответствует ответственность |
| Потребители | Кому действительно нужен публичный API |
@@ -30,6 +34,12 @@
- одна ли связная ответственность находится внутри модульной границы;
- есть ли у каждой самостоятельной ответственности ровно один ближайший владелец;
- принадлежит ли каждый доменный сценарий модулю слоя `domains`;
- находятся ли бизнес-правила, продуктовое состояние, смысл операций с предметными данными, предметные исходы и доменный UI у владельца сценария;
- только ли использует и компонует модуль `compositions` готовые публичные API доменов, не определяя и не дополняя их сценарии;
- не размещён ли сценарий временно в композиции только потому, что подходящий доменный модуль ещё не создан;
- не скрывает ли связывание нескольких доменных API новый порядок, условие, общий предметный результат или политику ошибок;
- обслуживает ли прямое использование `infra` собственную техническую потребность композиции, а не продуктовую операцию или доступ к предметным данным;
- соответствует ли ответственность роли выбранного слоя;
- владеет ли вложенный модуль отдельно сформулированной подответственностью;
- не стали ли группа или сегмент скрытыми владельцами;
@@ -43,6 +53,33 @@
Окончательные смысловые требования имеют класс `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`. Сопоставление задаётся локальной конфигурацией и не меняет смысл сущностей.
@@ -107,6 +144,16 @@ File-level проверка не заменяет модульную: два м
Изменение соответствует SLM, когда одновременно выполнены условия:
- ответственность и единственный ближайший владелец определены;
- каждый доменный сценарий целиком принадлежит модулю `domains` и используется композициями только через его публичный API;
- отсутствие готового доменного модуля не привело к временной реализации сценария в `compositions`;
- междоменная координация с собственным продуктовым результатом получила доменного владельца;
- каждый домен остаётся специализированным модулем и самостоятельно объявляет предметный контракт;
- публичный API домена не содержит DTO, source types или чужие error contracts;
- все внешние значения адаптированы к доменному контракту до использования в правилах, состоянии или доменном UI;
- ожидаемые неуспешные исходы определены текущим доменом независимо от способа их представления;
- реализация и интеграции используют только исходы, объявленные доменным контрактом;
- ошибки источников и зависимых доменов не пересекают публичную границу в исходной форме;
- runtime-идентификация предоставлена только при наличии реального потребителя и совместима с его средой;
- роль слоя соответствует ответственности;
- публичный API минимален и используется всеми внешними потребителями;
- зависимости разрешены и не образуют модульных циклов;