mirror of
https://github.com/gromlab-ru/slm-design.git
synced 2026-08-22 07:30:16 +03:00
feat: level-3 черновик
This commit is contained in:
@@ -1,109 +1,71 @@
|
||||
# Framework bindings внутри Domain
|
||||
# React module внутри Domain
|
||||
|
||||
> Рабочая заметка. Не является нормативным разделом спецификации.
|
||||
> Пояснение framework boundary Domain на примере React.
|
||||
|
||||
## Определение
|
||||
## Связанные правила
|
||||
|
||||
### FW-N001: Framework code определяется зависимостью от framework
|
||||
- [`SLM-L3-FRAMEWORK-R013`](../../rules/level-3.md#slm-l3-framework-r013)
|
||||
- [`SLM-L3-BUSINESS-A004`](../../rules/level-3.md#slm-l3-business-a004)
|
||||
- [`SLM-L3-ASSEMBLY-R010`](../../rules/level-3.md#slm-l3-assembly-r010)
|
||||
|
||||
К framework code относится код, существующий из-за React, Vue, Next.js или другого framework/runtime contract:
|
||||
## Имя и место module
|
||||
|
||||
- components;
|
||||
- providers и contexts;
|
||||
- framework hooks;
|
||||
- framework lifecycle;
|
||||
- directives и framework entrypoints;
|
||||
- framework-specific types;
|
||||
- server/client component boundaries.
|
||||
|
||||
Такой код может принадлежать Domain по смыслу, но не размещается внутри framework-neutral business.
|
||||
|
||||
## Роль binding
|
||||
|
||||
### FW-N002: Framework binding адаптирует готовый business API
|
||||
|
||||
Framework binding может:
|
||||
|
||||
- предоставить готовый business API через context/provider;
|
||||
- построить React/Vue hook доступа;
|
||||
- связать framework lifecycle с domain subscription;
|
||||
- предоставить domain-specific framework component;
|
||||
- получить API instance через props, context или preset.
|
||||
|
||||
Framework binding не изменяет business rules и не реализует source adapter вместо Domain preset/adapters.
|
||||
|
||||
## Возможная структура
|
||||
Framework-specific module находится непосредственно в Domain и называется именем framework:
|
||||
|
||||
```text
|
||||
domains/auth/{framework-binding}/
|
||||
├── providers/
|
||||
├── hooks/
|
||||
domains/auth/react/
|
||||
├── components/
|
||||
├── hooks/
|
||||
├── providers/
|
||||
├── types/
|
||||
└── index.ts
|
||||
```
|
||||
|
||||
`{framework-binding}` является placeholder. SLM пока не выбирает между `react`, `bindings/react`, `framework/react` и другим локальным соглашением. Чёткая граница определяется самостоятельным module и отдельным public entrypoint, а не обязательным именем родительской папки.
|
||||
`react` точно обозначает framework и не создаёт пустую промежуточную Group вроде `framework/react` или `bindings/react`. Если Domain действительно поддерживает другой framework, он получает отдельный sibling module, например `vue`.
|
||||
|
||||
Если одна папка предоставляет cohesive framework API, она является module. Если папка только классифицирует несколько самостоятельных binding modules, она является logical group и не имеет собственного `index.ts`.
|
||||
## Роль React module
|
||||
|
||||
## Reactive state
|
||||
React module может:
|
||||
|
||||
### FW-N003: Framework hook строится снаружи business
|
||||
- передать готовый `AuthApi` через context/provider;
|
||||
- создать hook доступа к API или framework-neutral state;
|
||||
- связать React lifecycle с subscription API;
|
||||
- реализовать domain-specific React component.
|
||||
|
||||
Если business предоставляет framework-neutral `getSnapshot` и `subscribe`, React binding может использовать `useSyncExternalStore`:
|
||||
Он не меняет business rules, не создаёт domain errors, не выбирает concrete adapters и не вызывает factory или preset. Сборка остаётся у composition graph owner; React module получает уже готовый instance.
|
||||
|
||||
```ts
|
||||
'use client'
|
||||
```tsx
|
||||
type AuthProviderProps = PropsWithChildren<{
|
||||
api: AuthApi
|
||||
}>
|
||||
|
||||
export const createUseAuth = (authApi: AuthApi) => {
|
||||
return () => {
|
||||
return useSyncExternalStore(
|
||||
authApi.subscribeAuthState,
|
||||
authApi.getAuthState,
|
||||
authApi.getAuthState,
|
||||
)
|
||||
}
|
||||
export const AuthProvider = ({ api, children }: AuthProviderProps) => {
|
||||
return <AuthContext.Provider value={api}>{children}</AuthContext.Provider>
|
||||
}
|
||||
```
|
||||
|
||||
Это только иллюстрация направления. Финальная форма state port должна учитывать реальный state/query runtime.
|
||||
## Reactive state
|
||||
|
||||
Business при таком подходе не импортирует React и не возвращает React hook как единственный способ чтения состояния.
|
||||
Если `AuthApi` предоставляет framework-neutral protocol `getSnapshot` и `subscribe`, React module может использовать `useSyncExternalStore`:
|
||||
|
||||
## Framework module как assembly site
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
### FW-N004: Provider может владеть API instance
|
||||
export const useAuthState = () => {
|
||||
const api = useAuth()
|
||||
|
||||
Provider вправе вызвать preset или factory, если provider является явным владельцем scope и lifecycle:
|
||||
|
||||
```text
|
||||
AuthProvider
|
||||
→ createBrowserAuth preset
|
||||
→ AuthApi instance
|
||||
→ context
|
||||
→ access hooks
|
||||
return useSyncExternalStore(
|
||||
api.subscribe,
|
||||
api.getSnapshot,
|
||||
api.getSnapshot,
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Provider construction не должен запускать I/O или subscription до framework commit/effect. Cleanup выполняется владельцем lifecycle.
|
||||
Business не импортирует React и не возвращает React hook как единственный способ наблюдать state. React module не создаёт subscription до commit и возвращает cleanup через protocol `useSyncExternalStore`.
|
||||
|
||||
Framework module не обязан собирать API. Он также может получить готовый instance от route/page/application graph owner.
|
||||
## Domain UI и compositions
|
||||
|
||||
## Framework-neutral и environment-neutral
|
||||
Component принадлежит `react`, если его responsibility ограничена domain contract: он работает с `AuthApi`, domain state и stable domain errors. Он не владеет page, route, redirect, product copy или composition нескольких domains.
|
||||
|
||||
Эти свойства различаются:
|
||||
|
||||
| Свойство | Запрещённая зависимость |
|
||||
|---|---|
|
||||
| Framework-neutral | React, Vue, Next lifecycle и types |
|
||||
| Environment-neutral | Browser-only, Node-only, server-only, env/runtime globals |
|
||||
|
||||
Business factory должна удовлетворять обоим свойствам. Framework binding по определению framework-specific, а preset по определению может быть environment-specific.
|
||||
|
||||
## Domain UI
|
||||
|
||||
### FW-N005: Framework принадлежность не доказывает Domain ownership
|
||||
|
||||
React component размещается внутри Domain только если его ответственность принадлежит Domain. Page, screen, route outcome, локальный текст ошибки и продуктовая композиция могут остаться в `compositions`.
|
||||
|
||||
Граница между domain-specific components и consumer compositions пока требует отдельных примеров.
|
||||
Screen, route outcome, локальный текст ошибки, redirect и page-specific UI остаются в `compositions`. Dependency от React сама по себе не доказывает принадлежность Domain.
|
||||
|
||||
Reference in New Issue
Block a user