mirror of
https://github.com/gromlab-ru/slm-design.git
synced 2026-08-22 07:30:16 +03:00
174 lines
6.5 KiB
Markdown
174 lines
6.5 KiB
Markdown
# Auth как проверочный пример
|
||
|
||
> Рабочая заметка на основе реального модуля `/home/gromov/projects/biocad/newbiocadru/apps/web/src/business/auth`. Код проекта не изменялся.
|
||
|
||
Цель примера: проверить гипотезы Domain на существующем SLM business-модуле, а не предложить немедленную миграцию.
|
||
|
||
## Текущее устройство
|
||
|
||
```text
|
||
business/auth/
|
||
├── auth.factory.ts
|
||
├── errors/
|
||
├── hooks/
|
||
├── mappers/
|
||
├── services/
|
||
├── tests/
|
||
├── types/
|
||
└── index.ts
|
||
```
|
||
|
||
Runtime-сборка находится отдельно:
|
||
|
||
```text
|
||
compositions/business/knv/auth/
|
||
├── adapters/
|
||
├── create-knv-auth-business.ts
|
||
└── index.ts
|
||
```
|
||
|
||
Новая сущность Domain может колоцировать обе ответственности без смешивания ролей:
|
||
|
||
```text
|
||
domains/auth/
|
||
├── business/
|
||
├── presets/
|
||
│ └── {preset-name}/
|
||
│ └── adapters/
|
||
└── {framework-binding}/
|
||
```
|
||
|
||
## Factory и client boundary
|
||
|
||
### AUTH-N001: Текущий AuthApi содержит client-oriented hook
|
||
|
||
`auth.factory.ts` импортирует `createAuthHook`, а `hooks/use-auth.hook.ts` содержит `'use client'`. Кроме того, `AuthDeps.session` описывает `useToken`.
|
||
|
||
Текущий transitive graph:
|
||
|
||
```text
|
||
authFactory
|
||
→ createAuthHook
|
||
→ 'use client'
|
||
```
|
||
|
||
Это практический пример того, почему neutral factory должна проверяться по всему transitive import graph, а framework hooks должны находиться в отдельном framework module Domain. Точный путь этого module пока не выбран.
|
||
|
||
Возможное направление:
|
||
|
||
```text
|
||
business AuthApi
|
||
→ framework-neutral state observation
|
||
|
||
React binding
|
||
→ useAuth над готовым AuthApi
|
||
```
|
||
|
||
Финальный state contract пока не выбран.
|
||
|
||
## Pure phone logic
|
||
|
||
### AUTH-N002: Нормализация телефона уже дублируется
|
||
|
||
Business содержит private `normalizePhoneOtpPhone`, а auth-widget содержит отдельный `getPhoneDigits` и собственный `PHONE_DIGITS_LENGTH`.
|
||
|
||
Это кандидат на public pure business function:
|
||
|
||
```ts
|
||
import {
|
||
normalizeAuthPhone,
|
||
validateAuthPhone,
|
||
} from '@/domains/auth/business'
|
||
```
|
||
|
||
Business service и UI могут использовать одну семантику. Business service всё равно повторно валидирует вход независимо от UI-проверки.
|
||
|
||
Существующий `business/user` показывает другой workaround: pure validators возвращаются через собранный `userFactory` API. Прямой pure export позволит не требовать assembly для детерминированной функции.
|
||
|
||
## Error contract
|
||
|
||
### AUTH-N003: Error contract фактически публичен, но описан не полностью
|
||
|
||
Business создаёт `AuthBusinessError` с `code` и `retryAfterSeconds`, но public `index.ts` экспортирует только type `AuthErrorCode`.
|
||
|
||
Consumer auth-widget поэтому:
|
||
|
||
- повторяет строковые error codes в message map;
|
||
- создаёт локальный `AuthErrorData`;
|
||
- вручную проверяет `code` и `retryAfterSeconds` в `unknown`;
|
||
- самостоятельно нормализует форму caught error.
|
||
|
||
Предварительное исправление границы:
|
||
|
||
```ts
|
||
// Public business API.
|
||
export { AUTH_ERROR_CODES, isAuthError }
|
||
export type { AuthError, AuthErrorCode }
|
||
|
||
// Business-private implementation.
|
||
class AuthBusinessError extends Error {}
|
||
const createAuthBusinessError = (...) => {}
|
||
```
|
||
|
||
Consumer получает безопасный observation contract, но не получает constructor и source mapping.
|
||
|
||
## Presets
|
||
|
||
### AUTH-N004: Текущий createKnvAuthBusiness является preset
|
||
|
||
`createKnvAuthBusiness()` выбирает `knvAuthPhoneAdapter` и `appAuthSessionAdapter`, затем вызывает `authFactory`.
|
||
|
||
В новой терминологии это application preset, внутри которого могут оставаться KNV-specific adapters:
|
||
|
||
```text
|
||
domains/auth/presets/application/create-application-auth.ts
|
||
```
|
||
|
||
Он не является единственно допустимым assembly site. Tests, SSR request composition и другой product preset могут напрямую вызвать ту же `authFactory`.
|
||
|
||
## SSR-вариант
|
||
|
||
Одна factory позволяет получить request-scoped API без второй реализации business:
|
||
|
||
```ts
|
||
import 'server-only'
|
||
|
||
export const createAuthForRequest = (input: AuthRequestInput) => {
|
||
return authFactory({
|
||
authPhone: createKnvServerAuthPhoneAdapter(input),
|
||
session: createRequestAuthSessionAdapter(input),
|
||
})
|
||
}
|
||
```
|
||
|
||
Browser preset использует другую реализацию тех же ports. Factory, business types, pure functions и error contract остаются общими.
|
||
|
||
## Предварительная целевая структура
|
||
|
||
```text
|
||
domains/auth/
|
||
├── business/
|
||
│ ├── auth.factory.ts
|
||
│ ├── errors/
|
||
│ ├── lib/
|
||
│ ├── mappers/
|
||
│ ├── services/
|
||
│ ├── tests/
|
||
│ ├── types/
|
||
│ └── index.ts
|
||
├── presets/
|
||
│ └── application/
|
||
│ ├── adapters/
|
||
│ ├── create-application-auth.ts
|
||
│ ├── create-application-auth.test.ts
|
||
│ └── index.ts
|
||
└── {framework-binding}/
|
||
└── index.ts
|
||
```
|
||
|
||
Это только проверочная структура. Она не фиксирует обязательность всех папок и не должна использоваться как scaffold checklist.
|
||
|
||
Server-only/request preset может быть добавлен отдельным module при реальной потребности. Он не образует обязательную `server`-ветку Domain.
|
||
|
||
Tests не используют общий testing preset. Business tests выполняют per-test assembly напрямую через `authFactory`, а production presets тестируются рядом с собственной реализацией только на wiring, scope и lifecycle.
|