mirror of
https://github.com/gromlab-ru/slm-design.git
synced 2026-08-22 07:30:16 +03:00
feat: Добавить уровни сложности архитектуры
This commit is contained in:
@@ -1,543 +0,0 @@
|
|||||||
# Реорганизация Business-модуля
|
|
||||||
|
|
||||||
> Статус: proposal для обсуждения. Это не принятый канон и не инструкция для механической миграции.
|
|
||||||
|
|
||||||
## Решение, которое нужно принять
|
|
||||||
|
|
||||||
`business/{domain}` должен быть вертикальным продуктовым модулем, а не только чистым источником доменных данных.
|
|
||||||
|
|
||||||
Один business-модуль владеет:
|
|
||||||
|
|
||||||
- доменной моделью, правилами, сценариями и ошибками;
|
|
||||||
- доменным состоянием и его переходами;
|
|
||||||
- адаптацией внешних возможностей к потребностям домена;
|
|
||||||
- React hooks, Provider и UI, выражающими один домен;
|
|
||||||
- отдельными server/client entrypoints, когда этого требует runtime Next.js.
|
|
||||||
|
|
||||||
React не является причиной вынести domain hooks, store или domain UI в `compositions`. Он является входным runtime-адаптером того же домена.
|
|
||||||
|
|
||||||
`compositions` остаётся местом, где связываются несколько доменов, выбирается scope graph и собирается page/route UI. Но он не становится владельцем `useAuth`, `AuthProvider`, `LoginForm` или domain store.
|
|
||||||
|
|
||||||
## Почему текущая модель не подходит
|
|
||||||
|
|
||||||
Logic-only business делит один домен по технической природе файлов:
|
|
||||||
|
|
||||||
```text
|
|
||||||
business/auth # types, factory, scenarios
|
|
||||||
compositions/business/auth # concrete runtime adapters
|
|
||||||
compositions/pages/login # provider, hooks, domain UI
|
|
||||||
```
|
|
||||||
|
|
||||||
В результате у `auth` нет одной физической и понятной границы. Доменное поведение, его runtime, доступ из React и UI существуют отдельно, хотя меняются совместно.
|
|
||||||
|
|
||||||
Особенно проблемны следующие свойства.
|
|
||||||
|
|
||||||
1. Hooks остаются частью public API domain, но их React execution model прячется за `deps`. Business формально не импортирует React, но API всё равно нельзя вызвать из Server Component, обычной функции или теста без React render context.
|
|
||||||
2. Domain state имеет трёх владельцев: модель и transitions в `business`, concrete store в integration composition, instance и lifecycle в page Provider. Невозможно коротко ответить, где находится `auth`.
|
|
||||||
3. Правило «единственный runtime export - фабрика» защищает от обхода DI, но одновременно запрещает безопасные domain hooks, Provider и компоненты. Оно ограничивает форму public API вместо утечки реализации.
|
|
||||||
4. Разделение server/client - это граница module graph и runtime, а не две разные продуктовые ответственности. Перенос client-кода в `compositions` не решает границу, а скрывает её.
|
|
||||||
5. Consumer composition вынужден знать, как React подключается к домену, хотя это часть внутренней реализации domain runtime.
|
|
||||||
|
|
||||||
Цель реорганизации - вернуть business-модулю вертикальное владение, не потеряв полезные инварианты текущей модели: ports, domain errors, DI, минимальные API, lifecycle и отсутствие runtime-циклов.
|
|
||||||
|
|
||||||
## Новое определение Business-модуля
|
|
||||||
|
|
||||||
> Business-модуль - минимальная вертикальная граница продуктового домена. Он создаёт domain runtime через фабрику и предоставляет этому runtime несколько явных интерфейсов: обычный TypeScript API, React API и при необходимости server API.
|
|
||||||
|
|
||||||
Это один модуль и один owner. Внутри него допустимы несколько зон с разными правилами зависимостей.
|
|
||||||
|
|
||||||
```text
|
|
||||||
business/auth/
|
|
||||||
├── kernel/ # доменная логика без React и concrete runtime
|
|
||||||
├── adapters/ # реализации исходящих ports
|
|
||||||
├── react/ # client-only React API домена
|
|
||||||
├── server/ # server-only сборка, только при необходимости
|
|
||||||
├── auth.factory.ts # создание domain runtime
|
|
||||||
├── index.ts # universal public API
|
|
||||||
├── react.ts # client-only public entrypoint
|
|
||||||
└── server.ts # server-only public entrypoint, optional
|
|
||||||
```
|
|
||||||
|
|
||||||
`kernel`, `adapters`, `react` и `server` - зоны ответственности внутри одного business-модуля. Они не являются новыми SLM-слоями и не должны автоматически появляться во всех доменах.
|
|
||||||
|
|
||||||
## Зоны и зависимости
|
|
||||||
|
|
||||||
### Kernel
|
|
||||||
|
|
||||||
`kernel/` содержит то, что определяет домен независимо от платформы:
|
|
||||||
|
|
||||||
- domain types и value objects;
|
|
||||||
- domain errors со стабильными `code`;
|
|
||||||
- правила, validators, normalizers и mappers;
|
|
||||||
- use cases, commands, queries и selectors;
|
|
||||||
- модель domain state, transitions и initial state;
|
|
||||||
- контракты исходящих ports.
|
|
||||||
|
|
||||||
`kernel` не импортирует:
|
|
||||||
|
|
||||||
- React, Next.js, browser API;
|
|
||||||
- SDK, HTTP client, storage implementation;
|
|
||||||
- Zustand, Redux, SWR, TanStack Query и другие concrete runtimes;
|
|
||||||
- `compositions`, `app` или UI.
|
|
||||||
|
|
||||||
Именно kernel делает business runtime переносимым между server, browser и test environment.
|
|
||||||
|
|
||||||
### Factory
|
|
||||||
|
|
||||||
Фабрика остаётся единственным способом создать instance domain runtime. Она принимает минимальный набор ports и возвращает поведение домена.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
export type AuthPorts = {
|
|
||||||
session: {
|
|
||||||
getCurrent: () => Promise<unknown>
|
|
||||||
signIn: (input: SignInInput) => Promise<unknown>
|
|
||||||
signOut: () => Promise<void>
|
|
||||||
}
|
|
||||||
tokenStorage: {
|
|
||||||
read: () => string | null
|
|
||||||
write: (token: string | null) => void
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AuthRuntime = {
|
|
||||||
getCurrentUser: () => Promise<User | null>
|
|
||||||
signIn: (input: SignInInput) => Promise<Session>
|
|
||||||
signOut: () => Promise<void>
|
|
||||||
getSnapshot: () => AuthState
|
|
||||||
subscribe: (listener: () => void) => () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const authFactory = (ports: AuthPorts): AuthRuntime => {
|
|
||||||
// Создаёт domain state, commands, queries и selectors.
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Фабрика:
|
|
||||||
|
|
||||||
- не создаёт HTTP client, browser storage или query client;
|
|
||||||
- не запускает I/O, subscriptions или timers при создании instance;
|
|
||||||
- не импортирует React;
|
|
||||||
- не возвращает raw store, raw context, SDK client или adapter;
|
|
||||||
- может возвращать methods, selectors, subscriptions и другие framework-neutral runtime operations.
|
|
||||||
|
|
||||||
Фабрика не обязана быть единственным runtime export модуля. Она является единственным местом создания business runtime.
|
|
||||||
|
|
||||||
### Adapters
|
|
||||||
|
|
||||||
`adapters/` реализует исходящие ports kernel поверх конкретной технологии:
|
|
||||||
|
|
||||||
```text
|
|
||||||
business/auth/adapters/
|
|
||||||
├── auth-api.adapter.ts
|
|
||||||
├── browser-token-storage.adapter.ts
|
|
||||||
└── server-token-storage.adapter.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
Адаптер нужен, когда concrete dependency не соответствует domain port или должен остаться внутренней деталью домена.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
export const createAuthSessionAdapter = (
|
|
||||||
apiClient: AuthApiClient,
|
|
||||||
): AuthPorts['session'] => ({
|
|
||||||
getCurrent: () => apiClient.auth.getCurrentSession(),
|
|
||||||
signIn: (input) => apiClient.auth.signIn({ body: input }),
|
|
||||||
signOut: () => apiClient.auth.signOut(),
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
Назначение адаптера не в том, чтобы «упростить вызов фабрики». Он сохраняет границу:
|
|
||||||
|
|
||||||
- kernel знает потребность домена, а не форму SDK;
|
|
||||||
- transport payload и вызов конкретного API не становятся business contract;
|
|
||||||
- browser и server могут реализовать один port по-разному;
|
|
||||||
- замена SDK, storage или event runtime не требует менять kernel.
|
|
||||||
|
|
||||||
Адаптер:
|
|
||||||
|
|
||||||
- импортирует port type из kernel и concrete `infra` dependency;
|
|
||||||
- преобразует domain input в transport input;
|
|
||||||
- возвращает raw result или source error в kernel для нормализации и error mapping;
|
|
||||||
- не формирует domain model, domain error или fallback;
|
|
||||||
- не экспортируется из public entrypoint.
|
|
||||||
|
|
||||||
Адаптер не является обязательным ритуалом. Если конкретная dependency уже точно реализует domain port и не протекает через public API, её можно передать фабрике напрямую. Не нужно создавать файл-обёртку из одной строки без адаптации или runtime-границы.
|
|
||||||
|
|
||||||
Domain-specific adapter живёт рядом с доменом, потому что только он знает, какой endpoint или storage key реализует именно `AuthPorts`. Общий технический client, transport, logger или storage primitive остаётся в `infra`.
|
|
||||||
|
|
||||||
### React
|
|
||||||
|
|
||||||
`react/` - client-only входной адаптер domain runtime. Он содержит:
|
|
||||||
|
|
||||||
- закрытый Context;
|
|
||||||
- Provider, принимающий или создающий текущий `AuthRuntime`;
|
|
||||||
- access hooks, например `useAuthRuntime`;
|
|
||||||
- domain hooks, например `useAuth`, `useCurrentUser`, `usePermissions`;
|
|
||||||
- client state/query integration, если она выражает доменное состояние;
|
|
||||||
- domain-specific interactive UI, например `LoginForm` и `LogoutButton`.
|
|
||||||
|
|
||||||
```text
|
|
||||||
business/auth/react/
|
|
||||||
├── auth.provider.tsx
|
|
||||||
├── hooks/
|
|
||||||
│ ├── use-auth-runtime.ts
|
|
||||||
│ ├── use-auth.ts
|
|
||||||
│ └── use-current-user.ts
|
|
||||||
├── ui/
|
|
||||||
│ ├── login-form.tsx
|
|
||||||
│ └── logout-button.tsx
|
|
||||||
└── create-auth-browser-runtime.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
React-код может импортировать kernel, factory и client-specific adapters. Обратный импорт запрещён:
|
|
||||||
|
|
||||||
```text
|
|
||||||
react → factory → kernel
|
|
||||||
react → browser adapter → infra
|
|
||||||
kernel -/→ react
|
|
||||||
factory -/→ react
|
|
||||||
```
|
|
||||||
|
|
||||||
Provider связывает статически экспортируемый React API с конкретным instance, созданным фабрикой:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
'use client'
|
|
||||||
|
|
||||||
const AuthRuntimeContext = createContext<AuthRuntime | null>(null)
|
|
||||||
|
|
||||||
export function AuthProvider({
|
|
||||||
runtime,
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
runtime: AuthRuntime
|
|
||||||
children: ReactNode
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<AuthRuntimeContext.Provider value={runtime}>
|
|
||||||
{children}
|
|
||||||
</AuthRuntimeContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```ts
|
|
||||||
export function useAuthRuntime(): AuthRuntime {
|
|
||||||
const runtime = useContext(AuthRuntimeContext)
|
|
||||||
|
|
||||||
if (!runtime) {
|
|
||||||
throw new Error('AuthProvider is missing')
|
|
||||||
}
|
|
||||||
|
|
||||||
return runtime
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```ts
|
|
||||||
export function useCurrentUser() {
|
|
||||||
const auth = useAuthRuntime()
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ['auth', 'current-user'],
|
|
||||||
queryFn: auth.getCurrentUser,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`useCurrentUser` принадлежит `auth`, хотя использует Query runtime: он выражает доменное понятие и работает только с `AuthRuntime`. Он не вызывает SDK и не выполняет domain normalization самостоятельно.
|
|
||||||
|
|
||||||
Компоненты из `react/ui/` могут вызывать hooks своего домена и использовать универсальные компоненты из `ui`. Они не должны:
|
|
||||||
|
|
||||||
- напрямую обращаться к SDK, storage или browser API;
|
|
||||||
- реализовывать business rule, error mapping или нормализацию;
|
|
||||||
- импортировать runtime другого домена;
|
|
||||||
- собирать UI из нескольких доменов.
|
|
||||||
|
|
||||||
Например, `LoginForm` принадлежит `auth`; `CheckoutHeader`, объединяющий `auth`, `cart` и `orders`, принадлежит composition module.
|
|
||||||
|
|
||||||
### Server
|
|
||||||
|
|
||||||
`server/` - optional зона для server-only assembly домена. Она нужна только если у домена действительно есть server-specific ports или запросный scope.
|
|
||||||
|
|
||||||
```text
|
|
||||||
business/auth/server/
|
|
||||||
└── create-auth-server-runtime.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
Server builder создаёт новый runtime на request scope и передаёт в фабрику server adapters:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import 'server-only'
|
|
||||||
|
|
||||||
export function createAuthServerRuntime(
|
|
||||||
input: AuthServerScopeInput,
|
|
||||||
): AuthRuntime {
|
|
||||||
return authFactory({
|
|
||||||
session: createServerSessionAdapter(input),
|
|
||||||
tokenStorage: createRequestTokenStorageAdapter(input),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`server/` не импортирует `react/`, а `react/` не импортирует `server/`. Два runtime создаются над одной factory/kernel, но имеют разные lifecycle и concrete ports.
|
|
||||||
|
|
||||||
Создавать `server/` симметрично в каждом домене не нужно. Если server использует только universal factory с явно переданными testable ports, отдельный entrypoint не добавляется.
|
|
||||||
|
|
||||||
## Public entrypoints
|
|
||||||
|
|
||||||
Ограничивать public API только фабрикой не нужно. Следует разделять public entrypoints по runtime, чтобы Next.js мог построить корректные module graphs.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// business/auth/index.ts
|
|
||||||
export { authFactory } from './auth.factory'
|
|
||||||
|
|
||||||
export type {
|
|
||||||
AuthPorts,
|
|
||||||
AuthRuntime,
|
|
||||||
AuthState,
|
|
||||||
SignInInput,
|
|
||||||
User,
|
|
||||||
} from './kernel'
|
|
||||||
```
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// business/auth/react.ts
|
|
||||||
'use client'
|
|
||||||
|
|
||||||
export { AuthProvider } from './react/auth.provider'
|
|
||||||
export { useAuth, useAuthRuntime, useCurrentUser } from './react/hooks'
|
|
||||||
export { LoginForm, LogoutButton } from './react/ui'
|
|
||||||
export { createAuthBrowserRuntime } from './react/create-auth-browser-runtime'
|
|
||||||
```
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// business/auth/server.ts
|
|
||||||
import 'server-only'
|
|
||||||
|
|
||||||
export { createAuthServerRuntime } from './server/create-auth-server-runtime'
|
|
||||||
export type { AuthServerScopeInput } from './server/auth-server-scope-input.type'
|
|
||||||
```
|
|
||||||
|
|
||||||
Правила:
|
|
||||||
|
|
||||||
- `index.ts` не импортирует и не реэкспортирует `react` или `server`;
|
|
||||||
- `react.ts` является client entrypoint и не импортирует `server`;
|
|
||||||
- `server.ts` является server entrypoint и не импортирует `react`;
|
|
||||||
- public API может содержать стабильные pure functions, factory, hooks, Provider и domain UI в соответствующем entrypoint;
|
|
||||||
- public API не раскрывает raw context, raw store, mutable singleton, persistence key, SDK client или private adapter;
|
|
||||||
- каждый runtime export имеет реального внешнего consumer.
|
|
||||||
|
|
||||||
Это не ослабление encapsulation. Оно заменяет запрет «runtime export вообще» на полезный запрет «не раскрывай concrete mutable implementation и не смешивай runtime graphs».
|
|
||||||
|
|
||||||
## SSR и RSC
|
|
||||||
|
|
||||||
Разделение entrypoints существует для Next.js module graph, а не для разделения business ownership.
|
|
||||||
|
|
||||||
```text
|
|
||||||
Server Component
|
|
||||||
→ @/business/auth/server или @/business/auth
|
|
||||||
→ authFactory
|
|
||||||
→ server ports/adapters
|
|
||||||
|
|
||||||
Client Component
|
|
||||||
→ @/business/auth/react
|
|
||||||
→ AuthProvider + hooks + UI
|
|
||||||
→ browser runtime
|
|
||||||
→ authFactory
|
|
||||||
→ browser ports/adapters
|
|
||||||
```
|
|
||||||
|
|
||||||
Один `AuthRuntime` нельзя передавать из Server Component в Client Component: он содержит functions и не сериализуется через RSC. Через границу передаются только serializable domain data, например `AuthSnapshot`, `User` или initial form state.
|
|
||||||
|
|
||||||
Server runtime:
|
|
||||||
|
|
||||||
- создаётся на request scope;
|
|
||||||
- получает headers, cookies, request ID и abort signal только через server input/adapters;
|
|
||||||
- не становится module singleton;
|
|
||||||
- не использует browser storage или React state.
|
|
||||||
|
|
||||||
Browser runtime:
|
|
||||||
|
|
||||||
- создаётся на Provider или client graph scope;
|
|
||||||
- не использует request credentials другого пользователя;
|
|
||||||
- не запускает I/O, subscriptions или timers во время module import;
|
|
||||||
- получает server bootstrap data только как serializable input.
|
|
||||||
|
|
||||||
Провайдер может принимать готовый browser runtime. Convenience API, который создаёт runtime внутри Provider, допустим только если constructor side-effect free и lifecycle явно определён. Этот выбор нужно проверить на полном примере до фиксации канона.
|
|
||||||
|
|
||||||
## Роль Compositions после реорганизации
|
|
||||||
|
|
||||||
`compositions` не владеет domain hooks, domain Provider, domain UI или domain adapters. Он отвечает за связи между модулями:
|
|
||||||
|
|
||||||
- собирает граф из нескольких готовых domain runtimes;
|
|
||||||
- выбирает application, route, page или request scope;
|
|
||||||
- передаёт суженные cross-domain dependencies при создании runtime;
|
|
||||||
- монтирует domain Providers в нужной точке React tree;
|
|
||||||
- реализует UI и orchestration, объединяющие несколько доменов;
|
|
||||||
- владеет page-local presentation state.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
const authRuntime = createAuthBrowserRuntime()
|
|
||||||
const profileRuntime = createProfileBrowserRuntime({
|
|
||||||
auth: pickAuthForProfile(authRuntime),
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AuthProvider runtime={authRuntime}>
|
|
||||||
<ProfileProvider runtime={profileRuntime}>
|
|
||||||
<ProfilePage />
|
|
||||||
</ProfileProvider>
|
|
||||||
</AuthProvider>
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
Это не означает, что composition реализует `AuthProvider` или `useAuth`: она только использует public React API домена и создаёт graph в scope, который реально им владеет.
|
|
||||||
|
|
||||||
## State и lifecycle
|
|
||||||
|
|
||||||
Нужно различать три разные ответственности.
|
|
||||||
|
|
||||||
| Ответственность | Владелец |
|
|
||||||
|---|---|
|
|
||||||
| Модель state, transitions, selectors, commands | kernel домена |
|
|
||||||
| Concrete implementation, например Zustand или Query | `react/` или runtime-specific adapter домена |
|
|
||||||
| Количество instances и время жизни | graph owner: Provider, route, page, application или request scope |
|
|
||||||
|
|
||||||
Concrete store не должен экспортироваться. React API отдаёт selectors, commands и hooks:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
export function useAuthState(): AuthState {
|
|
||||||
const auth = useAuthRuntime()
|
|
||||||
|
|
||||||
return useSyncExternalStore(
|
|
||||||
auth.subscribe,
|
|
||||||
auth.getSnapshot,
|
|
||||||
auth.getSnapshot,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Если Zustand, Redux или другой state runtime полезен только как техническая реализация, он должен быть закрыт в `react/` или adapter. Если его модель становится частью domain API, сначала нужно описать стабильный domain contract, а не экспортировать `StoreApi`.
|
|
||||||
|
|
||||||
Любые subscription, timer, socket и event listener:
|
|
||||||
|
|
||||||
- запускаются после mount/commit или в явно названной `start` operation;
|
|
||||||
- возвращают cleanup;
|
|
||||||
- не запускаются при import или создании factory instance;
|
|
||||||
- не делают browser runtime частью server graph.
|
|
||||||
|
|
||||||
## Domain UI и граница с Compositions
|
|
||||||
|
|
||||||
Возврат UI в business не означает, что любой UI становится domain UI.
|
|
||||||
|
|
||||||
| Сущность | Владелец |
|
|
||||||
|---|---|
|
|
||||||
| `Button`, `Modal`, `Tabs`, input primitive | `ui` |
|
|
||||||
| `LoginForm`, `LogoutButton`, `AuthRequired` | `business/auth/react/ui` |
|
|
||||||
| `CartSummary`, `AddToCartButton` | соответствующий business-домен |
|
|
||||||
| Header, объединяющий Auth, Cart и Navigation | `compositions` |
|
|
||||||
| Page, route, layout и screen | `compositions` |
|
|
||||||
| Sidebar open state, active tab и page-only flow | соответствующая composition |
|
|
||||||
|
|
||||||
Критерий для domain UI:
|
|
||||||
|
|
||||||
> Если сущность нельзя назвать и использовать без терминов конкретного домена, она вероятно принадлежит business-модулю.
|
|
||||||
|
|
||||||
Критерий для composition UI:
|
|
||||||
|
|
||||||
> Если сущность связывает несколько доменов, route/page scope или формирует конкретный экран, она принадлежит composition module.
|
|
||||||
|
|
||||||
## Что сохранить из текущей модели
|
|
||||||
|
|
||||||
Реорганизация не должна вернуть проблемы старого permissive подхода. Сохраняются следующие инварианты.
|
|
||||||
|
|
||||||
1. Concrete SDK, DTO и raw external errors не становятся public contract домена.
|
|
||||||
2. Домен нормализует внешние данные и наружу выдаёт только domain model и domain errors.
|
|
||||||
3. Cross-domain runtime dependencies передаются при сборке graph, сужаются до необходимого API и не образуют циклы.
|
|
||||||
4. Factory constructors и adapter constructors не делают I/O.
|
|
||||||
5. Lifecycle subscription/resource явно запускается и очищается владельцем scope.
|
|
||||||
6. Mutable internals, raw contexts, stores, adapters и clients закрыты от consumers.
|
|
||||||
7. Common UI остаётся в `ui`; domain UI не получает право быть бесконтрольным feature layer.
|
|
||||||
8. Product data не извлекается напрямую из SDK в page/screen/component, если она уже принадлежит domain scenario.
|
|
||||||
|
|
||||||
## Что меняется относительно текущей модели
|
|
||||||
|
|
||||||
| Текущая идея | Предложение |
|
|
||||||
|---|---|
|
|
||||||
| Business содержит только logic API | Business владеет vertical domain: kernel, runtime adapters и React API |
|
|
||||||
| React hooks - dependency wrapper business | Hooks - client input adapter домена |
|
|
||||||
| Concrete adapters в `compositions/business/{domain}` | Domain-specific adapters рядом с доменом; `infra` хранит только общие техсервисы |
|
|
||||||
| Factory - единственный runtime export | Factory - единственный creator domain runtime; public entrypoints могут экспортировать hooks, Provider и UI |
|
|
||||||
| Business не содержит UI | Domain-specific UI находится в `business/{domain}/react/ui` |
|
|
||||||
| Provider реализуется в composition | Domain Provider реализуется в business; composition выбирает место mount и graph scope |
|
|
||||||
| Каждый домен обязан иметь одинаковую server/client форму | `server/` добавляется только при реальной server-only необходимости |
|
|
||||||
|
|
||||||
## Не цели
|
|
||||||
|
|
||||||
- Не нужно создавать `kernel`, `adapters`, `react` и `server` в каждом модуле заранее.
|
|
||||||
- Не нужно переносить в business page/layout/route UI.
|
|
||||||
- Не нужно возвращать прямые SDK calls из hooks или компонентов.
|
|
||||||
- Не нужно создавать generic global `BusinessProvider` или service locator.
|
|
||||||
- Не нужно вводить module singleton как замену явному graph owner.
|
|
||||||
- Не нужно считать любой `useX` domain hook: многие hooks остаются page-local или universal UI hooks.
|
|
||||||
- Не нужно механически переносить все существующие `compositions/business/*` без подтверждения ownership на реальном домене.
|
|
||||||
|
|
||||||
## Проверочный пример перед изменением канонов
|
|
||||||
|
|
||||||
Новая модель должна быть проверена на одном полном домене, предпочтительно `auth`.
|
|
||||||
|
|
||||||
Минимальная цепочка:
|
|
||||||
|
|
||||||
```text
|
|
||||||
business/auth/kernel
|
|
||||||
→ AuthPorts, AuthRuntime, domain errors, session transitions
|
|
||||||
|
|
||||||
business/auth/adapters
|
|
||||||
→ HTTP session adapter, browser storage adapter, request storage adapter
|
|
||||||
|
|
||||||
business/auth/react
|
|
||||||
→ AuthProvider, useAuth, useCurrentUser, LoginForm, LogoutButton
|
|
||||||
|
|
||||||
business/auth/server (если нужен)
|
|
||||||
→ request-scoped AuthRuntime
|
|
||||||
|
|
||||||
composition
|
|
||||||
→ создаёт auth runtime в нужном scope и монтирует AuthProvider
|
|
||||||
```
|
|
||||||
|
|
||||||
Пример должен доказать:
|
|
||||||
|
|
||||||
1. Server Component получает current user через server или universal API без client imports.
|
|
||||||
2. Client Component получает то же доменное состояние через `useAuth` без SDK import.
|
|
||||||
3. `LoginForm` использует domain commands и domain errors без page-specific wiring.
|
|
||||||
4. Browser и server используют одну factory/kernel, но разные adapters и instances.
|
|
||||||
5. Нельзя передать runtime instance через RSC; передаётся только serializable snapshot.
|
|
||||||
6. Cross-domain dependency можно передать без runtime cycle.
|
|
||||||
7. Provider/store lifecycle не создаёт I/O при import и не оставляет subscription после unmount.
|
|
||||||
8. Public entrypoints не тянут server код в client bundle и client code в Server Component.
|
|
||||||
|
|
||||||
## Открытые вопросы для следующего шага
|
|
||||||
|
|
||||||
Эти вопросы нужно решить на проверочном примере, а не декларацией.
|
|
||||||
|
|
||||||
1. Где именно создаётся browser runtime по умолчанию: в graph composition или в convenience Provider домена? Вероятно, нужны обе формы: явный низкоуровневый Provider с `runtime` и ограниченный convenience root для независимого домена.
|
|
||||||
2. Какие зависимости вправе собирать domain-specific browser builder: только собственные adapters или также API других доменов? Рекомендуемое ограничение: другие domain APIs передаются снаружи как явный input.
|
|
||||||
3. Должен ли Query cache быть частью domain runtime или только React adapter? Базовая гипотеза: cache - React implementation detail, а domain runtime предоставляет commands/queries и invalidation intent в domain language.
|
|
||||||
4. В каких случаях domain adapter остаётся в приложении, а не рядом с business? Предлагаемый критерий: если адаптер реализует app-specific integration, недоступную другим приложениям монорепозитория, его можно держать в app assembly, сохраняя тот же port.
|
|
||||||
5. Нужно ли предоставлять Server Actions в `business/{domain}/server`? Базовая гипотеза: server action - framework entry и остаётся в app/composition; domain server facade предоставляет только business operation.
|
|
||||||
6. Какой минимальный contract нужен для server hydration domain state, чтобы не дублировать запрос на первом client render?
|
|
||||||
7. Какие типы domain UI допустимо экспортировать напрямую, а какие должны оставаться private implementation Provider/flow?
|
|
||||||
|
|
||||||
## Критерий принятия
|
|
||||||
|
|
||||||
Модель стоит переносить в каноны, только если проверочный домен позволяет одновременно ответить «да» на все вопросы:
|
|
||||||
|
|
||||||
- У домена один понятный owner, несмотря на server/client runtime surfaces?
|
|
||||||
- Фабрика по-прежнему является единственным creator business runtime?
|
|
||||||
- Hooks, Provider и domain UI colocated с доменом, а не вынесены ради технической чистоты?
|
|
||||||
- Server Component не тянет React/client module graph?
|
|
||||||
- Client Component не тянет server-only graph?
|
|
||||||
- Concrete implementation не протекает через public API?
|
|
||||||
- Сценарии, нормализация и errors не дублируются между server и client?
|
|
||||||
- Graph ownership и lifecycle instances по-прежнему явны?
|
|
||||||
- Новый подход проще объяснить и применить, чем текущие `business` + `compositions/business` + consumer composition?
|
|
||||||
|
|
||||||
Если хотя бы один ответ отрицательный, сначала нужно скорректировать proposal и проверить его на реальном коде, а не добавлять новые жёсткие правила.
|
|
||||||
@@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
[SLM Design Specification](./specification/index.md)
|
[SLM Design Specification](./specification/index.md)
|
||||||
|
|
||||||
|
Specification определяет base SLM и два независимых [architecture modes](./specification/architecture-modes.md): `SLM Advanced` и `SLM Pro`. Каждый mode является отдельным overlay непосредственно над base SLM.
|
||||||
|
|
||||||
## Границы текущего этапа
|
## Границы текущего этапа
|
||||||
|
|
||||||
На этом этапе в `docs2/` размещается только нормативная спецификация. Учебные материалы, руководства, примеры, справочники и agent skill будут проектироваться после стабилизации правил.
|
На этом этапе в `docs2/` размещается только нормативная спецификация. Учебные материалы, руководства, примеры, справочники и agent skill будут проектироваться после стабилизации правил.
|
||||||
|
|||||||
@@ -4,91 +4,69 @@ status: draft
|
|||||||
normative: true
|
normative: true
|
||||||
---
|
---
|
||||||
|
|
||||||
# Архитектурная модель
|
# Архитектурная Модель
|
||||||
|
|
||||||
## Структура приложения
|
## Структура приложения
|
||||||
|
|
||||||
**SLM-ARCH-001 - ОБЯЗАН.** SLM-приложение должно разделять код по ответственности между следующими слоями:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
src/
|
src/
|
||||||
├── app/
|
├── app/
|
||||||
├── compositions/
|
├── compositions/
|
||||||
├── domains/
|
|
||||||
├── infra/
|
├── infra/
|
||||||
├── ui/
|
├── ui/
|
||||||
└── shared/
|
└── shared/
|
||||||
```
|
```
|
||||||
|
|
||||||
Не каждый слой обязан содержать код в минимальном приложении, но роль каждого существующего модуля должна соответствовать одному владельцу.
|
**SLM-ARCH-001 - ОБЯЗАН.** Base SLM-приложение должно разделять код по ответственности между слоями `app`, `compositions`, `infra`, `ui` и `shared`.
|
||||||
|
|
||||||
|
Не каждый слой обязан содержать код в минимальном приложении. Пустые папки и speculative scaffolding не требуются.
|
||||||
|
|
||||||
## Группы ответственности
|
## Группы ответственности
|
||||||
|
|
||||||
| Группа | Слои | Ответственность |
|
| Группа | Слои | Ответственность |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Framework composition | `app`, `compositions` | Подключение к framework и сборка application flows |
|
| Framework composition | `app`, `compositions` | Подключение к framework и сборка application flows |
|
||||||
| Product | `domains` | Продуктовые модели, сценарии и runtime surfaces |
|
| Product | Product owner; в base SLM - `compositions` | Product semantics, UI и flows владеющего module |
|
||||||
| Technical | `infra`, `ui` | Технические capabilities и универсальный UI |
|
| Technical | `infra`, `ui` | Technical capabilities и универсальный UI |
|
||||||
| Foundation | `shared` | Детерминированный общий фундамент |
|
| Foundation | `shared` | Детерминированный общий фундамент |
|
||||||
|
|
||||||
## Верхнеуровневое направление
|
## Верхнеуровневое направление
|
||||||
|
|
||||||
```text
|
```text
|
||||||
app → compositions | shared
|
app -> compositions | shared
|
||||||
compositions → compositions | domains | infra | ui | shared
|
compositions -> compositions | infra | ui | shared
|
||||||
domains → infra | ui | shared согласно правилам внутренних зон
|
infra -> infra | shared
|
||||||
infra → infra | shared
|
ui -> ui | shared
|
||||||
ui → ui | shared
|
shared -/-> остальные SLM-слои
|
||||||
shared -/→ остальные SLM-слои
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Схема описывает imports между SLM-слоями проекта. Framework APIs и external packages регулируются ответственностью импортирующего слоя и не показаны как SLM-слои.
|
Framework APIs и external packages регулируются ответственностью импортирующего слоя и не показаны как SLM-слои.
|
||||||
|
|
||||||
**SLM-ARCH-002 - ОБЯЗАН.** Верхнеуровневое направление зависимостей между SLM-слоями должно соблюдаться для runtime imports и type imports, кроме явно описанных исключений.
|
**SLM-ARCH-002 - ОБЯЗАН.** Верхнеуровневое направление зависимостей между base SLM-слоями должно соблюдаться для runtime imports и type imports, кроме явно описанных исключений.
|
||||||
|
|
||||||
**SLM-ARCH-003 - ЗАПРЕЩЕНО.** Нижний слой не может импортировать `app` или `compositions`.
|
**SLM-ARCH-003 - ЗАПРЕЩЕНО.** Нижний слой не может импортировать `app` или `compositions`.
|
||||||
|
|
||||||
**SLM-ARCH-004 - ЗАПРЕЩЕНО.** `infra`, `ui` и `shared` не могут владеть product graph или выступать service locator для domain runtimes.
|
**SLM-ARCH-004 - ЗАПРЕЩЕНО.** `infra`, `ui` и `shared` не могут владеть product wiring или выступать service locator для application modules.
|
||||||
|
|
||||||
## Путь данных
|
## Путь данных
|
||||||
|
|
||||||
```text
|
```text
|
||||||
app
|
app
|
||||||
→ composition
|
-> product owner public API
|
||||||
→ domain runtime surface
|
-> infra public API
|
||||||
→ domain business scenario
|
-> external source
|
||||||
→ business-owned port
|
|
||||||
→ domain adapter
|
|
||||||
→ infra / SDK / storage / external source
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**SLM-ARCH-005 - ОБЯЗАН.** Каждый переход в цепочке продуктовых данных должен сохранять ownership: framework связывает, domain определяет semantics, adapter интегрирует, infra предоставляет technical capability.
|
**SLM-ARCH-005 - ОБЯЗАН.** Каждый переход product data должен сохранять ownership: framework связывает, product owner определяет semantics, а technical capability не присваивает себе product model.
|
||||||
|
|
||||||
## Путь UI
|
## Путь UI
|
||||||
|
|
||||||
```text
|
```text
|
||||||
app route
|
app route
|
||||||
→ page/layout composition
|
-> page/layout composition
|
||||||
→ domain UI и composition UI
|
-> product UI
|
||||||
→ universal UI
|
-> universal UI
|
||||||
→ shared styles/resources
|
-> shared styles/resources
|
||||||
```
|
```
|
||||||
|
|
||||||
Владение multi-domain и domain UI определяется правилами [SLM-CMP-006 - SLM-CMP-007](./layers/compositions.md#product-ui) и [SLM-FRM-013](./layers/domains/framework.md#domain-ui).
|
Product UI принадлежит product owner; в base SLM таким owner является composition. Универсальный product-agnostic UI принадлежит `ui`.
|
||||||
|
|
||||||
## Внутренняя модель domain
|
|
||||||
|
|
||||||
```text
|
|
||||||
domain client/server assembly
|
|
||||||
→ own business factory
|
|
||||||
→ own adapters
|
|
||||||
|
|
||||||
domain framework surface
|
|
||||||
→ own DomainRuntime через runtime access boundary
|
|
||||||
|
|
||||||
composition
|
|
||||||
→ создаёт несколько domain runtimes
|
|
||||||
→ передаёт готовые capabilities
|
|
||||||
```
|
|
||||||
|
|
||||||
Внутренняя assembly одного domain и cross-domain graph разделены правилами [Client и server assembly](./layers/domains/client-and-server.md) и [Compositions](./layers/compositions.md).
|
|
||||||
|
|||||||
81
docs2/specification/architecture-modes.md
Normal file
81
docs2/specification/architecture-modes.md
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
---
|
||||||
|
title: Архитектурные modes
|
||||||
|
status: draft
|
||||||
|
normative: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Архитектурные Modes
|
||||||
|
|
||||||
|
SLM является самостоятельной базовой архитектурой. Architecture mode - опциональный независимый overlay, который добавляет или явно заменяет отдельные правила base SLM.
|
||||||
|
|
||||||
|
```text
|
||||||
|
SLM Advanced = SLM + Advanced rules
|
||||||
|
SLM Pro = SLM + Pro rules
|
||||||
|
```
|
||||||
|
|
||||||
|
`SLM Advanced` и `SLM Pro` не наследуют друг друга. Совпадающее требование декларируется отдельно внутри каждого overlay и не создаёт общей mode-ветки.
|
||||||
|
|
||||||
|
## Выбор архитектуры
|
||||||
|
|
||||||
|
Приложение использует один из трёх вариантов:
|
||||||
|
|
||||||
|
```text
|
||||||
|
SLM
|
||||||
|
SLM + Advanced
|
||||||
|
SLM + Pro
|
||||||
|
```
|
||||||
|
|
||||||
|
**SLM-MODE-001 - ОБЯЗАН.** Приложение должно зафиксировать использование base SLM и, при наличии, ровно одного overlay: `Advanced` или `Pro`.
|
||||||
|
|
||||||
|
**SLM-MODE-002 - ЗАПРЕЩЕНО.** Одно приложение не может одновременно заявлять соответствие `SLM Advanced` и `SLM Pro`.
|
||||||
|
|
||||||
|
**SLM-MODE-003 - ОБЯЗАН.** Выбранный overlay должен применяться ко всему приложению в пределах одной SLM application boundary.
|
||||||
|
|
||||||
|
Выбор выполняет команда на стадии планирования. Сигналами могут быть количество product responsibilities, связанность modules, runtime state, client/server execution, lifecycle risks и количество команд разработки. Фиксированные числовые пороги не устанавливаются.
|
||||||
|
|
||||||
|
| Вариант | Когда рассматривать |
|
||||||
|
|---|---|
|
||||||
|
| `SLM` | Product responsibilities удобно удерживать внутри compositions без дополнительного слоя |
|
||||||
|
| `SLM Advanced` | Нужны самостоятельные domains, но команда хочет свободно выбирать их внутреннюю структуру и связи |
|
||||||
|
| `SLM Pro` | Нужны изолированные domains, явные runtime contracts, adapters, lifecycle и усиленные checks |
|
||||||
|
|
||||||
|
## Применимость правил
|
||||||
|
|
||||||
|
Base-правило имеет идентификатор вида:
|
||||||
|
|
||||||
|
```text
|
||||||
|
SLM-AREA-NNN
|
||||||
|
```
|
||||||
|
|
||||||
|
Mode-specific правила имеют идентификаторы:
|
||||||
|
|
||||||
|
```text
|
||||||
|
SLM-ADV-AREA-NNN
|
||||||
|
SLM-PRO-AREA-NNN
|
||||||
|
```
|
||||||
|
|
||||||
|
**SLM-MODE-004 - ОБЯЗАН.** Base-правила SLM применяются при любом выбранном варианте архитектуры. Если overlay явно заменяет base rule только в определённом scope, исходное base-правило продолжает действовать за пределами этого scope.
|
||||||
|
|
||||||
|
**SLM-MODE-005 - ОБЯЗАН.** Для `SLM Advanced` применяются только base-правила и правила из `modes/advanced`.
|
||||||
|
|
||||||
|
**SLM-MODE-006 - ОБЯЗАН.** Для `SLM Pro` применяются только base-правила и правила из `modes/pro`.
|
||||||
|
|
||||||
|
**SLM-MODE-007 - ЗАПРЕЩЕНО.** Правило другого overlay не может использоваться как обязательное требование, разрешение или исключение.
|
||||||
|
|
||||||
|
**SLM-MODE-008 - ОБЯЗАН.** Mode-specific правило, заменяющее base-поведение, должно явно назвать заменяемый base rule ID или нормативный раздел и точный scope замены.
|
||||||
|
|
||||||
|
## Независимые overlays
|
||||||
|
|
||||||
|
### SLM Advanced
|
||||||
|
|
||||||
|
[SLM Advanced](./modes/advanced/index.md) описывает полный Advanced-delta относительно base SLM.
|
||||||
|
|
||||||
|
### SLM Pro
|
||||||
|
|
||||||
|
[SLM Pro](./modes/pro/index.md) описывает полный Pro-delta относительно base SLM.
|
||||||
|
|
||||||
|
## Изменение overlay
|
||||||
|
|
||||||
|
**SLM-MODE-009 - МОЖЕТ.** Команда может подключить, заменить или удалить overlay при изменении требований к архитектуре.
|
||||||
|
|
||||||
|
**SLM-MODE-010 - ОБЯЗАН.** После изменения конфигурации приложение может заявлять соответствие только после выполнения применимых base-правил с учётом scoped replacements и, при наличии, полного rule set выбранного overlay.
|
||||||
@@ -4,13 +4,13 @@ status: draft
|
|||||||
normative: true
|
normative: true
|
||||||
---
|
---
|
||||||
|
|
||||||
# Основные инварианты
|
# Основные Инварианты
|
||||||
|
|
||||||
SLM Design организует frontend-приложение по владельцам ответственности. Архитектурная единица определяется не типом файла, а тем, кто владеет моделью, поведением, данными, runtime и lifecycle.
|
SLM Design организует frontend-приложение по владельцам ответственности. Архитектурная единица определяется не типом файла, а тем, кто владеет моделью, поведением, данными, runtime и lifecycle.
|
||||||
|
|
||||||
## Ответственность до размещения
|
## Ответственность до размещения
|
||||||
|
|
||||||
**SLM-FND-001 - ОБЯЗАН.** Перед размещением кода необходимо определить его владельца, public API, runtime-зависимости и lifecycle scope.
|
**SLM-FND-001 - ОБЯЗАН.** Перед размещением кода необходимо определить его владельца, public boundary, runtime dependencies и lifecycle scope.
|
||||||
|
|
||||||
**SLM-FND-002 - СЛЕДУЕТ.** Код следует размещать в минимальном scope, который полностью владеет его ответственностью.
|
**SLM-FND-002 - СЛЕДУЕТ.** Код следует размещать в минимальном scope, который полностью владеет его ответственностью.
|
||||||
|
|
||||||
@@ -18,13 +18,13 @@ SLM Design организует frontend-приложение по владел
|
|||||||
|
|
||||||
## Путь продуктовых данных
|
## Путь продуктовых данных
|
||||||
|
|
||||||
Внешний сервис может оставаться физическим источником данных. Domain business является единственным публичным шлюзом доменной истины внутри приложения. Точные требования определены правилами [SLM-DATA-001 - SLM-DATA-003](./state-and-data.md#domain-gateway) и [SLM-BUS-017 - SLM-BUS-020](./layers/domains/business.md#normalization-и-errors).
|
Product data проходят через public boundary текущего владельца согласно [SLM-DATA-001](./state-and-data.md#product-gateway). Внешний сервис может оставаться физическим источником данных, но transport contract не становится product model автоматически.
|
||||||
|
|
||||||
## Явные зависимости
|
## Явные зависимости
|
||||||
|
|
||||||
**SLM-FND-007 - ОБЯЗАН.** Runtime-возможности должны поступать владельцу поведения через явные contracts, а не через скрытые imports, service locator или global mutable state.
|
**SLM-FND-007 - ОБЯЗАН.** Runtime capabilities должны поступать владельцу поведения через разрешённые imports, явные arguments или contracts, а не через скрытый service locator или global mutable state.
|
||||||
|
|
||||||
**SLM-FND-008 - ЗАПРЕЩЕНО.** Type cast, barrel, alias, dynamic import или helper в `shared` не могут использоваться для обхода архитектурной границы.
|
**SLM-FND-008 - ЗАПРЕЩЕНО.** Type cast, barrel, alias, dynamic import или helper в `shared` не могут использоваться для обхода применимой архитектурной границы.
|
||||||
|
|
||||||
## Public API
|
## Public API
|
||||||
|
|
||||||
@@ -32,8 +32,8 @@ SLM Design организует frontend-приложение по владел
|
|||||||
|
|
||||||
## Scope и lifecycle
|
## Scope и lifecycle
|
||||||
|
|
||||||
Создание, scope, activation и cleanup runtime определены в [Runtime и lifecycle](./runtime-and-lifecycle.md).
|
Создание, scope, activation и cleanup применимых runtimes и resources определены в [Runtime и lifecycle](./runtime-and-lifecycle.md).
|
||||||
|
|
||||||
## Композиция доменов
|
## Overlays
|
||||||
|
|
||||||
Cross-domain runtime graph регулируется [SLM-CMP-001 - SLM-CMP-005](./layers/compositions.md#cross-domain-graph) и [Cross-domain boundary](./layers/domains/cross-domain-boundary.md).
|
Base SLM не вводит дополнительные архитектурные слои и специализированные runtime contracts. Каждый overlay самостоятельно определяет свои добавления и замены base-правил.
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ normative: true
|
|||||||
|
|
||||||
# SLM Design Specification
|
# SLM Design Specification
|
||||||
|
|
||||||
Эта директория содержит единый нормативный корпус SLM Design 2.0. Спецификация разделена на главы, но имеет общую версию, общий статус и единый приоритет правил.
|
Эта директория содержит единый нормативный корпус SLM Design 2.0. Base SLM является законченной минимальной архитектурой; дополнительные ограничения подключаются независимыми overlays `SLM Advanced` или `SLM Pro`.
|
||||||
|
|
||||||
Пока статус равен `draft`, документы описывают проектируемую архитектуру и не заменяют действующую документацию в `docs/`.
|
Пока статус равен `draft`, документы описывают проектируемую архитектуру и не заменяют действующую документацию в `docs/`.
|
||||||
|
|
||||||
@@ -20,7 +20,16 @@ normative: true
|
|||||||
| `СЛЕДУЕТ` | Рекомендуемое решение; отступление требует явного обоснования |
|
| `СЛЕДУЕТ` | Рекомендуемое решение; отступление требует явного обоснования |
|
||||||
| `МОЖЕТ` | Допустимый, но необязательный вариант |
|
| `МОЖЕТ` | Допустимый, но необязательный вариант |
|
||||||
|
|
||||||
Правила имеют стабильные идентификаторы вида `SLM-AREA-NNN`. Точное нормативное требование принадлежит только той главе, где объявлен его rule ID. Обзорная глава может ссылаться на правило, но не должна объявлять его повторно под новым ID.
|
Правила имеют стабильные идентификаторы. Base использует формат `SLM-AREA-NNN`, Advanced - `SLM-ADV-AREA-NNN`, Pro - `SLM-PRO-AREA-NNN`. Точное нормативное требование принадлежит только той главе, где объявлен его rule ID.
|
||||||
|
|
||||||
|
## Architecture modes
|
||||||
|
|
||||||
|
Base SLM не требует overlay. Если команда выбирает дополнительную архитектурную политику, она подключает ровно один независимый mode согласно [Архитектурным modes](./architecture-modes.md):
|
||||||
|
|
||||||
|
```text
|
||||||
|
SLM Advanced = SLM + Advanced rules
|
||||||
|
SLM Pro = SLM + Pro rules
|
||||||
|
```
|
||||||
|
|
||||||
## Приоритет
|
## Приоритет
|
||||||
|
|
||||||
@@ -30,7 +39,7 @@ normative: true
|
|||||||
|
|
||||||
**SLM-DOC-003 - ОБЯЗАН.** Изменение принятого архитектурного правила должно вноситься в главу, которая владеет соответствующим rule ID.
|
**SLM-DOC-003 - ОБЯЗАН.** Изменение принятого архитектурного правила должно вноситься в главу, которая владеет соответствующим rule ID.
|
||||||
|
|
||||||
## Главы
|
## Base SLM
|
||||||
|
|
||||||
### Основы
|
### Основы
|
||||||
|
|
||||||
@@ -43,19 +52,10 @@ normative: true
|
|||||||
- [Обзор слоёв](./layers/index.md)
|
- [Обзор слоёв](./layers/index.md)
|
||||||
- [App](./layers/app.md)
|
- [App](./layers/app.md)
|
||||||
- [Compositions](./layers/compositions.md)
|
- [Compositions](./layers/compositions.md)
|
||||||
- [Domains](./layers/domains/index.md)
|
|
||||||
- [Infra](./layers/infra.md)
|
- [Infra](./layers/infra.md)
|
||||||
- [UI](./layers/ui.md)
|
- [UI](./layers/ui.md)
|
||||||
- [Shared](./layers/shared.md)
|
- [Shared](./layers/shared.md)
|
||||||
|
|
||||||
### Внутренняя модель Domains
|
|
||||||
|
|
||||||
- [Business](./layers/domains/business.md)
|
|
||||||
- [Framework surface](./layers/domains/framework.md)
|
|
||||||
- [Ports и adapters](./layers/domains/ports-and-adapters.md)
|
|
||||||
- [Client и server assembly](./layers/domains/client-and-server.md)
|
|
||||||
- [Cross-domain boundary](./layers/domains/cross-domain-boundary.md)
|
|
||||||
|
|
||||||
### Общие правила
|
### Общие правила
|
||||||
|
|
||||||
- [Модули и группы](./modules-and-groups.md)
|
- [Модули и группы](./modules-and-groups.md)
|
||||||
@@ -66,8 +66,26 @@ normative: true
|
|||||||
- [Тестирование и соответствие](./testing-and-conformance.md)
|
- [Тестирование и соответствие](./testing-and-conformance.md)
|
||||||
- [Монорепозитории](./monorepo.md)
|
- [Монорепозитории](./monorepo.md)
|
||||||
|
|
||||||
|
## Overlays
|
||||||
|
|
||||||
|
### SLM Advanced
|
||||||
|
|
||||||
|
- [Отличия Advanced от base SLM](./modes/advanced/index.md)
|
||||||
|
- [Domains в SLM Advanced](./modes/advanced/domains.md)
|
||||||
|
|
||||||
|
### SLM Pro
|
||||||
|
|
||||||
|
- [Отличия Pro от base SLM](./modes/pro/index.md)
|
||||||
|
- [Domains в SLM Pro](./modes/pro/domains/index.md)
|
||||||
|
- [Business](./modes/pro/domains/business.md)
|
||||||
|
- [Framework surface](./modes/pro/domains/framework.md)
|
||||||
|
- [Ports и adapters](./modes/pro/domains/ports-and-adapters.md)
|
||||||
|
- [Client и server assembly](./modes/pro/domains/client-and-server.md)
|
||||||
|
- [Cross-domain boundary](./modes/pro/domains/cross-domain-boundary.md)
|
||||||
|
- [Тестирование Pro domains](./modes/pro/domains/testing.md)
|
||||||
|
|
||||||
## Область текущего draft
|
## Область текущего draft
|
||||||
|
|
||||||
Спецификация фиксирует уже согласованные границы слоёв, доменов, business-фабрик, adapters, runtime assembly и cross-domain composition.
|
Base SLM фиксирует ownership, пять основных слоёв, public boundaries, state и lifecycle. Текущие версии Advanced и Pro в первую очередь определяют собственные независимые модели слоя `domains`; будущие mode-specific правила могут относиться к любому разделу архитектуры.
|
||||||
|
|
||||||
Точная форма React Providers, окончательная политика package extraction для domains и единая модель query cache не фиксируются сверх явно объявленных в соответствующих главах инвариантов.
|
Точная форма React Providers, окончательная политика package extraction и единая модель query cache не фиксируются сверх явно объявленных инвариантов base или выбранного overlay.
|
||||||
|
|||||||
@@ -28,17 +28,17 @@ framework route
|
|||||||
→ composition entry
|
→ composition entry
|
||||||
```
|
```
|
||||||
|
|
||||||
**SLM-APP-002 - ЗАПРЕЩЕНО.** `app` не может владеть product page, screen, widget, domain scenario, store, domain Provider или cross-domain graph.
|
**SLM-APP-002 - ЗАПРЕЩЕНО.** `app` не может владеть product page, screen, widget, product scenario, store или application wiring.
|
||||||
|
|
||||||
**SLM-APP-003 - ЗАПРЕЩЕНО.** Route entry не должен напрямую собирать domain adapters, вызывать SDK или формировать product model.
|
**SLM-APP-003 - ЗАПРЕЩЕНО.** Route entry не должен напрямую собирать product integrations, вызывать SDK или формировать product model.
|
||||||
|
|
||||||
**SLM-APP-004 - ОБЯЗАН.** Framework-specific input должен быть считан в `app` и передан вниз в минимальной нормализованной форме.
|
**SLM-APP-004 - ОБЯЗАН.** Framework-specific input должен быть считан в `app` и передан вниз в минимальной нормализованной форме.
|
||||||
|
|
||||||
Механическая нормализация включает извлечение route params, headers и framework wrappers. Product validation, создание value objects и выбор domain outcome остаются в domain business.
|
Механическая нормализация включает извлечение route params, headers и framework wrappers. Product validation, создание value objects и выбор product outcome остаются у владельца product semantics.
|
||||||
|
|
||||||
**SLM-APP-005 - ЗАПРЕЩЕНО.** Другие SLM-слои не могут импортировать `app`.
|
Запрет другим SLM-слоям импортировать `app` определяется base-правилом `SLM-ARCH-003`.
|
||||||
|
|
||||||
**SLM-APP-006 - СЛЕДУЕТ.** Framework behavior, которому нужен product graph или product UI, следует реализовать готовым composition entry и только подключить из `app`.
|
**SLM-APP-006 - СЛЕДУЕТ.** Framework behavior, которому нужны product dependencies или product UI, следует реализовать готовым composition entry и только подключить из `app`.
|
||||||
|
|
||||||
**SLM-APP-007 - МОЖЕТ.** `app` может напрямую импортировать framework APIs и static/global resources из `shared`, если framework требует подключить их в root entry.
|
**SLM-APP-007 - МОЖЕТ.** `app` может напрямую импортировать framework APIs и static/global resources из `shared`, если framework требует подключить их в root entry.
|
||||||
|
|
||||||
@@ -57,14 +57,14 @@ app/
|
|||||||
└── page.tsx
|
└── page.tsx
|
||||||
```
|
```
|
||||||
|
|
||||||
## Недопустимые владельцы
|
## Примеры нарушений
|
||||||
|
|
||||||
Следующие сущности не должны определяться в `app`:
|
Следующие сущности являются примерами нарушений `SLM-APP-002` и `SLM-APP-003`:
|
||||||
|
|
||||||
- `ProductPage`;
|
- `ProductPage`;
|
||||||
- `AuthProvider`;
|
- product Provider;
|
||||||
- `createOrdersRuntime`;
|
- application service creator;
|
||||||
- page-local store;
|
- page-local store;
|
||||||
- domain mapper;
|
- product mapper;
|
||||||
- reusable product component;
|
- reusable product component;
|
||||||
- concrete product adapter.
|
- concrete product integration.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ normative: true
|
|||||||
|
|
||||||
# Слой Compositions
|
# Слой Compositions
|
||||||
|
|
||||||
`compositions` собирает application flows из готовых domain runtimes, infra capabilities, UI modules и других composition modules.
|
`compositions` собирает application flows из module public APIs, technical capabilities и UI modules и может владеть product logic в пределах своей ответственности.
|
||||||
|
|
||||||
## Ответственность
|
## Ответственность
|
||||||
|
|
||||||
@@ -18,10 +18,10 @@ Composition может быть:
|
|||||||
- screen;
|
- screen;
|
||||||
- widget;
|
- widget;
|
||||||
- provider composition;
|
- provider composition;
|
||||||
- multi-domain hook;
|
- multi-module hook;
|
||||||
- non-visual graph owner.
|
- non-visual application wiring owner.
|
||||||
|
|
||||||
Структура слоя свободна и должна отражать продуктовую навигацию приложения.
|
Структура слоя свободна и отражает продуктовую навигацию приложения.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
compositions/
|
compositions/
|
||||||
@@ -34,41 +34,29 @@ compositions/
|
|||||||
|
|
||||||
Эти папки являются groups, а не отдельными слоями.
|
Эти папки являются groups, а не отдельными слоями.
|
||||||
|
|
||||||
## Cross-domain graph
|
## Product ownership
|
||||||
|
|
||||||
**SLM-CMP-001 - ОБЯЗАН.** Runtime graph нескольких domains должен собираться в composition, которая владеет его scope.
|
**SLM-CMP-001 - ОБЯЗАН.** Product flow и его локальная product logic должны принадлежать минимальной composition, охватывающей всех consumers этой ответственности.
|
||||||
|
|
||||||
```ts
|
Composition может использовать public API `infra` для external operations, сохраняя product mapping, outcomes и fallback semantics у себя.
|
||||||
const auth = createAuthRuntime()
|
|
||||||
const user = createUserRuntime({ auth: auth.session })
|
|
||||||
const orders = createOrdersRuntime({ user: user.agreements })
|
|
||||||
```
|
|
||||||
|
|
||||||
**SLM-CMP-002 - ОБЯЗАН.** Composition должна создавать domain runtimes в явном ацикличном порядке.
|
## Public boundaries
|
||||||
|
|
||||||
**SLM-CMP-003 - ОБЯЗАН.** Cross-domain dependency должна передаваться как готовая минимальная capability, а не разрешаться service locator или domain import.
|
**SLM-CMP-005 - ЗАПРЕЩЕНО.** Composition не может импортировать private services, integrations, stores, Context или другие internal paths используемого module.
|
||||||
|
|
||||||
**SLM-CMP-004 - ЗАПРЕЩЕНО.** Composition не может повторять adapter wiring, если domain public assembly уже создаёт готовый runtime.
|
|
||||||
|
|
||||||
**SLM-CMP-005 - ЗАПРЕЩЕНО.** Composition не должна импортировать private business services, domain adapters, SDK-specific domain integration или внутренний Context domain.
|
|
||||||
|
|
||||||
**SLM-CMP-013 - ОБЯЗАН.** Composition должна использовать public client/server creator domain, если domain предоставляет runtime-specific assembly.
|
|
||||||
|
|
||||||
**SLM-CMP-014 - МОЖЕТ.** Composition может вызвать public business factory напрямую только для полностью universal domain без concrete adapters и runtime-specific assembly.
|
|
||||||
|
|
||||||
## Product UI
|
## Product UI
|
||||||
|
|
||||||
**SLM-CMP-006 - ОБЯЗАН.** UI, объединяющий несколько domains, route/page scope или application flow, принадлежит `compositions`.
|
**SLM-CMP-006 - ОБЯЗАН.** UI, объединяющий несколько самостоятельных modules, route/page scope либо application flow, принадлежит `compositions`.
|
||||||
|
|
||||||
Примеры:
|
Примеры:
|
||||||
|
|
||||||
- header, объединяющий auth, cart и navigation;
|
- application header;
|
||||||
- order flow, который требует auth и user agreements;
|
- order flow, объединяющий несколько product responsibilities;
|
||||||
- page screen;
|
- page screen;
|
||||||
- route guard с navigation outcome;
|
- route guard с navigation outcome;
|
||||||
- widget, использующий hooks двух domains.
|
- widget, использующий public APIs двух самостоятельных modules.
|
||||||
|
|
||||||
**SLM-CMP-007 - МОЖЕТ.** Composition может использовать domain UI и universal UI, передавать им props, callbacks и slots.
|
**SLM-CMP-007 - МОЖЕТ.** Composition может использовать product UI, опубликованный другими modules, и universal UI, передавая props, callbacks и slots.
|
||||||
|
|
||||||
## State
|
## State
|
||||||
|
|
||||||
@@ -82,15 +70,13 @@ const orders = createOrdersRuntime({ user: user.agreements })
|
|||||||
- presentation filters;
|
- presentation filters;
|
||||||
- состояние раскрытия section.
|
- состояние раскрытия section.
|
||||||
|
|
||||||
**SLM-CMP-009 - ЗАПРЕЩЕНО.** Page store не может становиться параллельным владельцем domain model или product cache.
|
**SLM-CMP-009 - ЗАПРЕЩЕНО.** Page store не может становиться параллельным владельцем product model или canonical product cache другого owner.
|
||||||
|
|
||||||
## Imports
|
## Imports
|
||||||
|
|
||||||
**SLM-CMP-010 - МОЖЕТ.** Composition module может импортировать public API других composition modules, domains, infra, ui и shared.
|
**SLM-CMP-010 - МОЖЕТ.** Composition module может импортировать public API других composition modules, infra, ui и shared.
|
||||||
|
|
||||||
**SLM-CMP-011 - ЗАПРЕЩЕНО.** Runtime-циклы между composition modules запрещены.
|
Runtime-циклы между composition modules запрещены base-правилом `SLM-API-016`.
|
||||||
|
|
||||||
**SLM-CMP-012 - ОБЯЗАН.** App-specific graph type должен отражать только реально предоставленные runtimes; `Partial<Graph>` с последующим приведением к полному graph запрещён.
|
|
||||||
|
|
||||||
**SLM-CMP-015 - ОБЯЗАН.** Client и server composition entries должны иметь раздельные public entrypoints и environment markers, если composition участвует в обоих runtime graphs.
|
**SLM-CMP-015 - ОБЯЗАН.** Client и server composition entries должны иметь раздельные public entrypoints и environment markers, если composition участвует в обоих runtime graphs.
|
||||||
|
|
||||||
|
|||||||
@@ -1,128 +0,0 @@
|
|||||||
---
|
|
||||||
title: Business domain
|
|
||||||
status: draft
|
|
||||||
normative: true
|
|
||||||
---
|
|
||||||
|
|
||||||
# Business
|
|
||||||
|
|
||||||
`business` является framework-neutral зоной domain и единственным владельцем его продуктовой semantics.
|
|
||||||
|
|
||||||
## Структура
|
|
||||||
|
|
||||||
```text
|
|
||||||
domains/{group...}/{domain}/business/
|
|
||||||
├── {domain}.factory.ts
|
|
||||||
├── index.ts
|
|
||||||
├── types/
|
|
||||||
├── ports/
|
|
||||||
├── services/
|
|
||||||
├── errors/
|
|
||||||
├── mappers/
|
|
||||||
├── selectors/
|
|
||||||
├── validators/
|
|
||||||
└── lib/
|
|
||||||
```
|
|
||||||
|
|
||||||
Конкретный набор внутренних segments определяется размером domain. Обязательны роль factory и public boundary, но не каждая папка из примера.
|
|
||||||
|
|
||||||
## Factory boundary
|
|
||||||
|
|
||||||
**SLM-BUS-001 - ОБЯЗАН.** Business должен создавать public runtime API через factory `{domain}Factory`.
|
|
||||||
|
|
||||||
**SLM-BUS-002 - ОБЯЗАН.** Factory должна принимать все runtime capabilities через business-owned dependency contracts.
|
|
||||||
|
|
||||||
**SLM-BUS-003 - ОБЯЗАН.** Factory должна возвращать framework-neutral DomainRuntime или logic API domain.
|
|
||||||
|
|
||||||
**SLM-BUS-004 - ЗАПРЕЩЕНО.** Factory не может возвращать React hooks, components, Providers, layouts, route guards или framework boundaries.
|
|
||||||
|
|
||||||
**SLM-BUS-005 - ЗАПРЕЩЕНО.** Factory constructor не может выполнять I/O, открывать socket, регистрировать subscription, запускать timer или читать hidden environment.
|
|
||||||
|
|
||||||
## Public API
|
|
||||||
|
|
||||||
**SLM-BUS-006 - ОБЯЗАН.** `business/index.ts` должен экспортировать единственное runtime value: factory.
|
|
||||||
|
|
||||||
**SLM-BUS-007 - МОЖЕТ.** `business/index.ts` может экспортировать business-owned types через `export type`.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
export { authFactory } from './auth.factory'
|
|
||||||
|
|
||||||
export type {
|
|
||||||
AuthDeps,
|
|
||||||
AuthFactory,
|
|
||||||
AuthRuntime,
|
|
||||||
AuthState,
|
|
||||||
} from './types'
|
|
||||||
```
|
|
||||||
|
|
||||||
**SLM-BUS-008 - ЗАПРЕЩЕНО.** Error classes, error guards, error code constants, selectors, validators, formatters, services, mappers и port implementations не экспортируются как отдельные runtime values.
|
|
||||||
|
|
||||||
Если внешнему consumer нужна такая capability, она должна быть осмысленной частью factory runtime API, а не обходным direct export.
|
|
||||||
|
|
||||||
## Runtime API
|
|
||||||
|
|
||||||
DomainRuntime может предоставлять:
|
|
||||||
|
|
||||||
- commands;
|
|
||||||
- imperative queries;
|
|
||||||
- snapshots;
|
|
||||||
- subscriptions;
|
|
||||||
- selectors через стабильные methods;
|
|
||||||
- validation operations;
|
|
||||||
- typed outcomes;
|
|
||||||
- explicit lifecycle operations.
|
|
||||||
|
|
||||||
**SLM-BUS-009 - ОБЯЗАН.** Runtime API должен говорить на языке domain и не повторять endpoint names, SDK tree или storage schema.
|
|
||||||
|
|
||||||
**SLM-BUS-010 - ЗАПРЕЩЕНО.** Public contract не может раскрывать generated DTO, SDK client, query-library result, concrete store API, raw Context или adapter.
|
|
||||||
|
|
||||||
## Dependencies и ports
|
|
||||||
|
|
||||||
**SLM-BUS-011 - ОБЯЗАН.** Business-owned dependency описывает минимальную внешнюю возможность на языке domain.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
export type AuthPhonePort = {
|
|
||||||
requestCode: (phone: string) => Promise<unknown>
|
|
||||||
verifyCode: (input: VerifyPhoneCodeInput) => Promise<unknown>
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**SLM-BUS-012 - ОБЯЗАН.** Ненадёжный внешний результат должен приниматься как `unknown`, если business обязан проверить его runtime-форму.
|
|
||||||
|
|
||||||
**SLM-BUS-013 - ЗАПРЕЩЕНО.** Business dependency не может быть generated DTO, полный SDK client, `StoreApi`, QueryClient или framework hook.
|
|
||||||
|
|
||||||
**SLM-BUS-014 - ОБЯЗАН.** Subscription port должен предоставлять cleanup contract.
|
|
||||||
|
|
||||||
## Imports
|
|
||||||
|
|
||||||
Business может runtime-импортировать:
|
|
||||||
|
|
||||||
- собственные файлы;
|
|
||||||
- детерминированный `shared`;
|
|
||||||
- pure libraries без I/O, hidden state и public type leakage.
|
|
||||||
|
|
||||||
Business может type-only импортировать стабильный public contract другого domain, если dependency невозможно корректно описать локальным port. Локальный consumer-owned port является предпочтительным вариантом.
|
|
||||||
|
|
||||||
**SLM-BUS-015 - ЗАПРЕЩЕНО.** Business не импортирует React, query runtime, state manager, SDK, generated operation, HTTP client, storage implementation, browser API, infra, composition или assembly.
|
|
||||||
|
|
||||||
Cross-domain imports дополнительно регулируются [SLM-XDOM-001 и SLM-XDOM-005 - SLM-XDOM-008](./cross-domain-boundary.md).
|
|
||||||
|
|
||||||
## Normalization и errors
|
|
||||||
|
|
||||||
**SLM-BUS-017 - ОБЯЗАН.** External result должен быть нормализован в business-owned model до выхода из DomainRuntime.
|
|
||||||
|
|
||||||
**SLM-BUS-018 - ОБЯЗАН.** Malformed successful response должен считаться нарушением runtime contract, а не валидным отсутствием данных.
|
|
||||||
|
|
||||||
**SLM-BUS-019 - ОБЯЗАН.** Expected domain outcome и technical failure должны быть различимы в public contract.
|
|
||||||
|
|
||||||
**SLM-BUS-020 - ЗАПРЕЩЕНО.** Source error, HTTP status, SDK error class, raw response и transport message не могут быть consumer contract.
|
|
||||||
|
|
||||||
Business может выражать ожидаемые outcomes через typed result или domain error. Эта draft-версия не предписывает единственную форму обработки ожидаемых ошибок, но требует business-owned semantics и стабильных discriminants.
|
|
||||||
|
|
||||||
## State
|
|
||||||
|
|
||||||
**SLM-BUS-021 - ОБЯЗАН.** Business владеет domain state model, допустимыми transitions и semantics commands/selectors.
|
|
||||||
|
|
||||||
**SLM-BUS-022 - ЗАПРЕЩЕНО.** Business не импортирует concrete store implementation.
|
|
||||||
|
|
||||||
Framework-neutral state runtime может быть создан самой factory или предоставлен через business-owned port. Выбор не должен раскрывать concrete implementation в public API.
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
---
|
|
||||||
title: Client и server assembly domain
|
|
||||||
status: draft
|
|
||||||
normative: true
|
|
||||||
---
|
|
||||||
|
|
||||||
# Client и Server Assembly
|
|
||||||
|
|
||||||
`client` и `server` создают готовые runtime-specific instances одного domain поверх его business factory и adapters.
|
|
||||||
|
|
||||||
## Client assembly
|
|
||||||
|
|
||||||
```text
|
|
||||||
domains/{group...}/{domain}/client/
|
|
||||||
├── create-{domain}-client-runtime.ts
|
|
||||||
└── index.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
```ts
|
|
||||||
export const createAuthClientRuntime = (): AuthRuntime => {
|
|
||||||
return authFactory({
|
|
||||||
phoneAuth: browserPhoneAuthAdapter,
|
|
||||||
session: browserSessionAdapter,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**SLM-ASM-001 - ОБЯЗАН.** Client assembly может импортировать только собственную business factory, собственные client adapters, собственную React surface и необходимые runtime-specific technical inputs.
|
|
||||||
|
|
||||||
**SLM-ASM-002 - ОБЯЗАН.** Client assembly должна возвращать готовый runtime собственного domain.
|
|
||||||
|
|
||||||
**SLM-ASM-003 - ЗАПРЕЩЕНО.** Client assembly одного domain не может импортировать creator или runtime другого domain.
|
|
||||||
|
|
||||||
**SLM-ASM-004 - МОЖЕТ.** Client assembly может принимать готовую внешнюю capability через собственный input contract.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
createUserClientRuntime({ auth: auth.session })
|
|
||||||
```
|
|
||||||
|
|
||||||
Такой input не даёт user domain права создавать AuthRuntime или импортировать его client entrypoint.
|
|
||||||
|
|
||||||
## Server assembly
|
|
||||||
|
|
||||||
```text
|
|
||||||
domains/{group...}/{domain}/server/
|
|
||||||
├── create-{domain}-server-runtime.ts
|
|
||||||
└── index.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
**SLM-ASM-005 - ОБЯЗАН.** Server assembly должна создавать новый runtime в scope, соответствующем request или другой явно выбранной server lifetime.
|
|
||||||
|
|
||||||
**SLM-ASM-006 - ЗАПРЕЩЕНО.** Request credentials, cookies, headers и user-specific state не могут сохраняться в process-level mutable singleton.
|
|
||||||
|
|
||||||
**SLM-ASM-007 - ОБЯЗАН.** Framework/request input используется только для создания server adapters и не протекает как raw framework object в business API.
|
|
||||||
|
|
||||||
**SLM-ASM-008 - ОБЯЗАН.** Server entrypoint должен иметь явный server-only marker, если framework предоставляет такой механизм.
|
|
||||||
|
|
||||||
**SLM-ASM-015 - МОЖЕТ.** Server assembly может принимать готовую внешнюю capability через собственный input contract на тех же условиях, что и client assembly.
|
|
||||||
|
|
||||||
**SLM-ASM-016 - ОБЯЗАН.** Server assembly может импортировать только собственную business factory, собственные server adapters и необходимые server technical inputs; импорт React/client surface запрещён.
|
|
||||||
|
|
||||||
## Constructor и activation
|
|
||||||
|
|
||||||
Assembly определяет способ создания, но не владеет полным cross-domain graph.
|
|
||||||
|
|
||||||
**SLM-ASM-009 - ЗАПРЕЩЕНО.** Вызов runtime creator не должен выполнять product request, открывать socket или запускать background resource.
|
|
||||||
|
|
||||||
```text
|
|
||||||
module import
|
|
||||||
→ определяет creator
|
|
||||||
|
|
||||||
creator call
|
|
||||||
→ создаёт runtime instance
|
|
||||||
|
|
||||||
explicit start
|
|
||||||
→ запускает resources
|
|
||||||
```
|
|
||||||
|
|
||||||
**SLM-ASM-010 - ОБЯЗАН.** Resources запускает graph owner в выбранном scope согласно [lifecycle rules](../../runtime-and-lifecycle.md).
|
|
||||||
|
|
||||||
## Public entrypoints
|
|
||||||
|
|
||||||
**SLM-ASM-011 - ОБЯЗАН.** Client и server assembly должны иметь разные public entrypoints.
|
|
||||||
|
|
||||||
**SLM-ASM-012 - ЗАПРЕЩЕНО.** Общий domain barrel не может runtime-реэкспортировать одновременно client и server surfaces.
|
|
||||||
|
|
||||||
## Server/client bridge
|
|
||||||
|
|
||||||
Client и server runtimes являются разными instances над общей business semantics.
|
|
||||||
|
|
||||||
**SLM-ASM-013 - ЗАПРЕЩЕНО.** DomainRuntime, functions, Context, store или query client нельзя передавать через serializable server/client boundary.
|
|
||||||
|
|
||||||
**SLM-ASM-014 - МОЖЕТ.** Server может передать client assembly только serializable business-owned bootstrap data без secrets и mutable runtime objects.
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
---
|
|
||||||
title: Cross-domain boundary
|
|
||||||
status: draft
|
|
||||||
normative: true
|
|
||||||
---
|
|
||||||
|
|
||||||
# Cross-domain Boundary
|
|
||||||
|
|
||||||
Domains не образуют скрытый runtime graph внутри слоя `domains`. Граф связывается только graph owner в `compositions`.
|
|
||||||
|
|
||||||
## Runtime imports
|
|
||||||
|
|
||||||
**SLM-XDOM-001 - ЗАПРЕЩЕНО.** `business` domain A не импортирует runtime values domain B.
|
|
||||||
|
|
||||||
**SLM-XDOM-002 - ЗАПРЕЩЕНО.** Framework surface domain A не импортирует hooks, Provider, Context, components или runtime domain B.
|
|
||||||
|
|
||||||
**SLM-XDOM-003 - ЗАПРЕЩЕНО.** Adapter domain A не импортирует adapter или runtime domain B.
|
|
||||||
|
|
||||||
**SLM-XDOM-004 - ЗАПРЕЩЕНО.** Client/server assembly domain A не импортирует runtime creator domain B.
|
|
||||||
|
|
||||||
Запрет распространяется на direct import, barrel re-export, dynamic import, lazy import и service locator resolution.
|
|
||||||
|
|
||||||
## Type-only contracts
|
|
||||||
|
|
||||||
**SLM-XDOM-005 - МОЖЕТ.** Business и client/server input contracts domain могут type-only импортировать минимальный стабильный business contract другого domain.
|
|
||||||
|
|
||||||
**SLM-XDOM-006 - СЛЕДУЕТ.** Зависимому domain следует объявлять consumer-owned port, если capability можно описать без зависимости от полного foreign API.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
export type UserAuthPort = {
|
|
||||||
getSessionSnapshot: () => SessionSnapshot
|
|
||||||
subscribeToSession: (listener: () => void) => () => void
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Type-only import не разрешает runtime import и не переносит ownership.
|
|
||||||
|
|
||||||
**SLM-XDOM-012 - ЗАПРЕЩЕНО.** Type dependency cycle между domains запрещён, даже если не создаёт runtime cycle.
|
|
||||||
|
|
||||||
## Runtime capability injection
|
|
||||||
|
|
||||||
**SLM-XDOM-007 - МОЖЕТ.** Domain runtime creator может принять готовую structurally compatible capability, созданную другим domain и переданную composition.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const auth = createAuthClientRuntime()
|
|
||||||
const user = createUserClientRuntime({ auth: auth.session })
|
|
||||||
```
|
|
||||||
|
|
||||||
User domain знает только свой input contract. Он не знает creator, Provider, adapters и scope AuthRuntime.
|
|
||||||
|
|
||||||
**SLM-XDOM-008 - ОБЯЗАН.** Передаваемая capability должна быть минимальной и не раскрывать raw store, Context, SDK client или mutable internals foreign domain.
|
|
||||||
|
|
||||||
**SLM-XDOM-013 - МОЖЕТ.** Structurally compatible foreign capability может реализовать consumer-owned port напрямую. Wrapper adapter создаётся только при необходимости преобразовать contracts или lifecycle.
|
|
||||||
|
|
||||||
## React composition
|
|
||||||
|
|
||||||
Если React-сущность использует runtime API двух domains, она принадлежит `compositions`.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
const ProtectedOrderForm = () => {
|
|
||||||
const auth = useAuth()
|
|
||||||
const order = useOrder()
|
|
||||||
|
|
||||||
return auth.isAuthenticated
|
|
||||||
? <OrderForm order={order} />
|
|
||||||
: <AuthPrompt />
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**SLM-XDOM-009 - ОБЯЗАН.** Domain UI может получать от composition только domain-local или presentation-neutral props, callbacks и slots. Foreign domain semantics остаётся во владеющей composition.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
<AuthRequired>
|
|
||||||
<OrderForm />
|
|
||||||
</AuthRequired>
|
|
||||||
```
|
|
||||||
|
|
||||||
Такое связывание выполняется в composition, а не внутри auth или orders.
|
|
||||||
|
|
||||||
## Events
|
|
||||||
|
|
||||||
**SLM-XDOM-010 - ЗАПРЕЩЕНО.** Domain не подписывается напрямую на event emitter другого domain через runtime import.
|
|
||||||
|
|
||||||
Composition может передать event capability через consumer-owned port:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const orders = createOrdersClientRuntime({
|
|
||||||
userEvents: {
|
|
||||||
subscribeToIdentity: user.identity.subscribe,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## Cycles
|
|
||||||
|
|
||||||
**SLM-XDOM-011 - ЗАПРЕЩЕНО.** Runtime dependency cycle между domains является нарушением границы и не может скрываться event bus, lazy resolution или two-way service locator.
|
|
||||||
|
|
||||||
Ненормативное пояснение: при обнаружении цикла следует пересмотреть один из вариантов:
|
|
||||||
|
|
||||||
- пересмотреть границы domains;
|
|
||||||
- перенести orchestration в composition;
|
|
||||||
- выделить отдельную product responsibility;
|
|
||||||
- инвертировать зависимость через consumer-owned port.
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
---
|
|
||||||
title: Framework surface domain
|
|
||||||
status: draft
|
|
||||||
normative: true
|
|
||||||
---
|
|
||||||
|
|
||||||
# Framework Surface
|
|
||||||
|
|
||||||
Framework surface адаптирует готовый DomainRuntime к execution model конкретного framework. В текущей структуре React surface располагается в `react/`.
|
|
||||||
|
|
||||||
## Структура React surface
|
|
||||||
|
|
||||||
```text
|
|
||||||
domains/{group...}/{domain}/react/
|
|
||||||
├── context/
|
|
||||||
├── providers/
|
|
||||||
├── hooks/
|
|
||||||
├── ui/
|
|
||||||
└── index.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
Ни один segment не обязателен без реальной потребности.
|
|
||||||
|
|
||||||
## Runtime access
|
|
||||||
|
|
||||||
**SLM-FRM-001 - ОБЯЗАН.** Framework surface должна работать с конкретным DomainRuntime через domain-owned runtime access boundary.
|
|
||||||
|
|
||||||
**SLM-FRM-002 - ЗАПРЕЩЕНО.** Framework hook или component не может самостоятельно вызывать business factory, создавать adapters или разрешать runtime из global service locator.
|
|
||||||
|
|
||||||
**SLM-FRM-003 - ОБЯЗАН.** Runtime access boundary должна получать готовый DomainRuntime извне и не создавать параллельное domain state.
|
|
||||||
|
|
||||||
Для React типичным механизмом является private Context, связывающий статически экспортированные hooks/components с переданным runtime instance. Это пояснение не предписывает точную форму или количество Providers в текущем draft.
|
|
||||||
|
|
||||||
## Imports
|
|
||||||
|
|
||||||
**SLM-FRM-004 - ОБЯЗАН.** React surface может импортировать business runtime contracts только через `import type`.
|
|
||||||
|
|
||||||
**SLM-FRM-005 - ЗАПРЕЩЕНО.** React surface не может runtime-импортировать business factory, private business services, selectors, validators, errors или constants.
|
|
||||||
|
|
||||||
**SLM-FRM-006 - ЗАПРЕЩЕНО.** React surface не может импортировать domain adapters, SDK, product infra client или assembly.
|
|
||||||
|
|
||||||
**SLM-FRM-007 - МОЖЕТ.** React surface может импортировать public API `ui`, `shared` и framework libraries, разрешённые её runtime profile.
|
|
||||||
|
|
||||||
**SLM-FRM-008 - ЗАПРЕЩЕНО.** React surface одного domain не импортирует runtime surface другого domain.
|
|
||||||
|
|
||||||
## Hooks
|
|
||||||
|
|
||||||
**SLM-FRM-009 - ОБЯЗАН.** Domain hook должен получать product data и behavior только через текущий DomainRuntime.
|
|
||||||
|
|
||||||
**SLM-FRM-010 - МОЖЕТ.** Hook может использовать framework query/cache runtime как private implementation поверх imperative DomainRuntime query.
|
|
||||||
|
|
||||||
**SLM-FRM-011 - ЗАПРЕЩЕНО.** Query hook не может использовать adapter или SDK call как fetcher в обход DomainRuntime.
|
|
||||||
|
|
||||||
**SLM-FRM-012 - ЗАПРЕЩЕНО.** Query-library types, cache keys и raw mutate API не могут становиться public business contract.
|
|
||||||
|
|
||||||
## Domain UI
|
|
||||||
|
|
||||||
Domain React UI может:
|
|
||||||
|
|
||||||
- вызывать hooks своего domain;
|
|
||||||
- использовать universal UI;
|
|
||||||
- отображать domain-owned states и outcomes;
|
|
||||||
- принимать callbacks, props и slots от composition.
|
|
||||||
|
|
||||||
**SLM-FRM-013 - ЗАПРЕЩЕНО.** Domain UI не может импортировать runtime другого domain или оркестрировать route/page flow.
|
|
||||||
|
|
||||||
Владение React UI, использующим несколько domains, определено правилом [SLM-CMP-006](../compositions.md#product-ui).
|
|
||||||
|
|
||||||
## Client boundary
|
|
||||||
|
|
||||||
**SLM-FRM-015 - ОБЯЗАН.** Entry point React hooks, Context и interactive UI должен быть явно отмечен как client runtime согласно правилам используемого framework.
|
|
||||||
|
|
||||||
**SLM-FRM-016 - ЗАПРЕЩЕНО.** Server-compatible React export не может попадать в client entrypoint только из-за нахождения рядом с client hooks или Provider.
|
|
||||||
|
|
||||||
React не является синонимом client runtime; environment profile определяется фактическими dependencies export.
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
---
|
|
||||||
title: Слой Domains
|
|
||||||
status: draft
|
|
||||||
normative: true
|
|
||||||
---
|
|
||||||
|
|
||||||
# Слой Domains
|
|
||||||
|
|
||||||
`domains` содержит законченные вертикальные продуктовые модули. Domain объединяет business logic, framework surfaces, concrete adapters и runtime-specific assembly одной предметной ответственности, не смешивая их внутренние направления зависимостей.
|
|
||||||
|
|
||||||
## Domain и group
|
|
||||||
|
|
||||||
**SLM-DOM-001 - ОБЯЗАН.** Конечный domain должен располагаться непосредственно в `domains` или внутри одной или нескольких навигационных groups.
|
|
||||||
|
|
||||||
```text
|
|
||||||
domains/{domain}
|
|
||||||
domains/{group}/{domain}
|
|
||||||
domains/{group}/{nested-group}/{domain}
|
|
||||||
```
|
|
||||||
|
|
||||||
**SLM-DOM-002 - ЗАПРЕЩЕНО.** Domain group не может иметь `index.ts`, public API, state, adapters, assembly или runtime.
|
|
||||||
|
|
||||||
```text
|
|
||||||
domains/
|
|
||||||
├── navigation/ # domain
|
|
||||||
└── knv/ # group
|
|
||||||
├── auth/ # domain
|
|
||||||
├── user/ # domain
|
|
||||||
└── orders/ # domain
|
|
||||||
```
|
|
||||||
|
|
||||||
**SLM-DOM-003 - ОБЯЗАН.** Первой архитектурной единицей в group tree является конечная папка, владеющая самостоятельной предметной ответственностью.
|
|
||||||
|
|
||||||
## Внутренние зоны
|
|
||||||
|
|
||||||
Базовая форма domain:
|
|
||||||
|
|
||||||
```text
|
|
||||||
domains/{group...}/{domain}/
|
|
||||||
├── business/
|
|
||||||
├── react/
|
|
||||||
├── adapters/
|
|
||||||
├── client/
|
|
||||||
└── server/
|
|
||||||
```
|
|
||||||
|
|
||||||
| Зона | Статус | Ответственность |
|
|
||||||
|---|---|---|
|
|
||||||
| [`business`](./business.md) | Обязательная | Domain model, factory, ports, scenarios, errors |
|
|
||||||
| [`react`](./framework.md) | Опциональная | React runtime access, hooks, Providers, domain UI |
|
|
||||||
| [`adapters`](./ports-and-adapters.md) | Опциональная | Concrete реализации business-owned ports |
|
|
||||||
| [`client`](./client-and-server.md) | Опциональная | Browser/client assembly одного domain |
|
|
||||||
| [`server`](./client-and-server.md) | Опциональная | Server/request assembly одного domain |
|
|
||||||
|
|
||||||
**SLM-DOM-004 - ОБЯЗАН.** Каждый domain должен содержать `business` как единственный владелец product model и business semantics.
|
|
||||||
|
|
||||||
**SLM-DOM-005 - СЛЕДУЕТ.** Опциональную зону следует добавлять только при наличии реального runtime consumer и самостоятельной ответственности.
|
|
||||||
|
|
||||||
**SLM-DOM-006 - ЗАПРЕЩЕНО.** Нельзя создавать пустые симметричные `react`, `adapters`, `client` или `server` на будущее.
|
|
||||||
|
|
||||||
**SLM-DOM-007 - ОБЯЗАН.** Domain zones должны соблюдать внутреннюю dependency direction, даже если физически находятся под одним владельцем.
|
|
||||||
|
|
||||||
## Domain ownership
|
|
||||||
|
|
||||||
Domain может владеть:
|
|
||||||
|
|
||||||
- model и value objects;
|
|
||||||
- product scenarios;
|
|
||||||
- domain state и transitions;
|
|
||||||
- ports;
|
|
||||||
- normalization и domain errors;
|
|
||||||
- framework hooks и UI одного domain;
|
|
||||||
- concrete integrations собственных ports;
|
|
||||||
- client/server runtime assembly собственного business.
|
|
||||||
|
|
||||||
Domain не владеет:
|
|
||||||
|
|
||||||
- page/route/layout composition;
|
|
||||||
- UI, объединяющим несколько domains;
|
|
||||||
- cross-domain graph;
|
|
||||||
- framework route entry;
|
|
||||||
- универсальным technical service;
|
|
||||||
- product-agnostic UI primitive.
|
|
||||||
|
|
||||||
## Product gateway
|
|
||||||
|
|
||||||
Framework surface и runtime assembly сохраняют business runtime единственным product gateway согласно [SLM-DATA-001 - SLM-DATA-003](../../state-and-data.md#domain-gateway).
|
|
||||||
|
|
||||||
## Cross-domain boundary
|
|
||||||
|
|
||||||
Domain может принять готовую внешнюю capability через contract, но не импортирует runtime surface другого domain. Точные правила определены в [cross-domain boundary](./cross-domain-boundary.md).
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
---
|
|
||||||
title: Ports и adapters domain
|
|
||||||
status: draft
|
|
||||||
normative: true
|
|
||||||
---
|
|
||||||
|
|
||||||
# Ports и Adapters
|
|
||||||
|
|
||||||
Port определяет потребность business. Adapter связывает эту потребность с concrete runtime.
|
|
||||||
|
|
||||||
## Ownership
|
|
||||||
|
|
||||||
```text
|
|
||||||
domain/
|
|
||||||
├── business/
|
|
||||||
│ └── ports/
|
|
||||||
└── adapters/
|
|
||||||
```
|
|
||||||
|
|
||||||
**SLM-ADP-001 - ОБЯЗАН.** Port должен принадлежать `business` того domain, который потребляет capability.
|
|
||||||
|
|
||||||
**SLM-ADP-002 - ОБЯЗАН.** Concrete adapter должен принадлежать тому же domain, но находиться вне `business`.
|
|
||||||
|
|
||||||
**SLM-ADP-003 - ЗАПРЕЩЕНО.** Infra или external SDK не могут объявлять business port от имени domain.
|
|
||||||
|
|
||||||
## Adapter contract
|
|
||||||
|
|
||||||
**SLM-ADP-004 - МОЖЕТ.** Adapter может выполнять только следующие integration responsibilities:
|
|
||||||
|
|
||||||
- импортировать type-only business port и domain input types;
|
|
||||||
- импортировать public infra API, SDK или platform runtime;
|
|
||||||
- переводить domain arguments в transport arguments;
|
|
||||||
- возвращать raw/unknown source result для business normalization;
|
|
||||||
- подписываться на concrete event source через явный lifecycle contract.
|
|
||||||
|
|
||||||
**SLM-ADP-005 - ЗАПРЕЩЕНО.** Adapter не может выполнять следующие domain/framework responsibilities:
|
|
||||||
|
|
||||||
- создавать domain error;
|
|
||||||
- выбирать domain fallback;
|
|
||||||
- реализовывать business rule;
|
|
||||||
- объявлять domain model;
|
|
||||||
- экспортировать concrete client consumer-коду;
|
|
||||||
- вызывать framework hook;
|
|
||||||
- обращаться к другому domain runtime.
|
|
||||||
|
|
||||||
**SLM-ADP-006 - ОБЯЗАН.** Adapter должен реализовывать ровно тот port contract, который необходим business.
|
|
||||||
|
|
||||||
**SLM-ADP-007 - ЗАПРЕЩЕНО.** Нельзя передавать полный client, если port требует ограниченный набор capabilities.
|
|
||||||
|
|
||||||
**SLM-ADP-008 - ЗАПРЕЩЕНО.** Adapter integration logic не должна писаться inline в composition или runtime assembly.
|
|
||||||
|
|
||||||
## Client и server adapters
|
|
||||||
|
|
||||||
Adapters могут быть разделены по runtime:
|
|
||||||
|
|
||||||
```text
|
|
||||||
adapters/
|
|
||||||
├── client/
|
|
||||||
│ ├── browser-session.adapter.ts
|
|
||||||
│ └── websocket-orders.adapter.ts
|
|
||||||
└── server/
|
|
||||||
├── request-session.adapter.ts
|
|
||||||
└── server-orders-api.adapter.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
**SLM-ADP-009 - ОБЯЗАН.** Client adapter не должен попадать в server graph, а server adapter - в client graph.
|
|
||||||
|
|
||||||
**SLM-ADP-010 - ОБЯЗАН.** Runtime-specific adapter должен иметь явный environment marker, если framework предоставляет такой механизм.
|
|
||||||
|
|
||||||
## Event sources
|
|
||||||
|
|
||||||
Socket, subscription и event listener реализуют event port:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
export type OrdersEventsPort = {
|
|
||||||
subscribe: (listener: (event: unknown) => void) => () => void
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**SLM-ADP-011 - ОБЯЗАН.** Event adapter должен возвращать cleanup и не открывать connection при module import.
|
|
||||||
|
|
||||||
**SLM-ADP-012 - ОБЯЗАН.** Wire event проходит business normalization до изменения domain state или передачи consumer-коду.
|
|
||||||
|
|
||||||
## Public boundary
|
|
||||||
|
|
||||||
**SLM-ADP-013 - ЗАПРЕЩЕНО.** `adapters` не имеет внешнего public API для app, compositions или других domains.
|
|
||||||
|
|
||||||
Adapters доступны только assembly собственного domain и собственным contract tests.
|
|
||||||
@@ -12,16 +12,15 @@ normative: true
|
|||||||
|
|
||||||
| Слой | Владеет | Не владеет |
|
| Слой | Владеет | Не владеет |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| [`app`](./app.md) | Framework routes, bootstrap, глобальные framework boundaries | Product UI, domain logic, page state, graph assembly |
|
| [`app`](./app.md) | Framework routes, bootstrap, глобальные framework boundaries | Product UI, product logic, page state, application wiring |
|
||||||
| [`compositions`](./compositions.md) | Pages, layouts, screens, widgets, cross-domain graph, scope | Domain model, domain adapters, universal UI primitives |
|
| [`compositions`](./compositions.md) | Pages, layouts, screens, widgets, product flows, application wiring и scope | Universal UI primitives, technical transports |
|
||||||
| [`domains`](./domains/index.md) | Product model, scenarios, ports, adapters, runtime surfaces | Route/page composition и UI нескольких domains |
|
| [`infra`](./infra.md) | Technical services, transports, platform integrations | Product semantics и application wiring |
|
||||||
| [`infra`](./infra.md) | Technical services, transports, platform integrations | Product semantics и domain graph |
|
|
||||||
| [`ui`](./ui.md) | Product-agnostic UI modules | Product scenarios и data sources |
|
| [`ui`](./ui.md) | Product-agnostic UI modules | Product scenarios и data sources |
|
||||||
| [`shared`](./shared.md) | Детерминированные общие resources | Runtime state, I/O и product knowledge |
|
| [`shared`](./shared.md) | Детерминированные общие resources | Runtime state, I/O и product knowledge |
|
||||||
|
|
||||||
## Общие правила
|
## Общие правила
|
||||||
|
|
||||||
**SLM-LAY-001 - ОБЯЗАН.** Модуль должен располагаться в слое, который владеет его основной ответственностью.
|
**SLM-LAY-001 - ОБЯЗАН.** Module должен располагаться в слое, который владеет его основной ответственностью.
|
||||||
|
|
||||||
**SLM-LAY-002 - ЗАПРЕЩЕНО.** Нельзя выбирать слой по техническому типу файла без определения владельца поведения и данных.
|
**SLM-LAY-002 - ЗАПРЕЩЕНО.** Нельзя выбирать слой по техническому типу файла без определения владельца поведения и данных.
|
||||||
|
|
||||||
@@ -29,15 +28,17 @@ normative: true
|
|||||||
|
|
||||||
**SLM-LAY-004 - ЗАПРЕЩЕНО.** Нельзя создавать proxy module в разрешённом слое только для обхода запрещённого направления import.
|
**SLM-LAY-004 - ЗАПРЕЩЕНО.** Нельзя создавать proxy module в разрешённом слое только для обхода запрещённого направления import.
|
||||||
|
|
||||||
**SLM-LAY-005 - СЛЕДУЕТ.** При смешанной ответственности module следует разделить по реальным владельцам. Cross-module и cross-domain orchestration следует выполнить в `compositions`; связь business с собственными adapters выполняется assembly соответствующего domain.
|
**SLM-LAY-005 - СЛЕДУЕТ.** При смешанной ответственности module следует разделить по реальным владельцам. Application flow и UI нескольких самостоятельных modules следует собирать в `compositions`.
|
||||||
|
|
||||||
## Выбор слоя
|
## Выбор слоя
|
||||||
|
|
||||||
| Вопрос | Слой |
|
| Вопрос | Слой |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Код существует только из-за framework route/bootstrap? | `app` |
|
| Код существует только из-за framework route/bootstrap? | `app` |
|
||||||
| Код собирает page, route, несколько modules или domains? | `compositions` |
|
| Код собирает page, route или несколько самостоятельных modules? | `compositions` |
|
||||||
| Код выражает продуктовую модель, сценарий или domain UI? | `domains` |
|
| Код выражает product flow или product responsibility без owner, введённого overlay? | `compositions` |
|
||||||
| Код предоставляет техническую capability приложения? | `infra` |
|
| Код предоставляет technical capability приложения? | `infra` |
|
||||||
| Компонент не содержит product semantics и сценария? | `ui` |
|
| Компонент не содержит product semantics и scenario? | `ui` |
|
||||||
| Код детерминирован, не знает продукт и не имеет runtime state? | `shared` |
|
| Код детерминирован, не знает продукт и не имеет runtime state? | `shared` |
|
||||||
|
|
||||||
|
Overlay может добавлять собственный слой и изменять ownership только в явно объявленном delta.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ normative: true
|
|||||||
|
|
||||||
# Слой Infra
|
# Слой Infra
|
||||||
|
|
||||||
`infra` содержит технические capabilities приложения, не определяющие продуктовую модель и сценарии.
|
`infra` содержит technical capabilities приложения, не определяющие product model и scenarios.
|
||||||
|
|
||||||
## Примеры modules
|
## Примеры modules
|
||||||
|
|
||||||
@@ -25,23 +25,23 @@ infra/
|
|||||||
|
|
||||||
## Правила
|
## Правила
|
||||||
|
|
||||||
**SLM-INF-001 - ОБЯЗАН.** Infra module должен описывать техническую capability, а не продуктовый domain.
|
**SLM-INF-001 - ОБЯЗАН.** Infra module должен описывать technical capability, а не product semantics или scenario.
|
||||||
|
|
||||||
**SLM-INF-002 - МОЖЕТ.** Infra module может импортировать public API другого infra module и `shared`.
|
**SLM-INF-002 - МОЖЕТ.** Infra module может импортировать public API другого infra module и `shared`.
|
||||||
|
|
||||||
**SLM-INF-003 - ЗАПРЕЩЕНО.** Infra module не может импортировать `domains`, `compositions` или `app`.
|
Запрет infra импортировать `compositions` или `app` определяется base-правилом `SLM-ARCH-003`.
|
||||||
|
|
||||||
**SLM-INF-004 - ЗАПРЕЩЕНО.** Infra не может собирать domain factory, хранить cross-domain graph или предоставлять generic product service locator.
|
**SLM-INF-004 - ЗАПРЕЩЕНО.** Infra не может владеть product wiring, собирать application graph или предоставлять generic product service locator.
|
||||||
|
|
||||||
**SLM-INF-005 - ЗАПРЕЩЕНО.** Infra не создаёт domain errors, domain fallback и domain model из transport DTO.
|
**SLM-INF-005 - ЗАПРЕЩЕНО.** Infra не создаёт product errors, product fallback и product model из transport DTO.
|
||||||
|
|
||||||
**SLM-INF-006 - МОЖЕТ.** Infra может экспортировать technical client, transport, event source, storage primitive или platform wrapper через собственный public API.
|
**SLM-INF-006 - МОЖЕТ.** Infra может экспортировать technical client, transport, event source, storage primitive или platform wrapper через собственный public API.
|
||||||
|
|
||||||
**SLM-INF-007 - ОБЯЗАН.** Generated SDK и transport details должны оставаться внутри infra или concrete domain adapter и не становиться public contract продуктовых consumers.
|
**SLM-INF-007 - ОБЯЗАН.** Generated SDK и transport details должны оставаться внутри technical или private integration boundary владельца и не становиться частью public product contract.
|
||||||
|
|
||||||
## Отличие от adapter
|
## Product integration
|
||||||
|
|
||||||
Infra знает технический механизм:
|
Infra знает technical mechanism:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
HTTP client
|
HTTP client
|
||||||
@@ -50,12 +50,4 @@ local storage primitive
|
|||||||
analytics SDK
|
analytics SDK
|
||||||
```
|
```
|
||||||
|
|
||||||
Domain adapter знает, какая часть этого механизма реализует конкретный business-owned port:
|
Product owner определяет semantics использования capability; infra предоставляет механизм через public API. Один infra module может использоваться несколькими product owners без знания их semantics.
|
||||||
|
|
||||||
```text
|
|
||||||
AuthPhonePort
|
|
||||||
OrdersEventsPort
|
|
||||||
UserAgreementsStoragePort
|
|
||||||
```
|
|
||||||
|
|
||||||
Один infra module может использоваться adapters нескольких domains без знания их product semantics.
|
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ normative: true
|
|||||||
|
|
||||||
**SLM-SHR-001 - ОБЯЗАН.** Результат shared utility должен определяться явными аргументами и не зависеть от скрытого runtime environment.
|
**SLM-SHR-001 - ОБЯЗАН.** Результат shared utility должен определяться явными аргументами и не зависеть от скрытого runtime environment.
|
||||||
|
|
||||||
**SLM-SHR-002 - ЗАПРЕЩЕНО.** `shared` не может импортировать `app`, `compositions`, `domains`, `infra` или `ui`.
|
**SLM-SHR-002 - ЗАПРЕЩЕНО.** `shared` не может импортировать `app`, `compositions`, `infra` или `ui`.
|
||||||
|
|
||||||
**SLM-SHR-003 - ЗАПРЕЩЕНО.** `shared` не может владеть product types, domain rules, runtime state, I/O, storage access или event subscriptions.
|
**SLM-SHR-003 - ЗАПРЕЩЕНО.** `shared` не может владеть product types, product rules, runtime state, I/O, storage access или event subscriptions.
|
||||||
|
|
||||||
**SLM-SHR-004 - ЗАПРЕЩЕНО.** Нельзя переносить domain helper, DTO, adapter contract или product config в `shared` для обхода import boundary.
|
**SLM-SHR-004 - ЗАПРЕЩЕНО.** Нельзя переносить product helper, DTO, integration contract или product config в `shared` для обхода import boundary.
|
||||||
|
|
||||||
**SLM-SHR-005 - СЛЕДУЕТ.** Код следует поднимать в `shared` только при подтверждённой product-agnostic semantics, а не из-за повторения нескольких строк.
|
**SLM-SHR-005 - СЛЕДУЕТ.** Код следует поднимать в `shared` только при подтверждённой product-agnostic semantics, а не из-за повторения нескольких строк.
|
||||||
|
|
||||||
@@ -34,9 +34,9 @@ normative: true
|
|||||||
|
|
||||||
| Код | Владелец |
|
| Код | Владелец |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Domain email validator с product rules | `domains/{domain}/business` |
|
| Email validator с product rules | Владеющий product module |
|
||||||
| Generic string trim utility | `shared` |
|
| Generic string trim utility | `shared` |
|
||||||
| Browser storage wrapper | `infra` |
|
| Browser storage wrapper | `infra` |
|
||||||
| Domain storage adapter | `domains/{domain}/adapters` |
|
| Product storage integration | Product owner; storage primitive - `infra` |
|
||||||
| UI spacing tokens | `shared` |
|
| UI spacing tokens | `shared` |
|
||||||
| Button consuming spacing tokens | `ui` |
|
| Button consuming spacing tokens | `ui` |
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ normative: true
|
|||||||
|
|
||||||
# Слой UI
|
# Слой UI
|
||||||
|
|
||||||
`ui` содержит reusable presentation modules без product scenario и domain ownership.
|
`ui` содержит reusable presentation modules без product scenario и product ownership.
|
||||||
|
|
||||||
## Примеры
|
## Примеры
|
||||||
|
|
||||||
@@ -23,26 +23,26 @@ ui/
|
|||||||
|
|
||||||
## Правила
|
## Правила
|
||||||
|
|
||||||
**SLM-UI-001 - ОБЯЗАН.** UI module должен быть применим без знания конкретного product domain.
|
**SLM-UI-001 - ОБЯЗАН.** UI module должен быть применим без product-specific knowledge.
|
||||||
|
|
||||||
**SLM-UI-002 - ЗАПРЕЩЕНО.** UI module не может импортировать `domains`, `compositions`, `app` или product-specific infra.
|
**SLM-UI-002 - ЗАПРЕЩЕНО.** UI module не может импортировать `compositions`, `app` или product-specific infra.
|
||||||
|
|
||||||
**SLM-UI-003 - МОЖЕТ.** UI module может импортировать public API других UI modules и `shared`.
|
**SLM-UI-003 - МОЖЕТ.** UI module может импортировать public API других UI modules и `shared`.
|
||||||
|
|
||||||
**SLM-UI-004 - ЗАПРЕЩЕНО.** UI module не выбирает product data source, не вызывает domain scenario и не владеет cross-domain behavior.
|
**SLM-UI-004 - ЗАПРЕЩЕНО.** UI module не выбирает product data source, не вызывает product scenario и не владеет multi-module behavior.
|
||||||
|
|
||||||
**SLM-UI-005 - МОЖЕТ.** UI module может владеть локальным interaction state, необходимым только для собственной presentation mechanics.
|
**SLM-UI-005 - МОЖЕТ.** UI module может владеть локальным interaction state, необходимым только для собственной presentation mechanics.
|
||||||
|
|
||||||
**SLM-UI-006 - ОБЯЗАН.** Компонент с product semantics должен принадлежать framework surface domain или composition, а не `ui`.
|
**SLM-UI-006 - ОБЯЗАН.** Компонент с product semantics должен принадлежать владеющему product module, а не `ui`.
|
||||||
|
|
||||||
## Классификация
|
## Классификация
|
||||||
|
|
||||||
| Сущность | Владелец |
|
| Сущность | Владелец |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `Button`, `Input`, `Modal` | `ui` |
|
| `Button`, `Input`, `Modal` | `ui` |
|
||||||
| `LoginForm` одного auth domain | domain framework surface |
|
| `LoginForm` одной auth responsibility | Владеющий product module |
|
||||||
| Header с auth и navigation | `compositions` |
|
| Application header | `compositions` |
|
||||||
| Generic date picker | `ui` |
|
| Generic date picker | `ui` |
|
||||||
| Medication schedule | domain или composition согласно используемым domains |
|
| Medication schedule | Владеющий product module согласно ownership |
|
||||||
|
|
||||||
Универсальность определяется отсутствием product knowledge, а не количеством текущих consumers.
|
Универсальность определяется отсутствием product knowledge, а не количеством текущих consumers.
|
||||||
|
|||||||
122
docs2/specification/modes/advanced/domains.md
Normal file
122
docs2/specification/modes/advanced/domains.md
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
---
|
||||||
|
title: Domains в SLM Advanced
|
||||||
|
status: draft
|
||||||
|
normative: true
|
||||||
|
overlay: advanced
|
||||||
|
base: slm
|
||||||
|
---
|
||||||
|
|
||||||
|
# Domains в SLM Advanced
|
||||||
|
|
||||||
|
> Overlay: `SLM Advanced`. Base: [SLM](../../index.md).
|
||||||
|
|
||||||
|
Domain является законченным вертикальным product module с одной предметной ответственностью и явным public boundary. Кроме base-правил modules и segments, Advanced не предписывает обязательную внутреннюю архитектуру domain.
|
||||||
|
|
||||||
|
## Domain и group
|
||||||
|
|
||||||
|
**SLM-ADV-DOM-001 - ОБЯЗАН.** Конечный domain должен располагаться непосредственно в `domains` или внутри одной или нескольких навигационных groups.
|
||||||
|
|
||||||
|
```text
|
||||||
|
domains/{domain}
|
||||||
|
domains/{group}/{domain}
|
||||||
|
domains/{group}/{nested-group}/{domain}
|
||||||
|
```
|
||||||
|
|
||||||
|
**SLM-ADV-DOM-002 - ОБЯЗАН.** Узел domain tree с собственным public API, state, integration или runtime должен классифицироваться как конечный domain, а не domain group.
|
||||||
|
|
||||||
|
```text
|
||||||
|
domains/
|
||||||
|
├── navigation/ # domain
|
||||||
|
└── knv/ # group
|
||||||
|
├── auth/ # domain
|
||||||
|
├── user/ # domain
|
||||||
|
└── orders/ # domain
|
||||||
|
```
|
||||||
|
|
||||||
|
**SLM-ADV-DOM-003 - ОБЯЗАН.** Первой архитектурной единицей в group tree является конечная папка, владеющая самостоятельной product responsibility.
|
||||||
|
|
||||||
|
## Ownership
|
||||||
|
|
||||||
|
**SLM-ADV-DOM-004 - ОБЯЗАН.** Domain должен владеть одной сформулированной product responsibility и предоставлять её внешним consumers через собственный public API.
|
||||||
|
|
||||||
|
Domain может владеть:
|
||||||
|
|
||||||
|
- product model и value objects;
|
||||||
|
- scenarios и operations;
|
||||||
|
- domain state и transitions;
|
||||||
|
- normalization и product errors;
|
||||||
|
- product source integration;
|
||||||
|
- framework hooks и UI одного domain;
|
||||||
|
- runtime-specific setup.
|
||||||
|
|
||||||
|
**SLM-ADV-DOM-005 - ЗАПРЕЩЕНО.** Domain не может владеть framework route entry, page/layout composition, UI нескольких самостоятельных product responsibilities, universal technical capability или product-agnostic UI primitive.
|
||||||
|
|
||||||
|
## Структура
|
||||||
|
|
||||||
|
```text
|
||||||
|
domains/knv/auth/
|
||||||
|
├── hooks/
|
||||||
|
├── providers/
|
||||||
|
├── services/
|
||||||
|
├── stores/
|
||||||
|
├── mappers/
|
||||||
|
├── types/
|
||||||
|
├── ui/
|
||||||
|
├── parts/
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Это пример, а не обязательный scaffold. Небольшой domain может состоять из одного файла и public entrypoint.
|
||||||
|
|
||||||
|
Domain может хранить файлы в корне и использовать любые необходимые segments согласно base-правилам [SLM-SEG-001 - SLM-SEG-003](../../segments.md#правила).
|
||||||
|
|
||||||
|
**SLM-ADV-DOM-006 - ЗАПРЕЩЕНО.** Нельзя создавать пустые segments или копировать полную структуру другого domain без текущей ответственности.
|
||||||
|
|
||||||
|
**SLM-ADV-DOM-007 - МОЖЕТ.** Domain может владеть hooks, Providers, Context, services, stores, mappers, types, product UI и другими implementation units своей ответственности.
|
||||||
|
|
||||||
|
## Public API
|
||||||
|
|
||||||
|
Public boundary Advanced domain следует base-правилам `SLM-API-001` и `SLM-API-002`.
|
||||||
|
|
||||||
|
**SLM-ADV-DOM-009 - МОЖЕТ.** Public API domain может экспортировать выбранные командой hooks, Providers, Context, components, service APIs, store access APIs и types как стабильный contract.
|
||||||
|
|
||||||
|
**SLM-ADV-DOM-010 - ЗАПРЕЩЕНО.** Если product responsibility получила domain owner, app, composition или infra не могут создавать параллельную модель этой ответственности либо обходить её public boundary.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
```text
|
||||||
|
composition -> domain
|
||||||
|
domain -> domain | infra | ui | shared
|
||||||
|
```
|
||||||
|
|
||||||
|
**SLM-ADV-DOM-011 - МОЖЕТ.** Domain может runtime-импортировать public API другого Advanced domain.
|
||||||
|
|
||||||
|
**SLM-ADV-DOM-012 - МОЖЕТ.** Domain может напрямую использовать public API `infra`, `ui` и `shared` без обязательной промежуточной abstraction.
|
||||||
|
|
||||||
|
Runtime cycles запрещены base-правилом `SLM-API-016`.
|
||||||
|
|
||||||
|
**SLM-ADV-DOM-013 - ЗАПРЕЩЕНО.** Type-only dependency cycle между domains запрещён, даже если runtime graph остаётся ацикличным.
|
||||||
|
|
||||||
|
## Data flow
|
||||||
|
|
||||||
|
```text
|
||||||
|
composition
|
||||||
|
-> domain public API
|
||||||
|
-> domain hook/service
|
||||||
|
-> infra
|
||||||
|
-> external source
|
||||||
|
```
|
||||||
|
|
||||||
|
**SLM-ADV-DOM-014 - ОБЯЗАН.** Product consumers за пределами domain должны получать его данные и поведение через public API domain, а не повторять тот же integration flow напрямую через `infra`.
|
||||||
|
|
||||||
|
## Product UI
|
||||||
|
|
||||||
|
**SLM-ADV-DOM-015 - МОЖЕТ.** Product UI одной domain responsibility может принадлежать этому domain.
|
||||||
|
|
||||||
|
UI нескольких самостоятельных responsibilities остаётся в `compositions` согласно base-правилу `SLM-CMP-006`.
|
||||||
|
|
||||||
|
## Monorepo boundary
|
||||||
|
|
||||||
|
**SLM-ADV-DOM-016 - ОБЯЗАН.** Advanced domain должен оставаться внутри `apps/{app}/src/domains` до принятия отдельной package-модели.
|
||||||
|
|
||||||
|
**SLM-ADV-DOM-017 - ЗАПРЕЩЕНО.** Workspace package не может называться Advanced Domain для целей Specification, если он не соответствует application path и ownership этой главы.
|
||||||
60
docs2/specification/modes/advanced/index.md
Normal file
60
docs2/specification/modes/advanced/index.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
title: SLM Advanced
|
||||||
|
status: draft
|
||||||
|
normative: true
|
||||||
|
overlay: advanced
|
||||||
|
base: slm
|
||||||
|
---
|
||||||
|
|
||||||
|
# SLM Advanced
|
||||||
|
|
||||||
|
`SLM Advanced` является независимым overlay непосредственно над [base SLM](../../index.md).
|
||||||
|
|
||||||
|
```text
|
||||||
|
SLM Advanced = SLM + Advanced rules
|
||||||
|
```
|
||||||
|
|
||||||
|
## Отличия от SLM
|
||||||
|
|
||||||
|
| Область | Base SLM | SLM Advanced |
|
||||||
|
|---|---|---|
|
||||||
|
| Product ownership | Product logic принадлежит compositions | Устойчивая product responsibility может быть извлечена в domain |
|
||||||
|
| Слои | `app`, `compositions`, `infra`, `ui`, `shared` | Добавляется `domains` |
|
||||||
|
| Структура domain | Отсутствует | Свободная, внутренние роли выбирает команда |
|
||||||
|
| Domain dependencies | Отсутствуют | Ацикличные imports через public API разрешены |
|
||||||
|
| External integration | Composition использует infra | Domain может использовать infra напрямую |
|
||||||
|
|
||||||
|
## Расширение архитектурной модели
|
||||||
|
|
||||||
|
**SLM-ADV-ARCH-001 - ОБЯЗАН.** SLM Advanced должен расширять набор base-слоёв слоем `domains` для самостоятельных product responsibilities.
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/
|
||||||
|
├── app/
|
||||||
|
├── compositions/
|
||||||
|
├── domains/
|
||||||
|
├── infra/
|
||||||
|
├── ui/
|
||||||
|
└── shared/
|
||||||
|
```
|
||||||
|
|
||||||
|
**SLM-ADV-ARCH-002 - ОБЯЗАН.** Дополнительные dependency edges Advanced должны соответствовать следующему направлению:
|
||||||
|
|
||||||
|
```text
|
||||||
|
compositions -> domains
|
||||||
|
domains -> domains | infra | ui | shared
|
||||||
|
```
|
||||||
|
|
||||||
|
Base dependency direction для остальных слоёв сохраняется.
|
||||||
|
|
||||||
|
## Изменение product ownership
|
||||||
|
|
||||||
|
**SLM-ADV-CMP-001 - ОБЯЗАН.** Если product responsibility получила domain owner, Advanced заменяет для этой ответственности base-правило `SLM-CMP-001`: domain владеет собственной product logic, а composition владеет application flow и связывает public APIs.
|
||||||
|
|
||||||
|
Product logic без domain owner продолжает следовать base SLM и принадлежит минимальной composition.
|
||||||
|
|
||||||
|
**SLM-ADV-CMP-010 - МОЖЕТ.** Composition module может импортировать public API Advanced domains в дополнение к imports, разрешённым base-правилом `SLM-CMP-010`.
|
||||||
|
|
||||||
|
## Advanced Domain Specification
|
||||||
|
|
||||||
|
Полная Advanced-модель слоя описана в [Domains](./domains.md). Других mode-specific отличий текущий draft Advanced не вводит.
|
||||||
130
docs2/specification/modes/pro/domains/business.md
Normal file
130
docs2/specification/modes/pro/domains/business.md
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
---
|
||||||
|
title: Business в SLM Pro
|
||||||
|
status: draft
|
||||||
|
normative: true
|
||||||
|
overlay: pro
|
||||||
|
base: slm
|
||||||
|
---
|
||||||
|
|
||||||
|
# Business
|
||||||
|
|
||||||
|
> Overlay: `SLM Pro`.
|
||||||
|
|
||||||
|
`business` является framework-neutral зоной domain и единственным владельцем его продуктовой semantics.
|
||||||
|
|
||||||
|
## Структура
|
||||||
|
|
||||||
|
```text
|
||||||
|
domains/{group...}/{domain}/business/
|
||||||
|
├── {domain}.factory.ts
|
||||||
|
├── index.ts
|
||||||
|
├── types/
|
||||||
|
├── ports/
|
||||||
|
├── services/
|
||||||
|
├── errors/
|
||||||
|
├── mappers/
|
||||||
|
├── selectors/
|
||||||
|
├── validators/
|
||||||
|
└── lib/
|
||||||
|
```
|
||||||
|
|
||||||
|
Конкретный набор внутренних segments определяется размером domain. Обязательны роль factory и public boundary, но не каждая папка из примера.
|
||||||
|
|
||||||
|
## Factory boundary
|
||||||
|
|
||||||
|
**SLM-PRO-BUS-001 - ОБЯЗАН.** Business должен создавать public runtime API через factory `{domain}Factory`.
|
||||||
|
|
||||||
|
**SLM-PRO-BUS-002 - ОБЯЗАН.** Factory должна принимать все runtime capabilities через business-owned dependency contracts.
|
||||||
|
|
||||||
|
**SLM-PRO-BUS-003 - ОБЯЗАН.** Factory должна возвращать framework-neutral DomainRuntime. Stateless logic API считается DomainRuntime и соблюдает тот же public boundary.
|
||||||
|
|
||||||
|
**SLM-PRO-BUS-004 - ЗАПРЕЩЕНО.** Factory не может возвращать React hooks, components, Providers, layouts, route guards или framework boundaries.
|
||||||
|
|
||||||
|
**SLM-PRO-BUS-005 - ЗАПРЕЩЕНО.** Factory constructor не может выполнять I/O, открывать socket, регистрировать subscription, запускать timer или читать hidden environment.
|
||||||
|
|
||||||
|
## Public API
|
||||||
|
|
||||||
|
**SLM-PRO-BUS-006 - ОБЯЗАН.** `business/index.ts` должен экспортировать единственное runtime value: factory.
|
||||||
|
|
||||||
|
**SLM-PRO-BUS-007 - МОЖЕТ.** `business/index.ts` может экспортировать business-owned types через `export type`.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export { authFactory } from './auth.factory'
|
||||||
|
|
||||||
|
export type {
|
||||||
|
AuthDeps,
|
||||||
|
AuthFactory,
|
||||||
|
AuthRuntime,
|
||||||
|
AuthState,
|
||||||
|
} from './types'
|
||||||
|
```
|
||||||
|
|
||||||
|
**SLM-PRO-BUS-008 - ЗАПРЕЩЕНО.** Error classes, error guards, error code constants, selectors, validators, formatters, services, mappers и port implementations не экспортируются как отдельные runtime values.
|
||||||
|
|
||||||
|
Если внешнему consumer нужна такая capability, она должна быть осмысленной частью factory runtime API, а не обходным direct export.
|
||||||
|
|
||||||
|
## Runtime API
|
||||||
|
|
||||||
|
DomainRuntime может предоставлять:
|
||||||
|
|
||||||
|
- commands;
|
||||||
|
- imperative queries;
|
||||||
|
- snapshots;
|
||||||
|
- subscriptions;
|
||||||
|
- selectors через стабильные methods;
|
||||||
|
- validation operations;
|
||||||
|
- typed outcomes;
|
||||||
|
- explicit lifecycle operations.
|
||||||
|
|
||||||
|
**SLM-PRO-BUS-009 - ОБЯЗАН.** Runtime API должен говорить на языке domain и не повторять endpoint names, SDK tree или storage schema.
|
||||||
|
|
||||||
|
**SLM-PRO-BUS-010 - ЗАПРЕЩЕНО.** Public contract не может раскрывать generated DTO, SDK client, query-library result, concrete store API, raw Context или adapter.
|
||||||
|
|
||||||
|
## Dependencies и ports
|
||||||
|
|
||||||
|
**SLM-PRO-BUS-011 - ОБЯЗАН.** Business-owned dependency описывает минимальную внешнюю возможность на языке domain.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export type AuthPhonePort = {
|
||||||
|
requestCode: (phone: string) => Promise<unknown>
|
||||||
|
verifyCode: (input: VerifyPhoneCodeInput) => Promise<unknown>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**SLM-PRO-BUS-012 - ОБЯЗАН.** Ненадёжный внешний результат должен приниматься как `unknown`, если business обязан проверить его runtime-форму.
|
||||||
|
|
||||||
|
**SLM-PRO-BUS-013 - ЗАПРЕЩЕНО.** Business dependency не может быть generated DTO, полный SDK client, `StoreApi`, QueryClient или framework hook.
|
||||||
|
|
||||||
|
**SLM-PRO-BUS-014 - ОБЯЗАН.** Subscription port должен предоставлять cleanup contract.
|
||||||
|
|
||||||
|
## Imports
|
||||||
|
|
||||||
|
Business может runtime-импортировать:
|
||||||
|
|
||||||
|
- собственные файлы;
|
||||||
|
- детерминированный `shared`;
|
||||||
|
- pure libraries без I/O, hidden state и public type leakage.
|
||||||
|
|
||||||
|
Business может type-only импортировать стабильный public contract другого domain, если dependency невозможно корректно описать локальным port. Локальный consumer-owned port является предпочтительным вариантом.
|
||||||
|
|
||||||
|
**SLM-PRO-BUS-015 - ЗАПРЕЩЕНО.** Business не импортирует React, query runtime, state manager, SDK, generated operation, HTTP client, storage implementation, browser API, infra, composition или assembly.
|
||||||
|
|
||||||
|
Cross-domain imports дополнительно регулируются правилами `SLM-PRO-XDOM-*` в [Cross-domain boundary](./cross-domain-boundary.md).
|
||||||
|
|
||||||
|
## Normalization и errors
|
||||||
|
|
||||||
|
**SLM-PRO-BUS-017 - ОБЯЗАН.** External result должен быть нормализован в business-owned model до выхода из DomainRuntime.
|
||||||
|
|
||||||
|
**SLM-PRO-BUS-018 - ОБЯЗАН.** Malformed successful response должен считаться нарушением runtime contract, а не валидным отсутствием данных.
|
||||||
|
|
||||||
|
**SLM-PRO-BUS-019 - ОБЯЗАН.** Expected domain outcome и technical failure должны быть различимы в public contract.
|
||||||
|
|
||||||
|
**SLM-PRO-BUS-020 - ЗАПРЕЩЕНО.** Source error, HTTP status, SDK error class, raw response и transport message не могут быть consumer contract.
|
||||||
|
|
||||||
|
Business может выражать ожидаемые outcomes через typed result или domain error. Эта draft-версия не предписывает единственную форму обработки ожидаемых ошибок, но требует business-owned semantics и стабильных discriminants.
|
||||||
|
|
||||||
|
## State
|
||||||
|
|
||||||
|
**SLM-PRO-BUS-021 - ОБЯЗАН.** Business владеет domain state model, допустимыми transitions и semantics commands/selectors.
|
||||||
|
|
||||||
|
Framework-neutral state runtime может быть создан самой factory или предоставлен через business-owned port. Concrete store implementation остаётся запрещённой dependency по [SLM-PRO-BUS-015](#imports) и не раскрывается в public API.
|
||||||
105
docs2/specification/modes/pro/domains/client-and-server.md
Normal file
105
docs2/specification/modes/pro/domains/client-and-server.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
---
|
||||||
|
title: Client и server assembly в SLM Pro
|
||||||
|
status: draft
|
||||||
|
normative: true
|
||||||
|
overlay: pro
|
||||||
|
base: slm
|
||||||
|
---
|
||||||
|
|
||||||
|
# Client и Server Assembly
|
||||||
|
|
||||||
|
> Overlay: `SLM Pro`.
|
||||||
|
|
||||||
|
`client` и `server` создают готовые runtime-specific instances одного domain поверх его business factory и adapters.
|
||||||
|
|
||||||
|
## Client assembly
|
||||||
|
|
||||||
|
```text
|
||||||
|
domains/{group...}/{domain}/client/
|
||||||
|
├── create-{domain}-client-runtime.ts
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export const createAuthClientRuntime = (): AuthRuntime => {
|
||||||
|
return authFactory({
|
||||||
|
phoneAuth: browserPhoneAuthAdapter,
|
||||||
|
session: browserSessionAdapter,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Client technical inputs ограничены client environment/config и platform capabilities, необходимыми для создания adapters собственного domain. Runtime values другого domain technical input не являются.
|
||||||
|
|
||||||
|
**SLM-PRO-ASM-001 - ОБЯЗАН.** Runtime imports client assembly должны ограничиваться собственной business factory, собственными client adapters, собственной React surface и необходимыми client technical inputs. Type-only foreign contracts допускаются по [SLM-PRO-XDOM-005](./cross-domain-boundary.md#type-only-contracts).
|
||||||
|
|
||||||
|
**SLM-PRO-ASM-002 - ОБЯЗАН.** Client assembly должна возвращать готовый runtime собственного domain.
|
||||||
|
|
||||||
|
Запрет на foreign runtime values определяется правилом [SLM-PRO-XDOM-001](./cross-domain-boundary.md#runtime-imports).
|
||||||
|
|
||||||
|
**SLM-PRO-ASM-004 - МОЖЕТ.** Client или server assembly может принимать готовую внешнюю capability через собственный input contract.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
createUserClientRuntime({ auth: auth.session })
|
||||||
|
```
|
||||||
|
|
||||||
|
Такой input не даёт user domain права создавать AuthRuntime или импортировать его client entrypoint.
|
||||||
|
|
||||||
|
## Server assembly
|
||||||
|
|
||||||
|
```text
|
||||||
|
domains/{group...}/{domain}/server/
|
||||||
|
├── create-{domain}-server-runtime.ts
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Server technical inputs ограничены request/framework data, server environment/config и platform capabilities, необходимыми для создания server adapters собственного domain. Runtime values другого domain technical input не являются.
|
||||||
|
|
||||||
|
**SLM-PRO-ASM-005 - ОБЯЗАН.** Server assembly должна создавать новый runtime в scope, соответствующем request или другой явно выбранной server lifetime.
|
||||||
|
|
||||||
|
**SLM-PRO-ASM-006 - ЗАПРЕЩЕНО.** Server assembly не может повторно использовать adapter или runtime instance, захвативший request credentials, cookies, headers или user-specific state другого scope.
|
||||||
|
|
||||||
|
**SLM-PRO-ASM-007 - ОБЯЗАН.** Framework/request input используется только для создания server adapters и не протекает как raw framework object в business API.
|
||||||
|
|
||||||
|
**SLM-PRO-ASM-008 - ОБЯЗАН.** Server entrypoint должен иметь явный server-only marker, если framework предоставляет такой механизм.
|
||||||
|
|
||||||
|
**SLM-PRO-ASM-016 - ОБЯЗАН.** Runtime imports server assembly должны ограничиваться собственной business factory, собственными server adapters и необходимыми server technical inputs; runtime import React/client surface запрещён. Type-only foreign contracts допускаются по [SLM-PRO-XDOM-005](./cross-domain-boundary.md#type-only-contracts).
|
||||||
|
|
||||||
|
## Constructor и activation
|
||||||
|
|
||||||
|
Assembly определяет способ создания, но не владеет полным cross-domain graph.
|
||||||
|
|
||||||
|
Отсутствие product request, socket connection и background resource при вызове runtime creator определяется base-правилом `SLM-LIFE-002`.
|
||||||
|
|
||||||
|
```text
|
||||||
|
module import
|
||||||
|
→ определяет creator
|
||||||
|
|
||||||
|
creator call
|
||||||
|
→ создаёт runtime instance
|
||||||
|
|
||||||
|
explicit start
|
||||||
|
→ запускает resources
|
||||||
|
```
|
||||||
|
|
||||||
|
**SLM-PRO-ASM-010 - ОБЯЗАН.** Resources запускает composition scope owner в выбранном scope согласно [lifecycle rules](../../../runtime-and-lifecycle.md).
|
||||||
|
|
||||||
|
## Public entrypoints
|
||||||
|
|
||||||
|
**SLM-PRO-CMP-004 - ЗАПРЕЩЕНО.** Composition не может повторять adapter wiring, если domain public assembly уже создаёт готовый runtime.
|
||||||
|
|
||||||
|
**SLM-PRO-CMP-013 - ОБЯЗАН.** Composition должна использовать public client/server creator domain, если domain предоставляет runtime-specific assembly.
|
||||||
|
|
||||||
|
**SLM-PRO-CMP-014 - МОЖЕТ.** Composition может вызвать public business factory напрямую только для universal domain, у которого нет external ports, concrete adapters и runtime-specific input.
|
||||||
|
|
||||||
|
**SLM-PRO-ASM-011 - ОБЯЗАН.** Client и server assembly должны иметь разные public entrypoints.
|
||||||
|
|
||||||
|
**SLM-PRO-ASM-012 - ЗАПРЕЩЕНО.** Общий domain barrel не может runtime-реэкспортировать одновременно client и server surfaces.
|
||||||
|
|
||||||
|
## Server/client bridge
|
||||||
|
|
||||||
|
Client и server runtimes являются разными instances над общей business semantics.
|
||||||
|
|
||||||
|
Запрет на передачу DomainRuntime, functions, Context, store или query client через serializable server/client boundary определяется правилом [SLM-DATA-012](../../../state-and-data.md#serializable-boundaries).
|
||||||
|
|
||||||
|
**SLM-PRO-ASM-014 - МОЖЕТ.** Server может передать client assembly только serializable business-owned bootstrap data без secrets и mutable runtime objects.
|
||||||
125
docs2/specification/modes/pro/domains/cross-domain-boundary.md
Normal file
125
docs2/specification/modes/pro/domains/cross-domain-boundary.md
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
---
|
||||||
|
title: Cross-domain boundary
|
||||||
|
status: draft
|
||||||
|
normative: true
|
||||||
|
overlay: pro
|
||||||
|
base: slm
|
||||||
|
---
|
||||||
|
|
||||||
|
# Cross-domain Boundary
|
||||||
|
|
||||||
|
> Overlay: `SLM Pro`.
|
||||||
|
|
||||||
|
Domains не образуют скрытый runtime graph внутри слоя `domains`. Граф связывается только graph owner в `compositions`.
|
||||||
|
|
||||||
|
## Composition graph
|
||||||
|
|
||||||
|
**SLM-PRO-CMP-001 - ОБЯЗАН.** Runtime graph нескольких domains должен собираться в composition, которая владеет его scope.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const auth = createAuthRuntime()
|
||||||
|
const user = createUserRuntime({ auth: auth.session })
|
||||||
|
const orders = createOrdersRuntime({ user: user.agreements })
|
||||||
|
```
|
||||||
|
|
||||||
|
**SLM-PRO-CMP-002 - ОБЯЗАН.** Composition должна создавать domain runtimes в явном ацикличном порядке.
|
||||||
|
|
||||||
|
**SLM-PRO-CMP-003 - ОБЯЗАН.** Cross-domain dependency должна передаваться как готовая минимальная capability, а не разрешаться service locator или domain import.
|
||||||
|
|
||||||
|
**SLM-PRO-CMP-012 - ОБЯЗАН.** App-specific graph type должен отражать только реально предоставленные runtimes; `Partial<Graph>` с последующим приведением к полному graph запрещён.
|
||||||
|
|
||||||
|
## Runtime imports
|
||||||
|
|
||||||
|
**SLM-PRO-XDOM-001 - ЗАПРЕЩЕНО.** Ни одна zone domain A не может импортировать, реэкспортировать, dynamic-import или разрешать через service locator runtime value domain B.
|
||||||
|
|
||||||
|
Запрет включает foreign business API, hooks, Provider, Context, components, adapters, runtime creators и event emitters.
|
||||||
|
|
||||||
|
Foreign runtime capability может поступить только argument-ом от composition согласно разделу [Runtime capability injection](#runtime-capability-injection).
|
||||||
|
|
||||||
|
## Type-only contracts
|
||||||
|
|
||||||
|
**SLM-PRO-API-008 - СЛЕДУЕТ.** Cross-domain capability следует описывать consumer-owned structural port вместо зависимости от полного foreign API type.
|
||||||
|
|
||||||
|
**SLM-PRO-XDOM-014 - ЗАПРЕЩЕНО.** Public contract зависимого domain не может реэкспортировать полный foreign DomainRuntime type как собственную cross-domain dependency.
|
||||||
|
|
||||||
|
**SLM-PRO-XDOM-005 - МОЖЕТ.** Business и client/server input contracts domain могут type-only импортировать минимальный стабильный business contract другого domain.
|
||||||
|
|
||||||
|
Предпочтение consumer-owned port определяется правилом `SLM-PRO-API-008`.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export type UserAuthPort = {
|
||||||
|
getSessionSnapshot: () => SessionSnapshot
|
||||||
|
subscribeToSession: (listener: () => void) => () => void
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Type-only import не разрешает runtime import и не переносит ownership.
|
||||||
|
|
||||||
|
**SLM-PRO-XDOM-012 - ЗАПРЕЩЕНО.** Type dependency cycle между domains запрещён, даже если не создаёт runtime cycle.
|
||||||
|
|
||||||
|
## Runtime capability injection
|
||||||
|
|
||||||
|
**SLM-PRO-XDOM-007 - МОЖЕТ.** Domain runtime creator может принять готовую structurally compatible capability, созданную другим domain и переданную composition.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const auth = createAuthClientRuntime()
|
||||||
|
const user = createUserClientRuntime({ auth: auth.session })
|
||||||
|
```
|
||||||
|
|
||||||
|
User domain знает только свой input contract. Он не знает creator, Provider, adapters и scope AuthRuntime.
|
||||||
|
|
||||||
|
**SLM-PRO-XDOM-008 - ОБЯЗАН.** Передаваемая capability должна быть минимальной и не раскрывать raw store, Context, SDK client или mutable internals foreign domain.
|
||||||
|
|
||||||
|
**SLM-PRO-XDOM-013 - МОЖЕТ.** Structurally compatible foreign capability может реализовать consumer-owned port напрямую. Wrapper adapter создаётся только при необходимости преобразовать contracts или lifecycle.
|
||||||
|
|
||||||
|
## React composition
|
||||||
|
|
||||||
|
Если React-сущность использует runtime API двух domains, она принадлежит `compositions`.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const ProtectedOrderForm = () => {
|
||||||
|
const auth = useAuth()
|
||||||
|
const order = useOrder()
|
||||||
|
|
||||||
|
return auth.isAuthenticated
|
||||||
|
? <OrderForm order={order} />
|
||||||
|
: <AuthPrompt />
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**SLM-PRO-XDOM-009 - ОБЯЗАН.** Props, callbacks и slots, передаваемые из composition в domain UI, должны оставаться domain-local или presentation-neutral. Foreign domain semantics остаётся во владеющей composition.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<AuthRequired>
|
||||||
|
<OrderForm />
|
||||||
|
</AuthRequired>
|
||||||
|
```
|
||||||
|
|
||||||
|
Такое связывание выполняется в composition, а не внутри auth или orders.
|
||||||
|
|
||||||
|
## Events
|
||||||
|
|
||||||
|
Прямая подписка на event emitter другого domain через runtime import запрещена правилом `SLM-PRO-XDOM-001`.
|
||||||
|
|
||||||
|
Composition может передать event capability через consumer-owned port:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const orders = createOrdersClientRuntime({
|
||||||
|
userEvents: {
|
||||||
|
subscribeToIdentity: user.identity.subscribe,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cycles
|
||||||
|
|
||||||
|
**SLM-PRO-XDOM-011 - ЗАПРЕЩЕНО.** Runtime dependency cycle между domains является нарушением границы и не может скрываться event bus, lazy resolution или two-way service locator.
|
||||||
|
|
||||||
|
**SLM-PRO-LIFE-008 - ОБЯЗАН.** Cross-domain graph запускается в dependency order и освобождается в обратном порядке.
|
||||||
|
|
||||||
|
Ненормативное пояснение: при обнаружении цикла следует пересмотреть один из вариантов:
|
||||||
|
|
||||||
|
- пересмотреть границы domains;
|
||||||
|
- перенести orchestration в composition;
|
||||||
|
- выделить отдельную product responsibility;
|
||||||
|
- инвертировать зависимость через consumer-owned port.
|
||||||
81
docs2/specification/modes/pro/domains/framework.md
Normal file
81
docs2/specification/modes/pro/domains/framework.md
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
---
|
||||||
|
title: Framework surface в SLM Pro
|
||||||
|
status: draft
|
||||||
|
normative: true
|
||||||
|
overlay: pro
|
||||||
|
base: slm
|
||||||
|
---
|
||||||
|
|
||||||
|
# Framework Surface
|
||||||
|
|
||||||
|
> Overlay: `SLM Pro`.
|
||||||
|
|
||||||
|
Framework surface адаптирует готовый DomainRuntime к execution model конкретного framework. В текущей структуре React surface располагается в `react/`.
|
||||||
|
|
||||||
|
## Структура React surface
|
||||||
|
|
||||||
|
```text
|
||||||
|
domains/{group...}/{domain}/react/
|
||||||
|
├── context/
|
||||||
|
├── providers/
|
||||||
|
├── hooks/
|
||||||
|
├── ui/
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Ни один segment не обязателен без реальной потребности.
|
||||||
|
|
||||||
|
## Runtime access
|
||||||
|
|
||||||
|
**SLM-PRO-FRM-001 - ОБЯЗАН.** Framework surface должна работать с конкретным DomainRuntime через domain-owned runtime access boundary.
|
||||||
|
|
||||||
|
**SLM-PRO-FRM-002 - ЗАПРЕЩЕНО.** Framework hook или component не может самостоятельно вызывать business factory, создавать adapters или разрешать runtime из global service locator.
|
||||||
|
|
||||||
|
**SLM-PRO-FRM-003 - ОБЯЗАН.** Runtime access boundary должна получать готовый DomainRuntime извне и не создавать параллельное domain state.
|
||||||
|
|
||||||
|
Для React типичным механизмом является private Context, связывающий статически экспортированные hooks/components с переданным runtime instance. Это пояснение не предписывает точную форму или количество Providers в текущем draft.
|
||||||
|
|
||||||
|
**Domain runtime Provider** - часть framework surface, получающая готовый DomainRuntime и предоставляющая его framework consumers одного domain. Provider не создаёт cross-domain graph автоматически.
|
||||||
|
|
||||||
|
## Imports
|
||||||
|
|
||||||
|
**SLM-PRO-FRM-004 - ОБЯЗАН.** React surface должна импортировать business runtime contracts только через `import type`.
|
||||||
|
|
||||||
|
**SLM-PRO-FRM-005 - ЗАПРЕЩЕНО.** React surface не может runtime-импортировать business factory, private business services, selectors, validators, errors или constants.
|
||||||
|
|
||||||
|
**SLM-PRO-FRM-006 - ЗАПРЕЩЕНО.** React surface не может импортировать domain adapters, SDK, product infra client или assembly.
|
||||||
|
|
||||||
|
**SLM-PRO-FRM-007 - МОЖЕТ.** React surface может импортировать public API `ui`, `shared` и framework libraries, разрешённые её runtime profile.
|
||||||
|
|
||||||
|
Cross-domain runtime imports framework surface запрещены правилом [SLM-PRO-XDOM-001](./cross-domain-boundary.md#runtime-imports).
|
||||||
|
|
||||||
|
## Hooks
|
||||||
|
|
||||||
|
**SLM-PRO-FRM-009 - ОБЯЗАН.** Domain hook должен получать product data и behavior только через текущий DomainRuntime.
|
||||||
|
|
||||||
|
**SLM-PRO-FRM-010 - МОЖЕТ.** Hook может использовать framework query/cache runtime как private implementation поверх imperative DomainRuntime query.
|
||||||
|
|
||||||
|
**SLM-PRO-FRM-011 - ЗАПРЕЩЕНО.** Query hook не может использовать adapter или SDK call как fetcher в обход DomainRuntime.
|
||||||
|
|
||||||
|
**SLM-PRO-FRM-012 - ЗАПРЕЩЕНО.** Query-library types, cache keys и raw mutate API не могут становиться public business contract.
|
||||||
|
|
||||||
|
## Domain UI
|
||||||
|
|
||||||
|
Domain React UI может:
|
||||||
|
|
||||||
|
- вызывать hooks своего domain;
|
||||||
|
- использовать universal UI;
|
||||||
|
- отображать domain-owned states и outcomes;
|
||||||
|
- принимать callbacks, props и slots от composition.
|
||||||
|
|
||||||
|
**SLM-PRO-FRM-013 - ЗАПРЕЩЕНО.** Domain UI не может импортировать runtime другого domain или оркестрировать route/page flow.
|
||||||
|
|
||||||
|
Владение React UI, использующим несколько domains, определено base-правилом [SLM-CMP-006](../../../layers/compositions.md#product-ui).
|
||||||
|
|
||||||
|
## Client boundary
|
||||||
|
|
||||||
|
**SLM-PRO-FRM-015 - ОБЯЗАН.** Entry point React hooks, Context и interactive UI должен быть явно отмечен как client runtime согласно правилам используемого framework.
|
||||||
|
|
||||||
|
**SLM-PRO-FRM-016 - ЗАПРЕЩЕНО.** Server-compatible React export не может попадать в client entrypoint только из-за нахождения рядом с client hooks или Provider.
|
||||||
|
|
||||||
|
React не является синонимом client runtime; environment profile определяется фактическими dependencies export.
|
||||||
158
docs2/specification/modes/pro/domains/index.md
Normal file
158
docs2/specification/modes/pro/domains/index.md
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
---
|
||||||
|
title: Domains в SLM Pro
|
||||||
|
status: draft
|
||||||
|
normative: true
|
||||||
|
overlay: pro
|
||||||
|
base: slm
|
||||||
|
---
|
||||||
|
|
||||||
|
# Domains в SLM Pro
|
||||||
|
|
||||||
|
> Overlay: `SLM Pro`. Base: [SLM](../../../index.md).
|
||||||
|
|
||||||
|
Domain является изолированным вертикальным product module с одной предметной ответственностью, явным public boundary и строгими внутренними dependency zones.
|
||||||
|
|
||||||
|
## Domain и group
|
||||||
|
|
||||||
|
**SLM-PRO-DOM-001 - ОБЯЗАН.** Конечный Pro domain должен располагаться непосредственно в `domains` или внутри одной или нескольких навигационных groups.
|
||||||
|
|
||||||
|
```text
|
||||||
|
domains/{domain}
|
||||||
|
domains/{group}/{domain}
|
||||||
|
domains/{group}/{nested-group}/{domain}
|
||||||
|
```
|
||||||
|
|
||||||
|
**SLM-PRO-DOM-002 - ОБЯЗАН.** Узел domain tree с собственным public API, state, integration, assembly или runtime должен классифицироваться как конечный domain, а не domain group.
|
||||||
|
|
||||||
|
```text
|
||||||
|
domains/
|
||||||
|
├── navigation/ # domain
|
||||||
|
└── knv/ # group
|
||||||
|
├── auth/ # domain
|
||||||
|
├── user/ # domain
|
||||||
|
└── orders/ # domain
|
||||||
|
```
|
||||||
|
|
||||||
|
**SLM-PRO-DOM-003 - ОБЯЗАН.** Первой архитектурной единицей в group tree является конечная папка, владеющая самостоятельной product responsibility.
|
||||||
|
|
||||||
|
## Ownership
|
||||||
|
|
||||||
|
**SLM-PRO-DOM-004 - ОБЯЗАН.** Pro domain должен владеть одной сформулированной product responsibility и предоставлять её внешним consumers через собственные public entrypoints.
|
||||||
|
|
||||||
|
Pro domain может владеть:
|
||||||
|
|
||||||
|
- product model и value objects;
|
||||||
|
- scenarios и operations;
|
||||||
|
- domain state и transitions;
|
||||||
|
- normalization и product errors;
|
||||||
|
- business-owned ports;
|
||||||
|
- concrete integrations собственных ports;
|
||||||
|
- framework hooks и UI одного domain;
|
||||||
|
- client/server runtime assembly.
|
||||||
|
|
||||||
|
**SLM-PRO-DOM-005 - ЗАПРЕЩЕНО.** Domain не может владеть framework route entry, page/layout composition, UI нескольких самостоятельных product responsibilities, universal technical capability или product-agnostic UI primitive.
|
||||||
|
|
||||||
|
Public entrypoints Pro domain следуют base-правилам `SLM-API-001` и `SLM-API-002`; Pro-главы вводят дополнительные ограничения exports.
|
||||||
|
|
||||||
|
**SLM-PRO-DOM-007 - ЗАПРЕЩЕНО.** Если product responsibility получила Pro domain owner, app, composition или infra не могут создавать параллельную модель этой ответственности либо обходить её public boundary.
|
||||||
|
|
||||||
|
**SLM-PRO-DOM-008 - ОБЯЗАН.** Для domain-owned responsibility это правило заменяет base-правило `SLM-CMP-001`: business владеет product logic, а composition владеет application flow и runtime graph.
|
||||||
|
|
||||||
|
Product responsibility считается устойчивой, если имеет самостоятельную product model или transitions, используется несколькими application flows либо владеет external integration/lifecycle contract.
|
||||||
|
|
||||||
|
**SLM-PRO-DOM-017 - ОБЯЗАН.** Каждая устойчивая product responsibility должна иметь Pro domain owner; route/page-local presentation flow остаётся ответственностью composition.
|
||||||
|
|
||||||
|
## Внутренние zones
|
||||||
|
|
||||||
|
```text
|
||||||
|
domains/{group...}/{domain}/
|
||||||
|
├── business/
|
||||||
|
├── react/
|
||||||
|
├── adapters/
|
||||||
|
├── client/
|
||||||
|
└── server/
|
||||||
|
```
|
||||||
|
|
||||||
|
| Zone | Статус | Ответственность |
|
||||||
|
|---|---|---|
|
||||||
|
| [`business`](./business.md) | Обязательная | Product model, factory, ports, scenarios, errors |
|
||||||
|
| [`react`](./framework.md) | Опциональная | React runtime access, hooks, Providers, domain UI |
|
||||||
|
| [`adapters`](./ports-and-adapters.md) | Опциональная | Concrete реализации business-owned ports |
|
||||||
|
| [`client`](./client-and-server.md) | Опциональная | Browser/client assembly одного domain |
|
||||||
|
| [`server`](./client-and-server.md) | Опциональная | Server/request assembly одного domain |
|
||||||
|
|
||||||
|
**SLM-PRO-DOM-009 - ОБЯЗАН.** Каждый Pro domain должен содержать `business` как единственного владельца product model и business semantics.
|
||||||
|
|
||||||
|
**SLM-PRO-DOM-010 - СЛЕДУЕТ.** Опциональную zone следует добавлять только при наличии реального runtime consumer и самостоятельной ответственности.
|
||||||
|
|
||||||
|
**SLM-PRO-DOM-011 - ЗАПРЕЩЕНО.** Нельзя создавать пустые симметричные `react`, `adapters`, `client` или `server` на будущее.
|
||||||
|
|
||||||
|
**SLM-PRO-DOM-012 - ОБЯЗАН.** Domain zones должны соблюдать внутреннюю dependency direction, даже если физически находятся под одним владельцем.
|
||||||
|
|
||||||
|
**SLM-PRO-MOD-001 - ОБЯЗАН.** `business`, `react`, `adapters`, `client` и `server` являются внутренними zones одного domain, а не самостоятельными верхнеуровневыми modules.
|
||||||
|
|
||||||
|
**SLM-PRO-SEG-001 - ЗАПРЕЩЕНО.** Domain zones нельзя трактовать как взаимозаменяемые generic segments.
|
||||||
|
|
||||||
|
Внутри каждой zone могут использоваться обычные base SLM segments по фактической необходимости.
|
||||||
|
|
||||||
|
## Внутреннее направление
|
||||||
|
|
||||||
|
```text
|
||||||
|
business -> shared | pure libraries
|
||||||
|
react -> ui | shared | framework libraries
|
||||||
|
adapters -> infra | SDK | platform runtime
|
||||||
|
client -> own business factory | own client adapters | own framework surface | client technical inputs
|
||||||
|
server -> own business factory | own server adapters | server technical inputs
|
||||||
|
```
|
||||||
|
|
||||||
|
Матрица описывает runtime imports. React surface может type-only импортировать собственные business contracts, adapters - собственные business ports/types, а client/server inputs - разрешённые cross-domain contracts.
|
||||||
|
|
||||||
|
## Путь данных
|
||||||
|
|
||||||
|
```text
|
||||||
|
composition
|
||||||
|
-> domain client/server assembly при наличии runtime-specific setup
|
||||||
|
или напрямую business factory для universal domain
|
||||||
|
-> DomainRuntime
|
||||||
|
-> business scenario
|
||||||
|
-> business-owned port
|
||||||
|
-> domain adapter
|
||||||
|
-> infra / SDK / storage / external source
|
||||||
|
```
|
||||||
|
|
||||||
|
**SLM-PRO-DOM-013 - ОБЯЗАН.** DomainRuntime, созданный business factory, должен быть единственным product gateway своего Pro domain для runtime consumers.
|
||||||
|
|
||||||
|
Stateless logic API также является DomainRuntime, если он создан factory и соблюдает тот же public boundary.
|
||||||
|
|
||||||
|
## Product UI
|
||||||
|
|
||||||
|
**SLM-PRO-DOM-014 - МОЖЕТ.** Product UI одной Pro domain responsibility может принадлежать framework surface этого domain.
|
||||||
|
|
||||||
|
UI нескольких самостоятельных responsibilities остаётся в `compositions` согласно base-правилу `SLM-CMP-006`.
|
||||||
|
|
||||||
|
## Cross-domain graph
|
||||||
|
|
||||||
|
```text
|
||||||
|
composition
|
||||||
|
-> создаёт несколько domain runtimes
|
||||||
|
-> передаёт готовые capabilities
|
||||||
|
```
|
||||||
|
|
||||||
|
Pro domain не создаёт runtime другого domain и не импортирует его runtime surface. Точные правила определены в [Cross-domain boundary](./cross-domain-boundary.md).
|
||||||
|
|
||||||
|
**Graph owner** - composition, являющаяся scope owner нескольких DomainRuntime, связанных направленными dependencies в одном ацикличном graph, и определяющая порядок их создания, activation и cleanup.
|
||||||
|
|
||||||
|
## Monorepo boundary
|
||||||
|
|
||||||
|
**SLM-PRO-DOM-015 - ОБЯЗАН.** Pro domain должен оставаться внутри `apps/{app}/src/domains` до принятия отдельной package-модели.
|
||||||
|
|
||||||
|
**SLM-PRO-DOM-016 - ЗАПРЕЩЕНО.** Workspace package не может называться Pro Domain для целей Specification, если он не соответствует application path и ownership этой главы.
|
||||||
|
|
||||||
|
## Главы Pro Domain Specification
|
||||||
|
|
||||||
|
- [Business](./business.md)
|
||||||
|
- [Framework surface](./framework.md)
|
||||||
|
- [Ports и adapters](./ports-and-adapters.md)
|
||||||
|
- [Client и server assembly](./client-and-server.md)
|
||||||
|
- [Cross-domain boundary](./cross-domain-boundary.md)
|
||||||
|
- [Тестирование Pro domains](./testing.md)
|
||||||
100
docs2/specification/modes/pro/domains/ports-and-adapters.md
Normal file
100
docs2/specification/modes/pro/domains/ports-and-adapters.md
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
---
|
||||||
|
title: Ports и adapters в SLM Pro
|
||||||
|
status: draft
|
||||||
|
normative: true
|
||||||
|
overlay: pro
|
||||||
|
base: slm
|
||||||
|
---
|
||||||
|
|
||||||
|
# Ports и Adapters
|
||||||
|
|
||||||
|
> Overlay: `SLM Pro`.
|
||||||
|
|
||||||
|
Port определяет потребность business. Adapter связывает эту потребность с concrete runtime.
|
||||||
|
|
||||||
|
## Ownership
|
||||||
|
|
||||||
|
```text
|
||||||
|
domain/
|
||||||
|
├── business/
|
||||||
|
│ └── ports/
|
||||||
|
└── adapters/
|
||||||
|
```
|
||||||
|
|
||||||
|
**SLM-PRO-ADP-001 - ОБЯЗАН.** Port должен принадлежать `business` того domain, который потребляет capability.
|
||||||
|
|
||||||
|
**SLM-PRO-ADP-002 - ОБЯЗАН.** Concrete adapter должен принадлежать тому же domain, но находиться вне `business`.
|
||||||
|
|
||||||
|
**SLM-PRO-ADP-003 - ЗАПРЕЩЕНО.** Infra или external SDK не могут объявлять business port от имени domain.
|
||||||
|
|
||||||
|
**SLM-PRO-ADP-014 - ОБЯЗАН.** External technical capability из infra, SDK, storage или platform runtime должна реализовывать business port через adapter собственного domain, а этот adapter должен подключаться assembly того же domain. Готовая capability другого DomainRuntime может удовлетворять consumer-owned port напрямую только по правилу [SLM-PRO-XDOM-013](./cross-domain-boundary.md#runtime-capability-injection).
|
||||||
|
|
||||||
|
## Adapter contract
|
||||||
|
|
||||||
|
**SLM-PRO-ADP-004 - ОБЯЗАН.** Responsibilities adapter должны ограничиваться применимыми integration operations:
|
||||||
|
|
||||||
|
- импортировать type-only business port и domain input types;
|
||||||
|
- импортировать public infra API, SDK или platform runtime;
|
||||||
|
- переводить domain arguments в transport arguments;
|
||||||
|
- возвращать raw/unknown source result для business normalization;
|
||||||
|
- подписываться на concrete event source через явный lifecycle contract.
|
||||||
|
|
||||||
|
**SLM-PRO-ADP-005 - ЗАПРЕЩЕНО.** Adapter не может выполнять следующие domain/framework responsibilities:
|
||||||
|
|
||||||
|
- создавать domain error;
|
||||||
|
- выбирать domain fallback;
|
||||||
|
- реализовывать business rule;
|
||||||
|
- объявлять domain model;
|
||||||
|
- экспортировать concrete client consumer-коду;
|
||||||
|
- вызывать framework hook;
|
||||||
|
- runtime-импортировать или самостоятельно разрешать другой domain runtime.
|
||||||
|
|
||||||
|
Adapter может работать с минимальной foreign capability, явно переданной composition, только для преобразования contract или lifecycle согласно `SLM-PRO-XDOM-013`.
|
||||||
|
|
||||||
|
**SLM-PRO-ADP-006 - ОБЯЗАН.** Adapter должен реализовывать ровно тот port contract, который необходим business.
|
||||||
|
|
||||||
|
**SLM-PRO-ADP-007 - ЗАПРЕЩЕНО.** Нельзя передавать полный client, если port требует ограниченный набор capabilities.
|
||||||
|
|
||||||
|
**SLM-PRO-ADP-008 - ЗАПРЕЩЕНО.** Adapter integration logic не должна писаться inline в composition или runtime assembly.
|
||||||
|
|
||||||
|
## Client и server adapters
|
||||||
|
|
||||||
|
Adapters могут быть разделены по runtime:
|
||||||
|
|
||||||
|
```text
|
||||||
|
adapters/
|
||||||
|
├── client/
|
||||||
|
│ ├── browser-session.adapter.ts
|
||||||
|
│ └── websocket-orders.adapter.ts
|
||||||
|
└── server/
|
||||||
|
├── request-session.adapter.ts
|
||||||
|
└── server-orders-api.adapter.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
**SLM-PRO-ADP-009 - ОБЯЗАН.** Client adapter не должен попадать в server graph, а server adapter - в client graph.
|
||||||
|
|
||||||
|
**SLM-PRO-ADP-010 - ОБЯЗАН.** Runtime-specific adapter должен иметь явный environment marker, если framework предоставляет такой механизм.
|
||||||
|
|
||||||
|
## Event sources
|
||||||
|
|
||||||
|
Socket, subscription и event listener реализуют event port:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export type OrdersEventsPort = {
|
||||||
|
subscribe: (listener: (event: unknown) => void) => () => void
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**SLM-PRO-ADP-011 - ОБЯЗАН.** Event adapter должен возвращать cleanup и не открывать connection при module import.
|
||||||
|
|
||||||
|
**SLM-PRO-ADP-012 - ОБЯЗАН.** Wire event проходит business normalization до изменения domain state или передачи consumer-коду.
|
||||||
|
|
||||||
|
**SLM-PRO-LIFE-013 - МОЖЕТ.** Один physical transport может обслуживать adapters нескольких domains, если transport остаётся domain-agnostic, а adapters получают суженные channels.
|
||||||
|
|
||||||
|
## Public boundary
|
||||||
|
|
||||||
|
**SLM-PRO-API-014 - ЗАПРЕЩЕНО.** Public business, framework, client или server entrypoint не может реэкспортировать concrete adapter внешним consumers.
|
||||||
|
|
||||||
|
**SLM-PRO-ADP-013 - ЗАПРЕЩЕНО.** `adapters` не имеет внешнего public API для app, compositions или других domains.
|
||||||
|
|
||||||
|
Adapters доступны только assembly собственного domain и собственным contract tests.
|
||||||
63
docs2/specification/modes/pro/domains/testing.md
Normal file
63
docs2/specification/modes/pro/domains/testing.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
title: Тестирование Pro domains
|
||||||
|
status: draft
|
||||||
|
normative: true
|
||||||
|
overlay: pro
|
||||||
|
base: slm
|
||||||
|
---
|
||||||
|
|
||||||
|
# Тестирование Pro Domains
|
||||||
|
|
||||||
|
> Overlay: `SLM Pro`.
|
||||||
|
|
||||||
|
Общие правила [тестирования и соответствия](../../../testing-and-conformance.md) дополняются проверками строгих business, adapter, assembly и framework boundaries.
|
||||||
|
|
||||||
|
## Business factory tests
|
||||||
|
|
||||||
|
**SLM-PRO-TEST-001 - ОБЯЗАН.** Каждый public method runtime API, возвращаемого factory, должен иметь factory-level tests.
|
||||||
|
|
||||||
|
Factory-level tests должны проверять применимые случаи:
|
||||||
|
|
||||||
|
- happy path;
|
||||||
|
- malformed external result;
|
||||||
|
- rejected dependency;
|
||||||
|
- синхронное исключение dependency;
|
||||||
|
- domain outcome/error semantics;
|
||||||
|
- side-effect order;
|
||||||
|
- state transition;
|
||||||
|
- отсутствие constructor-time I/O;
|
||||||
|
- public API shape.
|
||||||
|
|
||||||
|
**SLM-PRO-TEST-002 - ОБЯЗАН.** Factory-level test должен создавать runtime через public `business` entrypoint, а не deep-import factory internals.
|
||||||
|
|
||||||
|
## Adapter tests
|
||||||
|
|
||||||
|
**SLM-PRO-TEST-003 - ОБЯЗАН.** Adapter с mapping, transport payload, error channel или lifecycle должен иметь contract tests на применимые responsibilities.
|
||||||
|
|
||||||
|
**SLM-PRO-TEST-004 - ЗАПРЕЩЕНО.** Adapter test не должен дублировать business scenario tests или утверждать domain fallback/error semantics.
|
||||||
|
|
||||||
|
## Assembly tests
|
||||||
|
|
||||||
|
**SLM-PRO-TEST-005 - ОБЯЗАН.** Client/server assembly tests должны проверять корректную передачу ports, runtime profile isolation и отсутствие I/O при creation.
|
||||||
|
|
||||||
|
**SLM-PRO-TEST-006 - ОБЯЗАН.** Server assembly с request data должен иметь isolation test для параллельных scopes.
|
||||||
|
|
||||||
|
## Framework tests
|
||||||
|
|
||||||
|
**SLM-PRO-TEST-007 - ОБЯЗАН.** Framework surface tests должны проверять runtime access boundary, предсказуемую ошибку при отсутствии runtime boundary, mapping public outcomes и lifecycle integration.
|
||||||
|
|
||||||
|
## Composition tests
|
||||||
|
|
||||||
|
**SLM-PRO-TEST-009 - ОБЯЗАН.** Tests cross-domain composition должны проверять topology, точный graph contract, переданные capabilities и lifecycle cleanup.
|
||||||
|
|
||||||
|
**SLM-PRO-TEST-010 - ОБЯЗАН.** Scope с неполным набором domains не должен типизироваться как полный application graph.
|
||||||
|
|
||||||
|
## Architecture checks
|
||||||
|
|
||||||
|
**SLM-PRO-TEST-019 - ОБЯЗАН.** Pro repository checks должны проверять применимые строгие domain boundaries:
|
||||||
|
|
||||||
|
- client/server markers;
|
||||||
|
- forbidden runtime imports между domains;
|
||||||
|
- private adapters;
|
||||||
|
- business entrypoint shape;
|
||||||
|
- zone dependency direction.
|
||||||
55
docs2/specification/modes/pro/index.md
Normal file
55
docs2/specification/modes/pro/index.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
---
|
||||||
|
title: SLM Pro
|
||||||
|
status: draft
|
||||||
|
normative: true
|
||||||
|
overlay: pro
|
||||||
|
base: slm
|
||||||
|
---
|
||||||
|
|
||||||
|
# SLM Pro
|
||||||
|
|
||||||
|
`SLM Pro` является независимым overlay непосредственно над [base SLM](../../index.md).
|
||||||
|
|
||||||
|
```text
|
||||||
|
SLM Pro = SLM + Pro rules
|
||||||
|
```
|
||||||
|
|
||||||
|
## Отличия от SLM
|
||||||
|
|
||||||
|
| Область | Base SLM | SLM Pro |
|
||||||
|
|---|---|---|
|
||||||
|
| Product ownership | Product logic принадлежит compositions | Устойчивая product responsibility принадлежит изолированному domain |
|
||||||
|
| Слои | `app`, `compositions`, `infra`, `ui`, `shared` | Добавляется `domains` |
|
||||||
|
| Структура domain | Отсутствует | `business`, framework surface, adapters, client/server assembly |
|
||||||
|
| Domain dependencies | Отсутствуют | Cross-domain runtime imports запрещены, capabilities передаются composition |
|
||||||
|
| External integration | Composition использует infra | Private domain adapter реализует business-owned port |
|
||||||
|
| Testing | Risk-based base tests | Обязательные tests для используемых factory, adapter, assembly и graph boundaries |
|
||||||
|
|
||||||
|
## Расширение архитектурной модели
|
||||||
|
|
||||||
|
**SLM-PRO-ARCH-001 - ОБЯЗАН.** SLM Pro должен расширять набор base-слоёв слоем `domains` для изолированных product responsibilities.
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/
|
||||||
|
├── app/
|
||||||
|
├── compositions/
|
||||||
|
├── domains/
|
||||||
|
├── infra/
|
||||||
|
├── ui/
|
||||||
|
└── shared/
|
||||||
|
```
|
||||||
|
|
||||||
|
**SLM-PRO-ARCH-002 - ОБЯЗАН.** Дополнительные dependency edges Pro должны соответствовать следующему направлению:
|
||||||
|
|
||||||
|
```text
|
||||||
|
compositions -> domains
|
||||||
|
domains -> согласно внутренним Pro zones
|
||||||
|
```
|
||||||
|
|
||||||
|
Base dependency direction для остальных слоёв сохраняется.
|
||||||
|
|
||||||
|
**SLM-PRO-CMP-010 - МОЖЕТ.** Composition module может импортировать public entrypoints Pro domains в дополнение к imports, разрешённым base-правилом `SLM-CMP-010`.
|
||||||
|
|
||||||
|
## Pro Domain Specification
|
||||||
|
|
||||||
|
Полная Pro-модель слоя описана в [Domains](./domains/index.md). Других mode-specific отличий текущий draft Pro не вводит.
|
||||||
@@ -19,7 +19,6 @@ Module является минимальным самостоятельным в
|
|||||||
Типичные modules:
|
Типичные modules:
|
||||||
|
|
||||||
- page, layout, screen или widget в `compositions`;
|
- page, layout, screen или widget в `compositions`;
|
||||||
- конечный domain в `domains`;
|
|
||||||
- technical service в `infra`;
|
- technical service в `infra`;
|
||||||
- reusable UI module в `ui`.
|
- reusable UI module в `ui`.
|
||||||
|
|
||||||
@@ -35,13 +34,6 @@ Group классифицирует modules и другие groups, но не в
|
|||||||
|
|
||||||
**SLM-MOD-006 - МОЖЕТ.** Group может содержать другие groups и конечные modules.
|
**SLM-MOD-006 - МОЖЕТ.** Group может содержать другие groups и конечные modules.
|
||||||
|
|
||||||
```text
|
|
||||||
domains/
|
|
||||||
└── knv/ # group
|
|
||||||
├── auth/ # domain module
|
|
||||||
└── orders/ # domain module
|
|
||||||
```
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
compositions/
|
compositions/
|
||||||
└── pages/ # group
|
└── pages/ # group
|
||||||
@@ -49,17 +41,11 @@ compositions/
|
|||||||
└── profile/ # composition module
|
└── profile/ # composition module
|
||||||
```
|
```
|
||||||
|
|
||||||
## Domain zones
|
|
||||||
|
|
||||||
**SLM-MOD-007 - ОБЯЗАН.** `business`, `react`, `adapters`, `client` и `server` внутри конечного domain являются внутренними zones одного domain, а не самостоятельными верхнеуровневыми modules.
|
|
||||||
|
|
||||||
Zones могут иметь собственные entrypoints, но domain остаётся единым владельцем product responsibility.
|
|
||||||
|
|
||||||
## Component
|
## Component
|
||||||
|
|
||||||
Component является presentation unit внутри module и не считается самостоятельным архитектурным owner.
|
Component является presentation unit внутри module и не считается самостоятельным архитектурным owner.
|
||||||
|
|
||||||
**SLM-MOD-008 - ЗАПРЕЩЕНО.** Component не может самостоятельно выбирать product source, собирать domain runtime или оркестрировать несколько modules.
|
**SLM-MOD-008 - ЗАПРЕЩЕНО.** Component не может самостоятельно выбирать application-level product source, выполнять module wiring или оркестрировать несколько самостоятельных modules.
|
||||||
|
|
||||||
**SLM-MOD-009 - МОЖЕТ.** Component может владеть локальной presentation mechanics и рендерить другие components, разрешённые слоем владельца.
|
**SLM-MOD-009 - МОЖЕТ.** Component может владеть локальной presentation mechanics и рендерить другие components, разрешённые слоем владельца.
|
||||||
|
|
||||||
@@ -77,7 +63,7 @@ compositions/pages/home/
|
|||||||
└── index.ts
|
└── index.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
**SLM-MOD-011 - ЗАПРЕЩЕНО.** Nested module не может использоваться для сокрытия ответственности, которой фактически владеет другой слой или domain.
|
**SLM-MOD-011 - ЗАПРЕЩЕНО.** Nested module не может использоваться для сокрытия ответственности, которой фактически владеет другой module или layer.
|
||||||
|
|
||||||
## Scope evolution
|
## Scope evolution
|
||||||
|
|
||||||
|
|||||||
@@ -16,13 +16,12 @@ apps/
|
|||||||
└── src/
|
└── src/
|
||||||
├── app/
|
├── app/
|
||||||
├── compositions/
|
├── compositions/
|
||||||
├── domains/
|
|
||||||
├── infra/
|
├── infra/
|
||||||
├── ui/
|
├── ui/
|
||||||
└── shared/
|
└── shared/
|
||||||
```
|
```
|
||||||
|
|
||||||
**SLM-MONO-001 - ОБЯЗАН.** Каждое приложение должно самостоятельно определять свой product graph и application compositions.
|
**SLM-MONO-001 - ОБЯЗАН.** Каждое приложение должно самостоятельно определять свои application compositions, product ownership и runtime wiring.
|
||||||
|
|
||||||
**SLM-MONO-002 - ЗАПРЕЩЕНО.** Workspace package не может импортировать код из `apps/*`.
|
**SLM-MONO-002 - ЗАПРЕЩЕНО.** Workspace package не может импортировать код из `apps/*`.
|
||||||
|
|
||||||
@@ -32,7 +31,7 @@ apps/
|
|||||||
|
|
||||||
**SLM-MONO-004 - ОБЯЗАН.** Package должен иметь самостоятельного owner, public exports и подтверждённую reuse/ownership semantics.
|
**SLM-MONO-004 - ОБЯЗАН.** Package должен иметь самостоятельного owner, public exports и подтверждённую reuse/ownership semantics.
|
||||||
|
|
||||||
**SLM-MONO-005 - ЗАПРЕЩЕНО.** Нельзя создавать package только для обхода layer direction или скрытия cross-domain import.
|
**SLM-MONO-005 - ЗАПРЕЩЕНО.** Нельзя создавать package только для обхода layer direction, public API или иной объявленной dependency boundary.
|
||||||
|
|
||||||
**SLM-MONO-006 - ОБЯЗАН.** Consumers импортируют package через объявленный package export, а не через filesystem path к internal source.
|
**SLM-MONO-006 - ОБЯЗАН.** Consumers импортируют package через объявленный package export, а не через filesystem path к internal source.
|
||||||
|
|
||||||
@@ -44,18 +43,12 @@ apps/
|
|||||||
- technical infra client;
|
- technical infra client;
|
||||||
- deterministic shared foundation;
|
- deterministic shared foundation;
|
||||||
- schema/codegen/tooling package;
|
- schema/codegen/tooling package;
|
||||||
- configuration package без application graph.
|
- configuration package без application-specific wiring.
|
||||||
|
|
||||||
## Domains в packages
|
Base SLM не присваивает package дополнительный архитектурный статус автоматически.
|
||||||
|
|
||||||
Эта draft-версия определяет Domain как module внутри `apps/{app}/src/domains` и пока не определяет packaged domain как conforming SLM Domain.
|
|
||||||
|
|
||||||
**SLM-MONO-007 - ОБЯЗАН.** До принятия отдельной package-модели Domain должен оставаться внутри владеющего приложения.
|
|
||||||
|
|
||||||
**SLM-MONO-008 - ЗАПРЕЩЕНО.** Package не может называться Domain для целей этой версии Specification, если он не соответствует определённому application path и ownership.
|
|
||||||
|
|
||||||
## Dependency direction
|
## Dependency direction
|
||||||
|
|
||||||
**SLM-MONO-009 - ОБЯЗАН.** Package dependency graph должен оставаться ацикличным и соответствовать заявленной ответственности packages.
|
**SLM-MONO-009 - ОБЯЗАН.** Package dependency graph должен оставаться ацикличным и соответствовать заявленной ответственности packages.
|
||||||
|
|
||||||
**SLM-MONO-010 - ЗАПРЕЩЕНО.** Shared package не может импортировать domain, composition или app-specific infra package.
|
**SLM-MONO-010 - ЗАПРЕЩЕНО.** Shared package не может импортировать application composition или app-specific infra package.
|
||||||
|
|||||||
@@ -6,76 +6,50 @@ normative: true
|
|||||||
|
|
||||||
# Public API и Импорты
|
# Public API и Импорты
|
||||||
|
|
||||||
Public API ограничивает знание consumers о внутренней структуре module и отделяет runtime profiles.
|
Public API ограничивает знание consumers о внутренней структуре module. Точная форма entrypoint определяется владельцем и не требует обязательного `index.ts`.
|
||||||
|
|
||||||
## Общие правила
|
## Общие правила
|
||||||
|
|
||||||
**SLM-API-001 - ОБЯЗАН.** Межмодульный import должен использовать public entrypoint импортируемого module.
|
**SLM-API-001 - ОБЯЗАН.** Межмодульный import должен использовать объявленный public entrypoint импортируемого module.
|
||||||
|
|
||||||
**SLM-API-002 - ЗАПРЕЩЕНО.** Deep imports во внутренние segments, zones и files другого module запрещены.
|
**SLM-API-002 - ЗАПРЕЩЕНО.** Deep imports во внутренние segments, files и иные private paths другого module запрещены.
|
||||||
|
|
||||||
**SLM-API-003 - ОБЯЗАН.** Каждый runtime export должен иметь реального consumer за пределами владеющего entrypoint и стабильную ответственность. Sibling zone собственного domain и public-boundary test считаются consumers zone entrypoint.
|
**SLM-API-003 - ОБЯЗАН.** Каждый runtime export должен иметь реального consumer за пределами владеющего entrypoint и стабильную ответственность.
|
||||||
|
|
||||||
**SLM-API-004 - ЗАПРЕЩЕНО.** Public API не может экспортировать raw Context, mutable store, persistence key, concrete adapter, SDK client или internal service.
|
**SLM-API-004 - ЗАПРЕЩЕНО.** Public API не может случайно раскрывать implementation unit, который владелец считает private или lifecycle которого не является частью public contract.
|
||||||
|
|
||||||
**SLM-API-005 - ОБЯЗАН.** Alias или package subpath должен физически разрешаться TypeScript, tests и production build.
|
**SLM-API-005 - ОБЯЗАН.** Alias или package subpath должен физически разрешаться TypeScript, tests и production build.
|
||||||
|
|
||||||
|
**SLM-API-009 - МОЖЕТ.** Public entrypoint может быть root `index.ts`, отдельным named entry, package export или другим явно объявленным path.
|
||||||
|
|
||||||
|
**SLM-API-010 - ОБЯЗАН.** Public и private paths module должны быть различимы consumers и repository tooling.
|
||||||
|
|
||||||
## Layer matrix
|
## Layer matrix
|
||||||
|
|
||||||
| Importer | Runtime imports |
|
| Importer | Runtime imports |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `app` | Public composition entries, shared static/global resources |
|
| `app` | Public composition entries, shared static/global resources |
|
||||||
| `compositions` | Compositions, domain runtime surfaces, infra, ui, shared |
|
| `compositions` | Compositions, infra, ui, shared |
|
||||||
| Domain `business` | Own files, shared, pure libraries |
|
|
||||||
| Domain `react` | Own business types, ui, shared, framework libraries |
|
|
||||||
| Domain `adapters` | Own business types/ports, infra, SDK/platform runtime |
|
|
||||||
| Domain `client/server` | Own factory, own adapters, own runtime surface |
|
|
||||||
| `infra` | Infra, shared |
|
| `infra` | Infra, shared |
|
||||||
| `ui` | UI, shared |
|
| `ui` | UI, shared |
|
||||||
| `shared` | External pure libraries only |
|
| `shared` | External pure libraries only |
|
||||||
|
|
||||||
Cross-domain rules дополнительно ограничены [cross-domain boundary](./layers/domains/cross-domain-boundary.md).
|
|
||||||
|
|
||||||
## Type-only imports
|
## Type-only imports
|
||||||
|
|
||||||
**SLM-API-006 - МОЖЕТ.** `import type` может использоваться для разрешённого contract dependency без создания runtime edge.
|
**SLM-API-006 - МОЖЕТ.** `import type` может использоваться для разрешённого contract dependency без создания runtime edge.
|
||||||
|
|
||||||
**SLM-API-007 - ЗАПРЕЩЕНО.** Type-only import не разрешает перенос ownership, импорт concrete runtime type или обход layer boundary.
|
**SLM-API-007 - ЗАПРЕЩЕНО.** Type-only import не разрешает перенос ownership, импорт private concrete runtime type или обход layer boundary.
|
||||||
|
|
||||||
**SLM-API-008 - СЛЕДУЕТ.** Cross-domain capability следует описывать consumer-owned structural port вместо зависимости от полного foreign API type.
|
## Groups
|
||||||
|
|
||||||
## Business entrypoint
|
Отсутствие public entrypoint у group определяется base-правилом `SLM-MOD-004`.
|
||||||
|
|
||||||
```text
|
**SLM-API-015 - ОБЯЗАН.** Composition public API экспортирует только entry components, access APIs, types и contracts, необходимые внешним composition consumers.
|
||||||
domains/{domain}/business/index.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
Точный contract business entrypoint определён правилами [SLM-BUS-006 - SLM-BUS-008](./layers/domains/business.md#public-api).
|
|
||||||
|
|
||||||
## Runtime-specific domain entrypoints
|
|
||||||
|
|
||||||
Domain может иметь отдельные public surfaces:
|
|
||||||
|
|
||||||
```text
|
|
||||||
domains/{domain}/react
|
|
||||||
domains/{domain}/client
|
|
||||||
domains/{domain}/server
|
|
||||||
```
|
|
||||||
|
|
||||||
Разделение client/server exports и markers определено правилами [SLM-ASM-011 - SLM-ASM-014](./layers/domains/client-and-server.md#public-entrypoints).
|
|
||||||
|
|
||||||
Точная форма re-export между `react` и `client` в этом draft не предписана. Независимо от формы должны соблюдаться environment isolation и отсутствие обхода DomainRuntime.
|
|
||||||
|
|
||||||
## Groups и private zones
|
|
||||||
|
|
||||||
**SLM-API-013 - ЗАПРЕЩЕНО.** Group не имеет public entrypoint.
|
|
||||||
|
|
||||||
**SLM-API-014 - ЗАПРЕЩЕНО.** Domain `adapters` не экспортируется app, compositions или другим domains.
|
|
||||||
|
|
||||||
**SLM-API-015 - ОБЯЗАН.** Composition public API экспортирует только entry components, точные access hooks/types и contracts, необходимые внешним composition consumers.
|
|
||||||
|
|
||||||
## Cycles
|
## Cycles
|
||||||
|
|
||||||
**SLM-API-016 - ЗАПРЕЩЕНО.** Runtime import cycle между modules запрещён независимо от того, способен ли bundler его выполнить.
|
**SLM-API-016 - ЗАПРЕЩЕНО.** Runtime import cycle между modules запрещён независимо от того, способен ли bundler его выполнить.
|
||||||
|
|
||||||
**SLM-API-017 - ЗАПРЕЩЕНО.** Barrel не должен создавать скрытый cycle между ready composition и access API её children.
|
**SLM-API-017 - ЗАПРЕЩЕНО.** Barrel не должен создавать скрытый cycle между ready composition и access API её children.
|
||||||
|
|
||||||
|
Дополнительные entrypoints и import restrictions принадлежат overlay, который их вводит.
|
||||||
|
|||||||
@@ -6,24 +6,26 @@ normative: true
|
|||||||
|
|
||||||
# Runtime и Lifecycle
|
# Runtime и Lifecycle
|
||||||
|
|
||||||
Lifecycle является архитектурной частью любого mutable runtime, subscription и external resource.
|
Lifecycle является архитектурной частью любого mutable runtime, subscription и external resource. Эти правила не требуют создавать отдельный runtime или factory, если у module нет соответствующего состояния или resources.
|
||||||
|
|
||||||
## Три стадии
|
## Definition, creation и activation
|
||||||
|
|
||||||
|
Для module с создаваемым runtime применима модель:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
definition
|
definition
|
||||||
→ module объявляет creators
|
-> module объявляет creator
|
||||||
|
|
||||||
creation
|
creation
|
||||||
→ creator создаёт runtime instance без внешних effects
|
-> creator создаёт instance без external effects
|
||||||
|
|
||||||
activation
|
activation
|
||||||
→ graph owner запускает resources и получает cleanup
|
-> scope owner запускает resources и получает cleanup
|
||||||
```
|
```
|
||||||
|
|
||||||
**SLM-LIFE-001 - ЗАПРЕЩЕНО.** Module import не должен выполнять product I/O, открывать connection или регистрировать global listener.
|
**SLM-LIFE-001 - ЗАПРЕЩЕНО.** Module import не должен выполнять product I/O, открывать connection или регистрировать global listener.
|
||||||
|
|
||||||
**SLM-LIFE-002 - ОБЯЗАН.** Factory и runtime creator должны быть side-effect free относительно external resources.
|
**SLM-LIFE-002 - ОБЯЗАН.** Если module предоставляет factory или runtime creator, creation должна быть side-effect free относительно external resources.
|
||||||
|
|
||||||
**SLM-LIFE-003 - ОБЯЗАН.** Subscription, socket, timer и listener запускаются явной operation владельца scope.
|
**SLM-LIFE-003 - ОБЯЗАН.** Subscription, socket, timer и listener запускаются явной operation владельца scope.
|
||||||
|
|
||||||
@@ -40,49 +42,35 @@ activation
|
|||||||
| Request | Server composition/request builder |
|
| Request | Server composition/request builder |
|
||||||
| Test | Test setup/wrapper |
|
| Test | Test setup/wrapper |
|
||||||
|
|
||||||
**SLM-LIFE-005 - ОБЯЗАН.** Graph owner должен определить количество instances и duration каждого runtime.
|
**SLM-LIFE-005 - ОБЯЗАН.** Scope owner должен определить количество instances и duration каждого mutable runtime или resource.
|
||||||
|
|
||||||
**SLM-LIFE-006 - ЗАПРЕЩЕНО.** Module-level singleton не может использоваться как случайная замена application scope.
|
**SLM-LIFE-006 - ЗАПРЕЩЕНО.** Module-level singleton не может использоваться как случайная замена application scope.
|
||||||
|
|
||||||
**SLM-LIFE-007 - МОЖЕТ.** Application singleton допустим только при явном application ownership и отсутствии request-, identity- и user-specific data.
|
**SLM-LIFE-007 - МОЖЕТ.** Application singleton допустим только при явном application ownership и отсутствии request-, identity- и user-specific data.
|
||||||
|
|
||||||
## Graph activation
|
## Activation и cleanup
|
||||||
|
|
||||||
**SLM-LIFE-008 - ОБЯЗАН.** Cross-domain graph запускается в dependency order и освобождается в обратном порядке.
|
|
||||||
|
|
||||||
**SLM-LIFE-009 - ОБЯЗАН.** Повторный mount/unmount, включая development Strict Mode, не должен оставлять duplicate subscription или abandoned resource.
|
**SLM-LIFE-009 - ОБЯЗАН.** Повторный mount/unmount, включая development Strict Mode, не должен оставлять duplicate subscription или abandoned resource.
|
||||||
|
|
||||||
**SLM-LIFE-010 - СЛЕДУЕТ.** `start` и cleanup следует проектировать idempotent либо явно защищать от повторного вызова.
|
**SLM-LIFE-010 - СЛЕДУЕТ.** `start` и cleanup следует проектировать idempotent либо явно защищать от повторного вызова.
|
||||||
|
|
||||||
**SLM-LIFE-018 - ОБЯЗАН.** Если activation графа завершилась ошибкой, graph owner должен освободить уже успешно запущенную часть графа в обратном порядке.
|
**SLM-LIFE-018 - ОБЯЗАН.** Если activation составного resource set завершилась ошибкой, scope owner должен освободить уже успешно запущенную часть в обратном порядке.
|
||||||
|
|
||||||
**SLM-LIFE-019 - ОБЯЗАН.** Ошибка cleanup должна быть наблюдаемой и не должна препятствовать попытке освободить остальные resources графа.
|
**SLM-LIFE-019 - ОБЯЗАН.** Ошибка cleanup должна быть наблюдаемой и не должна препятствовать попытке освободить остальные resources scope.
|
||||||
|
|
||||||
## Events и sockets
|
## Events и sockets
|
||||||
|
|
||||||
Socket является technical transport, а его product events входят в domain через business-owned event port.
|
Product event обрабатывается владельцем product semantics; socket остаётся technical transport.
|
||||||
|
|
||||||
```text
|
**SLM-LIFE-011 - ЗАПРЕЩЕНО.** Framework component не может открывать product socket напрямую при render или module import.
|
||||||
socket transport
|
|
||||||
→ domain adapter
|
|
||||||
→ business event normalization
|
|
||||||
→ state transition или invalidation intent
|
|
||||||
→ framework projection
|
|
||||||
```
|
|
||||||
|
|
||||||
**SLM-LIFE-011 - ЗАПРЕЩЕНО.** Framework component не может подписываться на product socket напрямую.
|
**SLM-LIFE-012 - ОБЯЗАН.** Invalid event и connection failure должны преобразовываться в product state/outcome либо technical telemetry согласно их semantics; callback error нельзя терять через unobserved throw.
|
||||||
|
|
||||||
**SLM-LIFE-012 - ОБЯЗАН.** Invalid event и connection failure должны преобразовываться в domain state/outcome либо technical telemetry согласно их semantics; callback error нельзя терять через unobserved throw.
|
|
||||||
|
|
||||||
**SLM-LIFE-013 - МОЖЕТ.** Один physical transport может обслуживать adapters нескольких domains, если transport остаётся domain-agnostic, а adapters получают суженные channels.
|
|
||||||
|
|
||||||
## Revalidation events
|
## Revalidation events
|
||||||
|
|
||||||
Event может содержать domain update или только сообщать об устаревании данных.
|
Event может содержать product update или только сообщать об устаревании данных.
|
||||||
|
|
||||||
**SLM-LIFE-014 - ОБЯЗАН.** Invalidation intent должен выражаться domain language и не требовать import конкретной query library в business.
|
**SLM-LIFE-014 - ОБЯЗАН.** Invalidation intent должен выражаться product language и не требовать import конкретной query library в public product contract.
|
||||||
|
|
||||||
Framework surface может преобразовать domain invalidation event в private cache invalidation.
|
|
||||||
|
|
||||||
## Server runtime
|
## Server runtime
|
||||||
|
|
||||||
@@ -90,4 +78,6 @@ Framework surface может преобразовать domain invalidation even
|
|||||||
|
|
||||||
**SLM-LIFE-016 - ЗАПРЕЩЕНО.** Process singleton не может захватывать request headers, cookies, credentials, AbortSignal или user-specific cache.
|
**SLM-LIFE-016 - ЗАПРЕЩЕНО.** Process singleton не может захватывать request headers, cookies, credentials, AbortSignal или user-specific cache.
|
||||||
|
|
||||||
**SLM-LIFE-017 - ОБЯЗАН.** Request cancellation должна передаваться external operations через подходящий port/adapter, если runtime поддерживает cancellation.
|
**SLM-LIFE-017 - ОБЯЗАН.** Request cancellation должна передаваться external operations, если runtime и используемая integration поддерживают cancellation.
|
||||||
|
|
||||||
|
Overlay может вводить дополнительные lifecycle boundaries только внутри собственного delta.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ normative: true
|
|||||||
|
|
||||||
# Сегменты
|
# Сегменты
|
||||||
|
|
||||||
Segment группирует внутренние файлы module по устойчивой роли. Segment не является самостоятельным layer, module или domain.
|
Segment группирует внутренние файлы module по устойчивой роли. Segment не является самостоятельным layer или module.
|
||||||
|
|
||||||
## Базовые segments
|
## Базовые segments
|
||||||
|
|
||||||
@@ -31,11 +31,11 @@ Segment группирует внутренние файлы module по уст
|
|||||||
|
|
||||||
**SLM-SEG-002 - ЗАПРЕЩЕНО.** Нельзя создавать полный симметричный набор segments как scaffold без реального содержимого.
|
**SLM-SEG-002 - ЗАПРЕЩЕНО.** Нельзя создавать полный симметричный набор segments как scaffold без реального содержимого.
|
||||||
|
|
||||||
**SLM-SEG-003 - ОБЯЗАН.** Файл должен размещаться в segment согласно своей фактической роли, а не только расширению или имени.
|
**SLM-SEG-003 - ОБЯЗАН.** Если файл помещён в segment, роль segment должна соответствовать фактической роли файла, а не только его расширению или имени. Файлы могут оставаться в корне небольшого module.
|
||||||
|
|
||||||
**SLM-SEG-004 - ЗАПРЕЩЕНО.** Segment не имеет внешнего public API независимо от module owner.
|
**SLM-SEG-004 - ЗАПРЕЩЕНО.** Segment не имеет внешнего public API независимо от module owner.
|
||||||
|
|
||||||
**SLM-SEG-005 - ЗАПРЕЩЕНО.** Нельзя импортировать segment другого module через deep path.
|
Запрет deep import в segment другого module определяется base-правилом `SLM-API-002`.
|
||||||
|
|
||||||
## UI и Parts
|
## UI и Parts
|
||||||
|
|
||||||
@@ -51,25 +51,9 @@ Segment группирует внутренние файлы module по уст
|
|||||||
|
|
||||||
Примеры:
|
Примеры:
|
||||||
|
|
||||||
- domain hook - `domains/{domain}/react/hooks`;
|
- product hook - владеющий product module;
|
||||||
- page-local hook - владеющая page composition;
|
- page-local hook - владеющая page composition;
|
||||||
- reusable technical hook - соответствующий infra module;
|
- reusable technical hook - соответствующий infra module;
|
||||||
- product-agnostic UI hook - владеющий UI module.
|
- product-agnostic UI hook - владеющий UI module.
|
||||||
|
|
||||||
## Domain zones и segments
|
Segments являются только внутренними организационными ролями и не вводят дополнительных архитектурных zones.
|
||||||
|
|
||||||
**SLM-SEG-008 - ЗАПРЕЩЕНО.** Domain zones `business`, `react`, `adapters`, `client`, `server` нельзя трактовать как взаимозаменяемые generic segments.
|
|
||||||
|
|
||||||
Внутри zone могут существовать обычные segments:
|
|
||||||
|
|
||||||
```text
|
|
||||||
domain/
|
|
||||||
├── business/
|
|
||||||
│ ├── services/
|
|
||||||
│ ├── types/
|
|
||||||
│ └── mappers/
|
|
||||||
└── react/
|
|
||||||
├── hooks/
|
|
||||||
├── providers/
|
|
||||||
└── ui/
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -6,64 +6,64 @@ normative: true
|
|||||||
|
|
||||||
# State и Data
|
# State и Data
|
||||||
|
|
||||||
Данные и состояние должны иметь одного понятного владельца semantics, даже если runtime использует несколько caches и projections.
|
SLM рассматривает данные и состояние через одного владельца semantics, даже если runtime использует несколько caches и projections.
|
||||||
|
|
||||||
## Ownership matrix
|
## Ownership matrix
|
||||||
|
|
||||||
| Вид | Владелец |
|
| Вид | Владелец |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Domain model и transitions | Domain business |
|
| Product model и transitions | Product owner |
|
||||||
| Product source integration | Domain adapter |
|
| Product source integration | Product owner; technical mechanism остаётся в infra |
|
||||||
| Framework projection доменных данных | Domain framework surface |
|
| Framework projection product data | Public surface владельца product data |
|
||||||
| Page-local presentation state | Composition |
|
| Page-local presentation state | Composition |
|
||||||
| Component-local interaction | Владеющий component/module |
|
| Component-local interaction | Владеющий component/module |
|
||||||
| Technical connection/cache state | Infra или runtime-specific owner |
|
| Technical connection/cache state | Infra или runtime-specific owner |
|
||||||
| Request context | Server/framework scope |
|
| Request context | Server/framework scope |
|
||||||
| Universal UI state | Владеющий UI module |
|
| Universal UI state | Владеющий UI module |
|
||||||
|
|
||||||
## Domain gateway
|
## Product gateway
|
||||||
|
|
||||||
**SLM-DATA-001 - ОБЯЗАН.** Consumer получает product data только через public domain runtime surface.
|
**SLM-DATA-001 - ОБЯЗАН.** Consumer должен получать product data через public boundary владеющего module.
|
||||||
|
|
||||||
**SLM-DATA-002 - ЗАПРЕЩЕНО.** Composition, UI или app не могут маппить transport DTO в параллельную domain model.
|
**SLM-DATA-002 - ЗАПРЕЩЕНО.** Composition, UI или app не могут маппить transport DTO в параллельную product model, если модель уже имеет другого owner.
|
||||||
|
|
||||||
**SLM-DATA-003 - ОБЯЗАН.** Domain business владеет normalization, validation и semantics отсутствия данных.
|
**SLM-DATA-003 - ОБЯЗАН.** Product owner владеет normalization, validation и semantics отсутствия данных.
|
||||||
|
|
||||||
## Domain state
|
## Product state
|
||||||
|
|
||||||
**SLM-DATA-004 - ОБЯЗАН.** Domain state model и допустимые transitions определяются business независимо от concrete state manager.
|
**SLM-DATA-004 - ОБЯЗАН.** Product state model и допустимые transitions должны определяться product owner независимо от concrete state manager.
|
||||||
|
|
||||||
**SLM-DATA-005 - ЗАПРЕЩЕНО.** Raw store API не может становиться public domain contract.
|
**SLM-DATA-005 - ЗАПРЕЩЕНО.** Concrete mutable store implementation не может становиться public product contract без явно объявленного владельцем стабильного store access API.
|
||||||
|
|
||||||
**SLM-DATA-006 - ОБЯЗАН.** Mutable domain instance должен быть привязан к явному lifecycle scope.
|
**SLM-DATA-006 - ОБЯЗАН.** Mutable product instance должен быть привязан к явному lifecycle scope.
|
||||||
|
|
||||||
## Query cache
|
## Query cache
|
||||||
|
|
||||||
Framework или technical query cache может хранить projection результата DomainRuntime query.
|
Framework или technical query cache может хранить projection результата product query.
|
||||||
|
|
||||||
**SLM-DATA-007 - ОБЯЗАН.** Fetcher продуктового query должен вызывать DomainRuntime, а не adapter или SDK напрямую.
|
**SLM-DATA-007 - ОБЯЗАН.** Query/cache consumer за пределами product owner должен использовать public boundary владельца и не может обходить его прямым вызовом private integration или SDK.
|
||||||
|
|
||||||
**SLM-DATA-008 - ЗАПРЕЩЕНО.** Query cache не может объявлять собственную domain model, error taxonomy или fallback policy.
|
**SLM-DATA-008 - ЗАПРЕЩЕНО.** Query cache не может объявлять собственную product model, error taxonomy или fallback policy.
|
||||||
|
|
||||||
**SLM-DATA-009 - ОБЯЗАН.** User/session-scoped cache keys и invalidation должны изолировать данные разных identities и scopes без использования secret как публичного key contract.
|
**SLM-DATA-009 - ОБЯЗАН.** User/session-scoped cache keys и invalidation должны изолировать данные разных identities и scopes без использования secret как публичного key contract.
|
||||||
|
|
||||||
Эта draft-версия не предписывает единственное физическое место QueryClient/SWR cache. Конкретная модель должна сохранять правила gateway, lifecycle и identity isolation.
|
Эта draft-версия не предписывает единственное физическое место QueryClient/SWR cache. Конкретная модель оценивается по правилам public boundary владельца, lifecycle и identity isolation.
|
||||||
|
|
||||||
**SLM-DATA-015 - ОБЯЗАН.** Cache instance должен иметь явного creator и scope owner в composition или runtime setup.
|
**SLM-DATA-015 - ОБЯЗАН.** Cache instance должен иметь явного creator и scope owner в composition или runtime setup.
|
||||||
|
|
||||||
**SLM-DATA-016 - ОБЯЗАН.** Shared framework cache должен передаваться domain surfaces через framework-supported runtime boundary, а не через import app-specific infra singleton.
|
**SLM-DATA-016 - ОБЯЗАН.** Shared framework cache должен передаваться consumers через framework-supported runtime boundary, а не через import app-specific mutable singleton.
|
||||||
|
|
||||||
**SLM-DATA-017 - ОБЯЗАН.** Graph owner должен очищать или изолировать private cache при смене identity и завершении соответствующего scope.
|
**SLM-DATA-017 - ОБЯЗАН.** Scope owner должен очищать или изолировать private cache при смене identity и завершении соответствующего scope.
|
||||||
|
|
||||||
## Presentation state
|
## Presentation state
|
||||||
|
|
||||||
**SLM-DATA-010 - МОЖЕТ.** Composition или component может использовать concrete state manager для локального presentation state.
|
**SLM-DATA-010 - МОЖЕТ.** Composition или component может использовать concrete state manager для локального presentation state.
|
||||||
|
|
||||||
**SLM-DATA-011 - ЗАПРЕЩЕНО.** Presentation store не должен копировать DomainRuntime state как второй source of truth.
|
**SLM-DATA-011 - ЗАПРЕЩЕНО.** Presentation store не должен копировать canonical product state как второй source of truth.
|
||||||
|
|
||||||
## Serializable boundaries
|
## Serializable boundaries
|
||||||
|
|
||||||
**SLM-DATA-012 - ОБЯЗАН.** Через server/client boundary передаются только serializable business-owned data без functions, stores, clients, Context и resources.
|
**SLM-DATA-012 - ОБЯЗАН.** Через server/client boundary передаются только serializable product-owned data без functions, stores, clients, Context и resources.
|
||||||
|
|
||||||
**SLM-DATA-013 - ЗАПРЕЩЕНО.** Secrets, access tokens и request credentials не должны включаться в client bootstrap snapshot.
|
**SLM-DATA-013 - ЗАПРЕЩЕНО.** Secrets, access tokens и request credentials не должны включаться в client bootstrap snapshot.
|
||||||
|
|
||||||
|
|||||||
@@ -6,107 +6,56 @@ normative: true
|
|||||||
|
|
||||||
# Терминология
|
# Терминология
|
||||||
|
|
||||||
|
## Base SLM
|
||||||
|
|
||||||
|
**Base SLM** - самостоятельная минимальная архитектура, применяемая без дополнительного overlay.
|
||||||
|
|
||||||
|
## Overlay
|
||||||
|
|
||||||
|
**Overlay** - независимое опциональное нормативное расширение, применяемое непосредственно поверх base SLM. Overlay не наследует правила другого overlay.
|
||||||
|
|
||||||
## Слой
|
## Слой
|
||||||
|
|
||||||
**Layer** - верхнеуровневая зона `src`, определяющая вид ответственности и допустимые направления зависимостей.
|
**Layer** - верхнеуровневая зона `src`, определяющая вид ответственности и допустимые направления зависимостей.
|
||||||
|
|
||||||
SLM использует слои `app`, `compositions`, `domains`, `infra`, `ui` и `shared`.
|
Base SLM использует слои `app`, `compositions`, `infra`, `ui` и `shared`.
|
||||||
|
|
||||||
## Модуль
|
## Модуль
|
||||||
|
|
||||||
**Module** - минимальный самостоятельный владелец ответственности с public boundary. Модуль может содержать код разных технических типов, если весь этот код принадлежит одной ответственности.
|
**Module** - минимальный самостоятельный владелец ответственности с public boundary. Модуль может содержать код разных технических типов, если весь этот код принадлежит одной ответственности.
|
||||||
|
|
||||||
|
## Product owner
|
||||||
|
|
||||||
|
**Product owner** - module, владеющий product semantics, model, behavior, data boundary и public API одной ответственности.
|
||||||
|
|
||||||
## Группа
|
## Группа
|
||||||
|
|
||||||
**Group** - навигационная папка, классифицирующая модули или другие группы. Группа не является модулем, не имеет public API и не владеет runtime.
|
**Group** - навигационная папка, классифицирующая модули или другие группы. Группа не является модулем, не имеет public API и не владеет runtime.
|
||||||
|
|
||||||
## Домен
|
|
||||||
|
|
||||||
**Domain** - конечный продуктовый модуль в слое `domains`, владеющий одной предметной ответственностью и всеми её runtime surfaces.
|
|
||||||
|
|
||||||
Допустимые пути:
|
|
||||||
|
|
||||||
```text
|
|
||||||
domains/{domain}
|
|
||||||
domains/{group...}/{domain}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Группа доменов
|
|
||||||
|
|
||||||
**Domain group** - группа внутри `domains`, используемая только для навигации. Например, `knv` в пути `domains/knv/auth` является группой, если не имеет собственного public API, состояния и assembly.
|
|
||||||
|
|
||||||
## Зона домена
|
|
||||||
|
|
||||||
**Domain zone** - внутренняя архитектурная часть domain с отдельным направлением зависимостей. Базовые зоны: `business`, `react`, `adapters`, `client`, `server`.
|
|
||||||
|
|
||||||
Зона не является самостоятельным domain.
|
|
||||||
|
|
||||||
## Business
|
|
||||||
|
|
||||||
**Business** - framework-neutral зона domain, владеющая моделью, правилами, ports, сценариями, состоянием, нормализацией и domain errors. Business создаёт public logic runtime через factory.
|
|
||||||
|
|
||||||
## Factory
|
|
||||||
|
|
||||||
**Factory** - side-effect-free constructor, принимающий явные dependencies и возвращающий public business runtime API.
|
|
||||||
|
|
||||||
## DomainRuntime
|
|
||||||
|
|
||||||
**DomainRuntime** - созданный factory экземпляр доменного поведения. Он предоставляет commands, queries, snapshots, subscriptions и lifecycle operations, необходимые конкретному domain.
|
|
||||||
|
|
||||||
Factory создаёт DomainRuntime. DomainRuntime является публичным шлюзом к данным и поведению domain.
|
|
||||||
|
|
||||||
## Port
|
|
||||||
|
|
||||||
**Port** - business-owned contract внешней capability, необходимой domain. Port описывается языком domain и не раскрывает concrete SDK, transport или framework runtime.
|
|
||||||
|
|
||||||
## Adapter
|
|
||||||
|
|
||||||
**Adapter** - concrete реализация port поверх infra, SDK, storage, platform API, framework runtime или другого внешнего механизма.
|
|
||||||
|
|
||||||
Готовая capability одного DomainRuntime, структурно удовлетворяющая port другого domain, является cross-domain runtime dependency, а не concrete adapter автоматически. Wrapper adapter требуется только при реальном преобразовании contracts.
|
|
||||||
|
|
||||||
## Framework surface
|
|
||||||
|
|
||||||
**Framework surface** - API domain для конкретного UI/framework runtime. Для React он может включать runtime access boundary, hooks, Providers и domain UI.
|
|
||||||
|
|
||||||
Framework surface не является параллельным business API и не обращается к external source в обход DomainRuntime.
|
|
||||||
|
|
||||||
## Assembly
|
|
||||||
|
|
||||||
**Assembly** - связывание factory с concrete adapters и runtime-specific input для создания готового runtime одного domain.
|
|
||||||
|
|
||||||
## Composition
|
## Composition
|
||||||
|
|
||||||
**Composition** - модуль, связывающий готовые modules и domain runtimes в page, route, layout, screen, widget или другой application flow.
|
**Composition** - product module, связывающий public APIs и technical capabilities в page, route, layout, screen, widget или другой application flow.
|
||||||
|
|
||||||
## Graph owner
|
## Scope owner
|
||||||
|
|
||||||
**Graph owner** - composition, request setup или test setup, которое выбирает набор runtime instances, порядок их создания, lifecycle scope и cleanup.
|
**Scope owner** - composition, request setup, provider setup или test setup, которое выбирает runtime instances и resources, их lifetime, activation и cleanup.
|
||||||
|
|
||||||
## Domain runtime Provider
|
|
||||||
|
|
||||||
**Domain runtime Provider** - часть framework surface, передающая готовый runtime instance framework consumers одного domain. Она не является владельцем cross-domain graph автоматически.
|
|
||||||
|
|
||||||
## Provider composition
|
|
||||||
|
|
||||||
**Provider composition** - composition module, создающий или получающий несколько runtimes и монтирующий их framework boundaries в выбранном scope.
|
|
||||||
|
|
||||||
## Segment
|
## Segment
|
||||||
|
|
||||||
**Segment** - внутренняя папка модуля, группирующая файлы по роли, например `hooks`, `services`, `types`, `styles` или `lib`.
|
**Segment** - внутренняя папка модуля, группирующая файлы по роли, например `hooks`, `services`, `types`, `styles` или `lib`.
|
||||||
|
|
||||||
Domain zones не являются обычными segments.
|
|
||||||
|
|
||||||
## Компонент
|
## Компонент
|
||||||
|
|
||||||
**Component** - presentation unit внутри владеющего module. Компонент не является самостоятельным архитектурным owner и не выбирает источники данных или runtime dependencies.
|
**Component** - presentation unit внутри владеющего module. Компонент не является самостоятельным архитектурным owner и не выбирает application dependencies самостоятельно.
|
||||||
|
|
||||||
## Продуктовые данные
|
## Продуктовые данные
|
||||||
|
|
||||||
**Product data** - данные, состояние и outcomes, имеющие смысл в предметной области продукта. Transport DTO, raw SDK response и browser storage schema не являются доменной моделью автоматически.
|
**Product data** - данные, состояние и outcomes, имеющие смысл в предметной области продукта. Transport DTO, raw SDK response и browser storage schema не являются product model автоматически.
|
||||||
|
|
||||||
## Runtime dependency
|
## Runtime dependency
|
||||||
|
|
||||||
**Runtime dependency** - dependency, необходимая выполняемому коду: API другого объекта, external source, store, query runtime, event source, clock, environment или platform capability.
|
**Runtime dependency** - dependency, необходимая выполняемому коду: API другого объекта, external source, store, query runtime, event source, clock, environment или platform capability.
|
||||||
|
|
||||||
`import type` не создаёт runtime dependency, но может создавать статическую связанность contracts.
|
`import type` не создаёт runtime dependency, но может создавать статическую связанность contracts.
|
||||||
|
|
||||||
|
Термины, вводимые `SLM Advanced` или `SLM Pro`, определяются и имеют нормативную силу только внутри соответствующего overlay.
|
||||||
|
|||||||
@@ -6,74 +6,50 @@ normative: true
|
|||||||
|
|
||||||
# Тестирование и Соответствие
|
# Тестирование и Соответствие
|
||||||
|
|
||||||
Тесты проверяют public boundaries и runtime risks каждого owner, а не только внутренние helpers.
|
Тесты проверяют public boundaries и runtime risks каждого owner. Base SLM не требует создавать неиспользуемые архитектурные конструкции ради тестовой формы.
|
||||||
|
|
||||||
## Business factory tests
|
## Risk-based tests
|
||||||
|
|
||||||
**SLM-TEST-001 - ОБЯЗАН.** Каждый public method runtime API, возвращаемого factory, должен иметь factory-level tests.
|
**SLM-TEST-018 - ОБЯЗАН.** Tests изменённого module должны покрывать применимые риски его public behavior, data boundaries и lifecycle.
|
||||||
|
|
||||||
Factory-level tests должны проверять применимые случаи:
|
Типичные риски:
|
||||||
|
|
||||||
- happy path;
|
- public behavior;
|
||||||
- malformed external result;
|
- malformed external data;
|
||||||
- rejected dependency;
|
- rejected dependencies;
|
||||||
- синхронное исключение dependency;
|
- state transitions;
|
||||||
- domain outcome/error semantics;
|
- lifecycle activation и cleanup;
|
||||||
- side-effect order;
|
- request и identity isolation;
|
||||||
- state transition;
|
- client/server boundary;
|
||||||
- отсутствие constructor-time I/O;
|
- отсутствие import-time I/O.
|
||||||
- public API shape.
|
|
||||||
|
|
||||||
**SLM-TEST-002 - ОБЯЗАН.** Factory-level test должен создавать runtime через public `business` entrypoint, а не deep-import factory internals.
|
**SLM-TEST-008 - ОБЯЗАН.** Client/server import boundary должна проверяться инструментом, понимающим реальный framework module graph, если application имеет раздельные environment entries. DOM unit test не заменяет production build probe.
|
||||||
|
|
||||||
## Adapter tests
|
Mode-specific test suites принадлежат overlay, который вводит соответствующие конструкции.
|
||||||
|
|
||||||
**SLM-TEST-003 - ОБЯЗАН.** Adapter с mapping, transport payload, error channel или lifecycle должен иметь contract tests на применимые responsibilities.
|
|
||||||
|
|
||||||
**SLM-TEST-004 - ЗАПРЕЩЕНО.** Adapter test не должен дублировать business scenario tests или утверждать domain fallback/error semantics.
|
|
||||||
|
|
||||||
## Assembly tests
|
|
||||||
|
|
||||||
**SLM-TEST-005 - ОБЯЗАН.** Client/server assembly tests должны проверять корректную передачу ports, runtime profile isolation и отсутствие I/O при creation.
|
|
||||||
|
|
||||||
**SLM-TEST-006 - ОБЯЗАН.** Server assembly с request data должен иметь isolation test для параллельных scopes.
|
|
||||||
|
|
||||||
## Framework tests
|
|
||||||
|
|
||||||
**SLM-TEST-007 - ОБЯЗАН.** Framework surface tests должны проверять runtime access boundary, предсказуемую ошибку при отсутствии runtime boundary, mapping public outcomes и lifecycle integration.
|
|
||||||
|
|
||||||
**SLM-TEST-008 - ОБЯЗАН.** Client/server import boundary должна проверяться инструментом, понимающим реальный framework module graph; DOM unit test не заменяет production build probe.
|
|
||||||
|
|
||||||
## Composition tests
|
|
||||||
|
|
||||||
**SLM-TEST-009 - ОБЯЗАН.** Tests cross-domain composition должны проверять topology, точный graph contract, переданные capabilities и lifecycle cleanup.
|
|
||||||
|
|
||||||
**SLM-TEST-010 - ОБЯЗАН.** Scope с неполным набором domains не должен типизироваться как полный application graph.
|
|
||||||
|
|
||||||
## Architecture conformance
|
## Architecture conformance
|
||||||
|
|
||||||
Repository checks должны проверять применимые ограничения:
|
Типичные mechanically enforceable checks:
|
||||||
|
|
||||||
- направление imports;
|
- направление imports;
|
||||||
- deep imports;
|
- deep imports;
|
||||||
- public entrypoints;
|
- public entrypoints;
|
||||||
- runtime cycles;
|
- runtime cycles;
|
||||||
- client/server markers;
|
- заявленный overlay и его rule set;
|
||||||
- forbidden cross-domain imports;
|
|
||||||
- unique rule IDs документации;
|
- unique rule IDs документации;
|
||||||
- generated artifacts, если они используются.
|
- generated artifacts, если они используются.
|
||||||
|
|
||||||
**SLM-TEST-011 - ЗАПРЕЩЕНО.** Документированное правило не считается механически enforced, если repository tooling его фактически не проверяет.
|
**SLM-TEST-011 - ЗАПРЕЩЕНО.** Документированное правило не считается mechanically enforced, если repository tooling его фактически не проверяет.
|
||||||
|
|
||||||
## Единица соответствия
|
## Единица соответствия
|
||||||
|
|
||||||
**SLM-TEST-014 - ОБЯЗАН.** Application соответствует Specification, если все его modules и связи выполняют применимые обязательные правила.
|
**SLM-TEST-014 - ОБЯЗАН.** Application соответствует base SLM, если выполняет все base-правила. Соответствие заявленному overlay оценивается как base-правила с учётом точного scope каждой замены плюс полный rule set выбранного overlay.
|
||||||
|
|
||||||
**SLM-TEST-015 - ОБЯЗАН.** Изменение соответствует Specification, если новые и изменённые modules не создают новых нарушений и проходят применимые checks.
|
**SLM-TEST-015 - ОБЯЗАН.** Изменение соответствует заявленной архитектуре, если новые и изменённые modules не создают новых нарушений применимых base-правил или правил выбранного overlay и проходят существующие checks.
|
||||||
|
|
||||||
**SLM-TEST-016 - ОБЯЗАН.** Отступление от правила `СЛЕДУЕТ` должно быть зафиксировано в архитектурном review или принятом decision с указанием причины и scope.
|
**SLM-TEST-016 - ОБЯЗАН.** Отступление от правила `СЛЕДУЕТ` должно быть зафиксировано в архитектурном review или принятом decision с указанием причины и scope.
|
||||||
|
|
||||||
**SLM-TEST-017 - ОБЯЗАН.** Manual conformance и mechanical enforcement должны различаться явно; отсутствие автоматической проверки не отменяет нормативное правило.
|
**SLM-TEST-017 - ОБЯЗАН.** Manual conformance и mechanical enforcement должны различаться явно; отсутствие автоматической проверки не отменяет применимое нормативное правило.
|
||||||
|
|
||||||
## Completion gate
|
## Completion gate
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user