Files
backend/.ai/conventions/backend-architecture.md
T

19 KiB
Raw Blame History

Backend architecture

Module-first organization

Backend — domain-oriented modular monolith с лёгким разделением по ответственности. src/modules группирует код по бизнес-возможности: auth, banking/bank-cards, области investments. В инвестициях отдельные Nest modules владеют brokerage-accounts, portfolio, operations, income, upcoming-events, instruments, tbank-connection. instruments/stocks и instruments/bonds — slices внутри одного модуля.

Progressive structure

Три уровня организации: business module → responsibility layer → technical grouping при реальной необходимости. Компактный module с понятными controller/DTO/service может оставаться плоским. Ориентир 5–7 файлов не является порогом: учитывай число ответственностей, наличие domain/repository/use cases и удобство просмотра каталога.

Когда в module смешаны несколько services, transport, расчёты и persistence, группируй файлы по ответственности:

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 не требуются для этой организации.

В текущем коде слои используют auth, bank-cards, portfolio, operations и slices instruments/stocks, instruments/bonds. Общие application helpers инструментов находятся в instruments/application; instruments.module.ts компонует оба slice. stock-position.ts и instrument-position.ts формируют результат из portfolio DTO; instrument-market.ts обрабатывает внешние данные и ошибки Nest. Это application code, а не независимая domain model. brokerage-accounts, income, upcoming-events, tbank-connection остаются компактными плоскими modules. Наличие tests само по себе не требует слоёв.

Поиск и создание файла

При работе над 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.

Без зафиксированной необходимости не вводи *.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 на каждом уровне.

Каждый 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; use case получает данные через repository и сам Prisma queries не выполняет. Entity/interface на каждую таблицу не требуется. DTO остаются транспортными runtime classes; существующее использование DTO на application boundary допустимо по HTTP API.

Сохраняй существующий error contract: HTTP exceptions остаются на application/ transport boundary. Чистому расчёту не нужен новый framework ошибок. Точные ограничения — HTTP, Data, Security, Integrations.

Сценарии и размер сервисов

Разделяй независимые операции, а не строки. BankCardsService отвечает за карты, BankOperationsService — журнал/переводы/возвраты, ExpenseCategoriesService — категории, GetBankSummaryUseCase — сводку. BankCardsRepository сохраняет общий transaction retry и owner lookup; денежные транзакции не разрываются. Чистые расчёты банковских карт находятся в domain этого модуля.

У инструментов StockCatalogService / BondCatalogService хранят идентичность листинга и общий кэш slice; market service — котировки/свечи, income service — выплаты/личные позиции. GetStockCompanyUseCase владеет сведениями об эмитенте. Ключи, TTL и pending promises разделяют прежний экземпляр кэша; extract не должен менять generation. Portfolio и operations сохраняют связную координацию снимков/страниц и кэшей. Расчёты портфеля находятся в portfolio/domain; преобразование брокерских позиций — в tbank-portfolio.mapper.ts. bond-money.mapper.ts преобразует ответ клиента и не является value object.

Выбирай обычную функцию для чистого вычисления; repository contract — только для реальной границы. CQRS/buses/events, BaseRepository/BaseService, DI token для каждого класса, mapper/presenter для каждого DTO и новые dependencies не нужны для структурного переноса.

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.

Logging

Единственный logger — nestjs-pino; application events используют существующий StructuredLogger и request context. Выбирай стабильное имя события и разрешённые scalar fields. Wrapper отбрасывает неизвестные поля, но это не разрешение помещать secret в message/stack: review sensitive logging обязателен. Не добавляй второй logger или сырой request/body/error payload. Источник rationale — ADR 008; при secrets/user data читай также Security.

Проверка

npm run typecheck:app, npm run lint, targeted Jest по изменённому use case. Смысл boundaries проверяется review: backend не имеет отдельного автоматического FSD/DI architecture checker. Existing tests и build не доказывают правильный owner.