The Modular framework now supports explicit priority control for configuration feeders, allowing you to precisely control which configuration sources override others. This solves common issues like test isolation where environment variables would unintentionally override explicit test configurations.
import "github.com/GoCodeAlone/modular/feeders"
// Add feeders with priority control
config.AddFeeder(feeders.NewYamlFeeder("config.yaml").WithPriority(50))
config.AddFeeder(feeders.NewEnvFeeder().WithPriority(100))Key Concept: Higher priority values = applied later = override lower priority feeders
Problem: Environment variables from the host system override explicit test configuration.
Solution: Give test configuration higher priority than environment variables.
func TestWithIsolation(t *testing.T) {
// Host may have SDK_KEY="production-key"
t.Setenv("SDK_KEY", "host-value")
// Test wants specific configuration
yamlPath := createTestYAML(t, `sdkKey: "test-value"`)
config := modular.NewConfig()
config.AddFeeder(feeders.NewEnvFeeder().WithPriority(50)) // Lower priority
config.AddFeeder(feeders.NewYamlFeeder(yamlPath).WithPriority(100)) // Higher priority
config.AddStructKey("_main", &cfg)
config.Feed()
// Test gets explicit YAML value, not environment variable
assert.Equal(t, "test-value", cfg.SDKKey)
}Problem: Need environment variables to override default configuration files.
Solution: Give environment variables higher priority than config files.
// Production application setup
config := modular.NewConfig()
// Base configuration (lower priority)
config.AddFeeder(feeders.NewYamlFeeder("config.yaml").WithPriority(50))
// Environment overrides (higher priority)
config.AddFeeder(feeders.NewEnvFeeder().WithPriority(100))
config.AddFeeder(feeders.NewAffixedEnvFeeder("APP_", "_PROD").WithPriority(100))
config.AddStructKey("_main", &appConfig)
config.Feed()Problem: Multiple configuration sources with clear precedence hierarchy.
Solution: Use different priority levels for each layer.
config := modular.NewConfig()
// Layer 1: Defaults (lowest priority)
config.AddFeeder(feeders.NewYamlFeeder("defaults.yaml").WithPriority(10))
// Layer 2: Environment-specific configuration
config.AddFeeder(feeders.NewYamlFeeder("config-prod.yaml").WithPriority(50))
// Layer 3: Local .env file overrides
config.AddFeeder(feeders.NewDotEnvFeeder(".env").WithPriority(75))
// Layer 4: OS environment variables (highest priority)
config.AddFeeder(feeders.NewEnvFeeder().WithPriority(100))
config.AddStructKey("_main", &appConfig)
config.Feed()| Range | Purpose | Examples |
|---|---|---|
| 0-50 | Base/default configuration | Default YAML files, built-in defaults |
| 51-99 | Environment-specific configuration | .env files, environment-specific configs |
| 100+ | Runtime overrides | OS environment variables, command-line flags |
- Use consistent priority ranges across your application
- Document your priority scheme in application documentation
- Leave gaps between priorities (e.g., 10, 50, 100) for future additions
- Group related feeders at the same priority level
- Higher priority for more specific configuration (env vars > files > defaults)
Without explicit priorities:
- All feeders default to priority 0
- Sequential order is preserved (last feeder wins)
- Maintains backward compatibility with existing code
// These are equivalent:
config.AddFeeder(feeders.NewYamlFeeder("config.yaml"))
config.AddFeeder(feeders.NewEnvFeeder())
// Same as:
config.AddFeeder(feeders.NewYamlFeeder("config.yaml").WithPriority(0))
config.AddFeeder(feeders.NewEnvFeeder().WithPriority(0))- Before feeding configuration, all feeders are sorted by priority (ascending)
- Feeders with equal priority maintain their original order (stable sort)
- Feeders are applied in sorted order (lowest to highest priority)
- Later feeders override values set by earlier feeders
type PrioritizedFeeder interface {
Feeder
Priority() int
}All built-in feeders implement this interface:
YamlFeederJSONFeederTomlFeederEnvFeederDotEnvFeederAffixedEnvFeederTenantAffixedEnvFeeder
Enable verbose debugging to see feeder application order:
config := modular.NewConfig()
config.SetVerboseDebug(true, logger)
config.AddFeeder(feeders.NewYamlFeeder("config.yaml").WithPriority(50))
config.AddFeeder(feeders.NewEnvFeeder().WithPriority(100))
config.Feed()
// Output shows:
// Feeder order: index=0, type=*feeders.YamlFeeder, priority=50
// Feeder order: index=1, type=*feeders.EnvFeeder, priority=100No changes required! The priority system is fully backward compatible.
Optional upgrade to explicit priorities:
Before:
config.AddFeeder(feeders.NewYamlFeeder("config.yaml"))
config.AddFeeder(feeders.NewEnvFeeder())
// Relies on order: Env overrides YAMLAfter:
config.AddFeeder(feeders.NewYamlFeeder("config.yaml").WithPriority(50))
config.AddFeeder(feeders.NewEnvFeeder().WithPriority(100))
// Explicit: Env overrides YAML because of higher priorityBefore (broken - env vars override test config):
func TestMyFeature(t *testing.T) {
// Host environment interferes with test
config.AddFeeder(feeders.NewYamlFeeder("test-config.yaml"))
config.AddFeeder(feeders.NewEnvFeeder())
// Problem: Env vars override test config
}After (fixed - test config overrides env vars):
func TestMyFeature(t *testing.T) {
// Test configuration takes precedence
config.AddFeeder(feeders.NewEnvFeeder().WithPriority(50))
config.AddFeeder(feeders.NewYamlFeeder("test-config.yaml").WithPriority(100))
// Solution: Test config overrides env vars
}See these files for complete examples:
feeder_priority_test.go- Comprehensive test scenariosissue_reproduction_test.go- Original issue demonstrationexamples/basic-app/main.go- Usage commentsfeeders/DOCUMENTATION.md- Complete documentation
Q: What happens if I don't specify priority?
A: Default priority is 0. Original sequential behavior is preserved.
Q: Can I use negative priorities?
A: Yes, but it's not recommended. Use 0+ for clarity.
Q: What if two feeders have the same priority?
A: Original order is preserved (stable sort). Later feeder wins.
Q: Does this work with module-specific configuration?
A: Yes, priority applies to all configuration feeding, including module configs.
Q: Can I change priority after creating a feeder?
A: Yes, call WithPriority() again. It returns the feeder for chaining.
-
Enable verbose debugging:
config.SetVerboseDebug(true, logger)
-
Check feeder order in logs:
Feeder order: index=0, type=*feeders.YamlFeeder, priority=50 Feeder order: index=1, type=*feeders.EnvFeeder, priority=100 -
Verify priorities:
- Higher priority = applied later = overrides
- Lower priority = applied earlier = can be overridden
Symptom: Tests pass locally but fail in CI with different environment variables.
Solution: Use priority control to make test config override environment:
config.AddFeeder(feeders.NewEnvFeeder().WithPriority(50))
config.AddFeeder(feeders.NewYamlFeeder("test-config.yaml").WithPriority(100))- v1.12.0: Added priority control system with
WithPriority()method - Full backward compatibility maintained
- feeders/DOCUMENTATION.md - Complete feeder documentation
- CONFIG_PROVIDERS.md - Configuration provider patterns
- examples/ - Working examples