envconf

Receptek

Gyakori envconf minták: kompozíció, prefixek, tesztelés.

Három minta lefedi majdnem mindazt, amire egy éles projektnek szüksége van az Áttekintés oldal alap Load/MustLoad hívásán túl: egy root config összeállítása több modul configjából, ugyanannak a formának a többszöri betöltése különböző prefixekkel, és a konfigurációt olvasó kód tesztelése.

Root Config összeállítása modul-configokból

Minden gp-system modul, amelynek konfigurációra van szüksége, exportálja a saját Config structját, a saját mezőnevein tagelve, anélkül, hogy véleménye lenne arról, mi (ha egyáltalán bármi) prefixeli őket. Egy generált gpsystem projekt ezeket a structokat egyetlen, alkalmazás-szintű Config-ba komponálja, mezőkként beágyazva és envPrefix-szel prefixet választva mezőnként:

internal/platform/config/config.go
package config

import (
    "github.com/gp-system/dbx"
    "github.com/gp-system/events/outbox"
    "github.com/gp-system/framework/server"
    "github.com/gp-system/framework/worker"
)

type Config struct {
    Server server.Config
    DB     dbx.Config    `envPrefix:"DB_"`
    Worker worker.Config
    Outbox outbox.Config `envPrefix:"OUTBOX_"`
}
cmd/shop/main.go
cfg := envconf.MustLoad[config.Config]()

Egyetlen envconf.Load[AppConfig]() (vagy MustLoad) hívás a main() elején mindaz, amire egy folyamatnak szüksége van: minden modul beállítása egyetlen structban érkezik, teljesen típusosan, és a kódbázis semelyik más pontján nem parseol semmit tovább. A szabály egyszerű: egy modul a saját mezőit nevezi el, a beágyazó dönti el a prefixet. A dbx.Config saját tagjei HOST, PORT, USER, ...; a envPrefix:"DB_" alatt beágyazva DB_HOST, DB_PORT, DB_USER lesz belőlük. Egy prefix nélkül beágyazott struct (mint a fenti server.Config) a saját, prefix nélküli neveik alatt olvassa a változóit. A beágyazás tetszőlegesen mélyre mehet: a server.Config maga is beágyaz egy telemetry.Config-ot, prefix nélkül, mert a változói (OTEL_SERVICE_NAME, SENTRY_DSN, LOG_LEVEL) már iparági szabvány nevek, amiket minden OTel/Sentry-tudatos eszköz úgy vár, ahogy vannak. Lásd a Konfiguráció oldalt a teljes prefix-táblázatért, amibe a gpsystem saját moduljai rendeződnek.

Prefixelt multi-instance betöltések

Az envPrefix egy beágyazott mezőn csak akkor segít, ha a struct egyszer van beágyazva. Amikor egy folyamatnak valóban két független példányra van szüksége ugyanabból a konfig-formából (két Valkey-kapcsolat: egy a cache-hez, egy a queue-hoz), a LoadPrefixed ugyanazt a típust kétszer tölti be, két különböző felső szintű prefix alatt, struct-beágyazás nélkül:

type ValkeyConfig struct {
    Addr     string `env:"ADDR,required"`
    Password string `env:"PASSWORD"`
    DB       int    `env:"DB" envDefault:"0"`
}

cache, err := envconf.LoadPrefixed[ValkeyConfig]("CACHE_VALKEY_")  // CACHE_VALKEY_ADDR, CACHE_VALKEY_PASSWORD, CACHE_VALKEY_DB
queue, err := envconf.LoadPrefixed[ValkeyConfig]("QUEUE_VALKEY_")  // QUEUE_VALKEY_ADDR, QUEUE_VALKEY_PASSWORD, QUEUE_VALKEY_DB
.env
CACHE_VALKEY_ADDR=localhost:6379
QUEUE_VALKEY_ADDR=localhost:6380
QUEUE_VALKEY_DB=1

Minden hívás független: egy hiányzó required mező az egyik prefix alatt csak azt a hívást buktatja el, a másikat nem. A LoadPrefixed-hez akkor nyúlj, ha a két példány valóban külön kapcsolat, egymástól függetlenül változó beállításokkal; ha mindig ugyanazt az értéket osztanák meg, egy egyszerű, prefix nélküli Config mező egyszerűbb.

Tesztelés t.Setenv-vel

Mivel a Load/MustLoad a valódi folyamat-környezetet olvassa, a konfigurációtól függő kódot gyakorló tesztek közvetlenül a t.Setenv-vel állítják be, amit a Go automatikusan visszaállít a teszt után (és elbukik, ha a teszt párhuzamos, ami a helyes bukás: a környezeti változók folyamat-globális állapot):

func TestLoad_defaults(t *testing.T) {
    t.Setenv("DB_HOST", "localhost")
    t.Setenv("DB_USER", "test")

    cfg := envconf.MustLoad[config.Config]()

    if cfg.DB.Host != "localhost" {
        t.Errorf("DB.Host = %q, want %q", cfg.DB.Host, "localhost")
    }
}

func TestLoad_missingRequired(t *testing.T) {
    // a DB_HOST szándékosan nincs beállítva
    _, err := envconf.Load[config.Config]()
    if err == nil {
        t.Fatal("expected an error for missing required DB_HOST")
    }
}

Itt nincs szó .env fájlról: a t.Setenv valódi folyamat-környezeti változókat állít be a teszt idejére, pontosan azt, amit a Load először olvas (mielőtt bármilyen .env-értékre visszaesne), így a teszt determinisztikus, függetlenül attól, hogy létezik-e épp .env fájl a munkakönyvtárban.

A frameworkkel

Egy generált gpsystem projekt internal/platform/config csomagja pontosan a fenti kompozíciós minta, egyszer generálva és az add surface/add db/add worker által bővítve, ahogy modulok kerülnek a projektbe. Lásd a Konfiguráció oldalt a koncepció-szintű nézetért (miért explicit, csak boot-időben létező függőség a konfiguráció gpsystemben), és a Konfigurációs referenciát a teljes, generált-projektenkénti változótáblázatért.

Kapcsolódó oldalak

Copyright © 2026