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

@@ -1,6 +1,6 @@
# Архитектура SLM
SLM описывает владение ответственностями внутри одного фронтенд-приложения. Слой определяет роль кода, группа классифицирует модули, модуль владеет ответственностью, а сегмент организует реализацию владельца.
SLM описывает владение ответственностями внутри одного фронтенд-приложения. Слой определяет роль кода, группа классифицирует модули, модуль владеет ответственностью, домен специализирует модуль для предметной ответственности, а сегмент организует реализацию владельца.
## Владение как основа
@@ -16,6 +16,8 @@ SLM описывает владение ответственностями вн
Каждый файл принадлежит ближайшей модульной границе и реализует ответственность этого модуля. Место выполнения или вид кода не меняют владельца.
Доменный сценарий всегда получает владельца в слое `domains`. Его бизнес-правила, продуктовое состояние, операции с предметными данными, доменный UI и обслуживающие framework-механизмы остаются внутри доменной границы. Модуль `compositions` использует и компонует готовый публичный API домена, но не реализует сценарий вместо него. Если подходящего доменного модуля ещё нет, его отсутствие не является основанием временно разместить сценарий в композиции. Эта граница закреплена правилом [`SLM-DOMAIN-R022`](../rules/registry.md#slm-domain-r022) и подробно описана в разделе [Домены](./domains.md).
## Структурная модель
```text
@@ -37,8 +39,11 @@ SLM root
| Слой | Классифицирует код по архитектурной роли | Нет |
| Группа | Навигационно классифицирует модули внутри слоя | Нет |
| Модуль | Владеет одной самостоятельной ответственностью | Да |
| Домен | Специализирует модуль для предметной ответственности, контракта и ошибок | Да, как модуль |
| Сегмент | Организует внутренности одного модуля | Нет |
Домен не добавляет уровень в структурное дерево: в слое `domains` он занимает место обычного модуля и отличается дополнительными инвариантами.
Вложенный модуль является обычным модулем, размещённым внутри родительского. Он владеет отдельно сформулированной подответственностью и создаёт следующий рекурсивный структурный уровень.
Слой и группа находятся снаружи модульной границы. Сегмент находится внутри неё. Framework-компоненты, Providers, Guards, hooks, stores, services и другой внутренний код не являются архитектурными сущностями SLM и принадлежат ближайшему модулю.
@@ -67,7 +72,9 @@ SLM root
6. Спроектировать публичный API и допустимые [зависимости](./dependencies.md).
7. При необходимости классифицировать модули [группами](./groups.md), организовать внутренний код [сегментами](./segments.md) и выбрать физические пути.
Например, Header сам определяет расположение шапки, отображение навигации и состояние мобильного меню. Текущего пользователя он получает от модуля `auth`, а `Button` и `Avatar` — от модулей `ui`. Если удалить Header, авторизация и UI-компоненты останутся нужны приложению, поэтому Header использует их, но не владеет ими.
Для домена после определения сценариев сначала проектируются [доменный контракт и ожидаемые неуспешные исходы](./domains.md#порядок-создания-домена), и только затем выбираются источники данных и механизм их адаптации.
Например, Header сам определяет расположение шапки, отображение навигации и состояние мобильного меню. Текущего пользователя он получает от модуля `auth`, а `Button` и `Avatar` — от модулей `ui`. Если удалить Header, авторизация и UI-компоненты останутся нужны приложению, поэтому Header использует их, но не владеет ими. Header может сообщить через `infra` о показе собственной раскладки, но получение пользователя и события сценария авторизации остаются ответственностью `auth`.
Если ответственность или владелец не определены, файловая структура не может исправить архитектурную неопределённость.
@@ -96,7 +103,8 @@ SLM применяется внутри **SLM root** — границы стру
- общий ацикличный граф модулей;
- назначение групп и сегментов;
- внутреннюю глубину framework-компонентных единиц;
- владение состоянием и жизненным циклом ресурсов.
- владение состоянием и жизненным циклом ресурсов;
- независимость доменных контрактов и ошибок от внешних источников.
SLM не задаёт обязательный поток данных, конкретный framework, фиксированные имена сегментов, правила монорепозиториев или полный файловый стайлгайд.

View File

@@ -48,6 +48,37 @@ import { Button } from '@/ui/button/button'
Разрешённое направление не переносит владение. Если модуль `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. Если эта связь задаёт обязательный порядок, продуктовые условия, общий предметный результат или политику ошибок между доменами, она является отдельным доменным сценарием, а не внутренней логикой композиции.
## Зависимости внутри слоя
Модули одного слоя могут зависеть друг от друга в любом направлении при одновременном выполнении двух условий:
@@ -97,6 +128,11 @@ Lint-проверка модульных циклов должна:
## Связанные правила
- [`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)

View File

@@ -0,0 +1,220 @@
# Домены
Домен является специализированным SLM-модулем слоя `domains`. Он владеет одной связной предметной ответственностью, её сценариями, публичным контрактом, ошибками, состоянием, доменным UI и интеграцией с источниками данных.
Домен не создаёт новый структурный уровень над модулями. Он остаётся модулем-владельцем, отдельным узлом графа зависимостей и подчиняется всем общим [правилам модулей](./modules.md). Дополнительные правила домена защищают независимость предметной модели от внешних сервисов и технических контрактов.
## Место в структурной модели
```text
SLM root
└── domains # Слой
└── orders # Домен, специализированный модуль
├── index.ts # Публичный контракт
├── client.ts # Доменный UI при необходимости
├── ... # Внутренняя реализация
└── modules/ # Вложенные модули при необходимости
```
Как обычный модуль, домен:
- имеет одну физическую модульную границу;
- предоставляет единый логический публичный API через фасеты;
- владеет состоянием и жизненным циклом своей ответственности;
- использует другие модули только через их публичные API;
- может содержать сегменты и вложенные модули;
- участвует в общем ацикличном модульном графе.
Корень домена не является package-контейнером или группой модулей. Сегменты, функции преобразования, компоненты и source-specific код остаются внутренней реализацией ближайшего доменного модуля и не получают самостоятельного API только из-за технической роли.
## Что принадлежит домену
Домен полностью определяет предметный смысл ответственности:
- принимаемые команды, параметры и значения;
- возвращаемые модели и результаты;
- бизнес-правила, переходы и допустимые состояния;
- ожидаемые неуспешные исходы сценариев;
- смысл операций с продуктовыми данными;
- адаптацию внешних данных к доменному контракту;
- интерпретацию ошибок источника;
- состояние, доменный UI и framework-механизмы сценариев.
Техническая возможность сохраняет собственного владельца. Например, `infra` может владеть HTTP-транспортом, SDK runtime или storage, но домен определяет, зачем выполняется операция, какие данные она принимает и возвращает и какой предметный результат получает потребитель.
## Доменный контракт
Доменный контракт описывает ответственность в терминах продукта, а не источника данных. Он включает публичные входы, модели, результаты, события, доступные потребителям формы состояния и ожидаемые неуспешные исходы.
Контракт объявляется самим доменом. Даже если форма внешнего DTO временно совпадает с нужной моделью, домен создаёт собственную форму. Совпадение полей не передаёт источнику владение предметным контрактом.
```ts
// Один из возможных способов объявить доменный контракт.
export type Order = Readonly<{
id: OrderId
state: OrderState
total: Money
}>
export type GetOrderInput = Readonly<{
orderId: OrderId
}>
```
Недопустимо строить публичный контракт из типов источника:
```ts
// DTO источника стал доменной моделью.
export type Order = OrdersApiDto
// Внешний вызов стал публичным сценарием домена.
export const getOrder = ordersSdk.getOrder
// Форма результата выводится из SDK.
export type GetOrderResult = Awaited<ReturnType<typeof ordersSdk.getOrder>>
```
Источник может измениться, не меняя доменный контракт. Если новая форма источника не позволяет выполнить уже объявленный сценарий, меняется интеграция или принимается отдельное продуктовое решение, но контракт не подгоняется автоматически под DTO.
## Граница внешних данных
DTO, request types, response types и ошибки источника допускаются только во внутреннем интеграционном коде домена. До использования в правилах, состоянии, доменном UI или публичном результате внешнее значение адаптируется к доменному контракту.
Адаптация принадлежит домену, потому что только он определяет целевой предметный смысл. Она может быть реализована mapper-функцией, adapter-объектом, parser-ом или другим внутренним механизмом. SLM ограничивает результат пересечения границы, а не имя файла, функции или выбранный паттерн. Общий технический клиент при этом может принадлежать `infra`.
```ts
// Mapper является одним из возможных механизмов адаптации.
type OrderDto = Awaited<ReturnType<typeof ordersSdk.getOrder>>
const mapOrderDto = (dto: OrderDto): Order => ({
id: dto.order_id,
state: mapOrderState(dto.status),
total: mapMoney(dto.total),
})
```
Входящие и исходящие направления симметричны:
- response источника адаптируется к доменной модели;
- доменная команда адаптируется к контракту запроса источника;
- source-specific enum, nullable semantics и служебные поля не становятся частью доменной модели автоматически;
- невалидный ответ источника получает смысл, определённый доменом;
- DTO не сохраняется как продуктовое состояние и не передаётся доменному UI.
Внутренний механизм может быть близок к identity-преобразованию, но публичная граница остаётся независимой. Запрещены прямой реэкспорт, type alias, `Pick`, `Omit`, `ReturnType` или другое выведение публичной доменной модели из source type.
## Доменные ошибки
Домен самостоятельно определяет, какие неуспешные исходы его сценариев являются ожидаемыми и какой публичный контракт получают потребители. Ошибка источника не становится доменной ошибкой только потому, что была получена во время выполнения сценария.
SLM не устанавливает способ представления или передачи доменных ошибок. Проект может использовать exception, `Result`, discriminated union, отдельные типы сценариев или другую форму. Архитектурным инвариантом остаётся владелец смысла: потребитель зависит только от контракта текущего домена.
### Декларация и реализация
Доменная декларация определяет допустимые неуспешные исходы до реализации сценария и подключения источника. Реализация домена конструирует и возвращает только объявленные исходы, а интеграционный код преобразует ошибки источника в уже существующий доменный контракт.
Новый ожидаемый исход сначала добавляется в декларацию домена и только затем используется реализацией. Реализация, mapper, adapter или framework-механизм не объявляют собственные ошибки параллельно доменному контракту.
Декларация и реализация являются ролями внутри одного доменного модуля, а не новыми структурными сущностями, обязательными сегментами или именами файлов.
Публичный контракт ожидаемой ошибки не включает чужую ошибку в исходной форме:
- тип или экземпляр ошибки SDK;
- код и message внешнего сервиса;
- HTTP status или другой транспортный status источника;
- raw response payload;
- `cause`, stack trace или другие source-specific диагностические данные.
Домен может объявить собственные идентификаторы, данные для обработки и представления ошибки. Их форма, общий каталог, casing, имена полей и группировка по сценариям являются проектной policy, а не правилами SLM. Если проект выбирает машинные коды или единый union, соответствующие соглашения закрепляются в style guide и могут проверяться отдельным lint-правилом.
В примерах этой документации доменные коды ошибок записываются в `SCREAMING_SNAKE_CASE` по соглашению команды. Это соглашение определяет оформление примеров, но не является архитектурным требованием SLM.
```ts
// Публичная декларация домена Orders.
// Форма является project policy, а не обязательной формой SLM.
export type OrdersError =
| Readonly<{
code: 'ORDER_NOT_FOUND'
}>
| Readonly<{
code: 'ORDER_CANNOT_BE_CANCELLED'
payload: Readonly<{
currentState: OrderState
}>
}>
```
```ts
// Внутренняя реализация использует декларацию домена.
const createOrderNotFoundError = (): OrdersError => ({
code: 'ORDER_NOT_FOUND',
})
```
### Runtime-идентификация
SLM не требует универсального constructor, base class, marker, guard или parser для доменных ошибок. Домен предоставляет runtime-механизм идентификации только тогда, когда он необходим реальному потребителю и совместим с его средой выполнения.
| Условия использования | Возможный механизм |
|---|---|
| Типизированный результат внутри одного TypeScript-графа | Discriminated result без дополнительного guard |
| Ошибка поступает как `unknown` через `catch` | Domain guard или class с `instanceof` |
| Ошибка пересекает JSON, SSR, RSC, worker или другую serialization boundary | Сериализуемый discriminant и runtime parser |
| Значение приходит из недоверенной среды | Schema validation |
| Ошибка не покидает доменную реализацию | Публичный runtime-механизм не нужен |
Constructor или factory обычно остаётся внутренней частью реализации: внешние потребители распознают и обрабатывают доменные ошибки, но не создают их. Если потребителю действительно требуется runtime-идентификация, домен может открыть минимальную capability, например `isOrdersError` или `parseOrdersError`, через подходящий публичный фасет.
Механизм идентификации не переносит владение ошибкой. Общий product-agnostic marker или guard может принадлежать `shared`, но перечень ожидаемых исходов и domain-specific проверка остаются контрактом соответствующего домена.
## Преобразование ошибок источника
Домен интерпретирует ошибку источника в контексте текущего сценария. Одинаковый HTTP status может означать отсутствие предметного объекта, конфликт состояния, ошибку доступа или технический сбой, поэтому транспортный признак не передаётся потребителю как готовый доменный исход.
Ошибка источника, влияющая на публичный результат, преобразуется в собственный ожидаемый исход домена либо в неожиданный дефект согласно общей политике приложения. Raw ошибка может использоваться для внутренней диагностики и телеметрии, но не становится публичным контрактом домена.
Если домен использует API другого домена, он также не возвращает чужой error contract от своего имени. Неуспешный исход зависимого домена интерпретируется в терминах текущего сценария.
## Порядок создания домена
Интеграция с источником начинается только после определения предметной границы:
1. Сформулировать ответственность и сценарии домена.
2. Объявить доменный контракт входов, моделей и результатов.
3. Определить ожидаемые неуспешные исходы и их публичный контракт.
4. Определить границу между ожидаемым исходом и programming defect.
5. Только после этого определить внешние источники и технические зависимости.
6. Реализовать адаптацию запросов, ответов и ошибок выбранным внутренним механизмом.
7. Проверить сценарии и адаптацию источников независимо друг от друга.
8. Убедиться, что публичный API транзитивно не содержит типов источника.
Если контракт или семантика ошибок ещё не определены, запрос к реальному источнику не считается допустимым временным началом домена. Сначала создаётся предметная граница, затем к ней адаптируется источник.
## Проверка границы
При ревью домена проверяется:
- можно ли описать публичный контракт без упоминания API, endpoint, SDK или DTO;
- объявлены ли модели, результаты и ожидаемые неуспешные исходы самим доменом;
- использует ли реализация только исходы, объявленные доменной декларацией;
- не навязывает ли контракт источника форму доменной модели;
- адаптируются ли внешние значения до использования в правилах, состоянии и доменном UI;
- отсутствуют ли source types в публичных фасетах, состоянии и доменном UI;
- интерпретируются ли ошибки источника и зависимых доменов в терминах текущего сценария;
- не протекают ли наружу чужие error types, codes, messages, transport statuses, raw payload или cause;
- не маскируется ли programming defect под ожидаемый доменный исход;
- нужен ли реальным потребителям runtime-механизм идентификации и совместим ли он с их средой;
- не экспортирует ли домен constructor, guard, parser или schema без реального потребителя;
- не объявлена ли выбранная форма error contract универсальным требованием SLM.
## Связанные правила
- [`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-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)

View File

@@ -25,11 +25,13 @@ compositions/
├── layouts/ # Группа
│ └── main/ # Модуль
└── widgets/ # Группа
└── cart-summary/ # Модуль
└── dashboard/ # Модуль, компонующий несколько доменных API
```
Названия `pages`, `layouts` и `widgets` показывают один из вариантов навигации и не создают дополнительные слои или обязательные роли.
Модули `catalog`, `profile` и `dashboard` в примере отвечают только за представление и связывание готовых публичных API. Сценарии каталога, профиля и других предметных областей остаются в соответствующих модулях `domains`.
## Ограничения группы
Группа:
@@ -51,14 +53,14 @@ Barrel-файл, открывающий несколько модулей гру
Код импортирует конкретный модуль:
```ts
import { CartSummary } from '@/compositions/widgets/cart-summary'
import { Dashboard } from '@/compositions/widgets/dashboard'
```
Группа не становится промежуточной точкой доступа:
```ts
// Недопустимый API группы
import { CartSummary } from '@/compositions/widgets'
import { Dashboard } from '@/compositions/widgets'
```
Полные правила графа находятся в разделе [Зависимости](./dependencies.md).
@@ -76,6 +78,7 @@ import { CartSummary } from '@/compositions/widgets'
## Связанные правила
- [`SLM-DOMAIN-R022`](../rules/registry.md#slm-domain-r022)
- [`SLM-GROUP-R007`](../rules/registry.md#slm-group-r007)
- [`SLM-MODULE-R011`](../rules/registry.md#slm-module-r011)
- [`SLM-DEPENDENCY-A005`](../rules/registry.md#slm-dependency-a005)

View File

@@ -11,8 +11,8 @@ SLM определяет шесть ролей:
| Слой | Роль |
|---|---|
| `app` | Связь приложения с фреймворком: запуск, маршруты, преобразование внешних входных данных и подключение готовых публичных API |
| `compositions` | Сборка продуктового интерфейса: страницы, макеты, экраны, виджеты и другие композиции |
| `domains` | Предметные модели, правила, сценарии и продуктовое состояние |
| `compositions` | Представление и связывание готовых публичных API в страницы, макеты, экраны, виджеты и другие продуктовые композиции |
| `domains` | Полная реализация предметных ответственностей и сценариев, включая их модели, правила, состояние, операции с продуктовыми данными и доменный UI |
| `infra` | Технические сервисы и возможности приложения без собственной предметной модели |
| `ui` | Универсальные интерфейсные модули без зависимости от конкретной продуктовой композиции |
| `shared` | Детерминированный фундамент без знания о продукте, изменяемого состояния и ввода-вывода |
@@ -27,15 +27,19 @@ SLM определяет шесть ролей:
### Compositions
`compositions` содержит владельцев продуктовых композиций: страниц, макетов, экранов, виджетов, результатов маршрутов и интерфейса, объединяющего несколько ответственностей.
`compositions` содержит владельцев представления продуктовых композиций: страниц, макетов, экранов, виджетов, результатов маршрутов и интерфейса, объединяющего несколько готовых модульных возможностей. Композиция размещает и связывает публичные API доменных, инфраструктурных и UI-модулей, но не присваивает их ответственность.
Модуль композиции может владеть структурой страницы, расположением частей интерфейса и состоянием, смысл которого существует только внутри этой композиции. Он не определяет, не реализует, не расширяет и не замещает доменный сценарий. Количество потребителей и использование сценария только на одной странице не меняют эту границу.
Конкретная организация слоя определяется продуктом и фреймворком. Названия `pages`, `layouts`, `screens` и `widgets` могут использоваться как [группы](./groups.md), но не являются дополнительными слоями и не задают направление импортов.
### Domains
`domains` содержит модули-владельцы предметных ответственностей: моделей, правил, сценариев и продуктового состояния.
`domains` содержит модули-владельцы полных предметных ответственностей и доменных сценариев. Доменный модуль владеет не только моделями и бизнес-правилами, но и продуктовым состоянием, смыслом операций с продуктовыми данными, предметными исходами, доменным UI и framework-механизмами, которые обслуживают сценарий.
Доменный модуль является обычным SLM-модулем. Ему не требуется отдельная архитектурная форма только потому, что он находится в `domains`.
Домен является вертикальным владельцем ответственности, а не только каталогом независимой от интерфейса бизнес-логики. Внутри него могут находиться компоненты, Providers, hooks, stores, services и другой код, если он реализует принадлежащий домену результат. Универсальные визуальные элементы домен получает из `ui`, а технические возможности без предметной модели — из `infra`.
Домен является специализированным SLM-модулем: он сохраняет обычную модульную форму и получает дополнительные требования к предметному контракту, адаптации источников и ошибкам. Полная модель описана в разделе [Домены](./domains.md).
### Infra
@@ -53,6 +57,28 @@ SLM определяет шесть ролей:
В `shared` могут находиться обычные модули и небольшие немодульные ресурсы: чистые функции, общие типы, стили, декларативная конфигурация и статические файлы.
## Граница доменов и композиций
Граница определяется смыслом поведения, а не местом его вызова или отображения:
| Код определяет | Слой-владелец |
|---|---|
| Продуктовую операцию, правило, переход, предметный исход или состояние | `domains` |
| Получение или изменение продуктовых данных, параметры операции и смысл её ошибок | `domains` |
| Форму, список, карточку, Guard или другой UI, выраженный в терминах одного домена | `domains` |
| Расположение и связывание готовых публичных API на странице или экране | `compositions` |
| Состояние панели, секции или раскладки, имеющее смысл только в одной композиции | `compositions` |
| Универсальный визуальный элемент без предметного смысла | `ui` |
| HTTP-транспорт, тему, локализацию или техническую доставку телеметрии | `infra` |
Если для нового доменного сценария ещё нет подходящего владельца, сначала выбирается существующий или создаётся новый модуль в `domains`. Реализация сценария в `compositions` с намерением перенести её позже не является допустимым промежуточным архитектурным решением.
Композиция может показать рядом несколько доменных возможностей и вызвать их готовые публичные операции. Если координация определяет продуктовый порядок действий, условия, общий предметный результат, политику ошибок или компенсацию между доменами, такая координация сама является доменным сценарием и требует владельца в `domains`.
Разрешённая зависимость `compositions` от `infra` сохраняется. Композиция может использовать тему, локализацию, доставку метрик и другие технические возможности для собственной ответственности. Эта связь не разрешает получать или изменять продуктовые данные через HTTP-клиент, SDK или storage непосредственно из композиции: техническим механизмом владеет `infra`, а смысл такой операции и её продуктовый результат принадлежат домену.
Смысл события метрики принадлежит владельцу наблюдаемого поведения. Домен определяет событие доменного сценария, композиция — событие показа или взаимодействия со своей раскладкой, `app` — событие запуска или маршрутизации, а `infra` отвечает за техническую доставку телеметрии.
## Направление зависимостей
Слой ограничивает только межслойное направление. Модули одного слоя могут зависеть друг от друга через публичные API, если общий граф остаётся ацикличным.
@@ -77,5 +103,10 @@ SLM определяет шесть ролей:
- [`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-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-GROUP-R007`](../rules/registry.md#slm-group-r007)
- [`SLM-MODULE-R011`](../rules/registry.md#slm-module-r011)

View File

@@ -19,6 +19,8 @@
Props, импорты, Context, локальное состояние, lifecycle-код или количество файлов сами по себе не доказывают самостоятельность ответственности. Если ответственность самостоятельна, она получает ровно один модуль-владелец.
Для доменного сценария выбор владельца дополнительно ограничен ролью слоя: такой сценарий принадлежит модулю `domains`. Этот модуль является [доменом](./domains.md) и дополнительно владеет предметным контрактом, ожидаемыми неуспешными исходами и адаптацией источников. Модуль `compositions` может владеть представлением страницы или экрана и использовать готовый доменный API, но не становится владельцем сценария из-за места вызова, единственного потребителя или отсутствия уже созданного доменного модуля. Подробная граница описана в разделе [Слои](./layers.md#граница-доменов-и-композиций).
Размер не определяет модуль. Модуль может состоять из одного файла реализации, а большая папка может оставаться частью другого владельца.
## Ближайшая граница
@@ -232,6 +234,11 @@ Framework-компонент не превращается в архитекту
- [`SLM-MODULE-A004`](../rules/registry.md#slm-module-a004)
- [`SLM-MODULE-A014`](../rules/registry.md#slm-module-a014)
- [`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-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)