6.4 KiB
Business module внутри Domain
Рабочая заметка. Не является нормативным разделом спецификации.
Роль
BUS-N001: Business является семантическим ядром Domain
Business-модуль владеет:
- публичными бизнес-сценариями;
- business-owned types и contracts;
- business API;
- factory и ports;
- детерминированными доменными правилами;
- доменным error contract;
- преобразованием внешних результатов в доменные результаты.
Business не владеет concrete runtime, environment wiring и framework integration.
Public API business-модуля
BUS-N002: Business может экспортировать четыре категории сущностей
| Категория | Примеры |
|---|---|
| Factory | authFactory |
| Types и contracts | AuthApi, AuthDeps, AuthState, AuthErrorCode |
| Pure domain functions | normalizeAuthPhone, validateAuthPhone |
| Error observation contract | AUTH_ERROR_CODES, AuthError, isAuthError |
Это заменяет старую гипотезу, что business index.ts может экспортировать в runtime только factory.
Предварительный public API:
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,
AuthState,
}
Types
BUS-N003: Business contracts остаются внутри business
Отдельный model submodule пока не требуется. Типы размещаются по ownership:
| Тип | Место |
|---|---|
AuthApi, AuthDeps, AuthState |
domains/auth/business/types |
AuthError, AuthErrorCode |
domains/auth/business/types или errors |
| SDK DTO | Adapter или infra runtime |
| React provider props | Выбранный React binding module Domain |
| View model конкретного screen | Consumer composition |
types/ является segment business-модуля, а не самостоятельным общим хранилищем Domain.
Pure domain functions
BUS-N004: Детерминированная доменная функция может экспортироваться напрямую
Pure domain function:
- получает все данные через аргументы;
- возвращает результат только на основе аргументов;
- не использует
Deps; - не выполняет I/O;
- не читает mutable runtime state;
- не зависит от clock, random, env или platform API;
- не импортирует React, Vue, Next.js или state manager;
- использует business language и реализует доменное правило.
Примеры:
normalizeAuthPhone(value)
validateAuthPhone(value)
calculateOrderTotal(order)
hasRequiredUserAgreements(user)
Consumer может использовать такую функцию для раннего UX feedback. Business scenario всё равно обязан повторно проверить вход на своей границе.
BUS-N005: Не каждая pure function становится public
Функция остаётся private, если она нужна только одному service или является технической деталью реализации. Public export оправдан доменной семантикой и реальным внешним либо межмодульным consumer.
Папки domain/shared и domain/public не создаются только ради видимости. Public contract определяется entrypoint business-модуля.
Domain errors
BUS-N006: Создание и наблюдение ошибки являются разными контрактами
Business создаёт domain error. Consumer только распознаёт ошибку и читает поля, от которых зависит его поведение.
Public observation contract:
export const AUTH_ERROR_CODES = {
PHONE_OTP_PHONE_INVALID: 'AUTH_PHONE_OTP_PHONE_INVALID',
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 AuthErrorCode =
(typeof AUTH_ERROR_CODES)[keyof typeof AUTH_ERROR_CODES]
export type AuthError = Readonly<{
code: AuthErrorCode
retryAfterSeconds: number | null
}>
export const isAuthError = (value: unknown): value is AuthError => {
// Structural runtime validation.
}
Private creation contract:
class AuthBusinessError extends Error implements AuthError {
// Constructor, cause и source diagnostics.
}
const createAuthBusinessError = (...) => {
// Source error mapping.
}
BUS-N007: Error constructor не является consumer API
Consumer не должен создавать AuthBusinessError, выбирать source mapping или подделывать business failure. Поэтому наружу предполагается экспортировать:
- stable error code values;
- error code type;
- read-only observable error shape;
- runtime guard или parser.
Наружу не предполагается экспортировать:
- error constructor;
- error factory;
- source error mapper;
- transport-specific error data;
- internal fallback selection.
BUS-N008: Одних типов недостаточно при throw-based API
TypeScript не описывает checked exceptions. Для сигнатуры
(data: VerifyPhoneOtpData) => Promise<void>
значение в catch всё равно имеет тип unknown. Если consumer различает ошибки по code, business должен предоставить runtime discriminator либо перейти на typed Result.
Выбор между throw + guard и typed Result пока не закрыт окончательно. Текущий минимальный путь совместимости: throw + public observation contract.