Перейти к основному содержимому

Слои конфигурации

Когда приложение вызывает Config.load("app-config.yaml"), загрузчик автоматически находит и сливает три файла в строгом порядке приоритета.

Три слоя

СлойФайлКоммитится в gitНазначение
1. Baseapp-config.yamlОбщие значения для всех окружений.
2. Localapp-config.local.yaml✗ (в .gitignore)Переопределения, специфичные для разработчика. Не попадает в CI/staging/production.
3. Environmentapp-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

app-config.yaml
database:
host: "localhost"
port: 5432
pool_size: 20
app-config.production.yaml
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

Вложенные объекты мерджатся рекурсивно — каждое поле на каждом уровне вычисляется независимо.

Массивы — атомарная замена

app-config.yaml
dagstack:
plugin_dirs:
- plugins/
- examples/plugins/
app-config.production.yaml
dagstack:
plugin_dirs:
- /opt/dagstack/plugins/

Итог:

dagstack:
plugin_dirs:
- /opt/dagstack/plugins/ # массив полностью заменён, не конкатенирован

Это намеренное решение (ADR-0001 §3): конкатенация делает предсказуемость слияния сложной; приложение, которое хочет «добавить» элемент, должно указать массив целиком.

Null в слое-переопределении

app-config.yaml
cache:
redis:
url: "redis://localhost:6379/0"
ttl_min: 15
app-config.local.yaml
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:

config = Config.load_paths([
"config/base.yaml",
"config/integration-test.yaml",
"config/secrets-ci.yaml",
])
# Нет DAGSTACK_ENV-логики, порядок определяет priority.

Как узнать, какие слои применились

Для диагностики — метод source_ids / sourceIds / SourceIds возвращает список идентификаторов источников в порядке их загрузки:

print(config.source_ids())
# → ["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() + сравнение с отдельно загруженными слоями. :::

См. также