Konfiguráció
A gpsystemben a konfiguráció betöltése egyetlen lépés: env-változók egy típusos Go structba töltve, egyszer, a processz indulásakor. Nincs config() helper és nincs config-cache: a struct maga a cache, és az elgépelt kulcs nem null-t ad futás közben, hanem fordítási hibát.
Ez a 12-factor "config a környezetből" elve: a konfiguráció a processz indulásakor, a környezetből olvasódik be, és a bináris minden környezetben (dev, staging, production) ugyanaz, csak az env más. Maga a betöltő mechanizmus, az envconf, önálló modul (github.com/gp-system/envconf), semmilyen gpsystem-specifikus tudás nélkül: ez az oldal arról szól, mit csinál egy generált projekt ezzel, hogyan komponálja egy tucatnyi modul beállításait egyetlen structba.
Struct-kompozíció
Minden önálló modul saját env-tagelt Config structot exportál, a saját mezőit nevezve, anélkül, hogy tudná, mi (ha egyáltalán) fogja prefixelni. Egy generált projekt ezeket a structokat egyetlen gyökér-Config-ba komponálja envPrefix-szel:
package config
import (
"github.com/gp-system/dbx"
"github.com/gp-system/events/outbox"
"github.com/gp-system/gpsystem/server"
"github.com/gp-system/gpsystem/worker"
// gpsystem:imports
)
type Config struct {
Server server.Config
DB dbx.Config `envPrefix:"DB_"`
Worker worker.Config
Outbox outbox.Config `envPrefix:"OUTBOX_"`
// gpsystem:config
}
cfg := envconf.MustLoad[config.Config]()
A kompozíció szabálya egyszerű: egy modul a saját mezőit nevezi, a beágyazó dönti el a prefixet. A dbx.Config mezői HOST, PORT, USER, ...: a envPrefix:"DB_" alatt ezek DB_HOST, DB_PORT, DB_USER lesznek. A prefix nélkül beágyazott structok (server.Config, worker.Config) standard, top-level neveket olvasnak.
A beágyazás mélyebbre is megy: a server.Config maga is beágyazza a telemetry.Config-ot, ami a telemetry/otelx + telemetry/logx + telemetry/sentryx konfigját fogja össze: mind prefix nélkül, mert ezek iparági standard nevek (OTEL_SERVICE_NAME, OTEL_DEV_MODE, LOG_LEVEL, SENTRY_DSN), amiket minden OTel-/Sentry-kompatibilis eszköz ugyanígy vár. A worker.Config pedig a Valkey- és scheduler-konfigot prefixeli (VALKEY_ADDR, SCHEDULER_TIMEZONE).
Az env-prefix konvenciók
A modulok közt, amiket egy projekt ténylegesen bedrótoz, a prefixek egy konzisztens készletté állnak össze:
| Prefix | Modul | Példa változók |
|---|---|---|
| (nincs) | a kit server-je | LISTEN_ADDR, SHUTDOWN_TIMEOUT |
| (nincs) | a kit worker-je | WORKER_CONCURRENCY, WORKER_QUEUES |
| (nincs) | a kit realtime-ja | REALTIME_LISTEN_ADDR, REALTIME_CHANNEL_PREFIX |
DB_ | dbx | DB_HOST, DB_USER, DB_PASSWORD |
JWT_ | auth | JWT_SECRET, JWT_ACCESS_TOKEN_TTL |
MAIL_ | mail/smtp | MAIL_HOST, MAIL_FROM_ADDRESS |
VALKEY_ | queue | VALKEY_ADDR, VALKEY_PASSWORD |
OUTBOX_ | events/outbox | OUTBOX_POLL_INTERVAL, OUTBOX_BATCH_SIZE |
SCHEDULER_ | events/scheduler | SCHEDULER_TIMEZONE, SCHEDULER_LEASE_TTL |
S3_ | storage/driver/s3 | S3_ENDPOINT, S3_BUCKET |
| (nincs, standard) | telemetry (otelx/logx/sentryx) | OTEL_*, LOG_*, SENTRY_* |
A shop env-felülete, több fentiből összerakott részlet:
# server.Config: prefix nélkül
LISTEN_ADDR=:3000
SHUTDOWN_TIMEOUT=10s
# telemetry (a server.Config-ba ágyazva): standard nevek
OTEL_SERVICE_NAME=shop
OTEL_DEV_MODE=true
LOG_FORMAT=monolog
SENTRY_DSN=
# dbx.Config: DB_ prefixszel
DB_HOST=localhost
DB_USER=shop
DB_PASSWORD=secret
DB_NAME=shop
# worker.Config: WORKER_* és VALKEY_*
WORKER_CONCURRENCY=10
VALKEY_ADDR=localhost:6379
# outbox.Config: OUTBOX_ prefixszel
OUTBOX_POLL_INTERVAL=1s
OTEL_*, SENTRY_*), ott azt használja változatlanul; ahol nem, ott rövid, prefixelt neveket (DB_*, VALKEY_*, WORKER_*). Így a deployod ugyanúgy néz ki, mint bármely más OTel-instrumentált appé. A teljes változó-tábla, generált projektenként: Konfiguráció-referencia.A .env fájl betöltési sorrendje
Parsolás előtt, best-effort jelleggel, az envconf godotenv-en keresztül betölti a munkamappa .env fájlját, ha létezik: meglévő környezeti változót sosem ír felül, tehát a .env csak kitölti, amit a processz környezete még nem adott meg. Ez az egyetlen szabály teszi lehetővé, hogy ugyanaz a Config struct minden kontextusban helyesen viselkedjen:
- Lokális fejlesztés: a shellben semmi nincs exportálva; a generált projekt egy
.env.example-t szállít, amit.env-re másolsz, és minden érték a fájlból jön. - A dev docker-compose stack: a
mise run develindítja a compose service-eket (Postgres, Valkey, Mailpit, RustFS) és az app-konténert ugyanazzal a.env-vel; a fájlban lévő service-hostnevek (DB_HOST=postgres,VALKEY_ADDR=valkey:6379) a compose belső DNS-ére oldódnak fel, nemlocalhost-ra. A teljes stackhez lásd a Telepítés oldalt. - Staging/production: a valódi környezet (egy systemd unit, egy Kubernetes ConfigMap/Secret) közvetlenül állítja be a változókat; általában nincs is
.envfájl, és ha lenne is, a már beállított változók akkor is nyernének.
Az envconf.MustLoad a main() első sora: hiányzó required változónál a hiba a változó nevével bukik ki, induláskor. Egy service sem bootol félig konfigurálva, hogy aztán az első kérésnél derüljön ki, mi hiányzik. Magához a Load/MustLoad/LoadPrefixed-hoz lásd az envconf: Áttekintés oldalt, a fenti alapesetnél tovább menő kompozíciós és tesztelési mintákhoz (multi-instance kapcsolatok, t.Setenv tesztekben) pedig az envconf: Receptek oldalt.
A konfiguráció mint explicit függőség
A gpsystemben a konfiguráció a main()-ben egyszer materializálódik, és onnan explicit paraméterként utazik tovább (cfg.Server a server.Run-nak, cfg.DB a pg.MustNewPool-nak, a Dependencies structon át a moduloknak). Egy service soha nem nyúl a környezethez futás közben. Amit kap, az a boot-kori pillanatkép, ami tesztben tetszőlegesen konstruálható. Elgépelt mező esetén fordítási hibát kapsz, hiányzó required env esetén pedig hibát boot-kor, a változó nevével, sosem csendes null-t futás közben.
Kapcsolódó oldalak
- envconf: Áttekintés: a
Load/MustLoad/LoadPrefixedAPI, struct-tagek, önálló használat. - envconf: Receptek: gyökér-
Configkompozíció, prefixelt multi-instance kapcsolatok, tesztelést.Setenv-vel. - Konfiguráció-referencia: minden modul minden env-változója, defaultokkal.
- Alkalmazás-életciklus: mi történik a
MustLoadután. - Telepítés: a
.env.example-től az első bootig.