---
name: configuration-management
description: Configuration management for GoodGo services. Use for feature flags, dynamic config, environment variables, or secrets management.
dependencies: "zod>=3"
---

# Configuration Management Patterns

## When to Use This Skill

Use this skill when:
- Implementing feature flags and feature toggles
- Managing environment-specific configurations
- Implementing dynamic configuration reloading
- Managing secrets and sensitive configuration
- Implementing configuration validation
- Handling configuration versioning
- Building configuration services
- Implementing A/B testing with feature flags
- Managing configuration across multiple environments

## Core Concepts

### Configuration Types

1. **Static Configuration**: Environment variables, config files
2. **Dynamic Configuration**: Feature flags, runtime configs
3. **Secrets**: Sensitive data (API keys, passwords)
4. **Feature Flags**: Runtime feature toggles

### Configuration Sources

- Environment variables
- Configuration files (JSON, YAML)
- Configuration service (external)
- Feature flag service
- Secrets manager (Kubernetes Secrets, Vault)

## Feature Flags

### Feature Flag Service

```typescript
// src/core/config/feature-flag.service.ts
// EN: Feature flag service
// VI: Service feature flag
import { PrismaClient } from '@prisma/client';
import { logger } from '@goodgo/logger';

export interface FeatureFlag {
  key: string;
  enabled: boolean;
  userId?: string;
  percentage?: number; // EN: Percentage rollout / VI: Phần trăm rollout
  metadata?: Record<string, any>;
}

export class FeatureFlagService {
  constructor(private prisma: PrismaClient) {}

  /**
   * EN: Check if feature is enabled
   * VI: Kiểm tra xem feature có được bật không
   */
  async isEnabled(flagKey: string, userId?: string): Promise<boolean> {
    const flag = await this.prisma.featureFlag.findUnique({
      where: { key: flagKey },
    });

    if (!flag) {
      return false; // EN: Default to disabled / VI: Mặc định tắt
    }

    if (!flag.enabled) {
      return false;
    }

    // EN: User-specific flag
    // VI: Flag cụ thể cho user
    if (flag.userId && userId && flag.userId === userId) {
      return true;
    }

    // EN: Percentage rollout
    // VI: Rollout theo phần trăm
    if (flag.percentage !== null && userId) {
      const hash = this.hashUserId(userId);
      return hash < flag.percentage;
    }

    return flag.enabled;
  }

  private hashUserId(userId: string): number {
    // EN: Simple hash for consistent percentage
    // VI: Hash đơn giản cho phần trăm nhất quán
    let hash = 0;
    for (let i = 0; i < userId.length; i++) {
      hash = ((hash << 5) - hash + userId.charCodeAt(i)) & 0xffffffff;
    }
    return Math.abs(hash) % 100;
  }

  /**
   * EN: Get all enabled features for user
   * VI: Lấy tất cả features được bật cho user
   */
  async getEnabledFeatures(userId?: string): Promise<string[]> {
    const flags = await this.prisma.featureFlag.findMany({
      where: { enabled: true },
    });

    return flags
      .filter((flag) => {
        if (flag.userId && userId) {
          return flag.userId === userId;
        }
        if (flag.percentage !== null && userId) {
          return this.hashUserId(userId) < flag.percentage;
        }
        return true;
      })
      .map((flag) => flag.key);
  }
}
```

### Feature Flag Usage

```typescript
// src/modules/user/user.service.ts
// EN: Using feature flags in service
// VI: Sử dụng feature flags trong service
import { featureFlagService } from '../../core/config/feature-flag.service';

export class UserService {
  async createUser(data: CreateUserDto): Promise<User> {
    // EN: Check feature flag
    // VI: Kiểm tra feature flag
    const newAuthFlowEnabled = await featureFlagService.isEnabled(
      'new-auth-flow',
      data.userId
    );

    if (newAuthFlowEnabled) {
      // EN: Use new authentication flow
      // VI: Sử dụng authentication flow mới
      return await this.createUserWithNewFlow(data);
    }

    // EN: Use existing flow
    // VI: Sử dụng flow hiện tại
    return await this.createUserWithExistingFlow(data);
  }
}
```

## Dynamic Configuration

### Configuration Service

```typescript
// src/core/config/config.service.ts
// EN: Dynamic configuration service
// VI: Service configuration động
import { EventEmitter } from 'events';
import { logger } from '@goodgo/logger';

export interface ConfigValue {
  key: string;
  value: any;
  environment?: string;
  service?: string;
  version?: number;
}

export class ConfigService extends EventEmitter {
  private config: Map<string, any> = new Map();
  private loadInterval?: NodeJS.Timeout;

  /**
   * EN: Load configuration from source
   * VI: Tải configuration từ nguồn
   */
  async load(): Promise<void> {
    try {
      // EN: Load from database, file, or external service
      // VI: Tải từ database, file, hoặc external service
      const configs = await this.fetchConfigs();

      for (const config of configs) {
        const oldValue = this.config.get(config.key);
        this.config.set(config.key, config.value);

        // EN: Emit change event if value changed
        // VI: Emit event thay đổi nếu giá trị thay đổi
        if (oldValue !== config.value) {
          this.emit('config-changed', {
            key: config.key,
            oldValue,
            newValue: config.value,
          });
        }
      }

      logger.info('Configuration loaded', { count: configs.length });
    } catch (error) {
      logger.error('Failed to load configuration', { error });
      throw error;
    }
  }

  /**
   * EN: Get configuration value
   * VI: Lấy giá trị configuration
   */
  get<T = any>(key: string, defaultValue?: T): T {
    return (this.config.get(key) as T) ?? defaultValue!;
  }

  /**
   * EN: Start auto-reload
   * VI: Bắt đầu auto-reload
   */
  startAutoReload(intervalMs: number = 60000): void {
    this.loadInterval = setInterval(() => {
      this.load().catch((error) => {
        logger.error('Auto-reload failed', { error });
      });
    }, intervalMs);
  }

  /**
   * EN: Stop auto-reload
   * VI: Dừng auto-reload
   */
  stopAutoReload(): void {
    if (this.loadInterval) {
      clearInterval(this.loadInterval);
    }
  }

  private async fetchConfigs(): Promise<ConfigValue[]> {
    // EN: Fetch from database or external service
    // VI: Fetch từ database hoặc external service
    // Implementation depends on storage backend
    return [];
  }
}

export const configService = new ConfigService();

// EN: Listen for config changes
// VI: Lắng nghe thay đổi config
configService.on('config-changed', ({ key, oldValue, newValue }) => {
  logger.info('Configuration changed', { key, oldValue, newValue });
});
```

## Configuration Validation

```typescript
// src/core/config/config-validator.ts
// EN: Configuration validation with Zod
// VI: Validation configuration với Zod
import { z } from 'zod';
import { logger } from '@goodgo/logger';

const AppConfigSchema = z.object({
  port: z.number().int().min(1).max(65535),
  nodeEnv: z.enum(['development', 'staging', 'production']),
  databaseUrl: z.string().url(),
  redisUrl: z.string().url().optional(),
  logLevel: z.enum(['error', 'warn', 'info', 'debug']).default('info'),
  jwtSecret: z.string().min(32),
  jwtExpiresIn: z.string().default('15m'),
});

export type AppConfig = z.infer<typeof AppConfigSchema>;

export function validateConfig(env: Record<string, any>): AppConfig {
  try {
    return AppConfigSchema.parse({
      port: parseInt(env.PORT || '5000', 10),
      nodeEnv: env.NODE_ENV || 'development',
      databaseUrl: env.DATABASE_URL,
      redisUrl: env.REDIS_URL,
      logLevel: env.LOG_LEVEL,
      jwtSecret: env.JWT_SECRET,
      jwtExpiresIn: env.JWT_EXPIRES_IN,
    });
  } catch (error) {
    if (error instanceof z.ZodError) {
      logger.error('Configuration validation failed', {
        errors: error.errors,
      });
      throw new Error(`Invalid configuration: ${error.errors.map((e) => e.message).join(', ')}`);
    }
    throw error;
  }
}
```

## Secrets Management

```typescript
// src/core/config/secrets.service.ts
// EN: Secrets management service
// VI: Service quản lý secrets
import { logger } from '@goodgo/logger';

export class SecretsService {
  private secrets: Map<string, string> = new Map();
  private rotationInterval?: NodeJS.Timeout;

  /**
   * EN: Get secret value
   * VI: Lấy giá trị secret
   */
  async getSecret(key: string): Promise<string> {
    if (this.secrets.has(key)) {
      return this.secrets.get(key)!;
    }

    // EN: Load from Kubernetes Secrets, Vault, or environment
    // VI: Tải từ Kubernetes Secrets, Vault, hoặc environment
    const value = await this.loadSecret(key);
    this.secrets.set(key, value);
    return value;
  }

  /**
   * EN: Rotate secrets periodically
   * VI: Rotate secrets định kỳ
   */
  startRotation(intervalMs: number = 3600000): void {
    this.rotationInterval = setInterval(async () => {
      logger.info('Rotating secrets');
      // EN: Reload all secrets
      // VI: Tải lại tất cả secrets
      for (const key of this.secrets.keys()) {
        const newValue = await this.loadSecret(key);
        this.secrets.set(key, newValue);
      }
    }, intervalMs);
  }

  private async loadSecret(key: string): Promise<string> {
    // EN: Load from Kubernetes Secrets
    // VI: Tải từ Kubernetes Secrets
    if (process.env[`SECRET_${key.toUpperCase()}`]) {
      return process.env[`SECRET_${key.toUpperCase()}`]!;
    }

    // EN: Or load from external secrets manager
    // VI: Hoặc tải từ external secrets manager
    throw new Error(`Secret ${key} not found`);
  }
}
```

## Environment-Specific Configuration

```typescript
// src/config/app.config.ts
// EN: Environment-specific configuration
// VI: Configuration theo environment
import { validateConfig } from '../core/config/config-validator';

const envConfig = {
  development: {
    logLevel: 'debug',
    enableSwagger: true,
    databasePoolSize: 5,
  },
  staging: {
    logLevel: 'info',
    enableSwagger: true,
    databasePoolSize: 10,
  },
  production: {
    logLevel: 'warn',
    enableSwagger: false,
    databasePoolSize: 20,
  },
};

export function getAppConfig() {
  const baseConfig = validateConfig(process.env);
  const env = baseConfig.nodeEnv;
  const envSpecific = envConfig[env] || envConfig.development;

  return {
    ...baseConfig,
    ...envSpecific,
  };
}
```

## Best Practices

1. **Validation**: Always validate configuration at startup
2. **Defaults**: Provide sensible defaults
3. **Secrets**: Never commit secrets to code
4. **Feature Flags**: Use for gradual rollouts
5. **Reloading**: Support dynamic reloading where possible
6. **Monitoring**: Monitor configuration changes

## Common Mistakes

1. **Not Validating Config**: App crashes with cryptic errors
   ```typescript
   // ❌ BAD: No validation
   const port = process.env.PORT;

   // ✅ GOOD: Validate at startup
   const config = AppConfigSchema.parse(process.env);
   ```

2. **Committing Secrets**: Exposing credentials
   ```bash
   # ❌ BAD: Secrets in code
   JWT_SECRET=my-secret-key

   # ✅ GOOD: Use .env.example with placeholders
   JWT_SECRET=<your-jwt-secret>
   ```

3. **Feature Flags Without Fallback**: Breaking when flag not found
   ```typescript
   // ❌ BAD: No fallback
   if (await featureFlags.isEnabled('new-feature')) { ... }

   // ✅ GOOD: Default to disabled
   if (await featureFlags.isEnabled('new-feature') ?? false) { ... }
   ```

4. **Inconsistent Env Naming**: Confusion across environments
   ```bash
   # ❌ BAD: Inconsistent
   DB_URL, DATABASE_CONNECTION, postgres_url

   # ✅ GOOD: Consistent pattern
   DATABASE_URL, REDIS_URL, KAFKA_URL
   ```

## Quick Reference

| Config Type | Source | Example |
|-------------|--------|---------|
| **Static** | Environment vars | `DATABASE_URL` |
| **Dynamic** | Config service | `rate_limit_threshold` |
| **Secrets** | K8s Secrets/Vault | `JWT_SECRET` |
| **Feature Flags** | Database | `new_checkout_flow` |

**Environment Variable Naming:**
```bash
# Pattern: SERVICE_CATEGORY_NAME
DATABASE_URL          # Connection strings
REDIS_HOST            # Service hosts
JWT_SECRET            # Secrets (use K8s secrets)
LOG_LEVEL             # Configuration
FEATURE_NEW_UI        # Feature flags
```

**Zod Validation Pattern:**
```typescript
const ConfigSchema = z.object({
  port: z.coerce.number().default(3000),
  nodeEnv: z.enum(['development', 'staging', 'production']),
  databaseUrl: z.string().url(),
  logLevel: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
});

export const config = ConfigSchema.parse(process.env);
```

**Feature Flag Check Pattern:**
```typescript
// Simple check
const enabled = await featureFlags.isEnabled('feature-key');

// With user context (for gradual rollout)
const enabled = await featureFlags.isEnabled('feature-key', userId);
```

## Resources

- [Feature Flags Pattern](https://martinfowler.com/articles/feature-toggles.html)
- [12-Factor App Config](https://12factor.net/config)
- [Project Rules](../project-rules/SKILL.md) - GoodGo coding standards
