docs: дизайн DNS Autoresolver (Фаза 1 — ядро)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
# 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
|
||||
в нейтральную модель.
|
||||
|
||||
```go
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
Нейтральная модель:
|
||||
|
||||
```go
|
||||
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)
|
||||
}
|
||||
```
|
||||
|
||||
## Диф-движок
|
||||
|
||||
Чистая функция без сайд-эффектов:
|
||||
|
||||
```go
|
||||
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** — тесты хендлеров с мок-провайдером.
|
||||
|
||||
## Открытые вопросы (не блокируют Фазу 1)
|
||||
|
||||
1. Аутентификация в Selectel: статический API-ключ vs Keystone-токен проекта. Уточнить
|
||||
по докам (context7 / Selectel DNS "actual") при реализации.
|
||||
2. Формат хранения шаблонов в БД: JSONB-набор записей vs нормализованные таблицы записей.
|
||||
3. Механизм шифрования кредов: ключ из env vs внешний секрет-менеджер.
|
||||
|
||||
## Решённые развилки
|
||||
|
||||
- Язык: **Go** (backend), **React SPA** (frontend).
|
||||
- БД: **PostgreSQL**.
|
||||
- Применение: **diff + ручное подтверждение**.
|
||||
- Хранение: всё в БД, разделение по пользователям и проектам.
|
||||
- Типы: A/AAAA/CNAME/MX/TXT/SRV управляемые, NS/SOA read-only.
|
||||
- Git: репозиторий инициализирован.
|
||||
- Scope Фазы 1: надёжное API + диф-движок, минимальный UI.
|
||||
Reference in New Issue
Block a user