Configuration Management
Go memiliki beberapa cara untuk mengelola konfigurasi aplikasi, termasuk environment variables, file konfigurasi (JSON, YAML, TOML), dan command-line flags.
Contoh Masalah
Bagaimana cara:
- Mengelola konfigurasi
- Load dari berbagai sumber
- Validasi konfigurasi
- Hot reload
Penyelesaian
package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"os"
"path/filepath"
"sync"
"time"
)
// 1. Configuration structure
type DatabaseConfig struct {
Host string `json:"host"`
Port int `json:"port"`
User string `json:"user"`
Password string `json:"password"`
Database string `json:"database"`
}
type ServerConfig struct {
Host string `json:"host"`
Port int `json:"port"`
Timeout int `json:"timeout"`
}
type LogConfig struct {
Level string `json:"level"`
File string `json:"file"`
MaxSize int `json:"max_size"`
MaxBackups int `json:"max_backups"`
}
type Config struct {
Environment string `json:"environment"`
Debug bool `json:"debug"`
Database DatabaseConfig `json:"database"`
Server ServerConfig `json:"server"`
Log LogConfig `json:"log"`
}
// 2. Configuration manager
type ConfigManager struct {
sync.RWMutex
config *Config
configFile string
logger *log.Logger
}
// 3. Configuration validation
func (c *Config) Validate() error {
if c.Environment == "" {
return fmt.Errorf("environment must be set")
}
if c.Database.Host == "" {
return fmt.Errorf("database host must be set")
}
if c.Database.Port <= 0 {
return fmt.Errorf("invalid database port")
}
if c.Server.Port <= 0 {
return fmt.Errorf("invalid server port")
}
return nil
}
// 4. Configuration string representation
func (c *Config) String() string {
return fmt.Sprintf(
"Environment: %s\n"+
"Debug: %v\n"+
"Database:\n"+
" Host: %s\n"+
" Port: %d\n"+
" User: %s\n"+
" Database: %s\n"+
"Server:\n"+
" Host: %s\n"+
" Port: %d\n"+
" Timeout: %d\n"+
"Log:\n"+
" Level: %s\n"+
" File: %s\n"+
" MaxSize: %d\n"+
" MaxBackups: %d\n",
c.Environment,
c.Debug,
c.Database.Host,
c.Database.Port,
c.Database.User,
c.Database.Database,
c.Server.Host,
c.Server.Port,
c.Server.Timeout,
c.Log.Level,
c.Log.File,
c.Log.MaxSize,
c.Log.MaxBackups,
)
}
// 5. Create new config manager
func NewConfigManager(configFile string) *ConfigManager {
return &ConfigManager{
configFile: configFile,
logger: log.New(os.Stdout, "[Config] ", log.Ltime),
}
}
// 6. Load configuration
func (cm *ConfigManager) Load() error {
cm.Lock()
defer cm.Unlock()
// Read config file
data, err := os.ReadFile(cm.configFile)
if err != nil {
return fmt.Errorf("reading config file: %v", err)
}
// Parse JSON
config := &Config{}
if err := json.Unmarshal(data, config); err != nil {
return fmt.Errorf("parsing config file: %v", err)
}
// Load environment variables
cm.loadEnvVars(config)
// Load command line flags
cm.loadFlags(config)
// Validate
if err := config.Validate(); err != nil {
return fmt.Errorf("validating config: %v", err)
}
cm.config = config
return nil
}
// 7. Load environment variables
func (cm *ConfigManager) loadEnvVars(config *Config) {
if env := os.Getenv("APP_ENV"); env != "" {
config.Environment = env
}
if debug := os.Getenv("APP_DEBUG"); debug == "true" {
config.Debug = true
}
if dbHost := os.Getenv("DB_HOST"); dbHost != "" {
config.Database.Host = dbHost
}
// Add more environment variables as needed
}
// 8. Load command line flags
func (cm *ConfigManager) loadFlags(config *Config) {
// Define flags
env := flag.String("env", "", "Environment (development/production)")
debug := flag.Bool("debug", false, "Enable debug mode")
dbHost := flag.String("db-host", "", "Database host")
serverPort := flag.Int("port", 0, "Server port")
// Parse flags
flag.Parse()
// Apply flags if set
if *env != "" {
config.Environment = *env
}
if *debug {
config.Debug = true
}
if *dbHost != "" {
config.Database.Host = *dbHost
}
if *serverPort > 0 {
config.Server.Port = *serverPort
}
}
// 9. Get configuration
func (cm *ConfigManager) Get() *Config {
cm.RLock()
defer cm.RUnlock()
return cm.config
}
// 10. Watch for config changes
func (cm *ConfigManager) Watch() {
ticker := time.NewTicker(30 * time.Second)
go func() {
for range ticker.C {
if err := cm.Load(); err != nil {
cm.logger.Printf("Error reloading config: %v\n", err)
} else {
cm.logger.Println("Configuration reloaded")
}
}
}()
}
func main() {
// Create example config file
exampleConfig := Config{
Environment: "development",
Debug: true,
Database: DatabaseConfig{
Host: "localhost",
Port: 5432,
User: "user",
Password: "password",
Database: "myapp",
},
Server: ServerConfig{
Host: "localhost",
Port: 8080,
Timeout: 30,
},
Log: LogConfig{
Level: "info",
File: "app.log",
MaxSize: 100,
MaxBackups: 3,
},
}
// Write example config
configFile := "config.json"
data, _ := json.MarshalIndent(exampleConfig, "", " ")
os.WriteFile(configFile, data, 0644)
defer os.Remove(configFile)
// Create config manager
cm := NewConfigManager(configFile)
// Load initial configuration
if err := cm.Load(); err != nil {
log.Fatalf("Error loading config: %v", err)
}
// Start watching for changes
cm.Watch()
// Print current config
config := cm.Get()
fmt.Println("Current configuration:")
fmt.Println(config)
// Example: Update config file
time.Sleep(2 * time.Second)
exampleConfig.Server.Port = 9090
data, _ = json.MarshalIndent(exampleConfig, "", " ")
os.WriteFile(configFile, data, 0644)
// Wait for config reload
time.Sleep(2 * time.Second)
config = cm.Get()
fmt.Println("\nUpdated configuration:")
fmt.Println(config)
}
Penjelasan Kode
Config Sources
- JSON file
- Environment variables
- Command line flags
Features
- Validation
- Hot reload
- Thread safety
Best Practices
- Centralized config
- Type safety
- Default values
Output
11:57:22 [Config] Configuration loaded
Current configuration:
Environment: development
Debug: true
Database:
Host: localhost
Port: 5432
User: user
Database: myapp
Server:
Host: localhost
Port: 8080
Timeout: 30
Log:
Level: info
File: app.log
MaxSize: 100
MaxBackups: 3
11:57:24 [Config] Configuration reloaded
Updated configuration:
Environment: development
Debug: true
Database:
Host: localhost
Port: 5432
User: user
Database: myapp
Server:
Host: localhost
Port: 9090
Timeout: 30
Log:
Level: info
File: app.log
MaxSize: 100
MaxBackups: 3
Tips
- Gunakan strong typing
- Validasi semua input
- Sediakan defaults
- Handle reload errors
- Secure sensitive data