Files
slm-design/DRAFT/level-2/domains/auth-example.md
2026-08-02 22:53:05 +03:00

9.1 KiB
Raw Blame History

Переход домена auth с Level 1

Проверочный пример локального перехода от доменного модуля к пакету с Domain API, ports, adapters и default assembly.

Связанные правила

Исходная форма Level 1

domains/
├── auth/                         # Доменный модуль
│   ├── hooks/
│   ├── services/
│   ├── stores/
│   ├── ui/
│   └── index.ts                 # Общий API модуля
└── catalog/                     # Независимый доменный модуль
    └── index.ts

Level 1 разрешает external calls, framework hooks, state и Auth scenarios внутри одной module boundary.

Целевая форма Auth

domains/
├── auth/                         # Доменный пакет Level 2
│   ├── README.md
│   ├── api/                      # Один SLM-модуль
│   │   ├── errors/
│   │   ├── factories/
│   │   ├── 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
│   │   ├── identity-rest/        # SLM-модуль
│   │   ├── identity-realtime/    # SLM-модуль
│   │   └── request-session/      # SLM-модуль
│   ├── assemblies/               # Обязательная Group
│   │   ├── default/              # Штатный Auth graph
│   │   └── administration/       # Специальный trusted graph
│   └── react/                    # Framework Group
│       ├── session/              # Provider готового API
│       ├── queries/              # Query/cache projection
│       └── login-form/           # Переиспользуемый domain UI
└── catalog/                      # По-прежнему модуль Level 1
    └── index.ts

Корневой domains/auth/index.ts удаляется. catalog и остальные домены не меняют форму только из-за перехода Auth.

Перенос ответственности

Исходная часть Владелец Level 2 Публичный путь
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

export type AuthSessionApi = {
  getSession: () => Promise<AuthSession>
  requestPhoneOtp: (
    command: RequestPhoneOtpCommand,
  ) => Promise<RequestPhoneOtpOutcome>
  verifyPhoneOtp: (
    command: VerifyPhoneOtpCommand,
  ) => Promise<AuthSession>
}
export type AuthIdentityPort = {
  requestPhoneOtp: (
    command: AuthIdentityPortCommand,
  ) => Promise<AuthIdentityPortResult>
  verifyPhoneOtp: (
    command: VerifyIdentityPortCommand,
  ) => Promise<VerifyIdentityPortResult>
}

REST adapter реализует этот port поверх generated client. API проверяет records и преобразует port failures в AuthError.

Штатная сборка

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 использует:

import {
  createAuth,
} from '@/domains/auth/assemblies/default'

Он не импортирует factory или adapter напрямую.

Framework state

Старый auth/stores не переносится в api. React query/store projection принадлежит auth/react/queries:

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:

import type {
  AuthSessionApi,
} from '@/domains/auth/api'

User assembly принимает готовый API:

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, 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 и отсутствием обходных imports. Наличие других доменных модулей Level 1 не является миграционным долгом.