8.5 KiB
Framework Groups и модули
Пояснение domain-specific framework-кода, materialized state и RSC boundaries на примере React.
Связанные правила
SLM-L2-API-R006SLM-L2-DEPENDENCY-A012SLM-L2-FRAMEWORK-R014SLM-L2-FRAMEWORK-R015SLM-L2-API-A019SLM-L2-API-A022SLM-L2-STATE-R028
Framework Group
Папка для domain-specific React binding modules называется react:
domains/auth/react/ # Framework Group
├── session/ # SLM-модуль
│ ├── hooks/
│ ├── providers/
│ └── index.ts
├── queries/ # SLM-модуль
│ └── index.ts
└── login-form/ # SLM-модуль
├── components/
└── index.ts
react является Group, а не модулем. У неё нет index.ts, реализации, state, lifecycle или агрегирующего API. Если пакет поддерживает Vue, рядом появляется отдельная Group vue.
Каждый прямой дочерний каталог является обычным SLM-модулем со своей ответственностью, публичным API и узлом графа.
Framework binding module
Модуль принадлежит Framework Group, если его самостоятельная ответственность состоит в связывании готового Domain API своего домена с конкретным framework.
Framework binding может:
- передавать готовый API через Provider и context;
- предоставлять domain-specific hooks;
- хранить framework projection в query cache или store;
- отображать public models, outcomes и domain errors;
- реализовывать SSR prefetch и client hydration;
- реализовывать переиспользуемую domain-specific форму или guard;
- связывать framework lifecycle с явной realtime subscription.
Он не вызывает api/factory или assembly, не выбирает adapters, не импортирует SDK предметного external source и не определяет новые предметные операции.
Framework binding импортирует consumer types и deterministic runtime через разные фасеты:
import type {
AuthError,
AuthSessionApi,
} from '@/domains/auth/api'
import {
isAuthError,
} from '@/domains/auth/api/runtime'
Импорты api/ports, api/factory и adapters/* запрещены.
Готовый API
auth/react/session может владеть Provider для уже созданного AuthSessionApi:
'use client'
type AuthSessionProviderProps = PropsWithChildren<{
api: AuthSessionApi
}>
export const AuthSessionProvider = ({
api,
children,
}: AuthSessionProviderProps) => {
return (
<AuthSessionContext.Provider value={api}>
{children}
</AuthSessionContext.Provider>
)
}
Публичный путь модуля:
import {
AuthSessionProvider,
useAuthApi,
} from '@/domains/auth/react/session'
Импорт @/domains/auth/react запрещён, потому что Group не имеет API.
Query и store projection
auth/react/queries может использовать TanStack Query, SWR, Zustand или другой React runtime поверх готового API:
export const useAuthSessionQuery = () => {
const api = useAuthApi()
return useQuery({
queryKey: ['auth', 'session'],
queryFn: api.getSession,
})
}
Query keys, stale time, pending status и hydration принадлежат binding. Значения и ошибки поступают через Domain API. Framework types не становятся частью AuthSessionApi.
Framework projection не импортируется другим доменом. Cross-domain UI собирается в compositions.
Realtime binding
Binding может запускать subscription готового API в framework lifecycle:
component/provider scope
→ Domain API subscribe
→ verified domain events
→ query invalidation or API-owned projection
→ cleanup on scope end
Binding не импортирует WebSocket client и не разбирает frames. После cleanup он не принимает late callbacks. Если reconnect создаёт gap, binding обрабатывает публичный RESYNC_REQUIRED outcome и повторно загружает snapshot через Domain API.
Domain-specific UI
auth/react/login-form может владеть переиспользуемой формой, если она работает только с Auth API, public models и errors своего домена. Она может использовать публичный API соседнего auth/react/session, если статический граф остаётся ацикличным.
Конкретная страница, продуктовый текст, layout, redirect и выбор маршрута принадлежат compositions. Domain API может вернуть AUTH_REQUIRED, но переход на /login выбирает composition.
Framework-only SDK
SDK, доступный только через Provider, hook или component, может использоваться binding для получения opaque operation input:
CAPTCHA React component
→ opaque token
→ AuthApi command
Binding не использует SDK для самостоятельной предметной операции, не превращает SDK response в public domain model и не экспортирует SDK type через Domain API. Если SDK предоставляет reusable technical UI без предметной модели, его generic integration может принадлежать infra и ui, а composition связывает её с доменом.
Запрет cross-domain framework imports
Framework binding module не импортирует hooks, contexts, Providers, stores или components другого домена:
// Недопустимо: domains/user/react/profile
import {
useAuthSessionQuery,
} from '@/domains/auth/react/queries'
Cross-domain UI собирается в compositions:
const session = useAuthSessionQuery()
return (
<UserProfile
userId={session.data?.userId}
/>
)
Если User Domain API постоянно нуждается в Auth, готовый AuthSessionApi передаётся User assembly при сборке runtime-графа. User framework binding работает уже со своим API.
SSR, RSC и client boundary
Server prefetch и client hooks могут принадлежать разным modules Framework Group с совместимыми entry points. Они не разделяют API instance или mutable cache:
server binding
→ server API instance
→ prefetch
→ hydration payload
client binding
→ client API instance
→ hydrate
→ rendering
Server Component не передаёт API object в Client Component. Client reference и Server Action reference объявляются checker-у отдельно от executable imports. Если Client Component участвует в SSR или prerender, его server render graph проверяется отдельно от browser hydration graph; browser-only capability используется только через объявленную framework-deferred boundary.
Публичные API
import {
AuthSessionProvider,
} from '@/domains/auth/react/session'
import {
useAuthSessionQuery,
} from '@/domains/auth/react/queries'
import {
LoginForm,
} from '@/domains/auth/react/login-form'
Framework Group не реэкспортирует дочерние модули. Это сохраняет независимые ответственности и не превращает react в скрытый корневой модуль домена.