заведён канон общих конвенций для личных проектов
- 13 конвенций по осям arch / lang / stack / common; репозитории берут оттуда копии в свой docs/conventions/ и коммитят их у себя - conv — синхронизация копий: add / status / diff / pull / push, локальные регионы исключены из сравнения, поэтому расхождение не даёт шума
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
---
|
||||
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-специфики нет: секреты приходят из деплоя уже в файле, проверка их
|
||||
непустоты идёт вместе с остальной валидацией — см. базу.
|
||||
|
||||
<!-- local:поля -->
|
||||
<!-- /local -->
|
||||
|
||||
<!-- local:механизировано -->
|
||||
<!-- /local -->
|
||||
|
||||
## Связано
|
||||
|
||||
- `lang/go/time.md` — зона отображения и формат времени.
|
||||
- `lang/go/logging.md` — `slog`, которым падает невалидный конфиг.
|
||||
|
||||
<!-- local:связано -->
|
||||
<!-- /local -->
|
||||
Reference in New Issue
Block a user