feat: уточнить архитектурные границы SLM

This commit is contained in:
2026-07-30 21:44:47 +03:00
parent 4ce6bae61c
commit 15805e28df
28 changed files with 1006 additions and 553 deletions

View File

@@ -4,6 +4,7 @@
## Связанные правила
- [`SLM-L2-BUSINESS-R006`](../../rules/level-2.md#slm-l2-business-r006)
- [`SLM-L2-DEPENDENCY-A012`](../../rules/level-2.md#slm-l2-dependency-a012)
- [`SLM-L2-FRAMEWORK-R014`](../../rules/level-2.md#slm-l2-framework-r014)
- [`SLM-L2-FRAMEWORK-R015`](../../rules/level-2.md#slm-l2-framework-r015)
@@ -20,6 +21,8 @@ domains/auth/react/ # Framework Group
│ ├── hooks/
│ ├── providers/
│ └── index.ts
├── queries/ # SLM-модуль
│ └── index.ts
└── login-form/ # SLM-модуль
├── components/
└── index.ts
@@ -31,43 +34,44 @@ domains/auth/react/ # Framework Group
## Framework binding module
Модуль принадлежит Framework Group, если его самостоятельная ответственность состоит в связывании готового `DomainApi` одного домена с конкретным framework. Сам факт зависимости от framework недостаточен: framework-specific preset остаётся в `presets`, а page-specific модуль остаётся в `compositions`.
Модуль принадлежит Framework Group, если его самостоятельная ответственность состоит в связывании одного или нескольких готовых Domain API своего домена с конкретным framework. Сам факт зависимости от framework недостаточен: framework-specific assembly остаётся в `assemblies`, а page-specific модуль остаётся в `compositions`.
Framework binding module может:
- передавать готовый `DomainApi` через Provider и context;
- передавать готовые API через Provider и context;
- предоставлять domain-specific hooks;
- отображать состояние и безопасные ошибки домена;
- использовать framework-compatible state/query runtime;
- реализовывать переиспользуемую domain-specific форму или guard;
- связывать framework lifecycle с публичным API домена.
- связывать framework lifecycle с явными операциями Domain API.
Он не вызывает business-фабрику или preset, не выбирает adapters и не создаёт новые предметные сценарии.
Он не вызывает business-фабрику или assembly, не выбирает adapters и не создаёт новые предметные сценарии.
Framework binding module импортирует типы и runtime error contract через разные фасеты:
Framework binding импортирует типы и deterministic runtime через разные фасеты:
```ts
import type {
AuthApi,
AuthError,
AuthSessionApi,
} from '@/domains/auth/business'
import {
AUTH_ERROR_CODES,
isAuthError,
} from '@/domains/auth/business/error'
} from '@/domains/auth/business/runtime'
```
Импорт `business/factory` из Framework Group запрещён: готовый `DomainApi` передаётся модулю извне.
Импорт `business/factory` запрещён: готовые API передаются модулю извне.
## Модуль session
`auth/react/session` может владеть Provider и hooks доступа к уже созданному `AuthApi`:
`auth/react/session` может владеть Provider и hooks доступа к уже созданному `AuthSessionApi`:
```tsx
'use client'
type AuthSessionProviderProps = PropsWithChildren<{
api: AuthApi
api: AuthSessionApi
}>
export const AuthSessionProvider = ({
@@ -93,15 +97,34 @@ import {
Импорт `@/domains/auth/react` запрещён, потому что Group не имеет API.
## State/query runtime
`auth/react/queries` может использовать TanStack Query, SWR или другой React runtime поверх готового `AuthSessionApi`. Query cache остаётся framework projection, пока данные и transitions проходят через API, а optimistic values создаются или проверяются business-владельцем.
```ts
export const useAuthSessionQuery = () => {
const api = useAuthSession()
return useQuery({
queryKey: ['auth', 'session'],
queryFn: api.getCurrentSession,
})
}
```
Server prefetch и client hook могут быть разными модулями Framework Group с совместимыми environment markers. Общая query policy выносится в отдельный framework-модуль при наличии нескольких потребителей, а не дублируется в compositions.
Подробности описаны в [Состоянии и кэше](./state-cache.md).
## Модуль login-form
`auth/react/login-form` может владеть переиспользуемой формой, если она работает только с `AuthApi`, AuthState и AuthError. Она может использовать публичный API соседнего `auth/react/session`, если зависимость остаётся ацикличной.
`auth/react/login-form` может владеть переиспользуемой формой, если она работает только с Auth API, состоянием и ошибками своего домена. Она может использовать публичный API соседнего `auth/react/session`, если зависимость остаётся ацикличной.
Конкретная страница, продуктовый текст, layout, redirect и выбор маршрута принадлежат `compositions`. Поэтому domain-owned `AuthGuard` может решить, разрешён ли доступ, но политика перехода на `/login` остаётся у route composition.
Конкретная страница, продуктовый текст, layout, redirect и выбор маршрута принадлежат `compositions`. Domain-owned guard может решить, разрешено ли действие, но политика перехода на `/login` остаётся у route composition.
## Запрет cross-domain framework imports
Framework binding module не импортирует hooks, contexts, Providers, components или framework state другого доменного пакета:
Framework binding module не импортирует hooks, contexts, Providers, components или framework state другого домена:
```ts
// Недопустимо: domains/user/react/profile
@@ -121,7 +144,7 @@ return (
)
```
Передача через props является границей композиции, но не требует prop drilling внутри домена: каждый пакет может использовать собственный Provider и context. Если User business постоянно нуждается в Auth, готовый `AuthApi` передаётся User factory при сборке графа, а User framework module работает уже со своим `UserApi`.
Передача через props является границей композиции, но не требует prop drilling внутри домена: каждый пакет может использовать собственный Provider и context. Если User business постоянно нуждается в Auth, готовый `AuthSessionApi` передаётся User factory при сборке графа, а User framework module работает уже со своим API.
## Публичные API