- Removed the old `infra.Config` structure and replaced it with a new modular configuration system in `pkg/config`. - Introduced a `Config` struct in `cmd/web/config.go` to hold command-specific configurations. - Updated configuration loading functions to utilize the new `LoadConfig` method with generics for type safety. - Adjusted various initialization functions in `cmd/web/bootstrap.go` and `cmd/web/main.go` to reflect the new configuration structure. - Cleaned up the `config.yaml` by removing obsolete fields and ensuring it aligns with the new configuration schema.
Modular Configuration System
This package provides a modular configuration system for the Tender Management backend. It allows each command (web, scraper, worker, etc.) to have its own specific configuration while sharing common base configurations.
Architecture
Base Configuration (BaseConfig)
The BaseConfig struct contains common configuration fields that are used across all commands:
- Server: HTTP server configuration (host, port, timeouts)
- Database: MongoDB connection configuration
- Cache: Redis connection configuration
- Logging: Logging system configuration
Command-Specific Configuration
Each command can extend the BaseConfig by embedding it and adding its own specific configuration fields.
Usage
1. Create Command-Specific Config
Create a config file for your command (e.g., cmd/yourcommand/config.go):
package main
import (
"time"
"tm/pkg/config"
)
// Config holds configuration for your command
type Config struct {
config.BaseConfig
YourSpecific YourSpecificConfig `mapstructure:"your_specific"`
}
type YourSpecificConfig struct {
SomeField string `mapstructure:"some_field"`
SomeTimeout time.Duration `mapstructure:"some_timeout"`
SomeNumber int `mapstructure:"some_number"`
}
2. Load Configuration
In your command's main.go or bootstrap file:
func initConfig() Config {
conf, err := config.LoadConfig(".", &Config{})
if err != nil {
panic(fmt.Sprintf("Failed to load config: %v", err))
}
return *conf
}
3. Create YAML Configuration File
Create a config.yaml file in your command directory:
# Base configuration (common to all commands)
server:
host: "localhost"
port: 8080
timeout: 30s
read_timeout: 10s
write_timeout: 10s
database:
mongodb:
uri: "mongodb://localhost:27017"
name: "your_database"
timeout: 10s
max_pool_size: 100
cache:
redis:
host: "localhost"
port: 6379
password: ""
db: 0
pool_size: 10
logging:
level: "info"
format: "json"
output: "stdout"
file:
path: "/var/log/tm/your-command.log"
max_size: 100
max_backups: 5
max_age: 30
compress: true
# Your command-specific configuration
your_specific:
some_field: "value"
some_timeout: 30s
some_number: 42
Examples
Web Command Config
type Config struct {
config.BaseConfig
Queue QueueConfig `mapstructure:"queue"`
UserAuthorization AuthConfig `mapstructure:"user_authorization"`
CustomerAuthorization AuthConfig `mapstructure:"customer_authorization"`
AI AIConfig `mapstructure:"ai"`
RateLimit RateLimitConfig `mapstructure:"rate_limiting"`
}
Scraper Command Config
type Config struct {
config.BaseConfig
TED TEDConfig `mapstructure:"ted"`
}
type TEDConfig struct {
BaseURL string `mapstructure:"base_url"`
MaxRetries int `mapstructure:"max_retries"`
MaxConcurrency int `mapstructure:"max_concurrency"`
DownloadDir string `mapstructure:"download_dir"`
CleanupAfter time.Duration `mapstructure:"cleanup_after"`
}
Worker Command Config
type Config struct {
config.BaseConfig
Queue QueueConfig `mapstructure:"queue"`
AI AIConfig `mapstructure:"ai"`
Worker WorkerConfig `mapstructure:"worker"`
Scraping ScrapingConfig `mapstructure:"scraping"`
}
Features
Type Safety
The configuration loader uses Go generics to provide type-safe configuration loading. The function signature ensures that you get back exactly the type you expect.
Environment Variable Support
The configuration system automatically supports environment variable overrides via Viper's AutomaticEnv() feature.
YAML Support
Configuration files use YAML format for easy readability and maintenance.
Validation
Each command can implement its own validation logic after loading the configuration.
Best Practices
-
Keep BaseConfig minimal: Only add fields to
BaseConfigthat are truly common across all commands. -
Use descriptive names: Choose clear, descriptive names for your configuration structs and fields.
-
Document your config: Add comments to your configuration structs explaining what each field does.
-
Use appropriate types: Use
time.Durationfor durations, proper numeric types for numbers, etc. -
Provide defaults: Consider providing sensible defaults in your YAML files.
-
Environment variables: Use environment variables for sensitive data like API keys and passwords.
Migration from Old System
If you're migrating from the old infra.Config system:
- Remove imports of
tm/infra - Add imports of
tm/pkg/config - Change your config struct to embed
config.BaseConfig - Update your
LoadConfigcall to use the new generic function - Update function signatures to use the new config types
Example migration:
// Old way
func initConfig() infra.Config {
config, err := infra.LoadConfig(".")
if err != nil {
panic(fmt.Sprintf("Failed to load config: %v", err))
}
return *config
}
// New way
func initConfig() Config {
conf, err := config.LoadConfig(".", &Config{})
if err != nil {
panic(fmt.Sprintf("Failed to load config: %v", err))
}
return *conf
}