Files
tm_back/pkg/config/README.md
T
n.nakhostin 57c29dc58f Refactor Configuration System to Use Environment Variables
- Removed YAML configuration files for the scraper and web commands, transitioning to an environment-based configuration system.
- Updated configuration structs to utilize `env` tags for loading values from `.env` files, enhancing security and flexibility.
- Implemented reflection-based parsing for environment variables, ensuring type-safe configuration loading.
- Revised documentation to reflect the new configuration approach, including usage examples and best practices for environment variable naming conventions.
2025-09-09 18:19:42 +03:30

7.1 KiB

Environment-Based Configuration System

This package provides a configuration system for the Tender Management backend that loads configuration exclusively from .env files. It maintains the same structure as the previous YAML-based system while using environment variables for all configuration values.

Architecture

Base Configuration (BaseConfig)

The configuration system provides common configuration structs that are used across all commands:

  • ServerConfig: HTTP server configuration (host, port, timeouts)
  • DatabaseConfig: MongoDB connection configuration
  • CacheConfig: Redis connection configuration
  • LoggingConfig: Logging system configuration
  • RateLimitConfig: Rate limiting configuration

Command-Specific Configuration

Each command can extend the base configuration by embedding the common configs and adding their 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.ServerConfig
    config.DatabaseConfig
    config.CacheConfig
    config.LoggingConfig
    YourSpecific YourSpecificConfig
}

type YourSpecificConfig struct {
    SomeField    string        `env:"SOME_FIELD"`
    SomeTimeout  time.Duration `env:"SOME_TIMEOUT"`
    SomeNumber   int           `env:"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 .env Configuration File

Create a .env file in your command directory:

# Server Configuration
SERVER_HOST=localhost
SERVER_PORT=8080
SERVER_TIMEOUT=30s
SERVER_READ_TIMEOUT=10s
SERVER_WRITE_TIMEOUT=10s

# Database Configuration
MONGODB_URI=mongodb://localhost:27017
MONGODB_NAME=your_database
MONGODB_TIMEOUT=10s
MONGODB_MAX_POOL_SIZE=100

# Cache Configuration
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0
REDIS_POOL_SIZE=10

# Logging Configuration
LOG_LEVEL=info
LOG_FORMAT=json
LOG_OUTPUT=stdout
LOG_FILE_PATH=/var/log/tm/your-command.log
LOG_FILE_MAX_SIZE=100
LOG_FILE_MAX_BACKUPS=5
LOG_FILE_MAX_AGE=30
LOG_FILE_COMPRESS=true

# Your command-specific configuration
SOME_FIELD=value
SOME_TIMEOUT=30s
SOME_NUMBER=42

Examples

Web Command Config

type Config struct {
    config.ServerConfig
    config.DatabaseConfig
    config.CacheConfig
    config.LoggingConfig
    config.RateLimitConfig
    UserAuth     AuthConfig
    CustomerAuth AuthConfig
    Assets       AssetsConfig
}

type AuthConfig struct {
    JWT JWTConfig
}

type JWTConfig struct {
    AccessSecret     string `env:"USER_AUTH_ACCESS_SECRET"`
    RefreshSecret    string `env:"USER_AUTH_REFRESH_SECRET"`
    AccessExpiresIn  int    `env:"USER_AUTH_ACCESS_EXPIRES_IN"`
    RefreshExpiresIn int    `env:"USER_AUTH_REFRESH_EXPIRES_IN"`
}

type AssetsConfig struct {
    FlagsPath string `env:"ASSETS_FLAGS_PATH"`
}

Scraper Command Config

type Config struct {
    config.DatabaseConfig
    config.LoggingConfig
    TED ScraperConfig
}

type ScraperConfig struct {
    BaseURL          string        `env:"TED_BASE_URL"`
    Timeout          time.Duration `env:"TED_TIMEOUT"`
    MaxRetries       int           `env:"TED_MAX_RETRIES"`
    RetryDelay       time.Duration `env:"TED_RETRY_DELAY"`
    UserAgent        string        `env:"TED_USER_AGENT"`
    MaxConcurrency   int           `env:"TED_MAX_CONCURRENCY"`
    DownloadDir      string        `env:"TED_DOWNLOAD_DIR"`
    CleanupAfter     time.Duration `env:"TED_CLEANUP_AFTER"`
    ScrapingInterval time.Duration `env:"TED_SCRAPING_INTERVAL"`
}

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 loads all values from .env files using the github.com/subosito/gotenv library.

Reflection-Based Parsing

The system uses reflection to automatically parse environment variables into struct fields based on the env tags.

Supported Types

The configuration system supports the following Go types:

  • string
  • int, int8, int16, int32, int64
  • bool
  • float32, float64
  • time.Duration (parsed from strings like "30s", "1h", "5m")

Best Practices

  1. Use descriptive environment variable names: Choose clear, descriptive names for your environment variables (e.g., MONGODB_URI instead of DB_URI).

  2. Use consistent naming conventions: Use uppercase with underscores for environment variable names (e.g., SERVER_HOST, LOG_LEVEL).

  3. Group related variables: Use prefixes to group related configuration (e.g., MONGODB_*, REDIS_*, LOG_*).

  4. Document your configuration: Add comments in your .env files explaining what each variable does.

  5. Use appropriate types: Use time.Duration for durations, proper numeric types for numbers, etc.

  6. Provide sensible defaults: Consider providing sensible defaults in your code when environment variables are not set.

Migration from YAML System

If you're migrating from the old YAML-based configuration system:

  1. Remove mapstructure tags from your config structs
  2. Add env tags to your config struct fields
  3. Create .env files instead of config.yaml files
  4. Update your LoadConfig calls to use the new system
  5. Update function signatures to use the new config types

Example migration:

// Old way (YAML)
type Config struct {
    Server config.ServerConfig `mapstructure:"server"`
    Database config.DatabaseConfig `mapstructure:"database"`
}

// New way (.env)
type Config struct {
    Server config.ServerConfig
    Database config.DatabaseConfig
}

// Old way (YAML loading)
conf, err := config.LoadConfig(".", &Config{})

// New way (.env loading)
conf, err := config.LoadConfig(".", &Config{})

Environment Variable Naming Convention

The system follows these naming conventions:

  • Server: SERVER_* (e.g., SERVER_HOST, SERVER_PORT)
  • Database: MONGODB_* (e.g., MONGODB_URI, MONGODB_NAME)
  • Cache: REDIS_* (e.g., REDIS_HOST, REDIS_PORT)
  • Logging: LOG_* (e.g., LOG_LEVEL, LOG_FORMAT)
  • Rate Limiting: RATE_LIMIT_* (e.g., RATE_LIMIT_REQUESTS_PER_MINUTE)

Error Handling

The configuration system provides detailed error messages when:

  • The .env file cannot be loaded
  • Environment variables cannot be parsed into the expected types
  • Required fields are missing (handled by your application logic)

Security Considerations

  1. Never commit .env files: Add .env to your .gitignore file
  2. Use environment-specific files: Create different .env files for different environments (.env.local, .env.production, etc.)
  3. Validate sensitive values: Always validate sensitive configuration values like API keys and database URIs
  4. Use secure defaults: Provide secure default values for security-related configuration