config(configuration management)
Flexible configuration management system supporting multiple sources (files, environment variables, remote config centers) with unified reading, watching, and parsing interfaces. Protobuf is recommended for defining configuration structures.
The config module provides a flexible configuration management system that supports multiple configuration sources (files, environment variables, remote configuration centers) with unified reading, watching, and parsing interfaces. We recommend using Protocol Buffers (protobuf) to define configuration structures for better type safety, schema validation, and cross-language compatibility.
Features
- Multiple configuration sources support (files, environment variables, remote config centers)
- Unified configuration interface for consistent access patterns
- Real-time configuration watching with observer pattern
- Automatic configuration merging from multiple sources
- Type-safe configuration scanning to structs
- Hierarchical configuration with key-value access
- Configuration caching for improved performance
- Extensible source system for custom configuration providers
Basic Configuration Usage (Recommended: Protobuf)
First, define your configuration structure using Protocol Buffers:
1// config.proto
2syntax = "proto3";
3
4package config;
5
6option go_package = "github.com/yourproject/config";
7
8message AppConfig {
9 ServerConfig server = 1;
10 DatabaseConfig database = 2;
11}
12
13message ServerConfig {
14 string host = 1;
15 int32 port = 2;
16}
17
18message DatabaseConfig {
19 string host = 1;
20 int32 port = 2;
21 string username = 3;
22 string password = 4;
23}
Then use it in your Go application:
1package main
2
3import (
4 "context"
5 "fmt"
6 "log"
7
8 "github.com/crazyfrankie/frx/config"
9 "github.com/crazyfrankie/frx/config/file"
10 "github.com/crazyfrankie/frx/config/env"
11
12 // Import your generated protobuf config
13 configpb "github.com/yourproject/config"
14)
15
16func main() {
17 // Create configuration with multiple sources
18 c := config.New(
19 config.WithSource(
20 file.NewSource("config.yaml"),
21 env.NewSource("APP_"),
22 ),
23 )
24 defer c.Close()
25
26 // Load configuration
27 if err := c.Load(); err != nil {
28 log.Fatal(err)
29 }
30
31 // Scan configuration into protobuf struct
32 var cfg configpb.AppConfig
33 if err := c.Scan(&cfg); err != nil {
34 log.Fatal(err)
35 }
36
37 fmt.Printf("Server: %s:%d\n", cfg.Server.Host, cfg.Server.Port)
38 fmt.Printf("Database: %s:%d\n", cfg.Database.Host, cfg.Database.Port)
39}
Alternative: Traditional Struct Usage
If you prefer not to use protobuf, you can still use traditional Go structs:
1type AppConfig struct {
2 Server struct {
3 Host string `json:"host"`
4 Port int `json:"port"`
5 } `json:"server"`
6 Database struct {
7 Host string `json:"host"`
8 Port int `json:"port"`
9 Username string `json:"username"`
10 Password string `json:"password"`
11 } `json:"database"`
12}
Configuration Sources
File Source
1import "github.com/crazyfrankie/frx/config/file"
2
3// YAML file source
4yamlSource := file.NewSource("config.yaml")
5
6// JSON file source
7jsonSource := file.NewSource("config.json")
8
9// Multiple file sources
10c := config.New(
11 config.WithSource(
12 file.NewSource("config.yaml"),
13 file.NewSource("config.local.yaml"), // Override with local config
14 ),
15)
Environment Variable Source
1import "github.com/crazyfrankie/frx/config/env"
2
3// Environment variables with prefix
4envSource := env.NewSource("APP_")
5
6c := config.New(
7 config.WithSource(envSource),
8)
9
10// Environment variables will be mapped:
11// APP_SERVER_HOST -> server.host
12// APP_DATABASE_PORT -> database.port
Configuration Watching
1func main() {
2 c := config.New(
3 config.WithSource(file.NewSource("config.yaml")),
4 )
5 defer c.Close()
6
7 if err := c.Load(); err != nil {
8 log.Fatal(err)
9 }
10
11 // Watch for configuration changes
12 if err := c.Watch("server", func(key string, value config.Value) {
13 fmt.Printf("Configuration changed - %s: %v\n", key, value)
14
15 // Reload application configuration
16 var cfg AppConfig
17 if err := c.Scan(&cfg); err != nil {
18 log.Printf("Failed to reload config: %v", err)
19 return
20 }
21
22 // Apply new configuration
23 applyNewConfig(cfg)
24 }); err != nil {
25 log.Fatal(err)
26 }
27
28 // Keep application running
29 select {}
30}
31
32func applyNewConfig(cfg AppConfig) {
33 // Restart server with new configuration
34 fmt.Printf("Applying new config: %+v\n", cfg)
35}
Value Access and Type Conversion
1func main() {
2 c := config.New(
3 config.WithSource(file.NewSource("config.yaml")),
4 )
5 defer c.Close()
6
7 if err := c.Load(); err != nil {
8 log.Fatal(err)
9 }
10
11 // Get values by key
12 serverHost := c.Value("server.host")
13 if serverHost == nil {
14 log.Fatal("server.host not found")
15 }
16
17 // Type-safe value conversion
18 host, err := serverHost.String()
19 if err != nil {
20 log.Fatal(err)
21 }
22
23 port, err := c.Value("server.port").Int()
24 if err != nil {
25 log.Fatal(err)
26 }
27
28 enabled, err := c.Value("features.enabled").Bool()
29 if err != nil {
30 log.Fatal(err)
31 }
32
33 fmt.Printf("Server: %s:%d, Features enabled: %v\n", host, port, enabled)
34}
Configuration Merging
1func main() {
2 // Configuration sources are merged in order
3 // Later sources override earlier ones
4 c := config.New(
5 config.WithSource(
6 file.NewSource("config.default.yaml"), // Default configuration
7 file.NewSource("config.yaml"), // Environment-specific config
8 env.NewSource("APP_"), // Environment variables (highest priority)
9 ),
10 )
11 defer c.Close()
12
13 if err := c.Load(); err != nil {
14 log.Fatal(err)
15 }
16
17 // The final configuration is a merge of all sources
18 var cfg AppConfig
19 if err := c.Scan(&cfg); err != nil {
20 log.Fatal(err)
21 }
22}
Custom Configuration Source
1import "github.com/crazyfrankie/frx/config"
2
3// Implement custom source
4type customSource struct {
5 data map[string]interface{}
6}
7
8func (s *customSource) Load() ([]*config.KeyValue, error) {
9 var kvs []*config.KeyValue
10 for k, v := range s.data {
11 kvs = append(kvs, &config.KeyValue{
12 Key: k,
13 Value: []byte(fmt.Sprintf("%v", v)),
14 })
15 }
16 return kvs, nil
17}
18
19func (s *customSource) Watch() (config.Watcher, error) {
20 // Implement watcher if needed
21 return nil, nil
22}
23
24func NewCustomSource(data map[string]interface{}) config.Source {
25 return &customSource{data: data}
26}
27
28// Usage
29customData := map[string]interface{}{
30 "app.name": "MyApp",
31 "app.version": "1.0.0",
32}
33
34c := config.New(
35 config.WithSource(NewCustomSource(customData)),
36)