Стек, модульный монолит, runtime, очереди, безопасность, масштабирование и наблюдаемость.
1. Архитектурные принципы
- Модульный монолит на старте. Отдельные сервисы выделяются только по нагрузке.
- Асинхронность по умолчанию. Приём события отделён от выполнения действия.
- Идемпотентность. Повторная доставка не создаёт дублей.
- Изоляция коннекторов. Ошибка одного провайдера не влияет на остальные.
- Минимум данных. Храним только необходимое для запуска и диагностики.
- Наблюдаемость. Каждый запуск имеет
run_id и сквозной trace_id.
- Безопасность на границах. Все внешние входы валидируются и ограничиваются.
- Обратная совместимость. Версии коннекторов и API не ломают активные связки.
- Российская инфраструктура. Данные и резервные копии — в РФ.
- Готовность к росту. Горизонтальное масштабирование воркеров и API.
2. Стек
| Слой | Технология | Обоснование |
|---|
| Frontend | Next.js 15 (App Router), React 19, TypeScript | SSR/SSG, SEO, единый язык |
| UI | Tailwind CSS 4, shadcn/ui, Magic UI | Скорость, единый дизайн |
| Состояние/данные | TanStack Query, React Hook Form, Zod | Кэш, формы, валидация |
| Backend | NestJS 11 (Node.js 22, TypeScript) | Модульность, DI, OpenAPI |
| ORM | Prisma 6 | Миграции, типы, скорость разработки |
| БД | PostgreSQL 16 | Транзакции, JSONB, надёжность |
| Очереди | Redis 7 + BullMQ | Повторы, задержки, cron, rate limit |
| Файлы/логи | S3-совместимое хранилище (MinIO) | Экспорт, вложения, архивы |
| Платежи | ЮKassa API + webhooks | Карты РФ, СБП, чеки |
| Email | Unisender/Postmark-совместимый SMTP | Транзакционные письма |
| Мониторинг | Prometheus, Grafana, Loki, Sentry | Метрики, логи, ошибки |
| Тесты | Vitest, Playwright, k6, Testcontainers | Unit/e2e/нагрузка |
| CI/CD | GitHub Actions, Docker, Compose | Автоматизация |
| Reverse proxy | nginx (host) | Уже используется на сервере |
| Контейнеры | Docker 29, Compose v2 | Изоляция от других проектов |
2.1. Почему не микросервисы
- Команда MVP небольшая; микросервисы увеличат стоимость эксплуатации.
- Нагрузка на старте умеренная; вертикального масштабирования и 2–4 воркеров достаточно.
- Границы модулей соблюдаются внутри монолита, что позволяет выделить
runtime и connectors позже без переписывания.
2.2. Альтернативы и решения (ADR)
| Решение | Альтернативы | Выбор |
|---|
| Монолит vs микросервисы | Микросервисы, serverless | Модульный монолит |
| Очередь | RabbitMQ, Kafka, SQS | Redis + BullMQ (простота) |
| ORM | Drizzle, TypeORM, raw SQL | Prisma (скорость разработки) |
| Сессии | JWT в localStorage, серверные сессии | httpOnly cookie + серверная сессия |
| Коннекторы | Отдельные сервисы, плагины в процессе | Пакеты в монорепо, доверенный runtime |
| Деплой | Kubernetes, PaaS | Docker Compose на VPS |
3. Контекст системы (C4, уровень 1)
┌──────────────┐ ┌────────────────────┐
│ Пользователь │───────▶│ easy-connect Web │
└──────────────┘ │ (Next.js) │
└─────────┬──────────┘
│ HTTPS
┌─────────▼──────────┐
│ API (NestJS) │
│ auth, catalog, │
│ billing, admin │
└───┬───────┬────────┘
│ │
┌──────────────▼─┐ ┌─▼─────────────────┐
│ PostgreSQL │ │ Redis + BullMQ │
└────────────────┘ └─┬─────────────────┘
│ jobs
┌───────────▼────────────┐
│ Runtime Worker │
│ triggers, mapping, │
│ actions, retries │
└───┬───────────┬────────┘
│ │
┌──────────────▼─┐ ┌─────▼──────────────┐
│ Провайдеры A/B │ │ ЮKassa, Email, S3 │
│ (Telegram, CRM)│ │ │
└────────────────┘ └────────────────────┘
4. Компоненты
4.1. Web (Next.js)
- Публичные страницы (SSG/ISR).
- Кабинет (SSR + client components).
- Админка (SSR, отдельный layout).
- BFF-слой через Route Handlers для установки cookie и проксирования API (опционально).
- Не содержит бизнес-логики биллинга и runtime.
4.2. API (NestJS)
Модули:
| Модуль | Ответственность |
|---|
auth | Регистрация, вход, сессии, 2FA, OAuth-вход |
users | Профиль, настройки, удаление/экспорт |
catalog | Сервисы, связки, категории, поиск, статусы |
connections | OAuth/токены, шифрование, refresh, состояния |
activations | Создание, настройка, пауза, удаление |
runs | История, детали, retry, экспорт |
billing | Подписки, платежи, чеки, вебхуки ЮKassa |
notifications | Email, Telegram, in-app |
admin | Пользователи, каталог, инциденты, аудит |
ingest | Приём вебхуков, подписи, дедупликация |
runtime | Постановка задач, статусы, лимиты |
connectors | Реестр коннекторов, манифесты, версии |
health | Liveness/readiness, метрики |
4.3. Worker (BullMQ)
Отдельный процесс из того же образа. Очереди:
| Очередь | Назначение | Concurrency |
|---|
events | Нормализация и запуск | 20 |
actions | Вызов провайдера | 30 |
webhooks-out | Уведомления наружу (P2) | 10 |
polling | Периодический опрос | 10 |
maintenance | Очистка, агрегаты, экспорт | 2 |
notifications | Email/Telegram | 10 |
Ключ идемпотентности задачи: activation_id:event_id.
Порядок на активацию: groupKey = activation_id, concurrency 1.
4.4. Ingest Gateway
Выделенный лёгкий HTTP-обработчик (может быть частью API) с приоритетом:
- Проверить токен эндпоинта (constant-time).
- Проверить подпись/секрет.
- Ограничить размер и частоту.
- Записать событие (или передать в очередь с персистентностью).
- Ответить 202/200 ≤ 300 мс.
- Не выполнять бизнес-логику в request path.
4.5. Connector Runtime
- Реестр загружает манифесты и обработчики из пакетов
@easy-connect/connector-*.
- Валидация манифеста при старте (Zod).
- Вызов методов с таймаутом и контекстом (logger, secrets, http).
- Ограничение сетевых вызовов allowlist доменов коннектора.
- Запрет доступа к env/БД из коннектора (кроме SDK).
- Для сторонних коннекторов (P2) — изолированный runtime.
4.6. Хранилище секретов
- Токены/ключи шифруются AES-256-GCM.
- Ключ шифрования — переменная окружения/секрет-менеджер, не в БД.
- Дополнительно: envelope encryption с ключом per environment.
- Ротация ключей с перешифровкой фоновой задачей.
- В логи и API секреты не попадают; маскирование в последних 4 символах.
5. Runtime: последовательность запуска
Провайдер Ingest Redis/BullMQ Worker Провайдер B
│ │ │ │ │
│── webhook ───▶│ │ │ │
│ │─ verify token ────│ │ │
│ │─ verify signature │ │ │
│ │─ dedup check ─────│ │ │
│ │── enqueue event ─▶│ │ │
│◀── 202 ───────│ │ │ │
│ │ │── job ────────▶│ │
│ │ │ │─ load activation ──│
│ │ │ │─ check status ─────│
│ │ │ │─ decrypt tokens ───│
│ │ │ │─ normalize ────────│
│ │ │ │─ filter ───────────│
│ │ │ │─ map fields ───────│
│ │ │ │── action call ────▶│
│ │ │ │◀── response ───────│
│ │ │ │─ log (masked) ─────│
│ │ │ │─ update run ───────│
│ │ │ │─ notify on error ──│
5.1. Классификация ошибок
| Тип | Пример | Retry | Действие |
|---|
auth | 401, invalid_grant | Нет | needs_reauth, пауза, уведомление |
rate_limit | 429 | Да, по Retry-After | Backoff |
validation | 400, schema | Нет | Показать поля пользователю |
provider | 5xx | Да, 5 попыток | Backoff + circuit breaker |
network | timeout, DNS | Да | Backoff |
internal | bug | Да, 3 попытки | Sentry, алерт |
5.2. Идемпотентность
- Внешний ключ:
provider_event_id (если есть) или HMAC-SHA256 от нормализованного
payload + activation_id.
- TTL дедупликации: 24 часа (Redis) + уникальный индекс в
events.
- Для действий, поддерживающих idempotency key, передаём
run_id.
- Для действий без поддержки — предупреждение при ручном retry.
5.3. Rate limiting
- На эндпоинт: 60 req/min на токен, burst 20.
- На активацию: лимит из манифеста (например, 20 rps).
- На подключение: общий лимит провайдера, распределяется через Redis token bucket.
- На IP: 300 req/min для публичных эндпоинтов.
- При превышении: 429 +
Retry-After, метрика, алерт.
5.4. Circuit breaker
- Порог: 20 ошибок или 50 % за 60 с на коннектор.
- Открытие: 5 минут, затем half-open (1 пробный запрос).
- При открытии: события остаются в очереди с задержкой, статус коннектора
degraded.
- Пользователь видит предупреждение в кабинете и на
/status.
6. Модель развёртывания
6.1. Целевая топология (MVP)
Интернет
│ 80/443
┌──▼─────────────────────────────┐
│ nginx (host, существующий) │
│ easy-connect.ru → 127.0.0.1:5100
└──┬─────────────────────────────┘
│
┌──▼──────────────────────────────────────────────┐
│ Docker network easy-connect-net (изолированная) │
│ │
│ web (Next.js) 127.0.0.1:5100 │
│ api (NestJS) 127.0.0.1:5101 │
│ worker внутренняя сеть │
│ postgres:16 127.0.0.1:5434 (только local)│
│ redis:7 127.0.0.1:6381 │
│ minio 127.0.0.1:9004/9005 │
└─────────────────────────────────────────────────┘
Важно: порты выбраны вне диапазонов, занятых другими проектами
(3000–4400, 8080–8081, 9000–9003). Перед запуском порты проверяются ss -ltn.
Контейнеры не публикуются в интернет напрямую; только nginx.
6.2. Ресурсы
| Компонент | CPU | RAM | Диск |
|---|
| web | 0,25 | 256 МБ | — |
| api | 0,5 | 512 МБ | — |
| worker | 0,5 | 512 МБ | — |
| postgres | 0,5 | 768 МБ | 10 ГБ |
| redis | 0,25 | 256 МБ | 2 ГБ |
| minio | 0,25 | 256 МБ | 20 ГБ |
| Итого | 2,25 | ≈ 2,6 ГБ | 30 ГБ |
Сервер на момент анализа: 3,8 ГБ RAM, 58 ГБ диска, уже занято ~2,4 ГБ и 28 ГБ.
Полный стек на этом сервере не размещается без риска для других проектов.
Поэтому:
- На текущем этапе размещается только статический сайт (лендинг + ТЗ), без
контейнеров и без БД.
- Для production MVP требуется отдельный сервер/увеличение ресурсов.
Ориентир: 4 vCPU, 8 ГБ RAM, 80 ГБ SSD.
- Альтернатива: вынести PostgreSQL/Redis/MinIO на управляемые сервисы РФ.
6.3. Сети и изоляция
- Отдельная Docker-сеть
easy-connect-net.
- Отдельные volume:
ec_pgdata, ec_redis, ec_minio.
- Отдельный системный пользователь
easyconnect для файлов статики.
- Никаких изменений в чужих compose/nginx-конфигах.
- nginx: только новый файл
sites-available/easy-connect.conf + symlink.
7. Безопасность архитектуры
- Все внешние входы: TLS 1.2+, HSTS, CSP, X-Frame-Options, Referrer-Policy.
- Валидация схем (Zod) на API и в коннекторах.
- Защита от SSRF: allowlist доменов в манифесте, запрет приватных IP.
- Подпись вебхуков, constant-time сравнение, защита от replay.
- CSRF: SameSite=Lax + double-submit token для state-changing запросов.
- Rate limit на auth, ingest, billing.
- Секреты: только env/secret manager; .env не в git.
- Аудит действий администраторов.
- Принцип наименьших привилегий для OAuth scopes.
- Шифрование БД и бэкапов на диске (LUKS/облачный KMS — по возможностям).
- Регулярные обновления зависимостей (Renovate/Dependabot), SCA.
8. Наблюдаемость
8.1. Метрики (Prometheus)
http_requests_total{route,status,method}
http_request_duration_seconds (гистограмма)
runs_total{status,connector}
run_duration_seconds
queue_depth{queue}
queue_job_failures_total
connector_errors_total{connector,type}
connector_latency_seconds
webhook_ingest_latency_seconds
active_activations
billing_payments_total{status}
token_refresh_failures_total
8.2. Логи (Loki)
- JSON, уровни,
trace_id, run_id, user_id (хеш), activation_id.
- Секреты и PII маскируются на уровне логгера.
- Срок хранения: 30 дней (MVP), 90 дней для ошибок.
8.3. Трейсинг
- OpenTelemetry, экспорт в Tempo/Jaeger (P1).
- Спаны: ingest → enqueue → worker → connector → provider.
8.4. Алерты
| Алерт | Условие | Канал |
|---|
| API down | 3 failed checks | Telegram + email |
| Высокий error rate | > 5 % за 5 мин | Telegram |
| Очередь растёт | depth > 1000 5 мин | Telegram |
| Коннектор деградировал | circuit open | Telegram |
| Платежи не проходят | > 20 % ошибок | Telegram + email |
| Диск > 85 % | — | Telegram |
8.5. Health checks
/health/live — процесс жив.
/health/ready — БД, Redis, очередь доступны.
/metrics — только внутри сети/по токену.
9. Масштабирование
| Этап | Действие |
|---|
| 10k активаций | 1 API, 2 worker, Postgres 4 ГБ |
| 50k активаций | 2 API, 4 worker, реплика Postgres для чтения |
| 200k активаций | Выделить ingest, партиционировать runs/events, Redis Cluster |
| 1M активаций | Отдельные пулы воркеров по коннекторам, шардирование по tenant |
Партиционирование: runs и run_logs по месяцу (pg_partman или cron).
Архивация: старше 90 дней — в S3 (Parquet/JSONL), агрегаты в БД.
10. Резервное копирование и восстановление
- PostgreSQL:
pg_dump ежедневно + WAL-архив (PITR) при возможности.
- Redis: снапшоты не критичны, очередь восстанавливается из
events.
- MinIO: версионирование, репликация в другой регион (P1).
- Хранение бэкапов: 30 дней, шифрование, отдельный бакет.
- Проверка восстановления: ежемесячно на тестовом окружении.
- RPO: 24 часа (MVP), 1 час (v1.0). RTO: 4 часа.
11. Окружения
| Окружение | Назначение | Данные |
|---|
local | Разработка | Фикстуры, Docker Compose |
staging | Тестирование, приёмка | Анонимизированные |
production | Боевое | Реальные, РФ |
- Отдельные БД, Redis, S3-бакеты, ключи шифрования, OAuth-приложения.
- Конфигурация через env; секреты в GitHub Environments/сервере.
- Миграции Prisma — только вперёд, с проверкой в staging.
12. CI/CD
Push/PR → lint → typecheck → unit → build → integration → e2e (staging)
→ миграции (staging) → smoke → manual approve → production
→ миграции → rolling restart → smoke → откат при ошибке
- GitHub Actions.
- Docker-образы с тегом commit SHA.
- Деплой по SSH (docker compose pull/up -d) или через self-hosted runner.
- Нулевой простой: последовательный перезапуск API/воркеров, health-check.
- Откат: предыдущий тег образа + обратная миграция только если безопасно.
13. Технический долг и границы
- В MVP допускается единый процесс API и ingest; выделение — при нагрузке.
- Polling-коннекторы — только для сервисов без вебхуков.
- Хранение payload — 90 дней; полные тела не храним дольше 7 дней.
- Коннекторы в одном репозитории; сторонние — P2.
- Нет active-active в MVP; cold standby — резервная копия и IaC.