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

Объявление секции конфига

Каждый компонент / плагин / сервис приложения должен объявить свою секцию конфига — верхнеуровневый ключ в YAML и schema, которая этому ключу соответствует.

Шаг 1. Выбрать имя секции

Соглашения по именованию:

РекомендацияПример
snake_casedatabase, payment_provider, rate_limit
Существительное в ед. числеdatabase, не databases
Без _config / _settings суффиксаdatabase, не database_config
Короткое, но уникальное2–3 слова через _

Для плагинов имя секции нормативно: <kind>.<name> (ADR-0001 §4).

Шаг 2. Написать schema

app/database.py
from pydantic import BaseModel, Field, field_validator


class DatabaseConfig(BaseModel):
host: str
port: int = Field(5432, ge=1, le=65535)
name: str
user: str
password: str = Field(..., min_length=1)
pool_size: int = Field(20, ge=1, le=1000)
ssl: bool = False

@field_validator("host")
@classmethod
def must_not_be_wildcard(cls, v: str) -> str:
if v in ("0.0.0.0", "*"):
raise ValueError("host must be a concrete address")
return v

Требования к schema:

  • Все обязательные поля явно помечены (Field(...) без default в pydantic; z.string() без .default() в zod; validate:"required" в Go struct-tag).
  • Значения по умолчанию прописаны для всех опциональных полей.
  • Валидация диапазонов (min / max / pattern) — декларативно, чтобы не разруливать в рантайме.
  • Типизированные поля — избегайте Any / unknown; каждое поле с явным типом.

Шаг 3. Прочитать секцию

Метод различается по имени между биндингами (get_section / getSection / GetSection):

from dagstack.config import Config

config = Config.load("app-config.yaml")
db_cfg = config.get_section("database", DatabaseConfig)
# db_cfg — инстанс DatabaseConfig, уже валидирован.

Метод выполняет:

  1. Извлекает поддерево по path.
  2. Применяет schema-валидацию.
  3. Возвращает типизированный объект.

При неудаче → ConfigError(validation_failed) с указанием поля, ожидаемого типа и актуального значения (секретные поля замаскированы).

Шаг 4. Изоляция — читай только свою секцию

Правило: компонент читает только свою секцию. Чтение чужих секций — антипаттерн.

# Правильно — в сервисе работы с БД:
db_cfg = config.get_section("database", DatabaseConfig)

# Неправильно — в БД-сервисе читаем чужую секцию:
cache_cfg = config.get_section("cache", CacheConfig)
# Сервис БД теперь зависит от структуры cache.

Причины:

  • Связность. Если сервис A читает конфиг сервиса B, изменение schema сервиса B ломает сервис A. Скрытая связь без видимых границ.
  • Тестируемость. Мокать только database-секцию проще, чем мокать весь конфиг со всеми чужими секциями.
  • Безопасность. В production-сценарии сервисы могут иметь разные права на чтение секций (через governance-middleware); жёстко прописанное чтение чужой секции обходит политику доступа.

Правильная альтернатива: если два сервиса должны координироваться через общую настройку (например, общий max_payload_mb), вынесите её в отдельную секцию shared, которую читают оба:

shared:
max_payload_mb: 10

database:
host: "localhost"
pool_size: 20

api:
host: "0.0.0.0"
port: 8080

Шаг 5. Значения по умолчанию для опциональных полей

Все опциональные поля декларируются со значениями по умолчанию в schema, не в коде:

class DatabaseConfig(BaseModel):
host: str # обязательное
user: str # обязательное
password: str # обязательное
port: int = 5432 # значение по умолчанию в schema
pool_size: int = 20 # значение по умолчанию в schema
ssl: bool = False # значение по умолчанию в schema

Анти-паттерн — оборонять default в коде после чтения:

# НЕПРАВИЛЬНО — значения по умолчанию в коде:
cfg = config.get_section("database", DatabaseConfig)
port = cfg.port or 5432 # ← такое значение по умолчанию делает schema нечестной
pool = cfg.pool_size or 20

Если значение по умолчанию задано в schema, то get_section / getSection / GetSection всегда возвращает полностью заполненный объект. Коду не нужно гадать.

Шаг 6. Документирование секции

Каждая секция должна быть задокументирована:

  • Описание каждого поля — комментарий / docstring в schema.
  • Пример конфига — пример app-config.yaml в README компонента.
  • Пояснения по диапазонам — почему pool_size от 1 до 1000, а не без верхней границы.
class DatabaseConfig(BaseModel):
"""Configuration for application database connection.

Note: password is masked in diagnostic output automatically.
"""

host: str = Field(
...,
description="Database server host.",
examples=["localhost", "db.internal.example.com"],
)
port: int = Field(
5432,
description="TCP port of the database server.",
ge=1,
le=65535,
)
pool_size: int = Field(
20,
description="Connection pool size. Range: 1-1000. Typical dev: 5-10, prod: 50-200.",
ge=1,
le=1000,
)

pydantic.Field description / examples попадают в JSON-schema, который затем отображается в IDE-подсказках и в автогенерируемой документации.

См. также