Files
slm-design/DRAFT/level-3/domains/business.md

92 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Модуль бизнес-логики внутри домена
> Пояснение смыслового центра домена.
## Связанные правила
- [`SLM-L3-BUSINESS-R003`](../../rules/level-3.md#slm-l3-business-r003)
- [`SLM-L3-BUSINESS-A004`](../../rules/level-3.md#slm-l3-business-a004)
- [`SLM-L3-PORT-R007`](../../rules/level-3.md#slm-l3-port-r007)
- [`SLM-L1-MODULE-A004`](../../rules/level-1.md#slm-l1-module-a004)
## Роль
`business` — единственный обязательный модуль домена. Он владеет:
- публичными предметными сценариями и `DomainApi`;
- фабрикой, типом зависимостей `Deps` и портами;
- предметными типами и контрактами;
- детерминированными правилами, проверкой и нормализацией данных;
- публичным контрактом ошибок предметной области;
- моделью состояния, командами и средствами чтения этого состояния.
Модуль `business` не владеет SDK, реализацией хранилища, API браузера или Node.js, связью с фреймворком, конфигурацией среды и конкретной системой управления состоянием.
## Публичный API
Точка входа `business` открывает только контракт, необходимый потребителям, сборкам и адаптерам:
```ts
export { authFactory } from './auth.factory'
export { AUTH_ERROR_CODES, isAuthError } from './errors/auth-error'
export { normalizeAuthPhone, validateAuthPhone } from './lib/auth-phone'
export type {
AuthApi,
AuthDeps,
AuthError,
AuthErrorCode,
AuthFactory,
AuthPhonePort,
AuthSessionPort,
AuthState,
} from './types'
```
Типы портов экспортируются, потому что сборки и самостоятельные адаптеры реализуют эти контракты. Сервисы, внутренние преобразователи, конструктор ошибки, преобразование исходной ошибки, ключ хранения и конкретный механизм состояния остаются закрытыми.
## Типы и чистые функции
Каталоги `types`, `errors`, `lib`, `ports`, `services` и `tests` являются сегментами модуля `business`, а не отдельными API домена. Тип размещается у владельца:
| Контракт | Владелец |
|---|---|
| `AuthApi`, `AuthDeps`, `AuthState`, порты | `business` |
| DTO SDK и транспортная ошибка | Адаптер или `infra` |
| Свойства React-провайдера | `react` |
| Модель представления экрана | Модуль-потребитель в `compositions` |
Чистая предметная функция может быть публичной, только если выражает предметное правило и нужна реальному внешнему потребителю. Она получает все данные аргументами, детерминирована и не использует `Deps`, состояние, часы, генератор случайных значений, окружение или фреймворк.
Потребитель может применять `validateAuthPhone` для ранней подсказки в интерфейсе, но публичный сценарий повторно проверяет данные на собственной границе.
## Ошибки предметной области
При сбое публичный сценарий выдаёт только ошибку из контракта домена. Исходная ошибка, класс SDK, статус HTTP, тело ответа и транспортный код не становятся API потребителя.
```ts
export const AUTH_ERROR_CODES = {
PHONE_OTP_PHONE_INVALID: 'AUTH_PHONE_OTP_PHONE_INVALID',
PHONE_OTP_REQUEST_FAILED: 'AUTH_PHONE_OTP_REQUEST_FAILED',
PHONE_OTP_VERIFY_CODE_INVALID: 'AUTH_PHONE_OTP_VERIFY_CODE_INVALID',
PHONE_OTP_RESEND_TOO_SOON: 'AUTH_PHONE_OTP_RESEND_TOO_SOON',
} as const
export type AuthError = Readonly<{
code: AuthErrorCode
retryAfterSeconds: number | null
}>
export const isAuthError = (value: unknown): value is AuthError => {
// Проверка публичной формы ошибки во время выполнения.
}
```
Если публичный API использует исключения, точка входа экспортирует проверку типа, коды и доступную только для чтения форму ошибки, но не её конструктор или преобразователь исходной ошибки. Если проект выбирает размеченный тип `Result`, тот же контракт выражается в ветви результата. Один API не смешивает оба способа для одинаковых сценариев.
## Состояние домена
Модуль `business` определяет форму `AuthState`, начальное состояние, допустимые переходы и публичный способ наблюдения. Конкретное хранилище, сохранение данных, источник подписки и хук фреймворка реализуются снаружи через порты и адаптеры.
Независимый от фреймворка интерфейс наблюдения может состоять из `getSnapshot` и `subscribe`. Это часть API бизнес-логики, а не React-хук или `StoreApi` конкретной библиотеки.