Files
dns-autoresolver/docs/superpowers/specs/2026-07-03-dns-autoresolver-design.md
T

9.5 KiB

DNS Autoresolver — дизайн

Дата: 2026-07-03 Статус: черновик на ревью

Контекст и цель

Утилита для автонастройки и проверки DNS-зон у внешних провайдеров. Пользователь заранее готовит шаблоны базовых настроек зоны. Утилита подключается к учётной записи провайдера, получает список доменов (их может быть несколько), сверяет текущие настройки каждого домена с привязанным шаблоном, подсвечивает отклонения (diff) и предлагает привести зону в соответствие после ручного подтверждения.

Провайдеры расширяемы; первый — Selectel (DNS API v2 "actual", https://docs.selectel.ru/api/dns-actual/).

Проектируется как мультитенантный веб-сервис: UI/UX-сайт + Go API, авторизация, разделение по пользователям и их проектам, в перспективе — периодические проверки, уведомления и метрики для алертов.

Общая архитектура

                    ┌─────────────────┐
   React SPA  ◄────►│   Go API (REST) │
                    ├─────────────────┤
                    │  Domain core    │  диф-движок шаблон↔зона (чистые функции)
                    │  Provider layer │  интерфейс Provider (Selectel — первый)
                    │  Repository     │  PostgreSQL, multi-tenant
                    └────────┬────────┘
                             │
                    ┌────────▼────────┐
                    │   PostgreSQL    │  users/projects/provider_accounts/
                    │                 │  templates/domains/check_runs
                    └─────────────────┘

Каждый модуль имеет одну ответственность и общается через явные интерфейсы:

  • Provider layer — единственный, кто знает про конкретный API провайдера. Мапит его ответы в нейтральную модель Record. Домен-ядро не знает про Selectel.
  • Domain core — диф-движок: чистые функции без сайд-эффектов, тестируются изолированно.
  • Repository — доступ к PostgreSQL, все запросы размечены user_id/project_id.
  • Go API — REST/JSON, транспорт; оркестрирует provider + core + repository.
  • React SPA — весь UI; общается только через REST API.

Ключевая абстракция — Provider

Точка расширяемости. Новый провайдер = новая реализация интерфейса + маппинг его API в нейтральную модель.

type Provider interface {
    Name() string
    ListZones(ctx context.Context, creds Credentials) ([]Zone, error)
    GetRecords(ctx context.Context, creds Credentials, zoneID string) ([]Record, error)
    ApplyChanges(ctx context.Context, creds Credentials, zoneID string, cs Changeset) error
}

Нейтральная модель:

type Record struct {
    Type     string   // A, AAAA, CNAME, MX, TXT, SRV, NS, SOA
    Name     string   // относительное или FQDN имя
    Values   []string // значения (RRset)
    TTL      int
    Priority *int     // MX/SRV
    // SRV: разбирается из Values (priority weight port target)
}

Диф-движок

Чистая функция без сайд-эффектов:

func Diff(template []Record, actual []Record) Changeset
// Changeset{ ToAdd, ToUpdate, ToDelete, InSync []RecordDiff }
  • Сравнение по ключу (Type, Name), затем по нормализованным значениям/TTL.
  • Каждое отклонение помечается для подсветки в UI/API.
  • Применение (ApplyChanges) — только после ручного подтверждения, гранулярно по домену/записи.

Управляемые типы записей

Тип Режим Комментарий
A, AAAA, CNAME, MX, TXT Управляемые (diff + apply) ядро шаблонов
SRV Управляемые (diff + apply) почтовый автодискавери: _autodiscover._tcp, _submission._tcp, _imaps._tcp, _pop3s._tcp и т.п. — задаются в шаблонах
NS, SOA Read-only отклонения показываются, но не применяются автоматически

Модель данных (multi-tenant с первого дня)

users
  └─ projects
       ├─ provider_accounts   (provider, зашифрованные креды)
       ├─ templates           (набор Record, versioned)
       └─ domains             (zone, provider_account_id, template_id)
            └─ check_runs      (история сверок: результат diff, timestamp)
  • Креды провайдера шифруются в БД (например, AES-GCM с ключом из env/секрет-менеджера).
  • Даже в Фазе 1 без логина строки размечены user_id/project_id (единый системный владелец) — авторизация «включается» в Фазе 2 без миграции данных.
  • Шаблоны версионируются, чтобы check_runs ссылались на конкретную версию.

Фазы

Проект большой — режется на подсистемы, каждая со своим spec → план → реализация.

  • Фаза 1 (MVP-ядро): Provider-интерфейс + реализация Selectel, диф-движок, PostgreSQL-схема и repository, REST API, ручной apply. Мультитенантная схема, но единый владелец (без регистрации). React-UI тонкий; ядро покрыто тестами.
  • Фаза 2: Авторизация (регистрация/логин, сессии/JWT), полноценный React UI с визуальным дифом и управлением шаблонами/учётками.
  • Фаза 3: Периодические проверки по расписанию, уведомления, метрики для алертов.

Обработка ошибок

  • Provider layer оборачивает ошибки API (таймауты, 4xx/5xx, rate limit) в типизированные ошибки; retries с backoff для идемпотентных операций.
  • ApplyChanges не должен оставлять зону в промежуточном состоянии: применять changeset атомарно, где API позволяет; иначе — фиксировать частичный результат в check_runs.
  • API возвращает структурированные ошибки (code + message) для UI.

Тестирование

  • Диф-движок — модульные тесты на все ветки (add/update/delete/in-sync, SRV, NS/SOA RO).
  • Provider Selectel — тесты против замоканного HTTP (записанные ответы API).
  • Repository — интеграционные тесты на PostgreSQL (testcontainers или локальная БД).
  • API — тесты хендлеров с мок-провайдером.

Решённые развилки

  • Язык: Go (backend), React SPA (frontend).
  • БД: PostgreSQL.
  • Применение: diff + ручное подтверждение.
  • Хранение: всё в БД, разделение по пользователям и проектам.
  • Типы: A/AAAA/CNAME/MX/TXT/SRV управляемые, NS/SOA read-only.
  • Git: репозиторий инициализирован.
  • Scope Фазы 1: надёжное API + диф-движок, минимальный UI.
  • Аутентификация Selectel: статический API-ключ. При добавлении учётки в UI показываем инструкцию и ссылку, где и как его получить (панель Selectel).
  • Шаблоны в БД: JSONB — набор Record хранится как JSONB-документ (versioned).
  • Шифрование кредов: симметричный ключ из env (AES-GCM) в Фазе 1; внешний секрет-менеджер — возможное улучшение позже.

Заметки по UI (для будущих фаз)

  • Экран добавления provider-учётки Selectel содержит: поле API-ключа, краткую инструкцию и ссылку на страницу получения ключа в панели Selectel.