style: убрать излишний англицызм

This commit is contained in:
2026-07-30 17:25:44 +03:00
parent 038f941ac7
commit c0956ed2a0
19 changed files with 369 additions and 365 deletions

View File

@@ -1,8 +1,8 @@
# Домены Level 3
> Пояснение строгой внутренней архитектуры Domain.
> Пояснение строгой внутренней архитектуры домена.
Level 3 превращает доменный module Level 2 в Domain: немодульную предметную границу с несколькими modules разных технических ролей. Это не новый слой и не обязательный scaffold для каждого проекта.
Level 3 заменяет доменный модуль Level 2 немодульной предметной границей — доменом. Внутри неё размещаются модули с разными техническими ролями. Это не новый слой и не обязательный каркас для каждого проекта.
## Связанные правила
@@ -11,25 +11,25 @@ Level 3 превращает доменный module Level 2 в Domain: немо
- [`SLM-L3-BUSINESS-R003`](../../rules/level-3.md#slm-l3-business-r003)
- [`SLM-L1-MODULE-A004`](../../rules/level-1.md#slm-l1-module-a004)
## Роли внутри Domain
## Роли внутри домена
```text
Business определяет поведение и contract.
Ports описывают runtime capabilities business.
Adapters реализуют ports поверх concrete runtime.
Presets собирают API для execution context.
React module адаптирует готовый API к React.
Graph owner удерживает конкретный instance и выполняет lifecycle contract module-владельца.
Модуль бизнес-логики определяет поведение и публичный контракт.
Порты описывают возможности, которые нужны бизнес-логике.
Адаптеры реализуют порты в конкретной среде.
Типовые сборки повторяемо создают API.
Модуль React связывает готовый API с React.
Владелец графа удерживает экземпляр API и завершает его жизненный цикл.
```
| Роль | Структурный вид | Когда появляется |
|---|---|---|
| `business` | Обязательный module | Всегда |
| Preset | Module внутри `presets` | Нужна повторяемая assembly |
| Adapter | Private segment preset или module внутри `adapters` | Нужна concrete integration |
| `react` | Framework module непосредственно в Domain | Domain имеет React integration |
| Бизнес-логика | Обязательный модуль `business` | Всегда |
| Типовая сборка | Модуль внутри `presets` | Нужен повторяемый способ сборки |
| Адаптер | Закрытый сегмент сборки или модуль внутри `adapters` | Нужна техническая интеграция |
| Связь с React | Модуль `react` непосредственно в домене | Домен предоставляет API для React |
## Форма Domain
## Форма домена
```text
domains/auth/
@@ -53,13 +53,13 @@ domains/auth/
└── index.ts
```
`business` обязателен; остальные ветки появляются по необходимости. `presets` и `adapters` являются Groups без собственного runtime/API. `errors`, `lib`, `ports`, `services`, `types`, `hooks` и `providers` являются segments соответствующих module-владельцев.
Модуль `business` обязателен; остальные ветки появляются по необходимости. `presets` и `adapters` являются группами без собственного исполняемого кода и API. Каталоги `errors`, `lib`, `ports`, `services`, `types`, `hooks` и `providers` являются сегментами соответствующих модулей-владельцев.
Navigation Groups в слое `domains` допустимы, но не являются Domain и не изменяют его import boundary. Основные примеры Level 3 намеренно показывают Domain непосредственно в `domains`.
Навигационные группы в слое `domains` допустимы, но не являются доменами и не меняют их публичные границы. Основные примеры Level 3 показывают домены непосредственно в `domains`.
## Public API modules
## Публичные API модулей
Domain root не имеет `index.ts` и не реэкспортирует роли. Внешний consumer использует только public entrypoint нужного module:
Корень домена не имеет `index.ts` и не реэкспортирует дочерние модули. Внешний потребитель использует публичную точку входа нужного модуля:
```ts
import { authFactory, type AuthApi } from '@/domains/auth/business'
@@ -67,15 +67,15 @@ import { createApplicationAuth } from '@/domains/auth/presets/application'
import { AuthProvider, useAuth } from '@/domains/auth/react'
```
Private adapter внутри `presets/application/adapters` не получает external entrypoint. Promoted adapter module получает собственный API для modules Domain; доступ за пределами Domain допускается только как явно объявленная integration extension point.
Закрытый адаптер внутри `presets/application/adapters` не получает внешней точки входа. Адаптер, оформленный самостоятельным модулем, предоставляет минимальный публичный API. Доступ к нему за пределами домена допускается только как явно объявленная точка расширения интеграции.
## Карта раздела
- [Граница Domain](./domain.md)
- [Business module](./business.md)
- [Factory, ports и adapters](./factory-ports-adapters.md)
- [Presets и SSR](./presets.md)
- [React module](./framework-bindings.md)
- [Граница домена](./domain.md)
- [Модуль бизнес-логики](./business.md)
- [Фабрика, порты и адаптеры](./factory-ports-adapters.md)
- [Типовые сборки и SSR](./presets.md)
- [Модуль React](./framework-bindings.md)
- [Тестирование](./testing.md)
- [Auth как пример миграции](./auth-example.md)
- [Пример переноса домена](./auth-example.md)
- [Открытые вопросы](./open-questions.md)

View File

@@ -1,10 +1,10 @@
# Auth как пример миграции
# Перенос домена `auth`
> Проверочный пример Level 3. Он показывает направление декомпозиции, а не обязательный scaffold.
> Проверочный пример Level 3. Он показывает направление изменений, а не обязательный каркас.
## Исходная проблема
В более ранней форме SLM business contract Auth и concrete assembly могли находиться отдельно:
В более ранней форме SLM контракт бизнес-логики домена `auth` и его техническая сборка могли находиться в разных местах:
```text
business/auth/
@@ -21,7 +21,7 @@ compositions/business/auth/
└── index.ts
```
Такая форма отделяет pure business от concrete runtime, но разносит одну предметную область по разным архитектурным местам. Level 3 колоцирует их внутри Domain, не смешивая роли.
Такое устройство отделяет бизнес-логику от конкретной среды, но разносит одну предметную область по разным архитектурным местам. Level 3 размещает эти части внутри одного домена, сохраняя границы между их ролями.
## Целевая форма
@@ -51,18 +51,18 @@ domains/auth/
| Исходная часть | Назначение в Level 3 |
|---|---|
| `auth.factory.ts`, scenarios, validators, domain errors | `domains/auth/business` |
| SDK, storage и state manager integration | Private adapters выбранного preset |
| Повторяемый browser builder | `domains/auth/presets/application` |
| Request-specific cookies, headers и client | `domains/auth/presets/request` |
| React hooks, provider и domain UI | `domains/auth/react` |
| Page text, redirect и screen outcome | Consumer composition |
| `auth.factory.ts`, сценарии, проверки и ошибки домена | `domains/auth/business` |
| SDK, хранилище и конкретная система управления состоянием | Закрытые адаптеры выбранной сборки |
| Повторяемая сборка для браузерного приложения | `domains/auth/presets/application` |
| Файлы cookie, заголовки и клиент одного запроса | `domains/auth/presets/request` |
| React-хуки, провайдер и интерфейс домена | `domains/auth/react` |
| Текст страницы, перенаправление и устройство экрана | Модуль-потребитель в `compositions` |
## Проверка границ
`authFactory` не импортирует `useAuth`, `'use client'`, SDK или storage. React hook строится поверх готового `AuthApi`, например через framework-neutral `getSnapshot` и `subscribe`.
`authFactory` не импортирует `useAuth`, `'use client'`, SDK или хранилище. React-хук строится поверх готового `AuthApi`, например через независимые от фреймворка методы `getSnapshot` и `subscribe`.
Нормализация номера телефона может быть public pure business function:
Нормализация номера телефона может быть публичной чистой функцией бизнес-логики:
```ts
import {
@@ -71,11 +71,11 @@ import {
} from '@/domains/auth/business'
```
UI использует её для feedback, но `requestPhoneOtp` повторно валидирует значение внутри business scenario.
Интерфейс использует её для ранней подсказки, но `requestPhoneOtp` повторно проверяет значение внутри предметного сценария.
## Error contract
## Контракт ошибок
`AuthBusinessError` остаётся private implementation. Consumer получает только stable contract:
`AuthBusinessError` остаётся закрытой реализацией. Потребитель получает только устойчивый контракт:
```ts
import {
@@ -84,13 +84,13 @@ import {
} from '@/domains/auth/business'
```
Так React composition может выбрать сообщение или retry behavior по `code`, не зная SDK error, HTTP status или constructor private ошибки.
Так композиция React может выбрать сообщение или поведение повторной попытки по `code`, не зная класс ошибки SDK, статус HTTP или закрытый конструктор.
## Migration order
## Порядок перехода
1. Выделить `business` entrypoint и убедиться, что его transitive graph isomorphic.
2. Перенести concrete runtime в adapters выбранного preset.
3. Оформить повторяемую assembly как `presets/application`.
4. Перенести hooks и Provider в `react`, передавая им готовый API.
5. Сохранить page-specific UI и graph ownership в `compositions`.
6. Добавить factory, adapter, preset и React boundary tests до удаления старого пути.
1. Выделить точку входа `business` и убедиться, что её полный граф импортов не зависит от среды.
2. Перенести конкретные технические реализации в адаптеры выбранной сборки.
3. Оформить повторяемую сборку как `presets/application`.
4. Перенести хуки и провайдер в `react`, передавая им готовый API.
5. Сохранить интерфейс конкретной страницы и владение общим графом в `compositions`.
6. Добавить тесты фабрики, адаптеров, сборки и границы React до удаления старого пути.

View File

@@ -1,6 +1,6 @@
# Business module внутри Domain
# Модуль бизнес-логики внутри домена
> Пояснение semantic core Domain.
> Пояснение смыслового центра домена.
## Связанные правила
@@ -11,20 +11,20 @@
## Роль
`business` -- единственный обязательный module Domain. Он владеет:
`business` единственный обязательный модуль домена. Он владеет:
- public business scenarios и `DomainApi`;
- factory, `Deps` и ports;
- business-owned types и contracts;
- детерминированными rules, validation и normalization;
- domain error contract;
- семантикой domain state, commands и selectors.
- публичными предметными сценариями и `DomainApi`;
- фабрикой, типом зависимостей `Deps` и портами;
- предметными типами и контрактами;
- детерминированными правилами, проверкой и нормализацией данных;
- публичным контрактом ошибок предметной области;
- моделью состояния, командами и средствами чтения этого состояния.
Business не владеет SDK, storage implementation, browser/Node API, framework integration, environment wiring или concrete state manager.
Модуль `business` не владеет SDK, реализацией хранилища, API браузера или Node.js, связью с фреймворком, конфигурацией среды и конкретной системой управления состоянием.
## Public API
## Публичный API
Business entrypoint открывает только contract, нужный consumers, presets и adapters:
Точка входа `business` открывает только контракт, необходимый потребителям, сборкам и адаптерам:
```ts
export { authFactory } from './auth.factory'
@@ -43,26 +43,26 @@ export type {
} from './types'
```
Port types экспортируются, потому что preset и promoted adapter реализуют именно эти contracts. `services`, private mappers, error constructor, source mapper, persistence key и concrete state runtime остаются закрытыми.
Типы портов экспортируются, потому что сборки и самостоятельные адаптеры реализуют эти контракты. Сервисы, внутренние преобразователи, конструктор ошибки, преобразование исходной ошибки, ключ хранения и конкретный механизм состояния остаются закрытыми.
## Types и pure functions
## Типы и чистые функции
`types`, `errors`, `lib`, `ports`, `services` и `tests` -- segments business module, а не отдельные Domain APIs. Type размещается у владельца:
Каталоги `types`, `errors`, `lib`, `ports`, `services` и `tests` являются сегментами модуля `business`, а не отдельными API домена. Тип размещается у владельца:
| Contract | Владелец |
| Контракт | Владелец |
|---|---|
| `AuthApi`, `AuthDeps`, `AuthState`, ports | `business` |
| SDK DTO и transport error | Adapter или `infra` |
| React provider props | `react` |
| View model screen | Consumer composition |
| `AuthApi`, `AuthDeps`, `AuthState`, порты | `business` |
| DTO SDK и транспортная ошибка | Адаптер или `infra` |
| Свойства React-провайдера | `react` |
| Модель представления экрана | Модуль-потребитель в `compositions` |
Pure domain function может быть public, только если она выражает business rule и имеет реального external consumer. Она получает все данные аргументами, детерминирована, не использует `Deps`, state, clock, random, environment или framework runtime.
Чистая предметная функция может быть публичной, только если выражает предметное правило и нужна реальному внешнему потребителю. Она получает все данные аргументами, детерминирована и не использует `Deps`, состояние, часы, генератор случайных значений, окружение или фреймворк.
Consumer может применять `validateAuthPhone` для раннего UX feedback, но public business scenario повторяет validation на своей границе.
Потребитель может применять `validateAuthPhone` для ранней подсказки в интерфейсе, но публичный сценарий повторно проверяет данные на собственной границе.
## Domain errors
## Ошибки предметной области
Каждый public runtime scenario выдаёт только domain failure contract. Source error, SDK class, HTTP status, response body и transport code не становятся consumer API.
При сбое публичный сценарий выдаёт только ошибку из контракта домена. Исходная ошибка, класс SDK, статус HTTP, тело ответа и транспортный код не становятся API потребителя.
```ts
export const AUTH_ERROR_CODES = {
@@ -78,14 +78,14 @@ export type AuthError = Readonly<{
}>
export const isAuthError = (value: unknown): value is AuthError => {
// Runtime validation of the public observation shape.
// Проверка публичной формы ошибки во время выполнения.
}
```
Если public API использует exceptions, entrypoint экспортирует domain-specific guard, codes и read-only observation shape, но не constructor или source error mapper. Если проект выбирает discriminated `Result`, тот же contract должен быть выражен в result branch. Один business API не смешивает оба способа для одинаковых scenario.
Если публичный API использует исключения, точка входа экспортирует проверку типа, коды и доступную только для чтения форму ошибки, но не её конструктор или преобразователь исходной ошибки. Если проект выбирает размеченный тип `Result`, тот же контракт выражается в ветви результата. Один API не смешивает оба способа для одинаковых сценариев.
## Domain state
## Состояние домена
Business определяет форму `AuthState`, начальное состояние, допустимые transitions и public observation contract. Concrete store, persistence, subscription source и framework hook реализуются снаружи business через ports/adapters.
Модуль `business` определяет форму `AuthState`, начальное состояние, допустимые переходы и публичный способ наблюдения. Конкретное хранилище, сохранение данных, источник подписки и хук фреймворка реализуются снаружи через порты и адаптеры.
Framework-neutral observation может иметь форму `getSnapshot` и `subscribe`. Это protocol business API, а не React hook или `StoreApi` конкретной библиотеки.
Независимый от фреймворка интерфейс наблюдения может состоять из `getSnapshot` и `subscribe`. Это часть API бизнес-логики, а не React-хук или `StoreApi` конкретной библиотеки.

View File

@@ -1,4 +1,4 @@
# Граница Domain
# Граница домена
> Пояснение предметной и структурной границы Level 3.
@@ -12,31 +12,31 @@
## Предметная граница
Domain представляет одну связную предметную область: `auth`, `catalog`, `orders` или `checkout`. Он собирает её business contract, concrete integrations, повторяемые assemblies и framework bindings, но не становится большим module со смешанными ролями.
Домен представляет одну связную предметную область: `auth`, `catalog`, `orders` или `checkout`. Он объединяет её бизнес-логику, технические интеграции, повторяемые сборки и модули фреймворков, но не превращается в большой модуль со смешанными ролями.
Domain является предметной границей, а не владельцем runtime-кода в смысле Level 1. Каждый scenario, adapter, preset и framework binding остаётся ответственностью конкретного module. Такое разделение позволяет одной области иметь несколько public module APIs без нарушения правила о единственном владельце ответственности.
Домен является предметной границей, а не владельцем исполняемого кода в смысле Level 1. Каждый сценарий, адаптер и способ сборки принадлежит конкретному модулю. Поэтому одна предметная область может иметь несколько публичных API, не нарушая правило о единственном владельце ответственности.
## Структурные виды и роли
| Путь | Роль | Структурный вид |
|---|---|---|
| `domains/auth` | Предметная область Auth | Domain |
| `domains/auth/business` | Business | Module |
| `domains/auth/business/ports` | Business capabilities | Segment |
| `domains/auth/presets` | Навигация assemblies | Group |
| `domains/auth/presets/application` | Application preset | Module |
| `domains/auth/presets/application/adapters` | Private integrations preset | Segment |
| `domains/auth/adapters` | Навигация promoted adapters | Group |
| `domains/auth/adapters/identity-provider` | Reusable adapter | Module |
| `domains/auth/react` | React binding | Module |
| `domains/auth` | Предметная область авторизации | Домен |
| `domains/auth/business` | Бизнес-логика | Модуль |
| `domains/auth/business/ports` | Необходимые бизнес-логике возможности | Сегмент |
| `domains/auth/presets` | Навигация по типовым сборкам | Группа |
| `domains/auth/presets/application` | Сборка уровня приложения | Модуль |
| `domains/auth/presets/application/adapters` | Закрытые адаптеры сборки | Сегмент |
| `domains/auth/adapters` | Навигация по самостоятельным адаптерам | Группа |
| `domains/auth/adapters/identity-provider` | Повторно используемый адаптер | Модуль |
| `domains/auth/react` | Связь с React | Модуль |
Role отвечает на вопрос, что делает код. Structural kind отвечает на вопрос, какую архитектурную границу он образует. Имя папки само по себе не доказывает ни роль, ни structural kind.
Роль отвечает на вопрос, что делает код. Структурный вид определяет, какую архитектурную границу он образует. Имя папки само по себе не доказывает ни роль, ни структурный вид.
## Корень Domain
## Корень домена
Корень Domain не содержит реализацию, state, lifecycle resources, `index.ts` или общий barrel. Его прямыми детьми могут быть `business`, Groups `presets` и `adapters`, а также framework modules с именем framework, например `react`.
Корень домена не содержит реализацию, состояние, ресурсы жизненного цикла, `index.ts` или общий файл реэкспортов. Его прямыми детьми могут быть модуль `business`, группы `presets` и `adapters`, а также модули фреймворков, например `react`.
Не создаются автоматически корневые ветки `model`, `types`, `errors`, `lib`, `ui`, `client`, `server` или `tests`. Такая ветка должна либо быть segment module-владельца, либо иметь самостоятельную module responsibility, выраженную одной из ролей Domain.
Корневые ветки `model`, `types`, `errors`, `lib`, `ui`, `client`, `server` или `tests` не создаются автоматически. Такой каталог должен быть либо сегментом модуля-владельца, либо самостоятельным модулем с одной из допустимых ролей домена.
## Публичная граница
@@ -46,18 +46,18 @@ Role отвечает на вопрос, что делает код. Structural
@/domains/auth/react
```
Эти пути являются public API role modules. Root path `@/domains/auth` не существует как runtime boundary. Он не должен объединять isomorphic business, client React и server-only preset через `export *`.
Эти пути являются публичными API отдельных модулей. Корневого пути `@/domains/auth` для исполняемого кода не существует: он не должен объединять независимый от среды модуль `business`, клиентский React и серверную сборку через `export *`.
## Граница с другими слоями
| Ответственность | Владелец |
|---|---|
| Business scenarios, contracts, state semantics и errors | `domains/auth/business` |
| Concrete adapter одной assembly | Segment соответствующего preset |
| Reusable auth integration | Promoted adapter module |
| Повторяемая assembly `AuthApi` | Preset module |
| React provider, hook и domain-specific React UI | `domains/auth/react` |
| Page, route, redirect, screen и конкретный visual outcome | Module `compositions` |
| SDK wrapper или технический сервис без Auth semantics | Module `infra` |
| Предметные сценарии, контракты, модель состояния и ошибки | `domains/auth/business` |
| Технический адаптер одной сборки | Сегмент соответствующего модуля в `presets` |
| Повторно используемая интеграция авторизации | Самостоятельный модуль адаптера |
| Повторяемая сборка `AuthApi` | Модуль в `presets` |
| Провайдер, хук и относящийся к домену интерфейс React | `domains/auth/react` |
| Страница, маршрут, перенаправление, экран и конкретный визуальный результат | Модуль `compositions` |
| Обёртка над SDK или технический сервис без семантики авторизации | Модуль `infra` |
Framework dependency сама по себе не делает UI частью Domain. Component принадлежит `react` только когда он работает с domain contract и не определяет page, route или product composition.
Зависимость от фреймворка сама по себе не делает интерфейс частью домена. Компонент принадлежит `react`, только когда работает с контрактом домена и не определяет страницу, маршрут или продуктовую композицию.

View File

@@ -1,6 +1,6 @@
# Factory, ports и adapters
# Фабрика, порты и адаптеры
> Пояснение runtime boundary business module.
> Пояснение границы между бизнес-логикой и средой выполнения.
## Связанные правила
@@ -10,36 +10,36 @@
- [`SLM-L3-ADAPTER-R008`](../../rules/level-3.md#slm-l3-adapter-r008)
- [`SLM-L3-BUSINESS-A004`](../../rules/level-3.md#slm-l3-business-a004)
## Factory и API instance
## Фабрика и экземпляр API
```text
Factory + implementations ports -> business API instance
фабрика + реализации портов → экземпляр API бизнес-логики
```
Factory принадлежит `business`, получает полный `AuthDeps` и возвращает `AuthApi`:
Фабрика принадлежит модулю `business`, получает полный набор `AuthDeps` и возвращает `AuthApi`:
```ts
export type AuthFactory = (deps: AuthDeps) => AuthApi
```
Все presets одной factory предоставляют полный набор ports и получают одинаковый business API. Browser, request и server action не создают разные factory только из-за среды. Preset может открыть consumer суженный view API, но не меняет contract самой factory.
Все типовые сборки одной фабрики предоставляют полный набор портов и получают API одного контракта. Браузер, обработчик запроса и серверное действие не требуют разных фабрик только из-за среды выполнения. Сборка может открыть потребителю более узкое представление API, но не меняет контракт фабрики.
Factory construction создаёт только deterministic services и closures. Она не делает request, не читает cookies/storage/env, не запускает subscription/timer, не обращается к platform API, не выбирает adapter и не запускает framework lifecycle.
Вызов фабрики создаёт только объекты и замыкания без побочных эффектов. Он не выполняет запросы, не читает файлы cookie, хранилище или переменные окружения, не запускает подписки и таймеры, не обращается к API платформы, не выбирает адаптер и не выполняет операции жизненного цикла фреймворка.
## Isomorphic import graph
## Независимый от среды граф импортов
Проверяется весь production graph, достижимый из `business` entrypoint, а не только файл factory. Он не должен достигать:
Проверяется весь граф рабочего кода, достижимый из точки входа `business`, а не только файл фабрики. Он не должен достигать:
- React, Vue, Next.js и framework markers;
- browser-only, Node-only, `client-only` или `server-only` boundary;
- SDK, generated client, storage implementation или concrete state/query runtime;
- adapters, presets, framework modules и environment configuration.
- React, Vue, Next.js и служебных меток фреймворка;
- границ `client-only`, `server-only`, API браузера или Node.js;
- SDK, сгенерированного клиента, реализации хранилища или конкретной библиотеки состояния;
- адаптеров, сборок, модулей фреймворков и конфигурации среды.
Tree shaking не является доказательством изоляции. Type-only import concrete runtime создаёт ту же архитектурную зависимость и также запрещён.
Удаление неиспользуемого кода при сборке не доказывает изоляцию. Импорт только типов из конкретной реализации создаёт ту же архитектурную зависимость и также запрещён.
## Ports
## Порты
Port принадлежит business и описывает capability на business language:
Порт принадлежит бизнес-логике и описывает возможность на языке предметной области:
```ts
export type AuthPhonePort = {
@@ -54,21 +54,21 @@ export type AuthSessionPort = {
}
```
Port не принимает SDK client, generated operation, `Request`, `Window`, React hook, `StoreApi` или environment-specific type. Он абстрагирует implementation, а не доступность capability: optional port и method, который намеренно падает в одной среде, нарушают factory contract.
Порт не принимает клиент SDK, сгенерированную операцию, `Request`, `Window`, React-хук, `StoreApi` или тип конкретной среды. Он отделяет контракт от реализации, а не скрывает отсутствие возможности. Необязательный порт или метод, который намеренно падает в одной из сред, нарушает контракт фабрики.
`unknown` допустим только на границе непроверенного external result. Business обязан валидировать его до превращения в domain result, state или error. Если adapter уже может представить устойчивый business-owned result, port описывает именно этот result, а не concrete DTO.
`unknown` допустим только на границе непроверенного внешнего результата. Бизнес-логика обязана проверить такое значение до преобразования в предметный результат, состояние или ошибку. Если адаптер уже может вернуть устойчивый предметный результат, порт описывает этот результат, а не DTO конкретного транспорта.
## Adapters
## Адаптеры
Adapter соединяет business port и concrete runtime:
Адаптер соединяет порт с конкретной технической реализацией:
```text
business port <- adapter -> SDK / storage / platform / request input
порт business ← адаптер → SDK / хранилище / платформа / данные запроса
```
Adapter может преобразовать domain argument в transport argument, вызвать concrete source, нормализовать техническую форму к port contract и вернуть source failure. Он не определяет domain error code, business fallback, invariant или public method `AuthApi`.
Адаптер может преобразовать предметные аргументы в транспортные, вызвать внешний источник, привести технический результат к контракту порта и вернуть исходный сбой. Он не определяет код ошибки домена, резервное предметное поведение, инвариант или публичный метод `AuthApi`.
Default location -- private segment минимального preset owner:
По умолчанию адаптер является закрытым сегментом минимальной типовой сборки:
```text
domains/auth/presets/application/
@@ -77,7 +77,7 @@ domains/auth/presets/application/
└── index.ts
```
Если один adapter имеет несколько assembly consumers или самостоятельную integration responsibility, он становится promoted module:
Если адаптер нужен нескольким сборкам или имеет самостоятельную ответственность интеграции, он становится отдельным модулем:
```text
domains/auth/adapters/
@@ -85,4 +85,4 @@ domains/auth/adapters/
└── index.ts
```
Promoted adapter сохраняет минимальный public API. Его появление не делает concrete SDK частью public business contract.
Самостоятельный адаптер сохраняет минимальный публичный API. Его появление не делает конкретный SDK частью публичного контракта `business`.

View File

@@ -1,6 +1,6 @@
# React module внутри Domain
# Модуль React внутри домена
> Пояснение framework boundary Domain на примере React.
> Пояснение границы фреймворка на примере React.
## Связанные правила
@@ -8,9 +8,9 @@
- [`SLM-L3-BUSINESS-A004`](../../rules/level-3.md#slm-l3-business-a004)
- [`SLM-L3-ASSEMBLY-R010`](../../rules/level-3.md#slm-l3-assembly-r010)
## Имя и место module
## Имя и место модуля
Framework-specific module находится непосредственно в Domain и называется именем framework:
Зависящий от фреймворка модуль находится непосредственно в домене и называется именем фреймворка:
```text
domains/auth/react/
@@ -21,18 +21,18 @@ domains/auth/react/
└── index.ts
```
`react` точно обозначает framework и не создаёт пустую промежуточную Group вроде `framework/react` или `bindings/react`. Если Domain действительно поддерживает другой framework, он получает отдельный sibling module, например `vue`.
Имя `react` точно обозначает зависимость и не требует пустой промежуточной группы `framework/react` или `bindings/react`. Если домен действительно поддерживает другой фреймворк, он получает отдельный соседний модуль, например `vue`.
## Роль React module
## Роль модуля React
React module может:
Модуль React может:
- передать готовый `AuthApi` через context/provider;
- создать hook доступа к API или framework-neutral state;
- связать React lifecycle с subscription API;
- реализовать domain-specific React component.
- передавать готовый `AuthApi` через контекст и провайдер;
- предоставлять хук доступа к API или состоянию;
- связывать жизненный цикл React с подпиской;
- реализовывать относящийся к домену React-компонент.
Он не меняет business rules, не создаёт domain errors, не выбирает concrete adapters и не вызывает factory или preset. Сборка остаётся у composition graph owner; React module получает уже готовый instance.
Он не меняет предметные правила, не создаёт ошибки домена, не выбирает адаптеры и не вызывает фабрику или типовую сборку. Сборка остаётся у модуля-владельца графа; модуль React получает готовый экземпляр.
```tsx
type AuthProviderProps = PropsWithChildren<{
@@ -44,9 +44,9 @@ export const AuthProvider = ({ api, children }: AuthProviderProps) => {
}
```
## Reactive state
## Наблюдение за состоянием
Если `AuthApi` предоставляет framework-neutral protocol `getSnapshot` и `subscribe`, React module может использовать `useSyncExternalStore`:
Если `AuthApi` предоставляет независимый от фреймворка интерфейс `getSnapshot` и `subscribe`, модуль React может использовать `useSyncExternalStore`:
```tsx
'use client'
@@ -62,10 +62,10 @@ export const useAuthState = () => {
}
```
Business не импортирует React и не возвращает React hook как единственный способ наблюдать state. React module не создаёт subscription до commit и возвращает cleanup через protocol `useSyncExternalStore`.
Модуль `business` не импортирует React и не возвращает React-хук как единственный способ наблюдать состояние. Подпиской и её очисткой управляет `useSyncExternalStore`.
## Domain UI и compositions
## Интерфейс домена и композиции
Component принадлежит `react`, если его responsibility ограничена domain contract: он работает с `AuthApi`, domain state и stable domain errors. Он не владеет page, route, redirect, product copy или composition нескольких domains.
Компонент принадлежит `react`, если его ответственность ограничена контрактом домена: он работает с `AuthApi`, состоянием и устойчивыми ошибками домена. Он не владеет страницей, маршрутом, перенаправлением, продуктовым текстом или композицией нескольких доменов.
Screen, route outcome, локальный текст ошибки, redirect и page-specific UI остаются в `compositions`. Dependency от React сама по себе не доказывает принадлежность Domain.
Экран, результат маршрута, локальный текст ошибки, перенаправление и интерфейс конкретной страницы остаются в `compositions`. Зависимость от React сама по себе не доказывает принадлежность домену.

View File

@@ -4,21 +4,21 @@
## Зафиксированные решения
- Domain является сущностью только Level 3; в Level 2 предметная область остаётся одним domain module.
- Domain root не имеет общего runtime barrel.
- Framework module называется именем framework и размещается непосредственно в Domain: `domains/auth/react`.
- Framework module получает готовый API и не выполняет assembly.
- Runtime-взаимодействие business разных Domains проходит через consumer-owned port и graph owner.
- Navigation Groups допустимы в `domains`, но не являются частью базовых примеров.
- Домен является сущностью только Level 3; в Level 2 предметная область остаётся одним доменным модулем.
- Корень домена не имеет общей точки входа для исполняемого кода.
- Модуль фреймворка называется его именем и размещается непосредственно в домене: `domains/auth/react`.
- Модуль фреймворка получает готовый API и не выполняет сборку.
- Взаимодействие бизнес-логики разных доменов во время выполнения проходит через порт потребителя и владельца графа.
- Навигационные группы допустимы в `domains`, но не являются частью базовых примеров.
## Failure transport
## Форма передачи ошибок
Level 3 требует stable domain failure contract, но не навязывает проекту единый transport: exception с domain-specific runtime guard или discriminated `Result`. Нужно проверить, нужна ли общая политика для всех Domain одного приложения и как она влияет на server actions/RPC serialization.
Level 3 требует устойчивый контракт ошибок домена, но не навязывает единый способ передачи: исключение с проверкой типа во время выполнения или размеченный `Result`. Нужно проверить, нужна ли общая политика для всех доменов одного приложения и как она влияет на серверные действия и сериализацию RPC.
## Reactive state protocol
## Наблюдение за состоянием
Нужно проверить на реальном SSR/hydration кейсе точную форму framework-neutral observation protocol: initial snapshot, concurrent rendering, invalidation, subscription cleanup и поведение после request boundary. `getSnapshot` и `subscribe` пока являются базовой иллюстрацией, а не обязательной файловой формой.
На реальном примере SSR и гидратации нужно проверить точную форму независимого от фреймворка интерфейса наблюдения: начальный снимок, параллельный рендеринг, сброс данных, очистку подписки и поведение после завершения запроса. Методы `getSnapshot` и `subscribe` пока служат иллюстрацией, а не обязательной файловой формой.
## Architecture lint
## Автоматическая проверка архитектуры
Нужно выбрать формат project configuration для автоматической проверки Domain roots, role modules, public entrypoints, environment labels и запрещённых transitive imports. Проверка должна опираться на graph и metadata, а не только на имена папок.
Нужно выбрать формат конфигурации проекта для автоматической проверки корней доменов, их модулей, публичных точек входа, меток сред и запрещённых транзитивных импортов. Проверка должна опираться на граф и описание структуры, а не только на имена папок.

View File

@@ -1,6 +1,6 @@
# Presets и SSR
# Типовые сборки и SSR
> Пояснение повторяемой assembly Domain.
> Пояснение повторяемой сборки домена.
## Связанные правила
@@ -9,20 +9,20 @@
- [`SLM-L3-ENVIRONMENT-A012`](../../rules/level-3.md#slm-l3-environment-a012)
- [`SLM-L3-FACTORY-R006`](../../rules/level-3.md#slm-l3-factory-r006)
## Роль preset
## Роль типовой сборки
Preset -- именованная повторяемая assembly одной business factory для конкретного execution context. Он выбирает concrete implementations ports, создаёт `AuthApi` и передаёт caller lifecycle operations, определённые module-владельцами resources.
Модуль в группе `presets` задаёт именованный повторяемый способ создания API одной фабрики для конкретного контекста выполнения. Он выбирает реализации портов, создаёт `AuthApi` и передаёт вызывающему коду операции жизненного цикла, определённые модулями-владельцами ресурсов.
```text
authFactory
├── presets/application -> browser-compatible AuthApi
├── presets/request -> request-scoped AuthApi
└── presets/server-action -> server action AuthApi
├── presets/application → AuthApi уровня приложения
├── presets/request → AuthApi одного запроса
└── presets/server-action → AuthApi серверного действия
```
Среда определяется preset и adapters, а не `mode` внутри factory. Tests создают per-test assembly напрямую через factory и не требуют общего `presets/testing`.
Среда определяется выбранной сборкой и её адаптерами, а не параметром `mode` внутри фабрики. Тесты создают отдельную сборку напрямую через фабрику и не требуют общего модуля `presets/testing`.
## Структура и public API
## Структура и публичный API
```text
domains/auth/presets/application/
@@ -32,7 +32,7 @@ domains/auth/presets/application/
└── index.ts
```
`application` -- пример имени. Preset называется по execution scope или устойчивому назначению: `application`, `request`, `server-action`. Он не называется по temporary consumer, если configuration не предназначена для повторного использования.
`application` — только пример имени. Модуль называется по контексту выполнения или устойчивому назначению: `application`, `request`, `server-action`. Временный потребитель не должен давать имя повторно используемой конфигурации.
```ts
export const createApplicationAuth = (): AuthApi => {
@@ -43,13 +43,13 @@ export const createApplicationAuth = (): AuthApi => {
}
```
Preset не добавляет scenario, не меняет error mapping и не скрывает business rule. Он также не становится монополией на factory: явный composition graph owner может собрать одноразовый graph, если он принимает на себя все обязанности assembly.
Типовая сборка не добавляет сценарии, не меняет преобразование ошибок и не скрывает предметные правила. Одноразовый владелец графа может вызвать фабрику напрямую, если сам выбирает все порты и отвечает за жизненный цикл результата.
## Scope и lifecycle
## Область жизни
Preset объявляет ожидаемый scope API instance. Application preset используется в application scope; request preset создаёт новый instance для каждого request. Graph owner удерживает instance только в этом scope и не хранит request data в application singleton.
Модуль сборки объявляет ожидаемую область жизни экземпляра API. Сборка `application` используется в течение жизни приложения, а `request` создаёт новый экземпляр для каждого запроса. Владелец графа не хранит данные одного запроса в общем экземпляре приложения.
Если assembly создаёт lifecycle resource, caller получает явный cleanup handle:
Если сборка создаёт ресурс жизненного цикла, вызывающий код получает явную операцию очистки:
```ts
export type AuthRequestAssembly = {
@@ -72,11 +72,11 @@ export const createAuthForRequest = (
}
```
Factory и preset construction не запускают I/O или subscriptions. Если domain lifecycle должен начать resource, module-владелец выражает это отдельной API operation; graph owner вызывает её после начала scope и выполняет предоставленный cleanup при его завершении.
Создание API через фабрику или типовую сборку не запускает ввод-вывод и подписки. Если ресурс нужно запустить явно, модуль-владелец предоставляет отдельную операцию. Владелец графа вызывает её после начала своей области жизни и выполняет очистку при завершении.
## Server-only boundary
## Серверная граница
Server preset имеет отдельный entrypoint и marker выбранного framework/build system:
Серверная сборка имеет отдельную точку входа и служебную метку выбранного фреймворка или сборщика:
```ts
import 'server-only'
@@ -84,6 +84,6 @@ import 'server-only'
export { createAuthForRequest } from './create-auth-for-request'
```
Этот entrypoint не реэкспортируется через `business`, `react` или client-compatible preset. Server adapter может иметь собственный marker для защиты от ошибочного прямого import.
Эта точка входа не реэкспортируется через `business`, `react` или клиентскую сборку. Серверный адаптер также может иметь собственную метку, защищающую от ошибочного прямого импорта.
Framework module не вызывает preset и не создаёт factory. Он получает готовый `AuthApi` от graph owner, поэтому React lifecycle не смешивается с concrete assembly.
Модуль фреймворка не вызывает сборку и не создаёт фабрику. Он получает готовый `AuthApi` от владельца графа, поэтому жизненный цикл React не смешивается с технической сборкой зависимостей.

View File

@@ -1,8 +1,8 @@
# Тестирование Domain
# Тестирование домена
> Verification границ и behavior Level 3.
> Проверка границ и поведения Level 3.
## Связанное правило
## Связанные правила
- [`SLM-L3-TEST-R014`](../../rules/level-3.md#slm-l3-test-r014)
- [`SLM-L3-FACTORY-R006`](../../rules/level-3.md#slm-l3-factory-r006)
@@ -10,21 +10,21 @@
## Принцип размещения
Тест живёт у module-владельца проверяемой ответственности. У Domain нет общей корневой папки `tests/`.
Тест находится рядом с модулем-владельцем проверяемой ответственности. У домена нет общей корневой папки `tests/`.
| Проверяемая граница | Владелец теста |
|---|---|
| Business scenarios, state и domain errors | `business` |
| Pure rule, mapper, parser или guard | Colocated segment `business` |
| Concrete port implementation | Adapter |
| Wiring, scope и cleanup assembly | Preset |
| Provider, hook и React lifecycle | `react` |
| Cross-domain graph | Composition graph owner |
| Полный пользовательский поток | E2E entry приложения |
| Предметные сценарии, состояние и ошибки домена | `business` |
| Чистая функция бизнес-логики | Соответствующий сегмент `business` |
| Реализация порта | Адаптер |
| Выбор зависимостей, область жизни и очистка | Модуль в `presets` |
| Провайдер, хук и жизненный цикл React | `react` |
| Граф нескольких доменов | Модуль-владелец графа |
| Полный пользовательский поток | Точка входа сквозного теста приложения |
## Factory-level tests
## Тесты через фабрику
Factory-level tests являются главным доказательством public business behavior. Они импортируют только public API `business` и передают controlled ports:
Тесты через фабрику являются главным доказательством публичного поведения бизнес-логики. Они импортируют только публичный API `business` и передают управляемые тестовые реализации портов:
```ts
import {
@@ -43,28 +43,28 @@ it('maps source failure to domain error', async () => {
})
```
Factory-level suite проверяет форму public API, отсутствие side effects при construction, happy path, input validation, malformed port result, rejected promise, synchronous throw, domain error code, порядок effects, state transitions и значимые concurrent calls.
Такой набор тестов проверяет форму публичного API, отсутствие побочных эффектов при создании, успешные и ошибочные сценарии, проверку входных данных, переходы состояния и порядок внешних операций.
Business test не использует React, production SDK, storage или production preset. Если scenario нельзя проверить без них, runtime boundary проникла внутрь business.
Тест `business` не использует React, реальный SDK, хранилище или типовую сборку приложения. Если сценарий нельзя проверить без них, техническая зависимость проникла внутрь бизнес-логики.
## Test harness
## Вспомогательная тестовая сборка
Private test harness уменьшает boilerplate, но не является preset:
Закрытая тестовая функция уменьшает повторение, но не является модулем в `presets`:
```ts
const { api, ports, state } = createAuthTestHarness({ requestCode })
```
Harness создаёт новый instance на каждый test case, допускает scenario-specific overrides и не экспортируется через production entrypoint. `presets/testing` не создаётся по умолчанию.
Она создаёт новый экземпляр для каждого теста, допускает нужные сценарию замены и не экспортируется через рабочую точку входа. Модуль `presets/testing` по умолчанию не создаётся.
## Тесты остальных ролей
Adapter test проверяет concrete operation, transport payload, mapping аргументов, raw result/error согласно port contract и subscription cleanup. Он не повторяет domain error mapping или scenario matrix.
Тест адаптера проверяет вызванную техническую операцию, переданные данные, преобразование аргументов, результат или ошибку согласно контракту порта и очистку подписки. Он не повторяет преобразование ошибок домена и полный набор предметных сценариев.
Preset test проверяет полный набор ports, выбор adapters, отсутствие I/O при construction, scope instance, передачу cleanup handle и server/client import boundary. Он не повторяет happy path business.
Тест типовой сборки проверяет полный набор портов, выбор адаптеров, отсутствие ввода-вывода при создании, область жизни экземпляра, передачу операции очистки и границу клиента и сервера. Он не повторяет успешные предметные сценарии.
React test получает fake `AuthApi` и проверяет Provider, access hook, update по `subscribe`, cleanup после unmount и поведение в Strict Mode. Smoke test с real factory добавляется только при отдельном integration risk.
Тест React получает тестовый `AuthApi` и проверяет провайдер, хук доступа, обновление по `subscribe`, очистку после размонтирования и поведение в `StrictMode`. Минимальный интеграционный тест с настоящей фабрикой добавляется только при отдельном риске интеграции.
## Минимальный набор
Файл test создаётся вместе с реальным risk, а не ради scaffold. Однако public business scenario не считается завершённым без factory-level tests; production preset без assembly test; adapter с нетривиальным transport mapping без adapter test; React binding с lifecycle behavior без framework test.
Тест создаётся в ответ на реальный риск, а не ради заполнения каркаса. При этом публичный предметный сценарий требует теста через фабрику, типовая сборка приложения — теста сборки, адаптер с нетривиальным преобразованием данных — теста адаптера, а модуль React с поведением жизненного цикла — теста фреймворка.