Слои конфигурации
Когда приложение вызывает Config.load("app-config.yaml"), загрузчик автоматически находит и сливает три файла в строгом порядке приоритета.
Три слоя
| Слой | Файл | Коммитится в git | Назначение |
|---|---|---|---|
| 1. Base | app-config.yaml | ✓ | Общие значения для всех окружений. |
| 2. Local | app-config.local.yaml | ✗ (в .gitignore) | Переопределения, специфичные для разработчика. Не попадает в CI/staging/production. |
| 3. Environment | app-config.${DAGSTACK_ENV}.yaml | ✓ | Переопределения для конкретного окружения (production, staging, preview). |
Порядок слияния — снизу вверх, более высокий слой перекрывает нижний:
app-config.yaml (base)
↓
app-config.local.yaml (+ переопределения разработчика, если файл есть)
↓
app-config.${DAGSTACK_ENV}.yaml (+ переопределения окружения, если DAGSTACK_ENV задан и файл есть)
↓
Config
DAGSTACK_ENV — env-переменная в отдельном пространстве имён
Выбор имени DAGSTACK_ENV (вместо универсального APP_ENV / NODE_ENV / RAILS_ENV) — нормативное решение ADR-0001. Причина: в развёртываниях со смешанными фреймворками dagstack-приложение может сосуществовать с Spring / Next / Rails на том же хосте. Общий APP_ENV приведёт к случайному перекрытию значений между фреймворками.
# Production
export DAGSTACK_ENV=production
python main.py # загрузит app-config.yaml + app-config.production.yaml
# Staging
export DAGSTACK_ENV=staging
python main.py # загрузит app-config.yaml + app-config.staging.yaml
# Без DAGSTACK_ENV — только base + local (если есть)
unset DAGSTACK_ENV
python main.py # загрузит app-config.yaml + app-config.local.yaml (если есть)
Правила merge
Объекты — deep merge
database:
host: "localhost"
port: 5432
pool_size: 20
database:
host: "prod-db.internal.example.com"
pool_size: 100
Итог при DAGSTACK_ENV=production:
database:
host: "prod-db.internal.example.com" # из production
port: 5432 # из base (не переопределён)
pool_size: 100 # из production
Вложенные объекты мерджатся рекурсивно — каждое поле на каждом уровне вычисляется независимо.
Массивы — атомарная замена
dagstack:
plugin_dirs:
- plugins/
- examples/plugins/
dagstack:
plugin_dirs:
- /opt/dagstack/plugins/
Итог:
dagstack:
plugin_dirs:
- /opt/dagstack/plugins/ # массив полностью заменён, не конкатенирован
Это намеренное решение (ADR-0001 §3): конкатенация делает предсказуемость слияния сложной; приложение, которое хочет «добавить» элемент, должно указать массив целиком.
Null в слое-переопределении
cache:
redis:
url: "redis://localhost:6379/0"
ttl_min: 15
cache:
redis: null # отключить кеш для локальной разработки
Итог: cache.redis = null (отсутствие всего объекта). Это не то же самое, что отсутствие redis в файле-переопределении — последний не изменяет base.
Типичные структуры
Минимум
app-config.yaml # всё что нужно в dev
# local / env не нужны пока
Dev + production
app-config.yaml # общее
app-config.local.yaml # переопределения разработчика (gitignored)
app-config.production.yaml # развёртывание в production
Полный набор окружений
app-config.yaml
app-config.local.yaml # каждый разработчик держит свой
app-config.development.yaml # общие переопределения dev-кластера
app-config.staging.yaml
app-config.production.yaml
app-config.preview.yaml # preview-environments для PR
Явное перечисление слоёв
Когда auto-discovery не подходит (например, тесты с нестандартным путём),
используйте load_paths / loadFrom(YamlFileSource[]) / LoadFrom:
- Python
- TypeScript
- Go
config = Config.load_paths([
"config/base.yaml",
"config/integration-test.yaml",
"config/secrets-ci.yaml",
])
# Нет DAGSTACK_ENV-логики, порядок определяет priority.
import { Config, YamlFileSource } from "@dagstack/config";
const config = await Config.loadFrom([
new YamlFileSource("config/base.yaml"),
new YamlFileSource("config/integration-test.yaml"),
new YamlFileSource("config/secrets-ci.yaml"),
]);
// Порядок определяет priority; DAGSTACK_ENV не применяется.
cfg, err := config.LoadFrom(context.Background(), []config.Source{
config.NewYamlFileSource("config/base.yaml"),
config.NewYamlFileSource("config/integration-test.yaml"),
config.NewYamlFileSource("config/secrets-ci.yaml"),
})
// Порядок определяет priority; DAGSTACK_ENV не применяется.
Как узнать, какие слои применились
Для диагностики — метод source_ids / sourceIds / SourceIds возвращает
список идентификаторов источников в порядке их загрузки:
- Python
- TypeScript
- Go
print(config.source_ids())
# → ["yaml:app-config.yaml", "yaml:app-config.local.yaml",
# "yaml:app-config.production.yaml"]
console.log(config.sourceIds());
// → ["yaml:app-config.yaml", "yaml:app-config.local.yaml",
// "yaml:app-config.production.yaml"]
fmt.Println(cfg.SourceIDs())
// → [yaml:app-config.yaml yaml:app-config.local.yaml
// yaml:app-config.production.yaml]
:::note Per-path source trace — Phase 2+
Метод вида config.trace("database.host") (показывает, из какого файла и
с какой строки приехало конкретное значение) в v0.1/v0.2 не реализован
ни в одном биндинге. Для отладки «почему значение не такое, как я ожидал»
используйте snapshot() + сравнение с отдельно загруженными слоями.
:::
См. также
- Источники (ConfigSource) — как YamlFileSource вписывается в общую модель.
- Подстановка env-переменных — интерполяция работает одинаково во всех слоях.
- ADR-0001 §3 Config layering and merge — нормативные правила.