moex-vibe/apps/docs/docs/architecture.md
Sergey Krylov 106467e5c4
All checks were successful
CI / lint (pull_request) Successful in 2m8s
CI / test (pull_request) Successful in 1m57s
CI / build (pull_request) Successful in 2m5s
CI / lint (push) Successful in 2m1s
CI / test (push) Successful in 1m51s
CI / build (push) Successful in 2m9s
docs: translate docs to russian
2026-06-16 05:13:34 +03:00

4.8 KiB
Raw Blame History

Архитектура

Архитектура системы

flowchart LR
    Browser["Browser<br/>(React SPA)"]
    API["NestJS API<br/>:3000"]
    Cache["In-memory cache<br/>(cache-manager)"]
    MOEX["MOEX ISS API<br/>iss.moex.com"]
    DB[("SQLite<br/>(Prisma)")]

    Browser -->|"/api/v1/*"| API
    Browser -->|"Cookie: refreshToken"| API
    Browser -->|"Authorization: Bearer"| API
    API -->|"getOrFetch()"| Cache
    API -->|"GET /iss/*.json"| MOEX
    API -->|"Prisma ORM"| DB
    Cache -->|"данные"| API
    DB -->|"пользователи и портфели"| API
    MOEX -->|"сырые данные"| API

Внутренние модули backend

flowchart LR
    Auth["AuthModule<br/>JWT + bcrypt + guards"]
    Shares["SharesModule"]
    Bonds["BondsModule"]
    Candles["CandlesModule"]
    Securities["SecuritiesModule"]
    Portfolio["PortfolioModule"]
    Health["HealthModule"]
    MoexClient["MoexClientModule<br/>p-queue + circuit breaker"]
    CacheService["CacheService<br/>(global)"]
    Prisma["PrismaService<br/>(global)"]

    Auth -->|"CRUD пользователей"| Prisma
    Portfolio -->|"портфели и позиции"| Prisma
    MoexClient -->|"запрашивает"| Shares
    MoexClient -->|"запрашивает"| Bonds
    MoexClient -->|"запрашивает"| Candles
    MoexClient -->|"запрашивает"| Securities
    MoexClient -->|"обогащает"| Portfolio
    Shares -->|"использует"| CacheService
    Bonds -->|"использует"| CacheService
    Candles -->|"использует"| CacheService
    Securities -->|"использует"| CacheService
    Portfolio -->|"использует"| CacheService

Поток авторизованного запроса

sequenceDiagram
    participant User as Пользователь
    participant Frontend as React SPA
    participant Backend as NestJS API
    participant Cache as In-memory cache
    participant MOEX as MOEX ISS
    participant AuthDB as SQLite

    User->>Frontend: Открывает акцию SBER
    Note over Frontend: access token хранится в памяти
    Frontend->>Backend: GET /securities/shares/SBER (Authorization: Bearer)
    Backend->>Backend: JwtAuthGuard проверяет token
    Backend->>Cache: getOrFetch('share:SBER')
    alt cache miss
        Cache->>Backend: null
        Backend->>MOEX: GET /iss/.../SBER.json
        MOEX-->>Backend: сырые данные
        Backend->>Cache: set('share:SBER', ..., TTL=900)
    else cache hit
        Cache-->>Backend: cached data
    end
    Backend-->>Frontend: { data, meta }
    Frontend-->>User: отрисованный UI

Архитектурные решения

Все архитектурные решения описаны в ADR-страницах этой документации:

ADR Кратко
ADR-001 Backend — единственная точка доступа к MOEX
ADR-002 In-memory cache через cache-manager
ADR-003 Rate limiting через p-queue
ADR-004 Архитектура feature-модулей
ADR-005 OpenAPI codegen для frontend
ADR-006 Отказ от CCI (Custom Components Infrastructure)
ADR-007 Двухуровневая стратегия cache
ADR-008 Authentication и Authorization
ADR-009 Доменная модель портфеля
ADR-010 Расчёт цен на backend

Формат ответа

Все ответы API обёрнуты в единый формат:

{
  "data": { ... },
  "meta": {
    "fromCache": false,
    "cachedAt": "2026-06-13T12:00:00.000Z"
  }
}

При ошибках возвращается:

{
  "statusCode": 404,
  "message": "Share SBER not found",
  "error": "Not Found",
  "timestamp": "2026-06-13T12:00:00.000Z",
  "path": "/api/v1/securities/shares/SBER"
}

Глобальная конфигурация

  • Глобальный префикс: /api/v1
  • ValidationPipe: transform: true, whitelist: true
  • HttpExceptionFilter (catch-all)
  • TransformInterceptor (авто-обёртка в ApiResponse)
  • RequestLoggingMiddleware (логирует METHOD /path STATUS DURATIONms)
  • JwtAuthGuard (глобально, @Public() для открытых эндпоинтов)
  • RolesGuard (проверка ролей)
  • CORS: разрешён для всех origins, credentials: true