docs: simplify conventions layout
Backend security / security (pull_request) Successful in 7s
Backend verify / verify (pull_request) Successful in 2m3s
Conventional commits / commits (pull_request) Successful in 24s
Backend security / security (push) Successful in 7s
Backend verify / verify (push) Successful in 2m10s

This commit was merged in pull request #18.
This commit is contained in:
2026-09-15 05:58:33 +03:00
parent 9810d47e42
commit 016782657a
13 changed files with 263 additions and 276 deletions
-9
View File
@@ -1,9 +0,0 @@
# Backend conventions: вход
Актуальный routing по задаче, пути и boundary находится в [AGENTS](../AGENTS.md).
Выбирай только активированные темы; при новой границе повтори routing.
[Architecture](architecture.md) объясняет mental model, [SOURCES](SOURCES.md)
ведёт к системным фактам, specifications и ADR по конкретному вопросу.
Этот адрес сохранён для существующих входящих ссылок; второй копии правил
и таблицы маршрутов здесь нет.
-19
View File
@@ -1,19 +0,0 @@
# Источники backend
| Что нужно установить | Владелец |
| --- | --- |
| Implementation decision и безопасный pattern | [AGENTS](../AGENTS.md) |
| HTTP errors, DTO semantics, public wire shape | [docs: HTTP API](../../docs/architecture/http-api.md); runtime OpenAPI из Nest — единственный editable source |
| Auth/session, account access, token encryption | [Аутентификация](../../docs/domains/authentication/overview.md), [доступ к данным](../../docs/architecture/containers.md#хранимые-данные-и-границы-доступа), [подключение брокера](../../docs/integrations/tbank.md) |
| Prisma schema/data ownership и migration context | [docs: Data](../../docs/architecture/containers.md#хранимые-данные-и-границы-доступа) |
| T-Bank/gRPC/cache, logging | [T-Bank](../../docs/integrations/tbank.md), [свежесть данных](../../docs/architecture/cross-cutting/data-freshness.md), [наблюдаемость](../../docs/architecture/http-api.md#наблюдаемость) |
| Money, account/position/income/cache meanings | [docs: Domain](../../docs/glossary.md) |
| Поведение конкретной feature | [docs: Features](../../docs/README.md#предметные-области) |
| Существующие behavioral requirements при необходимости | [specs index](../../specs/AI_INDEX.md) |
| Versions/scripts и scope автоматических checks | package.json/lockfile, ESLint/TypeScript/Jest/Prisma configs |
SOURCES — карта, не требование прочитать все ссылки. Code показывает реализацию,
ADR — rationale, docs — текущие факты; старый пример не отменяет контракт.
Исследование предлагает варианты, а actual source разрешает выбор. При missing
или conflicting security/data facts сначала установи источник. Для обычной
работы OpenSpec/SDD не требуется.
+222 -4
View File
@@ -1,4 +1,6 @@
# Backend mental model # Backend architecture
## Устройство и data flow
NestJS + TypeScript modular monolith. Composition root `AppModule` подключает NestJS + TypeScript modular monolith. Composition root `AppModule` подключает
бизнес-модули, validated configuration, Prisma и Pino. Общая `configureApp` бизнес-модули, validated configuration, Prisma и Pino. Общая `configureApp`
@@ -19,7 +21,7 @@ HTTP controller → application service/use case → pure domain rules
presentation/application/domain/infrastructure; компактные остаются плоскими. presentation/application/domain/infrastructure; компактные остаются плоскими.
`*.module.ts` — Nest DI, `*.api.ts` — используемые cross-module exports. `*.module.ts` — Nest DI, `*.api.ts` — используемые cross-module exports.
Application использует конкретные providers; обязательных ports на каждый Application использует конкретные providers; обязательных ports на каждый
класс нет. [Module convention](conventions/backend-architecture.md). класс нет.
Stocks/bonds — slices одного instruments module: catalog владеет identity/cache, Stocks/bonds — slices одного instruments module: catalog владеет identity/cache,
market — котировками, income — выплатами/личными позициями. Общая orchestration market — котировками, income — выплатами/личными позициями. Общая orchestration
@@ -29,7 +31,7 @@ domain из-за extraction в функцию. Boundary mappers преобраз
PrismaService владеет DB lifecycle. Самостоятельные use cases получают данные PrismaService владеет DB lifecycle. Самостоятельные use cases получают данные
через concrete repositories; существующие связные CRUD/transaction services через concrete repositories; существующие связные CRUD/transaction services
сохраняют прямые queries по [data convention](conventions/backend-data.md). сохраняют прямые queries по [data convention](conventions/data.md).
Money — currency + decimal string; расчёты используют точные общие helpers. Money — currency + decimal string; расчёты используют точные общие helpers.
Auth управляет identity/session; TbankAccessService проверяет доступ к счёту и Auth управляет identity/session; TbankAccessService проверяет доступ к счёту и
@@ -41,4 +43,220 @@ Runtime OpenAPI из Nest → предоставленный frontend snapshot
Problem Details фильтруются общей HTTP boundary; StructuredLogger добавляет Problem Details фильтруются общей HTTP boundary; StructuredLogger добавляет
request context и ограничивает поля событий. Jest unit tests, изолированные request context и ограничивает поля событий. Jest unit tests, изолированные
SQLite/HTTP suites и OpenAPI parity доказывают разные части поведения. SQLite/HTTP suites и OpenAPI parity доказывают разные части поведения.
Системные потоки и ADR выбираются через [SOURCES](SOURCES.md). Системные потоки и ADR выбираются через [Источники](../AGENTS.md#источники).
## Module-first organization
Сначала выбери business owner в `modules/`, затем ответственность внутри него.
### Progressive structure
Три уровня организации: business module → responsibility layer → technical grouping
при реальной необходимости. Компактный module с понятными controller/DTO/service
может оставаться плоским. Ориентир 5–7 файлов не является порогом: учитывай число
ответственностей, наличие domain/repository/use cases и удобство просмотра каталога.
Когда в module смешаны несколько services, transport, расчёты и persistence,
группируй файлы по ответственности:
```text
feature/
├── feature.module.ts
├── feature.api.ts # только при существующих внешних потребителях
├── presentation/ # HTTP, DTO, guards
├── application/ # сценарии, orchestration, application input
├── domain/ # чистые бизнес-правила и расчёты
└── infrastructure/ # persistence, framework и boundary mapping
```
Создавай только нужные слои. В корне сложного module оставляй composition и
публичную boundary; production code и colocated tests размещай в слое владельца.
Внутри business slice действует то же правило, без отдельного Nest module ради папок.
`controllers/`, `dto/`, `services/`, `use-cases/`, `persistence/`, `mappers/`
допустимы только внутри слоя, когда множество однотипных файлов мешает навигации.
Для одного controller или пары DTO дополнительная поддиректория не нужна.
Пустые папки, новые barrels, CQRS и interfaces не требуются для этой организации.
## Поиск и создание файла
При работе над feature проходи владельцев в порядке:
1. Найди business module.
2. Прочитай его `*.module.ts` (для slice — composition родительского module).
3. Проверь `presentation/`: controller и transport contract.
4. Проследи вызовы в `application/`: сценарии и services.
5. Проверь используемые правила в `domain/`.
6. Проверь `infrastructure/`: repository и технические реализации.
7. По обнаруженным зависимостям переходи в `integrations/` и `common/`.
В плоском module классифицируй файлы по suffix и следуй их ответственности.
Отсутствующий слой пропускай. Если business owner известен, начинай локально,
а не с repository-wide search.
**Rule of locality:** code that belongs to one business capability stays inside
that capability unless it is cross-cutting. Например, расчёты карт принадлежат
bank-cards/domain, даже если реализованы обычными функциями.
Перед созданием файла определи:
1. Какой business module владеет изменением? Сначала owner, затем common при
подтверждённой общей потребности.
2. Какова ответственность: HTTP/DTO/guard → presentation; сценарий/orchestration →
application; чистое business rule/calculation → domain; persistence или
module-specific technical implementation → infrastructure; внешний протокол → integrations.
3. Какой approved suffix из таблицы соответствует этой ответственности?
4. Какой слой владеет файлом? В компактном плоском module сохрани размещение рядом
с owner; в слоистом используй соответствующий слой.
5. Достаточно ли colocate файл рядом с существующими? Level 3 grouping создавай
только при заметном улучшении навигации по множеству однотипных файлов.
Если owner или роль не определены, сначала установи ответственность. Generic
`utils.ts`, `helpers.ts`, `manager.ts`, `handler.ts` и arbitrary directory её не заменяют.
## Naming & File Suffix Convention
Имена файлов — `kebab-case`. File suffix определяет responsibility, directory
определяет architectural layer. Размещение в таблице предпочтительно для
слоистого module; компактный плоский module сохраняет файлы в корне.
Cross-module consumers во всех строках ограничены публичной границей ниже.
| Suffix | Когда использовать | Где | Кто зависит | Когда не использовать |
| --- | --- | --- | --- | --- |
| `*.controller.ts` | HTTP endpoint / transport | presentation | Nest wiring | DB, расчёты, SDK, сложная оркестрация |
| `*.dto.ts` | Request/response, validation, OpenAPI | presentation | Controller; application boundary при совпадении shape | Универсальная domain model |
| `*.use-case.ts` | Один самостоятельный application scenario | application | Controller, другой orchestrator | Prisma queries, Request/Response, SDK implementation |
| `*.service.ts` | Конкретная cohesive reusable capability | рядом с owner; application/domain/integration по роли | Сценарии и services; domain — только от чистого domain service | Свалка несвязанных сценариев |
| `*.repository.ts` | Persistence queries и согласованные DB reads/writes | infrastructure | Application service/use case | Domain calculations, HTTP request, external transport |
| `*.client.ts` | Низкоуровневый внешний HTTP/gRPC/SDK | integrations или infrastructure | Application orchestration, boundary mapper | Пользовательский lifecycle и domain rules |
| `*.mapper.ts` | Meaningful conversion между моделями | обычно infrastructure у преобразуемой boundary | Код соответствующей boundary | Формальное копирование нескольких одинаковых полей |
| `*.policy.ts` | Cohesive decision rule | domain/application/integration по входным данным | Владелец правила; domain — от чистой policy | Persistence, transport, generic strategy |
| `*.guard.ts` | Nest guard | presentation | Nest/controller | Основная account authorization и финансовые правила |
| `*.strategy.ts` | Framework strategy, например JWT | infrastructure | Framework wiring | Обычный service без strategy contract |
| `*.module.ts` | Nest imports/providers/controllers/exports | module root | Composition root, importing module | Бизнес-логика |
| `*.types.ts` | Малая группа связанных TS types | рядом с owner | Потребители модели на разрешённых слоях | Глобальные types всего module, runtime validation |
| `*.api.ts` | Явный список публичных exports | root конкретного module/integration | Другие modules | DTO/client/service implementation или generic API logic |
| `*-input.ts` | Application input: нормализация и проверка формы, отличающейся от wire DTO | application | Application code | Копия DTO под новым именем; вариант `*.input.ts` |
| `*.calculations.ts`, `*.rules.ts` или предметное имя | Чистые бизнес-расчёты, value semantics | domain | Application и domain | Nest/Prisma/HTTP/SDK зависимости |
| `*.spec.ts` | Существующая unit/component проверка поведения | рядом с owner/slice | Test runner | Архитектурные проверки папок и trivial wiring |
`bank-card-input.ts` использует выбранный `*-input.ts`: переводит Money в
копейки, нормализует значения и проверяет application input. DTO не дублируется
отдельным Input type. `rub-money.ts` — domain-правила RUB; `bond-money.mapper.ts`
— преобразование ответа клиента. Эти роли не определяются словом money в имени.
Технические framework suffixes (`*.filter.ts`, `*.middleware.ts`,
`*.decorator.ts`, `*.config.ts`, `*.validation.ts`) сохраняют соответствующие
существующие роли в common/config. `tbank.crypto.ts` — локальная реализация
шифрования токена; имя не является разрешением создавать generic crypto layer.
HTTP/e2e naming и расположение — в [Testing](conventions/testing.md).
Без зафиксированной необходимости не вводи `*.helper.ts`, `*.utils.ts`,
`*.manager.ts`, `*.processor.ts`, `*.handler.ts`, `*.provider.ts`, `*.factory.ts`.
Новая naming convention сначала получает конкретную роль и обоснование здесь.
Для data access выбран только `*.repository.ts`, не persistence/store/dao.
## Публичные границы
`*.module.ts` задаёт DI boundary; `*.api.ts` перечисляет используемые другими
модулями providers, функции и типы. Внешний production import идёт через этот
API или файл Nest module. Чистый `integrations/tbank/decimal.ts` — отдельный
публичный вход для protobuf conversion: импорт арифметики не загружает DI/Prisma.
Внутри модуля используй прямые локальные импорты.
Tests могут импортировать внутренний owner для проверки поведения и mocks.
Новый export требует текущего потребителя; controller и persistence остаются
приватными. Не добавляй `index.ts` на каждом уровне.
Circular dependencies устраняй изменением ownership/composition. `forwardRef()`
не является стандартным исправлением boundaries; исключение требует конкретной
неизбежной связи и проверки DI initialization.
Каждый provider регистрируется у одного владельца. Потребители импортируют
его module, а не повторяют provider: это сохраняет единственный кэш и лимитер.
`AuthModule` экспортирует guard; наличие JWT не заменяет account access check.
- `income` читает операции через `operations.api.ts`.
- `instruments` получает личные позиции через `portfolio.api.ts`.
- `tbank-connection` использует T-Bank integration и публичный сброс портфеля.
- `portfolio` и другие инвестиционные модули используют `TbankModule`, который
не зависит от business modules. Так управление подключением не образует цикл.
- `investment-calendar.ts` и `account-reference.dto.ts` разделяются только
инвестиционными областями; это не глобальная библиотека бизнес-правил.
## Ответственность и направление зависимостей
| Владелец | Ответственность |
| --- | --- |
| Controller / presentation | Route, verified identity, transport DTO, validation, status/cookie; вызов сценария |
| Use case / application service | Оркестрация, business validation, доступ и согласованность результата |
| Domain | Чистые правила и расчёты; без NestJS, Prisma, HTTP/Swagger и внешних SDK |
| Persistence / infrastructure | Запросы, транзакции, технические реализации границ |
| `src/integrations/{tbank,moex,ncr}` | Внешние протоколы, декодирование, retry/deadline/cache; без зависимостей от HTTP DTO business modules |
| `src/prisma` | Общий DB provider и lifecycle; не generic repository |
Основной вызов: presentation → application → domain; application использует
конкретные infrastructure providers, зарегистрированные module. Infrastructure
может использовать application types/input и domain rules; новые interfaces
ради инверсии зависимостей не обязательны. Domain не импортирует presentation или
infrastructure. Существующие связные CRUD/transaction services могут сохранять прямые queries
по [Data](conventions/data.md); use case получает данные через repository и сам
Prisma queries не выполняет. Entity/interface на каждую таблицу не требуется.
DTO остаются транспортными runtime classes; существующее использование DTO
на application boundary допустимо по [HTTP API](conventions/http-api.md).
Сохраняй существующий error contract: HTTP exceptions остаются на application/
transport boundary. Чистому расчёту не нужен новый framework ошибок.
Mapping и stable codes — по [Errors](conventions/error-handling.md).
Точные ограничения — [HTTP](conventions/http-api.md), [Data](conventions/data.md),
[Security](conventions/security-auth.md), [Integrations](conventions/integrations.md).
## Сценарии и размер сервисов
Разделяй независимые операции, а не строки. Пример: `BankCardsService` владеет
картами, `BankOperationsService` — журналом/переводами, `GetBankSummaryUseCase` —
сводкой через repository и чистый расчёт. Не разрывай денежную transaction,
общий cache/limiter или pending promises при extraction.
Расширение abstractions — по [change scope](../../.ai/conventions/code-health.md#change-scope).
## Common
`common` содержит общий error filter, logging, API decorators, тип `Money`,
его HTTP `MoneyDto` и точную decimal-арифметику, используемые несколькими областями. Код одного
модуля принадлежит этому модулю: например, RUB/kopecks rules — банковским картам,
protobuf units/nano conversion — T-Bank integration. Domain использует
`money.types.ts`, а не Swagger DTO; это общая форма денег для карт и портфеля.
Давай функциям предметные имена; не создавай глобальные `utils.ts`/`helpers.ts`.
## Domain literals and magic values
Общий выбор symbolic values и числовых policies —
[Domain values](../../.ai/conventions/code-health.md#domain-values).
Сохраняй strict TS и explicit boundary types. Для недоверенного input выбирай
`unknown` + runtime narrowing; отключённый lint `no-explicit-any` не делает
`any` нормой domain/application model. `declare` допустим для заполняемых извне
DTO fields, не для сокрытия неинициализированного owned state.
OpenAPI enum metadata и runtime validation должны читать тот же vocabulary,
что application/domain. Например, `BankOperationKind` принадлежит bank-cards/domain,
а DTO получает допустимые значения через `Object.values(BankOperationKind)`.
Frontend переиспользует generated OpenAPI types/values по возможностям генератора,
а не определяет независимый backend-owned словарь. Внешние/generated enums
сохраняй; generated Prisma/protocol code вручную не меняй.
`@typescript-eslint/no-magic-numbers` включён как error; базовое ESLint rule
отключено в пользу TS-версии. Исключения для очевидных чисел и их bigint форм,
индексов, default values, enum/type declarations и readonly properties заданы
в ESLint config. Test fixtures/expected values остаются inline, остальные lint
правила продолжают действовать. Numeric policy получает предметное имя и единицы;
semantic magic strings проверяются conventions/review без нового инструмента.
## Logging
События, levels, correlation и safe fields — по
[Observability](conventions/observability.md).
## Проверка
`npm run typecheck:app`, `npm run lint`, targeted Jest по изменённому use case.
Смысл boundaries проверяется review: backend не имеет отдельного автоматического
FSD/DI architecture checker. Existing tests и build не доказывают правильный owner.
-218
View File
@@ -1,218 +0,0 @@
# Backend architecture
## Module-first organization
Устройство и data flow — в [mental model](../architecture.md).
Сначала выбери business owner в `modules/`, затем ответственность внутри него.
### Progressive structure
Три уровня организации: business module → responsibility layer → technical grouping
при реальной необходимости. Компактный module с понятными controller/DTO/service
может оставаться плоским. Ориентир 5–7 файлов не является порогом: учитывай число
ответственностей, наличие domain/repository/use cases и удобство просмотра каталога.
Когда в module смешаны несколько services, transport, расчёты и persistence,
группируй файлы по ответственности:
```text
feature/
├── feature.module.ts
├── feature.api.ts # только при существующих внешних потребителях
├── presentation/ # HTTP, DTO, guards
├── application/ # сценарии, orchestration, application input
├── domain/ # чистые бизнес-правила и расчёты
└── infrastructure/ # persistence, framework и boundary mapping
```
Создавай только нужные слои. В корне сложного module оставляй composition и
публичную boundary; production code и colocated tests размещай в слое владельца.
Внутри business slice действует то же правило, без отдельного Nest module ради папок.
`controllers/`, `dto/`, `services/`, `use-cases/`, `persistence/`, `mappers/`
допустимы только внутри слоя, когда множество однотипных файлов мешает навигации.
Для одного controller или пары DTO дополнительная поддиректория не нужна.
Пустые папки, новые barrels, CQRS и interfaces не требуются для этой организации.
## Поиск и создание файла
При работе над feature проходи владельцев в порядке:
1. Найди business module.
2. Прочитай его `*.module.ts` (для slice — composition родительского module).
3. Проверь `presentation/`: controller и transport contract.
4. Проследи вызовы в `application/`: сценарии и services.
5. Проверь используемые правила в `domain/`.
6. Проверь `infrastructure/`: repository и технические реализации.
7. По обнаруженным зависимостям переходи в `integrations/` и `common/`.
В плоском module классифицируй файлы по suffix и следуй их ответственности.
Отсутствующий слой пропускай. Если business owner известен, начинай локально,
а не с repository-wide search.
**Rule of locality:** code that belongs to one business capability stays inside
that capability unless it is cross-cutting. Например, расчёты карт принадлежат
bank-cards/domain, даже если реализованы обычными функциями.
Перед созданием файла определи:
1. Какой business module владеет изменением? Сначала owner, затем common при
подтверждённой общей потребности.
2. Какова ответственность: HTTP/DTO/guard → presentation; сценарий/orchestration →
application; чистое business rule/calculation → domain; persistence или
module-specific technical implementation → infrastructure; внешний протокол → integrations.
3. Какой approved suffix из таблицы соответствует этой ответственности?
4. Какой слой владеет файлом? В компактном плоском module сохрани размещение рядом
с owner; в слоистом используй соответствующий слой.
5. Достаточно ли colocate файл рядом с существующими? Level 3 grouping создавай
только при заметном улучшении навигации по множеству однотипных файлов.
Если owner или роль не определены, сначала установи ответственность. Generic
`utils.ts`, `helpers.ts`, `manager.ts`, `handler.ts` и arbitrary directory её не заменяют.
## Naming & File Suffix Convention
Имена файлов — `kebab-case`. File suffix определяет responsibility, directory
определяет architectural layer. Размещение в таблице предпочтительно для
слоистого module; компактный плоский module сохраняет файлы в корне.
Cross-module consumers во всех строках ограничены публичной границей ниже.
| Suffix | Когда использовать | Где | Кто зависит | Когда не использовать |
| --- | --- | --- | --- | --- |
| `*.controller.ts` | HTTP endpoint / transport | presentation | Nest wiring | DB, расчёты, SDK, сложная оркестрация |
| `*.dto.ts` | Request/response, validation, OpenAPI | presentation | Controller; application boundary при совпадении shape | Универсальная domain model |
| `*.use-case.ts` | Один самостоятельный application scenario | application | Controller, другой orchestrator | Prisma queries, Request/Response, SDK implementation |
| `*.service.ts` | Конкретная cohesive reusable capability | рядом с owner; application/domain/integration по роли | Сценарии и services; domain — только от чистого domain service | Свалка несвязанных сценариев |
| `*.repository.ts` | Persistence queries и согласованные DB reads/writes | infrastructure | Application service/use case | Domain calculations, HTTP request, external transport |
| `*.client.ts` | Низкоуровневый внешний HTTP/gRPC/SDK | integrations или infrastructure | Application orchestration, boundary mapper | Пользовательский lifecycle и domain rules |
| `*.mapper.ts` | Meaningful conversion между моделями | обычно infrastructure у преобразуемой boundary | Код соответствующей boundary | Формальное копирование нескольких одинаковых полей |
| `*.policy.ts` | Cohesive decision rule | domain/application/integration по входным данным | Владелец правила; domain — от чистой policy | Persistence, transport, generic strategy |
| `*.guard.ts` | Nest guard | presentation | Nest/controller | Основная account authorization и финансовые правила |
| `*.strategy.ts` | Framework strategy, например JWT | infrastructure | Framework wiring | Обычный service без strategy contract |
| `*.module.ts` | Nest imports/providers/controllers/exports | module root | Composition root, importing module | Бизнес-логика |
| `*.types.ts` | Малая группа связанных TS types | рядом с owner | Потребители модели на разрешённых слоях | Глобальные types всего module, runtime validation |
| `*.api.ts` | Явный список публичных exports | root конкретного module/integration | Другие modules | DTO/client/service implementation или generic API logic |
| `*-input.ts` | Application input: нормализация и проверка формы, отличающейся от wire DTO | application | Application code | Копия DTO под новым именем; вариант `*.input.ts` |
| `*.calculations.ts`, `*.rules.ts` или предметное имя | Чистые бизнес-расчёты, value semantics | domain | Application и domain | Nest/Prisma/HTTP/SDK зависимости |
| `*.spec.ts` | Существующая unit/component проверка поведения | рядом с owner/slice | Test runner | Архитектурные проверки папок и trivial wiring |
`bank-card-input.ts` использует выбранный `*-input.ts`: переводит Money в
копейки, нормализует значения и проверяет application input. DTO не дублируется
отдельным Input type. `rub-money.ts` — domain-правила RUB; `bond-money.mapper.ts`
— преобразование ответа клиента. Эти роли не определяются словом money в имени.
Технические framework suffixes (`*.filter.ts`, `*.middleware.ts`,
`*.decorator.ts`, `*.config.ts`, `*.validation.ts`) сохраняют соответствующие
существующие роли в common/config. `tbank.crypto.ts` — локальная реализация
шифрования токена; имя не является разрешением создавать generic crypto layer.
HTTP/e2e naming и расположение — в [Testing](backend-testing.md).
Без зафиксированной необходимости не вводи `*.helper.ts`, `*.utils.ts`,
`*.manager.ts`, `*.processor.ts`, `*.handler.ts`, `*.provider.ts`, `*.factory.ts`.
Новая naming convention сначала получает конкретную роль и обоснование здесь.
Для data access выбран только `*.repository.ts`, не persistence/store/dao.
## Публичные границы
`*.module.ts` задаёт DI boundary; `*.api.ts` перечисляет используемые другими
модулями providers, функции и типы. Внешний production import идёт через этот
API или файл Nest module. Чистый `integrations/tbank/decimal.ts` — отдельный
публичный вход для protobuf conversion: импорт арифметики не загружает DI/Prisma.
Внутри модуля используй прямые локальные импорты.
Tests могут импортировать внутренний owner для проверки поведения и mocks.
Новый export требует текущего потребителя; controller и persistence остаются
приватными. Не добавляй `index.ts` на каждом уровне.
Circular dependencies устраняй изменением ownership/composition. `forwardRef()`
не является стандартным исправлением boundaries; исключение требует конкретной
неизбежной связи и проверки DI initialization.
Каждый provider регистрируется у одного владельца. Потребители импортируют
его module, а не повторяют provider: это сохраняет единственный кэш и лимитер.
`AuthModule` экспортирует guard; наличие JWT не заменяет account access check.
- `income` читает операции через `operations.api.ts`.
- `instruments` получает личные позиции через `portfolio.api.ts`.
- `tbank-connection` использует T-Bank integration и публичный сброс портфеля.
- `portfolio` и другие инвестиционные модули используют `TbankModule`, который
не зависит от business modules. Так управление подключением не образует цикл.
- `investment-calendar.ts` и `account-reference.dto.ts` разделяются только
инвестиционными областями; это не глобальная библиотека бизнес-правил.
## Ответственность и направление зависимостей
| Владелец | Ответственность |
| --- | --- |
| Controller / presentation | Route, verified identity, transport DTO, validation, status/cookie; вызов сценария |
| Use case / application service | Оркестрация, business validation, доступ и согласованность результата |
| Domain | Чистые правила и расчёты; без NestJS, Prisma, HTTP/Swagger и внешних SDK |
| Persistence / infrastructure | Запросы, транзакции, технические реализации границ |
| `src/integrations/{tbank,moex,ncr}` | Внешние протоколы, декодирование, retry/deadline/cache; без зависимостей от HTTP DTO business modules |
| `src/prisma` | Общий DB provider и lifecycle; не generic repository |
Основной вызов: presentation → application → domain; application использует
конкретные infrastructure providers, зарегистрированные module. Infrastructure
может использовать application types/input и domain rules; новые interfaces
ради инверсии зависимостей не обязательны. Domain не импортирует presentation или
infrastructure. Существующие связные CRUD/transaction services могут сохранять прямые queries
по [Data](backend-data.md); use case получает данные через repository и сам
Prisma queries не выполняет. Entity/interface на каждую таблицу не требуется.
DTO остаются транспортными runtime classes; существующее использование DTO
на application boundary допустимо по [HTTP API](backend-http-api.md).
Сохраняй существующий error contract: HTTP exceptions остаются на application/
transport boundary. Чистому расчёту не нужен новый framework ошибок.
Mapping и stable codes — по [Errors](backend-error-handling.md).
Точные ограничения — [HTTP](backend-http-api.md), [Data](backend-data.md),
[Security](security-auth.md), [Integrations](backend-integrations.md).
## Сценарии и размер сервисов
Разделяй независимые операции, а не строки. Пример: `BankCardsService` владеет
картами, `BankOperationsService` — журналом/переводами, `GetBankSummaryUseCase` —
сводкой через repository и чистый расчёт. Не разрывай денежную transaction,
общий cache/limiter или pending promises при extraction.
Расширение abstractions — по [change scope](../../../.ai/conventions/code-health.md#change-scope).
## Common
`common` содержит общий error filter, logging, API decorators, тип `Money`,
его HTTP `MoneyDto` и точную decimal-арифметику, используемые несколькими областями. Код одного
модуля принадлежит этому модулю: например, RUB/kopecks rules — банковским картам,
protobuf units/nano conversion — T-Bank integration. Domain использует
`money.types.ts`, а не Swagger DTO; это общая форма денег для карт и портфеля.
Давай функциям предметные имена; не создавай глобальные `utils.ts`/`helpers.ts`.
## Domain literals and magic values
Общий выбор symbolic values и числовых policies —
[Domain values](../../../.ai/conventions/code-health.md#domain-values).
Сохраняй strict TS и explicit boundary types. Для недоверенного input выбирай
`unknown` + runtime narrowing; отключённый lint `no-explicit-any` не делает
`any` нормой domain/application model. `declare` допустим для заполняемых извне
DTO fields, не для сокрытия неинициализированного owned state.
OpenAPI enum metadata и runtime validation должны читать тот же vocabulary,
что application/domain. Например, `BankOperationKind` принадлежит bank-cards/domain,
а DTO получает допустимые значения через `Object.values(BankOperationKind)`.
Frontend переиспользует generated OpenAPI types/values по возможностям генератора,
а не определяет независимый backend-owned словарь. Внешние/generated enums
сохраняй; generated Prisma/protocol code вручную не меняй.
`@typescript-eslint/no-magic-numbers` включён как error; базовое ESLint rule
отключено в пользу TS-версии. Исключения для очевидных чисел и их bigint форм,
индексов, default values, enum/type declarations и readonly properties заданы
в ESLint config. Test fixtures/expected values остаются inline, остальные lint
правила продолжают действовать. Numeric policy получает предметное имя и единицы;
semantic magic strings проверяются conventions/review без нового инструмента.
## Logging
События, levels, correlation и safe fields — по
[Observability](backend-observability.md).
## Проверка
`npm run typecheck:app`, `npm run lint`, targeted Jest по изменённому use case.
Смысл boundaries проверяется review: backend не имеет отдельного автоматического
FSD/DI architecture checker. Existing tests и build не доказывают правильный owner.
@@ -15,7 +15,7 @@ PrismaService — существующий shared DB provider с lifecycle пр
| Независимые reads | Не добавляй transaction ради внешнего вида | | Независимые reads | Не добавляй transaction ради внешнего вида |
| Общая persistence abstraction | Только при реальной общей ответственности; generic repository для одной Prisma реализации не нужен | | Общая persistence abstraction | Только при реальной общей ответственности; generic repository для одной Prisma реализации не нужен |
Размещение repository определяется [Architecture](backend-architecture.md): Размещение repository определяется [Architecture](../architecture.md):
в слоистом module это `infrastructure/`. в слоистом module это `infrastructure/`.
Repository — конкретный класс `*.repository.ts` у business owner, без обязательных Repository — конкретный класс `*.repository.ts` у business owner, без обязательных
interface/port/adapter. Он владеет queries, includes/filtering, DB mapping и interface/port/adapter. Он владеет queries, includes/filtering, DB mapping и
@@ -81,4 +81,4 @@ client обновляй из schema. Не переписывай уже прим
`npx prisma validate`; после schema change `npx prisma generate` и `npx prisma validate`; после schema change `npx prisma generate` и
`npm run typecheck:app`. Invariants/atomicity/migration safety требуют tests `npm run typecheck:app`. Invariants/atomicity/migration safety требуют tests
на соответствующей boundary: mock Prisma не доказывает реальную transaction. на соответствующей boundary: mock Prisma не доказывает реальную transaction.
Выбор integration/e2e и известные ограничения — в [Testing](backend-testing.md). Выбор integration/e2e и известные ограничения — в [Testing](testing.md).
@@ -29,7 +29,7 @@ error framework/registry. Если domain outcome требует HTTP interpreta
SQL, credentials или raw upstream payload в ответе. SQL, credentials или raw upstream payload в ответе.
- External auth failure и временная недоступность имеют разные outcomes; - External auth failure и временная недоступность имеют разные outcomes;
translation выполняется на existing integration/access boundary. translation выполняется на existing integration/access boundary.
- Логи используют [observability](backend-observability.md). Redaction не - Логи используют [observability](observability.md). Redaction не
делает произвольное сообщение ошибки безопасным. делает произвольное сообщение ошибки безопасным.
## Проверка ## Проверка
@@ -37,4 +37,4 @@ error framework/registry. Если domain outcome требует HTTP interpreta
Для изменённого кода проверь observable status/code и сохранность данных. Для изменённого кода проверь observable status/code и сохранность данных.
Для validation нужен request через `configureApp`: fabricated `ValidationError` Для validation нужен request через `configureApp`: fabricated `ValidationError`
в unit test filter не доказывает форму, выходящую из реального ValidationPipe. в unit test filter не доказывает форму, выходящую из реального ValidationPipe.
OpenAPI parity проверяет schema, а не response pipeline; [Testing](backend-testing.md). OpenAPI parity проверяет schema, а не response pipeline; [Testing](testing.md).
@@ -27,7 +27,7 @@ module или presentation. DTO можно передать application boundary
формы; отдельный Input с теми же полями не нужен. Domain не импортирует DTO: формы; отдельный Input с теми же полями не нужен. Domain не импортирует DTO:
например, чистые денежные расчёты используют `Money`, а Swagger — `MoneyDto`. например, чистые денежные расчёты используют `Money`, а Swagger — `MoneyDto`.
Нормализованная форма input нужна только при смысловом отличии от wire contract; Нормализованная форма input нужна только при смысловом отличии от wire contract;
выбор suffix принадлежит [Architecture](backend-architecture.md). выбор suffix принадлежит [Architecture](../architecture.md).
ValidationPipe уже настроен в `configureApp` (`transform`, `whitelist`). ValidationPipe уже настроен в `configureApp` (`transform`, `whitelist`).
Не обещай rejection неизвестных fields: `forbidNonWhitelisted` не включён. Не обещай rejection неизвестных fields: `forbidNonWhitelisted` не включён.
Для query numbers/arrays выбирай явное преобразование и валидаторы, как в Для query numbers/arrays выбирай явное преобразование и валидаторы, как в
@@ -37,7 +37,7 @@ ValidationPipe уже настроен в `configureApp` (`transform`, `whitelis
Используй типизированные response DTO, `ApiOkResponse`/другой фактический status Используй типизированные response DTO, `ApiOkResponse`/другой фактический status
и существующий `ApiProblemResponses`. Prisma model не является public contract: и существующий `ApiProblemResponses`. Prisma model не является public contract:
response выбирает поля явно. Stable codes, RFC 9457 профиль и mapping принадлежат response выбирает поля явно. Stable codes, RFC 9457 профиль и mapping принадлежат
[Errors](backend-error-handling.md). [Errors](error-handling.md).
**Безопасный путь:** используй exception и общий filter, а не локальный **Безопасный путь:** используй exception и общий filter, а не локальный
`res.status(...).json(...)`. Исключение — транспортная операция вроде cookie; `res.status(...).json(...)`. Исключение — транспортная операция вроде cookie;
@@ -64,4 +64,4 @@ helper ради первого endpoint. Новая pagination явно опре
errors на boundary, где они возникают. Unit вызов controller не запускает pipes, errors на boundary, где они возникают. Unit вызов controller не запускает pipes,
guards или serialization. `npm run test:openapi-contract` проверяет schema parity, guards или serialization. `npm run test:openapi-contract` проверяет schema parity,
но не поведение invalid request; выбирай соответствующий test по но не поведение invalid request; выбирай соответствующий test по
[Testing](backend-testing.md). Финальный gate не заменяет semantic review ошибок. [Testing](testing.md). Финальный gate не заменяет semantic review ошибок.
@@ -18,7 +18,7 @@ RPC, credentials/access и resilience. Эти владельцы не объед
`*.client.ts` содержит transport и его retries; `*.api.ts` только перечисляет `*.client.ts` содержит transport и его retries; `*.api.ts` только перечисляет
публичные exports. Все шесть `auth/operations/portfolio/tbank/moex/ncr.api.ts` публичные exports. Все шесть `auth/operations/portfolio/tbank/moex/ncr.api.ts`
имеют одинаковую роль программной границы, определённую в имеют одинаковую роль программной границы, определённую в
[Architecture](backend-architecture.md). Логика, DTO definitions и HTTP routes [Architecture](../architecture.md). Логика, DTO definitions и HTTP routes
в эти файлы не помещаются. Type exports обозначаются `export type`. в эти файлы не помещаются. Type exports обозначаются `export type`.
Преобразование provider records происходит на boundary: например, Преобразование provider records происходит на boundary: например,
@@ -31,7 +31,7 @@ client или mapper для простого копирования полей.
Для нового account use case сначала используй TbankAccessService: access, Для нового account use case сначала используй TbankAccessService: access,
connection generation и invalid-token translation не дублируются в callers. connection generation и invalid-token translation не дублируются в callers.
Mapper внешних финансовых значений остаётся внутри business capability, Mapper внешних финансовых значений остаётся внутри business capability,
обычно в infrastructure по [Architecture](backend-architecture.md). обычно в infrastructure по [Architecture](../architecture.md).
## Retry, cache и protocol ## Retry, cache и protocol
@@ -69,4 +69,4 @@ Generated artifacts, если есть, создавай из source, без р
upstream responses. Проверь queue/deadline/retry limit, token translation, upstream responses. Проверь queue/deadline/retry limit, token translation,
cache isolation и late response для затронутых ветвей. Fixtures синтетические; cache isolation и late response для затронутых ветвей. Fixtures синтетические;
реальная сеть, секреты и пользовательские accounts не нужны. реальная сеть, секреты и пользовательские accounts не нужны.
Logging использует [existing logger](backend-architecture.md#logging). Logging использует [existing logger](../architecture.md#logging).
@@ -15,7 +15,7 @@
или upstream error. Secret в `message/stack` не становится безопасным из-за или upstream error. Secret в `message/stack` не становится безопасным из-за
allowlist; правила [Security](security-auth.md) обязательны для sensitive data. allowlist; правила [Security](security-auth.md) обязательны для sensitive data.
- Для ошибки используй public code и безопасный context операции/попыток; - Для ошибки используй public code и безопасный context операции/попыток;
[error mapping](backend-error-handling.md) не отдаёт внутренние детали клиенту. [error mapping](error-handling.md) не отдаёт внутренние детали клиенту.
Причины — [ADR 008](../../../docs/adr/008-pino-backend-logging.md), системное Причины — [ADR 008](../../../docs/adr/008-pino-backend-logging.md), системное
поведение — [HTTP observability](../../../docs/architecture/http-api.md#наблюдаемость). поведение — [HTTP observability](../../../docs/architecture/http-api.md#наблюдаемость).
+1 -1
View File
@@ -55,6 +55,6 @@ cache invalidation при замене/deletion и ограничение key ro
фиктивным success или log. Logger allowlist/redaction не делает произвольный фиктивным success или log. Logger allowlist/redaction не делает произвольный
message безопасным. message безопасным.
Targeted auth/access/crypto/logging tests + [Testing](backend-testing.md). Targeted auth/access/crypto/logging tests + [Testing](testing.md).
Cookie/guard/HTTP changes требуют configured HTTP proof; frontend Bearer/retry Cookie/guard/HTTP changes требуют configured HTTP proof; frontend Bearer/retry
проверяется MSW. Schema parity не доказывает отсутствие утечки secret. проверяется MSW. Schema parity не доказывает отсутствие утечки secret.
+25 -10
View File
@@ -2,20 +2,19 @@
Общие инструкции — [workspace AGENTS](../AGENTS.md). Объединяй triggers задачи, Общие инструкции — [workspace AGENTS](../AGENTS.md). Объединяй triggers задачи,
путей и обнаруженных boundaries; читай только нужные темы до решения. путей и обнаруженных boundaries; читай только нужные темы до решения.
[Sources](.ai/SOURCES.md) выбирает факты/spec/ADR по возникшему вопросу. Системные факты/spec/ADR выбирай в разделе [Источники](#источники) по вопросу.
| Решение / сигнал | Читать | | Решение / сигнал | Читать |
| --- | --- | | --- | --- |
| Первое знакомство, data flow, общая архитектура | [Mental model](.ai/architecture.md) | | Устройство/data flow, module/layer/public API/DI/suffix, owner | [Architecture](.ai/architecture.md) |
| Module/layer/public API/DI/suffix, owner, перенос responsibility | [Modules/application](.ai/conventions/backend-architecture.md) | | TS vocabulary, domain literals, magic values | [Types/values](.ai/architecture.md#domain-literals-and-magic-values) |
| TS vocabulary, domain literals, magic values | [Types/values](.ai/conventions/backend-architecture.md#domain-literals-and-magic-values) | | Controller/DTO/validation, route/status/response, OpenAPI | [HTTP API](.ai/conventions/http-api.md) |
| Controller/DTO/validation, route/status/response, OpenAPI | [HTTP API](.ai/conventions/backend-http-api.md) | | Application/domain/HTTP error, exception, code | [Errors](.ai/conventions/error-handling.md) |
| Application/domain/HTTP error, exception, code | [Errors](.ai/conventions/backend-error-handling.md) | | Repository/Prisma/query, transaction, migration, atomic write | [Data](.ai/conventions/data.md) |
| Repository/Prisma/query, transaction, migration, atomic write | [Data](.ai/conventions/backend-data.md) | | Tests или behavior с regression risk | [Testing](.ai/conventions/testing.md) |
| Tests или behavior с regression risk | [Testing](.ai/conventions/backend-testing.md) |
| JWT/cookie/session/guard/access, encryption, secrets | [Security](.ai/conventions/security-auth.md) | | JWT/cookie/session/guard/access, encryption, secrets | [Security](.ai/conventions/security-auth.md) |
| External client/gateway/SDK/proto, retry/rate limit/cache | [Integrations](.ai/conventions/backend-integrations.md) | | External client/gateway/SDK/proto, retry/rate limit/cache | [Integrations](.ai/conventions/integrations.md) |
| Logging/context/levels, sensitive logs | [Observability](.ai/conventions/backend-observability.md); sensitive data также Security | | Logging/context/levels, sensitive logs | [Observability](.ai/conventions/observability.md); sensitive data также Security |
| Изменение публичного wire contract / Swagger metadata | HTTP API + [HTTP contract source](../docs/architecture/http-api.md#источник-контракта) | | Изменение публичного wire contract / Swagger metadata | HTTP API + [HTTP contract source](../docs/architecture/http-api.md#источник-контракта) |
| Helper/dependency/новый pattern, refactor/scope | [Code health](../.ai/conventions/code-health.md), для пакета — [Dependencies](../.ai/conventions/dependencies.md) | | Helper/dependency/новый pattern, refactor/scope | [Code health](../.ai/conventions/code-health.md), для пакета — [Dependencies](../.ai/conventions/dependencies.md) |
| Выбор gate, tooling, завершение | [Guardrails](../.ai/conventions/guardrails.md) | | Выбор gate, tooling, завершение | [Guardrails](../.ai/conventions/guardrails.md) |
@@ -25,3 +24,19 @@
Повтори routing по итоговому diff, включая новые файлы. Migration backlog Повтори routing по итоговому diff, включая новые файлы. Migration backlog
читается только при соответствующем finding через читается только при соответствующем finding через
[plan](../.ai/migrations/conventions-v1/plan.md). [plan](../.ai/migrations/conventions-v1/plan.md).
## Источники
Открывай только источник по возникшему вопросу. Code показывает реализацию,
docs — текущую систему, ADR — rationale, specs — requirements. Старый пример
не отменяет контракт; conflicting security/data facts выясняй до решения.
| Вопрос | Источник |
| --- | --- |
| HTTP errors/DTO/wire shape | [HTTP API](../docs/architecture/http-api.md); editable OpenAPI генерирует Nest |
| Auth/session, account access, secrets | [Authentication](../docs/domains/authentication/overview.md), [T-Bank](../docs/integrations/tbank.md) |
| Prisma/data ownership, хранение и доступ | [Containers](../docs/architecture/containers.md#хранимые-данные-и-границы-доступа) |
| T-Bank/cache/logging | [T-Bank](../docs/integrations/tbank.md), [Freshness](../docs/architecture/cross-cutting/data-freshness.md), [Observability](../docs/architecture/http-api.md#наблюдаемость) |
| Money, account/position/income/cache meanings | [Глоссарий](../docs/glossary.md) |
| Поведение области / требования | [Области docs](../docs/README.md#предметные-области), [Specs index](../specs/AI_INDEX.md) |
| Versions/scripts/check scope | Manifest/lockfile, ESLint/TypeScript/Jest/Prisma configs |
+4 -4
View File
@@ -78,10 +78,10 @@ runtime-валидацию. В обычном коде нужны инициал
## Документация ## Документация
- [Router conventions](.ai/CONVENTIONS.md) и [источники](.ai/SOURCES.md) - [AGENTS: правила и источники](AGENTS.md)
- [Backend architecture](.ai/conventions/backend-architecture.md) - [Backend architecture](.ai/architecture.md)
- [HTTP API](.ai/conventions/backend-http-api.md), [data](.ai/conventions/backend-data.md) и [testing](.ai/conventions/backend-testing.md) - [HTTP API](.ai/conventions/http-api.md), [data](.ai/conventions/data.md) и [testing](.ai/conventions/testing.md)
- [Security/auth](.ai/conventions/security-auth.md) и [integrations](.ai/conventions/backend-integrations.md) - [Security/auth](.ai/conventions/security-auth.md) и [integrations](.ai/conventions/integrations.md)
- [Карта проектных фактов](../docs/README.md) - [Карта проектных фактов](../docs/README.md)
- [API contract и runtime OpenAPI](../docs/architecture/http-api.md#источник-контракта) - [API contract и runtime OpenAPI](../docs/architecture/http-api.md#источник-контракта)