--- status: рекомендуемая extends: arch/config.md --- # Конфигурация: реализация на Go Как `arch/config.md` выглядит в Go-приложении. ## Формат и загрузчик - TOML. Разбор и валидация — целиком в `internal/config`; наружу отдаётся готовая структура `Config`. - Одна корневая структура `Config` с под-структурами по секциям — имена структур совпадают с именами секций, чтобы конфиг и код читались рядом. - Умолчания — в `Default()`, поверх накладывается разобранный файл. - Флаг `--config=path` переопределяет путь; по умолчанию `config.toml` в рабочей директории, образец — `config.example.toml`. ## Длительности `time.Duration` не разбирается из строки TOML сама по себе — нужен свой тип с `UnmarshalText`, отдающий `time.Duration`: ```go type Duration time.Duration func (d *Duration) UnmarshalText(b []byte) error { … } func (d Duration) Std() time.Duration { … } ``` Так в конфиге видна единица измерения (`poll_interval = "5s"`), а не голое число. Цена: ошибка в длительности всплывает **на разборе TOML**, до общей валидации, поэтому в общий сбор проблем она не попадает — про неё узнаёшь отдельно и первой. ## Чтение окружения Приложение не читает окружение для конфигурации. Механизируется `forbidigo`, и паттерн должен покрывать **все** входы, а не только `os.Getenv`: ``` ^os\.(Getenv|LookupEnv|Environ|ExpandEnv)$ ``` Правило про приложение, поэтому за его границей запрет не действует: - **тесты** — не приложение: интеграционному тесту нормально брать креды внешнего сервиса из окружения; - **переменные рантайма** — те, что читает не наш код, а Go или ОС (`GOMEMLIMIT`, `GOMAXPROCS`, `GODEBUG`, `TZ`). Отдельный случай — переменные, которые читает **стандартная библиотека от имени приложения**: дефолтный `http.Transport` уважает `HTTP_PROXY`/`HTTPS_PROXY`. Формально их читает не наш код, но это конфигурация поведения приложения, поэтому прокси задаётся полем конфига и явным `Transport`, а не окружением. ## Валидация - Проверки собираются `errors.Join`, чтобы за один запуск показать **все** проблемы конфига, а не первую. - IANA-зона валидируется `time.LoadLocation`. База зон встраивается импортом `_ "time/tzdata"` **в `main`**, а не в библиотечном пакете: иначе ~450 КБ zoneinfo навязываются каждому импортёру. Со встроенной базой ошибка `LoadLocation` означает битое имя зоны, а не отсутствие zoneinfo в контейнере. - Невалидный конфиг — `slog` уровня `ERROR` и `os.Exit(1)` из `main`, до старта серверов и воркеров. ## Секреты Go-специфики нет: секреты приходят из деплоя уже в файле, проверка их непустоты идёт вместе с остальной валидацией — см. базу. ## Связано - `lang/go/time.md` — зона отображения и формат времени. - `lang/go/logging.md` — `slog`, которым падает невалидный конфиг.