Tier: Foundational · Status: Full · Java original: Spring Boot
@ConfigurationProperties· .NET project:IConfiguration+IOptions<T>
config brings Spring Boot–style typed, layered configuration
binding to Go. Application authors declare a Go struct with
yaml-tagged fields and call config.Load[T](ctx, sources...); the
loader merges the sources in precedence order, resolves the active
profile, and binds the flat dot-keyed map onto the struct.
type AppCfg struct {
Web struct {
Port int `yaml:"port"`
Host string `yaml:"host"`
} `yaml:"web"`
Cache struct { Adapter string `yaml:"adapter"` } `yaml:"cache"`
}
cfg, err := config.Load[AppCfg](ctx,
config.FromYAML("application.yaml"),
config.FromEnv("FIREFLY"),
)config.NewLayered(s1, s2, ...) merges from left to right — last
write wins. The canonical chain is:
- Defaults (
Static{Entries: ...}) - Base YAML (
FromOptionalYAML("application.yaml")) - Profile YAML (
FromOptionalYAML("application-prod.yaml")) - Environment (
FromEnv("FIREFLY")—FIREFLY_WEB_PORT→web.port) - CLI flags (
NewFlagSource()—flags.Set("web.port", "9090"))
So an environment override always beats a YAML file, and a CLI override always beats both.
FIREFLY_PROFILE selects the profile-specific YAML file. The
canonical helper:
cfg, _ := config.LoadFromProfile[AppCfg](ctx, "/etc/firefly", "application", "dev")reads application.yaml, then application-{FIREFLY_PROFILE,fallback}.yaml,
then FIREFLY_* env vars.
| Symbol | Purpose |
|---|---|
Source interface |
Anything producing a flat map[string]string |
Static{NameValue, Entries} |
Hard-coded source |
FromYAML(path) *YAMLSource |
Required YAML file |
FromOptionalYAML(path) *YAMLSource |
Tolerates absent file |
FromEnv(prefix) *EnvSource |
Reads <PREFIX>_FOO_BAR → foo.bar |
NewFlagSource() + .Set(key, value) |
Programmatic / CLI overrides |
NewLayered(sources...).Map(ctx) |
Compute the merged map |
Load[T](ctx, sources...) (T, error) |
Merge + bind onto a fresh T |
LoadFromProfile[T](ctx, dir, app, fallback) (T, error) |
Profile-aware convenience |
Bind(flat, &target) |
Bind a pre-merged map onto a pointer |
ActiveProfile(fallback) string |
FIREFLY_PROFILE lookup |
ProfileSources(dir, app, profile) []Source |
Build the YAML chain for a profile |
string, bool, all int* / uint*, float32 / float64, and
[]string (comma-separated). Use time.Duration via an int64 field
plus your own conversion (time.Duration(cfg.TimeoutMs) * time.Millisecond) — keeps the binder dependency-free.
The embedded YAML scanner accepts: mappings, scalars, nested mappings,
sequences of scalars (rendered as comma-joined). It rejects flow
sequences, anchors, aliases, multi-doc, and tags — bring your own
parser (gopkg.in/yaml.v3) if you need them. The .NET port uses
Microsoft's YAML reader; the parser surface here matches what every
production application.yaml actually uses.
import (
"context"
"github.com/fireflyframework/fireflyframework-go/config"
)
type AppCfg struct {
Web struct {
Port int `yaml:"port"`
} `yaml:"web"`
Cache struct {
Adapter string `yaml:"adapter"`
TTL int64 `yaml:"ttl"`
} `yaml:"cache"`
Tags []string `yaml:"tags"`
}
cfg, err := config.LoadFromProfile[AppCfg](ctx, "/etc/orders", "application", "dev")
if err != nil { panic(err) }
fmt.Println(cfg.Web.Port)cd config
go test ./...Suite covers static + YAML + env merge order, profile selection,
optional-YAML absence tolerance, and leaf-kind binding for strings,
bools, ints, floats, and []string.