mirror of
https://github.com/gromlab-ru/slm-design.git
synced 2026-08-22 07:30:16 +03:00
feat: доменный API
This commit is contained in:
@@ -1,109 +1,152 @@
|
||||
# Зависимости Level 2
|
||||
|
||||
> Уточнение графа зависимостей внутри и между доменными границами.
|
||||
> Уточнение статического import-графа и runtime injection graph внутри и между доменными границами.
|
||||
|
||||
## Связанные правила
|
||||
|
||||
- [`SLM-L2-BUSINESS-A007`](../rules/level-2.md#slm-l2-business-a007)
|
||||
- [`SLM-L2-API-A007`](../rules/level-2.md#slm-l2-api-a007)
|
||||
- [`SLM-L2-DEPENDENCY-A012`](../rules/level-2.md#slm-l2-dependency-a012)
|
||||
- [`SLM-L2-ENVIRONMENT-A013`](../rules/level-2.md#slm-l2-environment-a013)
|
||||
- [`SLM-L2-DOMAIN-A026`](../rules/level-2.md#slm-l2-domain-a026)
|
||||
- [`SLM-L2-BUSINESS-A019`](../rules/level-2.md#slm-l2-business-a019)
|
||||
- [`SLM-L2-API-A019`](../rules/level-2.md#slm-l2-api-a019)
|
||||
- [`SLM-L2-ADAPTER-R021`](../rules/level-2.md#slm-l2-adapter-r021)
|
||||
- [`SLM-L2-BUSINESS-A022`](../rules/level-2.md#slm-l2-business-a022)
|
||||
- [`SLM-L2-API-A022`](../rules/level-2.md#slm-l2-api-a022)
|
||||
- [`SLM-L2-PORT-R027`](../rules/level-2.md#slm-l2-port-r027)
|
||||
- [`SLM-L2-ASSEMBLY-R030`](../rules/level-2.md#slm-l2-assembly-r030)
|
||||
- [`SLM-L1-DEPENDENCY-A005`](../rules/level-1.md#slm-l1-dependency-a005)
|
||||
|
||||
## Матрица внутри пакета
|
||||
## Статическая матрица внутри пакета
|
||||
|
||||
| Исходный модуль | Допустимые зависимости |
|
||||
|---|---|
|
||||
| `business` | Собственные файлы, объявленные environment-neutral ресурсы `shared`, business-safe packages, type-only контракты и `business/runtime` других доменов |
|
||||
| Adapter module | Type-only barrel собственного `business`, `infra`, concrete technical runtime, `shared` |
|
||||
| Assembly | Type-only barrel, `factory` и при необходимости `runtime` собственного `business`, публичные adapters своего домена, type-only API других доменов, `shared` |
|
||||
| Framework binding module | Type-only barrel и `runtime` собственного `business`, публичные framework-модули своего домена, framework/state/query runtime, `ui`, `shared` |
|
||||
| Место сборки графа | Assemblies либо `business/factory` и adapter-модули, `business/runtime`, framework-модули входящих в граф доменов, а также разрешённые матрицей `infra`, `ui` и `shared` |
|
||||
| `api` | Собственные файлы, объявленные environment-neutral ресурсы `shared`, API-safe packages, type-only Domain API и `api/runtime` других доменов |
|
||||
| Adapter module | `api/ports` своего домена, `infra`, concrete provider runtime, `shared` |
|
||||
| Assembly | `api`, `api/ports`, `api/factory`, при необходимости `api/runtime` своего домена, публичные adapters своего домена, type-only Domain API других доменов, `shared` |
|
||||
| Framework binding module | `api` и `api/runtime` своего домена, публичные framework modules своего домена, framework/state/query runtime, `ui`, `shared` |
|
||||
| Graph owner | Assemblies и framework modules входящих в граф доменов, а также разрешённые матрицей `infra`, `ui` и `shared` |
|
||||
|
||||
`business` не достигает adapters, assemblies, framework-модулей, product SDK, storage, state/query manager, API браузера или Node.js. Проверяется весь транзитивный import-граф публичных фасетов.
|
||||
Модуль `api` не достигает adapters, assemblies, framework modules, product SDK, storage, state/query manager, DOM, Node.js API или других environment-specific capabilities. Проверяется весь транзитивный executable и type graph его фасетов.
|
||||
|
||||
Adapter module импортирует из собственного `business` только типы технических зависимостей. Он не импортирует `business/factory`, потому что не собирает API. Публичный deterministic runtime обычно также не нужен adapter: внешний результат возвращается business для проверки и error mapping.
|
||||
Adapter импортирует contract только через `api/ports`. Он не импортирует factory и consumer-facing runtime, потому что не создаёт API и не выбирает публичный domain outcome.
|
||||
|
||||
Assembly не содержит inline adapters. Она импортирует production implementations через публичные API конкретных модулей `adapters/*`.
|
||||
Assembly импортирует только adapters собственного домена. Production graph owner не импортирует concrete adapters или `api/factory`: он вызывает готовые assembly builders.
|
||||
|
||||
## Междоменные импорты
|
||||
## Публичные фасеты
|
||||
|
||||
При связи, пересекающей границу пакета Level 2, разрешены два вида статического импорта из другого домена:
|
||||
```text
|
||||
api
|
||||
→ import type прикладных contracts
|
||||
|
||||
```ts
|
||||
import type { AuthSessionApi } from '@/domains/auth/business'
|
||||
import { isAuthError } from '@/domains/auth/business/runtime'
|
||||
api/ports
|
||||
→ import type adapters, assemblies и tests
|
||||
|
||||
api/factory
|
||||
→ runtime import assemblies и API tests
|
||||
|
||||
api/runtime
|
||||
→ runtime import реальных consumers
|
||||
```
|
||||
|
||||
Первый импорт предоставляет type-only контракт для runtime-инъекции. Второй предоставляет только публичную детерминированную функцию или значение. Оба импорта являются рёбрами общего DAG.
|
||||
Символьная type-проверка ports может быть строже обычного path allowlist. Проект объявляет, какие files и modules считаются adapters, assemblies и test boundaries.
|
||||
|
||||
## Междоменные статические импорты
|
||||
|
||||
Если связь пересекает границу пакета Level 2, разрешены:
|
||||
|
||||
```ts
|
||||
import type {
|
||||
AuthSessionApi,
|
||||
} from '@/domains/auth/api'
|
||||
|
||||
import {
|
||||
isAuthError,
|
||||
} from '@/domains/auth/api/runtime'
|
||||
```
|
||||
|
||||
Для доменного модуля Level 1 используется type-only импорт его обычного публичного API.
|
||||
|
||||
Запрещено импортировать из другого домена:
|
||||
|
||||
- `business/factory`;
|
||||
- готовый API instance или singleton;
|
||||
- `api/factory`;
|
||||
- `api/ports`;
|
||||
- готовый API singleton;
|
||||
- assembly;
|
||||
- adapter;
|
||||
- framework state, hook, context, Provider или component;
|
||||
- любой внутренний путь `business`.
|
||||
- любой внутренний путь `api`.
|
||||
|
||||
Такая модель действует и между двумя пакетами Level 2, и между модулем Level 1 и пакетом Level 2. Между двумя простыми доменными модулями продолжают действовать правила Level 1.
|
||||
Runtime-импорт `api/runtime` остаётся статическим ребром общего DAG. Если он создаёт цикл, границы доменов или владелец pure-функции пересматриваются.
|
||||
|
||||
## Детерминированный runtime
|
||||
## Runtime-инъекция cross-domain API
|
||||
|
||||
`business/runtime` закрывает случай общей продуктовой pure-функции, которую нельзя переносить в `shared` из-за знания о предметной области:
|
||||
|
||||
```ts
|
||||
import {
|
||||
normalizeAuthIdentifier,
|
||||
} from '@/domains/auth/business/runtime'
|
||||
```
|
||||
|
||||
Функция остаётся собственностью Auth, а зависимый домен получает явное runtime-ребро к её публичному контракту. Если связь создаёт цикл, границы доменов или владелец общего понятия пересматриваются.
|
||||
|
||||
Перенос в `shared` допустим только после устранения продуктового знания, а не как обход cross-domain правила.
|
||||
|
||||
## Runtime-инъекция API
|
||||
|
||||
Готовый API другого домена передаётся assembly или одноразовому месту сборки аргументом. Код зависимого домена не импортирует его runtime-фабрику или сборку:
|
||||
Готовый API другого домена передаётся assembly аргументом:
|
||||
|
||||
```text
|
||||
createAuthForRequest()
|
||||
createAuth()
|
||||
→ AuthSessionApi
|
||||
→ createUserForRequest({ auth })
|
||||
→ createUser({ auth })
|
||||
→ UserProfileApi
|
||||
```
|
||||
|
||||
Место сборки графа создаёт независимые домены раньше зависимых. Если сбой Auth становится результатом публичного сценария User, наружу выходит только собственная ошибка User. При exception-модели User может распознать ожидаемый Auth error через публичный guard из `auth/business/runtime`.
|
||||
User assembly передаёт `auth` своей factory. Она не импортирует runtime instance Auth.
|
||||
|
||||
Cross-domain API не превращается автоматически в local port. Bridge port нужен только при реальном translation contract. Structural copy чужого API скрывает owner и затрудняет обнаружение runtime-цикла.
|
||||
|
||||
## Runtime dependency graph
|
||||
|
||||
Статический DAG импортов не показывает все runtime edges, передаваемые аргументами. Architecture mapping объявляет либо review явно восстанавливает:
|
||||
|
||||
- assembly inputs;
|
||||
- создаваемые Domain API;
|
||||
- public APIs и construction points доменных модулей Level 1;
|
||||
- передаваемые factories dependencies;
|
||||
- callbacks и late-bound capabilities, пересекающие Level 2 boundary;
|
||||
- scope и multiplicity;
|
||||
- cleanup order.
|
||||
|
||||
Graph owner создаёт независимые APIs раньше зависимых и освобождает их в обратном порядке. Цикл `A API → B API → A API` запрещён, даже если одна сторона является модулем Level 1, а callback, lazy holder или local structural type сохраняет статически ацикличный import graph.
|
||||
|
||||
Lazy provider или registry не является автоматическим исключением. Для него требуется отдельный readiness, lifecycle и failure contract, а сам runtime edge остаётся частью graph review.
|
||||
|
||||
## Совместное применение Level 1 и Level 2
|
||||
|
||||
Один SLM root может постоянно содержать обе формы. Каждая предметная область объявляется либо модулем, либо пакетом, поэтому checker однозначно определяет применимые правила.
|
||||
Один SLM root может постоянно содержать обе формы. Между двумя доменными модулями Level 1 продолжают действовать обычные правила Level 1.
|
||||
|
||||
Runtime-взаимодействие с пакетом Level 2 собирается за пределами доменного кода. Если существующий модуль Level 1 должен принимать готовый API, его собственный публичный контракт явно предоставляет такую точку инъекции. Если это невозможно без скрытого singleton или обратного импорта, зависимый модуль рефакторится либо переводится на Level 2.
|
||||
Если хотя бы одна сторона является пакетом Level 2, runtime API создаёт внешний graph owner и передаёт assembly зависимого пакета Level 2 либо явной construction point/public callback зависимого модуля Level 1. Если у модуля Level 1 такой точки нет и связь невозможна без global singleton или обратного импорта, модуль рефакторится либо переводится на Level 2.
|
||||
|
||||
Runtime-значение из модуля Level 1, необходимое пакету Level 2, также передаётся внешним graph owner как API, callback или другой явно типизированный аргумент. Код пакета не импортирует executable API модуля Level 1 напрямую.
|
||||
Переход формы остаётся локальным для предметной ответственности, но change radius включает все входящие imports и composition roots выбранного домена.
|
||||
|
||||
## Framework-состояние
|
||||
## Framework state
|
||||
|
||||
Framework binding module использует framework API только своего доменного пакета:
|
||||
Framework binding использует framework API только своего доменного пакета:
|
||||
|
||||
```ts
|
||||
// Допустимо внутри domains/auth/react/login-form
|
||||
import { useAuthSession } from '@/domains/auth/react/session'
|
||||
|
||||
// Недопустимо внутри domains/user/react/profile
|
||||
import { useAuthSession } from '@/domains/auth/react/session'
|
||||
// Допустимо внутри domains/auth/react/queries
|
||||
import {
|
||||
useAuthApi,
|
||||
} from '@/domains/auth/react/session'
|
||||
```
|
||||
|
||||
Во втором случае composition читает состояния обоих доменов и передаёт необходимые данные или callbacks через публичные свойства компонентов.
|
||||
```ts
|
||||
// Недопустимо внутри domains/user/react/profile
|
||||
import {
|
||||
useAuthApi,
|
||||
} from '@/domains/auth/react/session'
|
||||
```
|
||||
|
||||
State/query cache внутри framework binding не создаёт исключение для cross-domain imports. Он работает поверх переданного API своего домена.
|
||||
Во втором случае composition читает projections обоих доменов и передаёт values или callbacks через публичные props. Если User Domain API зависит от Auth, связь выполняется assemblies на runtime graph level.
|
||||
|
||||
## Границы сред
|
||||
## Границы сред и RSC
|
||||
|
||||
Каждый client-, server- или shared-entry point имеет совместимый транзитивный import-граф. Серверная assembly или adapter не реэкспортируется через `business`, Framework Group или клиентскую assembly.
|
||||
Каждая declared client, server, edge, worker или shared entry point проверяется под реальными resolver conditions. Название `assemblies/default` не объявляет environment compatibility.
|
||||
|
||||
Tree shaking не является доказательством изоляции. Проверка выполняется до удаления неиспользуемого кода.
|
||||
Tree shaking и runtime condition не доказывают изоляцию. Server-only adapter не достигается из client entry, даже если ветка считается неиспользуемой.
|
||||
|
||||
Checker различает:
|
||||
|
||||
- executable import edge;
|
||||
- type-only import edge;
|
||||
- framework reference edge;
|
||||
- dynamic import с объявленным target capability set.
|
||||
|
||||
Server Component выполняется в server scope. Ссылка на Client Component и invocation Server Action анализируются как framework references, а не как обычное совместное выполнение. Для SSR-enabled Client Component отдельно проверяются server prerender graph, browser hydration graph и объявленные framework-deferred browser edges. Необъявленный или неанализируемый dynamic import запрещается либо явно allowlist-ится project policy.
|
||||
|
||||
Reference in New Issue
Block a user