mirror of
https://github.com/gromlab-ru/slm-design.git
synced 2026-08-22 07:30:16 +03:00
feat: доменный API
This commit is contained in:
@@ -1,15 +1,18 @@
|
||||
# Переход домена auth с Level 1
|
||||
|
||||
> Проверочный пример локального перехода от доменного модуля к доменному пакету.
|
||||
> Проверочный пример локального перехода от доменного модуля к пакету с Domain API, ports, adapters и default assembly.
|
||||
|
||||
## Связанные правила
|
||||
|
||||
- [`SLM-L2-DEPENDENCY-A012`](../../rules/level-2.md#slm-l2-dependency-a012)
|
||||
- [`SLM-L2-DOMAIN-A026`](../../rules/level-2.md#slm-l2-domain-a026)
|
||||
- [`SLM-L2-BUSINESS-A019`](../../rules/level-2.md#slm-l2-business-a019)
|
||||
- [`SLM-L2-API-R005`](../../rules/level-2.md#slm-l2-api-r005)
|
||||
- [`SLM-L2-API-A019`](../../rules/level-2.md#slm-l2-api-a019)
|
||||
- [`SLM-L2-ASSEMBLY-A020`](../../rules/level-2.md#slm-l2-assembly-a020)
|
||||
- [`SLM-L2-ADAPTER-R021`](../../rules/level-2.md#slm-l2-adapter-r021)
|
||||
- [`SLM-L2-BUSINESS-A022`](../../rules/level-2.md#slm-l2-business-a022)
|
||||
- [`SLM-L2-API-A022`](../../rules/level-2.md#slm-l2-api-a022)
|
||||
- [`SLM-L2-DOMAIN-A026`](../../rules/level-2.md#slm-l2-domain-a026)
|
||||
- [`SLM-L2-PORT-R027`](../../rules/level-2.md#slm-l2-port-r027)
|
||||
- [`SLM-L2-STATE-R028`](../../rules/level-2.md#slm-l2-state-r028)
|
||||
|
||||
## Исходная форма Level 1
|
||||
|
||||
@@ -25,7 +28,7 @@ domains/
|
||||
└── index.ts
|
||||
```
|
||||
|
||||
Level 1 разрешает business-сценариям, framework hooks, state adapter и локальной сборке Auth находиться внутри одного модуля.
|
||||
Level 1 разрешает external calls, framework hooks, state и Auth scenarios внутри одной module boundary.
|
||||
|
||||
## Целевая форма Auth
|
||||
|
||||
@@ -33,107 +36,169 @@ Level 1 разрешает business-сценариям, framework hooks, state a
|
||||
domains/
|
||||
├── auth/ # Доменный пакет Level 2
|
||||
│ ├── README.md
|
||||
│ ├── business/ # Один SLM-модуль
|
||||
│ ├── api/ # Один SLM-модуль
|
||||
│ │ ├── errors/
|
||||
│ │ ├── factories/
|
||||
│ │ ├── services/
|
||||
│ │ ├── types/
|
||||
│ │ ├── index.ts # Только public types нескольких API
|
||||
│ │ ├── factory.ts # Public factories entry
|
||||
│ │ └── runtime.ts # Error codes, guards, public pure runtime
|
||||
│ │ ├── models/
|
||||
│ │ ├── operations/
|
||||
│ │ ├── ports/
|
||||
│ │ ├── index.ts # Consumer-facing types
|
||||
│ │ ├── ports.ts # Implementer-facing types
|
||||
│ │ ├── factory.ts # Domain API factories
|
||||
│ │ └── runtime.ts # Guards и public pure runtime
|
||||
│ ├── adapters/ # Group
|
||||
│ │ ├── phone-http/ # SLM-модуль
|
||||
│ │ ├── browser-session/ # SLM-модуль
|
||||
│ │ ├── identity-rest/ # SLM-модуль
|
||||
│ │ ├── identity-realtime/ # SLM-модуль
|
||||
│ │ └── request-session/ # SLM-модуль
|
||||
│ ├── assemblies/ # Обязательная Group
|
||||
│ │ ├── browser/ # Только AuthSessionApi
|
||||
│ │ └── request/ # Session + Administration API
|
||||
│ │ ├── default/ # Штатный Auth graph
|
||||
│ │ └── administration/ # Специальный trusted graph
|
||||
│ └── react/ # Framework Group
|
||||
│ ├── session/ # SLM-модуль
|
||||
│ └── login-form/ # SLM-модуль
|
||||
│ ├── session/ # Provider готового API
|
||||
│ ├── queries/ # Query/cache projection
|
||||
│ └── login-form/ # Переиспользуемый domain UI
|
||||
└── catalog/ # По-прежнему модуль Level 1
|
||||
└── index.ts
|
||||
```
|
||||
|
||||
Корневой `domains/auth/index.ts` удаляется. Потребители переходят на публичные API конкретных модулей. `catalog` и остальные домены не меняют форму только из-за перехода Auth.
|
||||
Корневой `domains/auth/index.ts` удаляется. `catalog` и остальные домены не меняют форму только из-за перехода Auth.
|
||||
|
||||
## Перенос ответственности
|
||||
|
||||
| Исходная часть | Владелец Level 2 | Публичный путь |
|
||||
|---|---|---|
|
||||
| Session-сценарии и public types | `auth/business` | `auth/business` |
|
||||
| Administration-сценарии и public types | `auth/business` | `auth/business` |
|
||||
| Runtime-фабрики API | `auth/business` | `auth/business/factory` |
|
||||
| Коды, guards и public pure-функции | `auth/business` | `auth/business/runtime` |
|
||||
| Browser storage и HTTP adapters | Соответствующий adapter-модуль | `auth/adapters/*` |
|
||||
| Cookies, request data и server adapters | Соответствующий adapter-модуль | `auth/adapters/*` |
|
||||
| Browser graph | `auth/assemblies/browser` | `auth/assemblies/browser` |
|
||||
| Request graph | `auth/assemblies/request` | `auth/assemblies/request` |
|
||||
| Session operations и public models | `auth/api` | `auth/api` |
|
||||
| Port contracts и failures | `auth/api` | `auth/api/ports` |
|
||||
| Runtime factories | `auth/api` | `auth/api/factory` |
|
||||
| Error guards и public pure-функции | `auth/api` | `auth/api/runtime` |
|
||||
| REST provider mapping | `auth/adapters/identity-rest` | Adapter API для assembly |
|
||||
| Realtime protocol и correlation | `auth/adapters/identity-realtime` | Adapter API для assembly |
|
||||
| Request cookies mapping | `auth/adapters/request-session` | Adapter API для assembly |
|
||||
| Штатный production graph | `auth/assemblies/default` | `auth/assemblies/default` |
|
||||
| Trusted administration graph | `auth/assemblies/administration` | `auth/assemblies/administration` |
|
||||
| Provider и session hooks | `auth/react/session` | `auth/react/session` |
|
||||
| Query/cache/hydration | `auth/react/queries` | `auth/react/queries` |
|
||||
| Переиспользуемая форма | `auth/react/login-form` | `auth/react/login-form` |
|
||||
| Страница, текст и redirect | `compositions` | API конкретной composition |
|
||||
|
||||
## Новые импорты
|
||||
## Domain API и port
|
||||
|
||||
```ts
|
||||
import type {
|
||||
AuthAdministrationApi,
|
||||
AuthError,
|
||||
AuthErrorCode,
|
||||
AuthSessionApi,
|
||||
} from '@/domains/auth/business'
|
||||
|
||||
import {
|
||||
authAdministrationFactory,
|
||||
authSessionFactory,
|
||||
} from '@/domains/auth/business/factory'
|
||||
|
||||
import {
|
||||
AUTH_ERROR_CODES,
|
||||
isAuthError,
|
||||
} from '@/domains/auth/business/runtime'
|
||||
|
||||
import { createPhoneHttpAdapter } from '@/domains/auth/adapters/phone-http'
|
||||
import { createBrowserAuth } from '@/domains/auth/assemblies/browser'
|
||||
import { AuthSessionProvider } from '@/domains/auth/react/session'
|
||||
import { LoginForm } from '@/domains/auth/react/login-form'
|
||||
```
|
||||
|
||||
## Cross-domain граф
|
||||
|
||||
Если User package зависит от Auth, он получает только type-only API contract и при необходимости deterministic runtime:
|
||||
|
||||
```ts
|
||||
import type { AuthSessionApi } from '@/domains/auth/business'
|
||||
import { isAuthError } from '@/domains/auth/business/runtime'
|
||||
|
||||
export type UserDeps = {
|
||||
auth: Pick<AuthSessionApi, 'getSnapshot'>
|
||||
export type AuthSessionApi = {
|
||||
getSession: () => Promise<AuthSession>
|
||||
requestPhoneOtp: (
|
||||
command: RequestPhoneOtpCommand,
|
||||
) => Promise<RequestPhoneOtpOutcome>
|
||||
verifyPhoneOtp: (
|
||||
command: VerifyPhoneOtpCommand,
|
||||
) => Promise<AuthSession>
|
||||
}
|
||||
```
|
||||
|
||||
Место сборки создаёт instances:
|
||||
|
||||
```ts
|
||||
const auth = createBrowserAuth()
|
||||
const user = createBrowserUser({ auth: auth.session })
|
||||
export type AuthIdentityPort = {
|
||||
requestPhoneOtp: (
|
||||
command: AuthIdentityPortCommand,
|
||||
) => Promise<AuthIdentityPortResult>
|
||||
verifyPhoneOtp: (
|
||||
command: VerifyIdentityPortCommand,
|
||||
) => Promise<VerifyIdentityPortResult>
|
||||
}
|
||||
```
|
||||
|
||||
User не импортирует Auth factory или assembly, а его React-модули не импортируют `useAuthSession` или Auth components.
|
||||
REST adapter реализует этот port поверх generated client. API проверяет records и преобразует port failures в `AuthError`.
|
||||
|
||||
Если User остаётся модулем Level 1, runtime-связь также выполняется снаружи доменных границ. Для этого его собственный API должен иметь явную точку передачи нужного Auth behavior; иначе именно User требуется рефакторинг или переход, но несвязанные домены не затрагиваются.
|
||||
## Штатная сборка
|
||||
|
||||
```ts
|
||||
import {
|
||||
createAuthSessionApi,
|
||||
} from '@/domains/auth/api/factory'
|
||||
|
||||
import {
|
||||
createIdentityRestAdapter,
|
||||
} from '@/domains/auth/adapters/identity-rest'
|
||||
|
||||
export const createAuth = (): AuthGraph => ({
|
||||
session: createAuthSessionApi({
|
||||
identity: createIdentityRestAdapter(),
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
Обычный production consumer использует:
|
||||
|
||||
```ts
|
||||
import {
|
||||
createAuth,
|
||||
} from '@/domains/auth/assemblies/default'
|
||||
```
|
||||
|
||||
Он не импортирует factory или adapter напрямую.
|
||||
|
||||
## Framework state
|
||||
|
||||
Старый `auth/stores` не переносится в `api`. React query/store projection принадлежит `auth/react/queries`:
|
||||
|
||||
```ts
|
||||
export const useAuthSessionQuery = () => {
|
||||
const api = useAuthApi()
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['auth', 'session'],
|
||||
queryFn: api.getSession,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
При Vue или другом framework та же модель и errors Domain API материализуются его собственными средствами.
|
||||
|
||||
## Realtime
|
||||
|
||||
`identity-realtime` скрывает socket protocol, operation IDs, acknowledgements и reconnect. Domain API возвращает обычный command outcome и публикует проверенные Auth events.
|
||||
|
||||
Если disconnect произошёл до acknowledgement, API не утверждает ложный отказ и может вернуть `AUTH_OPERATION_OUTCOME_UNKNOWN`. После gap binding получает `RESYNC_REQUIRED` и повторно вызывает `getSession()`.
|
||||
|
||||
## RSC
|
||||
|
||||
Server Component создаёт request-scoped Auth graph и передаёт Client Component только сериализуемый `AuthSession` или hydration payload. Client Component создаёт отдельный client graph; при SSR его render должен быть совместим с server prerender, а browser-only capabilities остаются в deferred effects.
|
||||
|
||||
`assemblies/default` используется в обоих местах только если её executable graph действительно совместим со всеми declared conditions. Иначе появляется отдельная assembly, например `auth/assemblies/rsc`.
|
||||
|
||||
## Cross-domain graph
|
||||
|
||||
Если User package зависит от Auth, он импортирует только type contract:
|
||||
|
||||
```ts
|
||||
import type {
|
||||
AuthSessionApi,
|
||||
} from '@/domains/auth/api'
|
||||
```
|
||||
|
||||
User assembly принимает готовый API:
|
||||
|
||||
```ts
|
||||
const auth = createAuth()
|
||||
const user = createUser({
|
||||
auth: auth.session,
|
||||
})
|
||||
```
|
||||
|
||||
User не импортирует Auth factory, port, adapter, assembly или React hooks. Если User остаётся модулем Level 1, его public API должен иметь явную точку передачи нужного Auth behavior.
|
||||
|
||||
## Порядок перехода
|
||||
|
||||
1. Выбрать один доменный модуль и зафиксировать его внешние consumers.
|
||||
2. Объявить `business` с type-only и factory entry points.
|
||||
3. Разделить сценарии на независимо собираемые Domain API без дублирования методов.
|
||||
4. Добавить `business/runtime`, только если внешним consumers нужны codes, guards или pure-функции.
|
||||
5. Оформить каждую связную production implementation модулем `adapters/*`.
|
||||
6. Создать минимум одну assembly и перенести туда повторяемый выбор API и adapters.
|
||||
7. Разделить React-ответственности на модули внутри Group `react`.
|
||||
8. Перенести страницы, redirects и multi-domain UI в `compositions`.
|
||||
9. Перевести внешние импорты на разрешённые public paths.
|
||||
10. Удалить старый root `index.ts` Auth и объявить пакетную форму checker-у.
|
||||
1. Зафиксировать consumers, external sources, state, errors и lifecycle исходного Auth module.
|
||||
2. Объявить consumer-facing Domain API и public models.
|
||||
3. Объявить dependency ports, records и closed failures.
|
||||
4. Реализовать factory и проверить Domain API через fake ports.
|
||||
5. Оформить каждую production implementation модулем `adapters/*` и добавить contract tests.
|
||||
6. Создать `assemblies/default` для штатного production context.
|
||||
7. Добавить специальные assemblies только для реально отличающихся graphs.
|
||||
8. Перенести framework state, cache и hydration в modules Group `react`.
|
||||
9. Перенести страницы, redirects и multi-domain UI в `compositions`.
|
||||
10. Перевести внешние imports на разрешённые public paths.
|
||||
11. Обновить dependency-connected graph owners и cross-domain inputs.
|
||||
12. Удалить старый root `index.ts` Auth и объявить package checker-у.
|
||||
|
||||
Завершённость перехода определяется только границей Auth. Наличие других доменных модулей Level 1 не является миграционным долгом.
|
||||
Завершённость перехода определяется одной формой Auth и отсутствием обходных imports. Наличие других доменных модулей Level 1 не является миграционным долгом.
|
||||
|
||||
Reference in New Issue
Block a user