Files
n.nakhostin 05a307e345 Enhance Configuration System with Environment Variable Priority Handling
- Updated the configuration loading mechanism to prioritize OS environment variables over .env file values, improving flexibility and security.
- Added a new function, GetEnvWithPriority, to explicitly check the source of configuration values.
- Revised documentation to reflect the new priority system, including detailed examples and usage instructions for mixed configuration approaches.
- Enhanced error handling for .env file loading, ensuring graceful handling of missing files without disrupting the application flow.
2025-09-15 14:52:12 +03:30

383 lines
11 KiB
Markdown

# Environment-Based Configuration System
This package provides a configuration system for the Tender Management backend that loads configuration from both OS environment variables and `.env` files, with **OS environment variables taking priority** over `.env` file values. It maintains the same structure as the previous YAML-based system while using environment variables for all configuration values.
## Priority System
The configuration loader follows this priority order:
1. **OS Environment Variables** (highest priority) - System-level environment variables
2. **`.env` File Values** (lower priority) - File-based configuration for development
This means that if you set `SERVER_PORT=8080` in your `.env` file but have `SERVER_PORT=9000` as an OS environment variable, the system will use `9000`.
## 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`):
```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:
```go
func initConfig() Config {
conf, err := config.LoadConfig(".", &Config{})
if err != nil {
panic(fmt.Sprintf("Failed to load config: %v", err))
}
return *conf
}
```
### 3. Configuration Sources
#### Option A: Using .env File Only (Development)
Create a `.env` file in your command directory:
```env
# 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
```
#### Option B: Using OS Environment Variables (Production)
```bash
# Set environment variables directly
export SERVER_HOST=0.0.0.0
export SERVER_PORT=8080
export MONGODB_URI=mongodb://mongo:27017
export MONGODB_NAME=production_db
export LOG_LEVEL=error
# Run your application
./your-command
```
#### Option C: Mixed Approach (Recommended)
1. Create a `.env` file with development defaults
2. Override specific values with OS environment variables for production:
```bash
# .env file contains development defaults
# Override critical values with OS env vars
export MONGODB_URI=mongodb://production-server:27017
export MONGODB_NAME=production_db
export LOG_LEVEL=error
# Run application - will use .env defaults except for overridden values
./your-command
```
## Priority Example
Given this `.env` file:
```env
SERVER_HOST=localhost
SERVER_PORT=8080
LOG_LEVEL=debug
```
And these OS environment variables:
```bash
export SERVER_PORT=9000
export LOG_LEVEL=error
```
The final configuration will be:
- `SERVER_HOST=localhost` (from .env file, no OS override)
- `SERVER_PORT=9000` (OS environment variable overrides .env)
- `LOG_LEVEL=error` (OS environment variable overrides .env)
## Advanced Features
### Checking Configuration Source
You can check whether a configuration value came from OS environment or .env file:
```go
import "tm/pkg/config"
func main() {
value, isFromOS := config.GetEnvWithPriority("SERVER_PORT")
if isFromOS {
fmt.Printf("SERVER_PORT=%s (from OS environment)\n", value)
} else {
fmt.Printf("SERVER_PORT=%s (from .env file)\n", value)
}
}
```
### Graceful .env File Handling
The system gracefully handles missing `.env` files:
- If `.env` file exists: loads values as defaults
- If `.env` file is missing: continues with OS environment variables only
- No errors are thrown for missing `.env` files
## Examples
### Web Command Config
```go
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
```go
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 Priority
The configuration system loads values with explicit priority:
1. OS environment variables (system-level, highest priority)
2. .env file values (file-based, development defaults)
### 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 in .env**: Use `.env` files to provide development defaults and override with OS environment variables for production.
7. **Leverage the priority system**: Use `.env` for development defaults and OS environment variables for production overrides.
8. **Keep secrets in OS environment variables**: Never put sensitive values like API keys or passwords in `.env` files; use OS environment variables instead.
## 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:
```go
// 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) - same API!
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 exists but cannot be loaded
- Environment variables cannot be parsed into the expected types
- Required fields are missing (handled by your application logic)
Note: Missing `.env` files do not cause errors - the system will continue with OS environment variables only.
## Security Considerations
1. **Never commit `.env` files with secrets**: Add `.env` to your `.gitignore` file and never include sensitive values
2. **Use OS environment variables for production secrets**: Set sensitive values like API keys and database passwords as OS environment variables
3. **Use environment-specific files**: Create different `.env` files for different environments (`.env.local`, `.env.development`, etc.)
4. **Validate sensitive values**: Always validate sensitive configuration values like API keys and database URIs
5. **Use secure defaults**: Provide secure default values for security-related configuration
6. **Leverage priority system for security**: Keep development defaults in `.env` and override with secure OS environment variables in production
## Deployment Strategies
### Development
```bash
# Use .env file for all configuration
./your-command
```
### Staging
```bash
# Use .env for defaults, override key values
export MONGODB_URI=mongodb://staging-db:27017
export LOG_LEVEL=warn
./your-command
```
### Production
```bash
# Override all critical values with OS environment variables
export MONGODB_URI=mongodb://prod-cluster:27017
export MONGODB_NAME=production
export LOG_LEVEL=error
export SERVER_HOST=0.0.0.0
./your-command
```
### Docker
```dockerfile
# Set production values in Dockerfile or docker-compose
ENV MONGODB_URI=mongodb://mongo:27017
ENV LOG_LEVEL=info
ENV SERVER_HOST=0.0.0.0
```