configuration-management

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Configuration Management

配置管理

Overview

概述

Comprehensive guide to managing application configuration across environments, including environment variables, configuration files, secrets, feature flags, and following 12-factor app methodology.
本指南全面介绍了跨环境管理应用程序配置的方法,包括环境变量、配置文件、密钥、Feature Flags,以及遵循12-factor app方法论。

When to Use

适用场景

  • Setting up configuration for different environments
  • Managing secrets and credentials
  • Implementing feature flags
  • Creating configuration hierarchies
  • Following 12-factor app principles
  • Migrating configuration to cloud services
  • Implementing dynamic configuration
  • Managing multi-tenant configurations
  • 为不同环境配置应用程序
  • 管理密钥和凭证
  • 实现Feature Flags
  • 创建配置层级
  • 遵循12-factor app原则
  • 将配置迁移到云服务
  • 实现动态配置
  • 管理多租户配置

Instructions

操作指南

1. Environment Variables

1. Environment Variables

Basic Setup (.env files)

基础设置(.env文件)

bash
undefined
bash
undefined

.env.development

.env.development

NODE_ENV=development PORT=3000 DATABASE_URL=postgresql://localhost:5432/myapp_dev REDIS_URL=redis://localhost:6379 LOG_LEVEL=debug API_KEY=dev-api-key-12345
NODE_ENV=development PORT=3000 DATABASE_URL=postgresql://localhost:5432/myapp_dev REDIS_URL=redis://localhost:6379 LOG_LEVEL=debug API_KEY=dev-api-key-12345

.env.production

.env.production

NODE_ENV=production PORT=8080 DATABASE_URL=${DATABASE_URL} # From environment REDIS_URL=${REDIS_URL} LOG_LEVEL=info API_KEY=${API_KEY} # From secret manager
NODE_ENV=production PORT=8080 DATABASE_URL=${DATABASE_URL} # From environment REDIS_URL=${REDIS_URL} LOG_LEVEL=info API_KEY=${API_KEY} # From secret manager

.env.test

.env.test

NODE_ENV=test DATABASE_URL=postgresql://localhost:5432/myapp_test LOG_LEVEL=error
undefined
NODE_ENV=test DATABASE_URL=postgresql://localhost:5432/myapp_test LOG_LEVEL=error
undefined

Loading Environment Variables

加载环境变量

typescript
// config/env.ts
import dotenv from 'dotenv';
import path from 'path';

// Load environment-specific .env file
const envFile = `.env.${process.env.NODE_ENV || 'development'}`;
dotenv.config({ path: path.resolve(process.cwd(), envFile) });

// Validate required variables
const required = ['DATABASE_URL', 'PORT', 'API_KEY'];
const missing = required.filter(key => !process.env[key]);

if (missing.length > 0) {
  throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
}

// Export typed configuration
export const config = {
  env: process.env.NODE_ENV || 'development',
  port: parseInt(process.env.PORT || '3000', 10),
  database: {
    url: process.env.DATABASE_URL!,
    poolSize: parseInt(process.env.DB_POOL_SIZE || '10', 10)
  },
  redis: {
    url: process.env.REDIS_URL || 'redis://localhost:6379'
  },
  logging: {
    level: process.env.LOG_LEVEL || 'info'
  },
  api: {
    key: process.env.API_KEY!,
    timeout: parseInt(process.env.API_TIMEOUT || '5000', 10)
  }
} as const;
typescript
// config/env.ts
import dotenv from 'dotenv';
import path from 'path';

// Load environment-specific .env file
const envFile = `.env.${process.env.NODE_ENV || 'development'}`;
dotenv.config({ path: path.resolve(process.cwd(), envFile) });

// Validate required variables
const required = ['DATABASE_URL', 'PORT', 'API_KEY'];
const missing = required.filter(key => !process.env[key]);

if (missing.length > 0) {
  throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
}

// Export typed configuration
export const config = {
  env: process.env.NODE_ENV || 'development',
  port: parseInt(process.env.PORT || '3000', 10),
  database: {
    url: process.env.DATABASE_URL!,
    poolSize: parseInt(process.env.DB_POOL_SIZE || '10', 10)
  },
  redis: {
    url: process.env.REDIS_URL || 'redis://localhost:6379'
  },
  logging: {
    level: process.env.LOG_LEVEL || 'info'
  },
  api: {
    key: process.env.API_KEY!,
    timeout: parseInt(process.env.API_TIMEOUT || '5000', 10)
  }
} as const;

Python Configuration

Python配置

python
undefined
python
undefined

config/settings.py

config/settings.py

import os from pathlib import Path from dotenv import load_dotenv
import os from pathlib import Path from dotenv import load_dotenv

Load .env file

Load .env file

env_file = f'.env.{os.getenv("ENVIRONMENT", "development")}' load_dotenv(Path(file).parent.parent / env_file)
class Config: """Base configuration""" ENV = os.getenv('ENVIRONMENT', 'development') DEBUG = os.getenv('DEBUG', 'False').lower() == 'true' SECRET_KEY = os.getenv('SECRET_KEY')
# Database
DATABASE_URL = os.getenv('DATABASE_URL')
DB_POOL_SIZE = int(os.getenv('DB_POOL_SIZE', 10))

# Redis
REDIS_URL = os.getenv('REDIS_URL', 'redis://localhost:6379')

# API
API_KEY = os.getenv('API_KEY')
API_TIMEOUT = int(os.getenv('API_TIMEOUT', 5000))
class DevelopmentConfig(Config): """Development configuration""" DEBUG = True LOG_LEVEL = 'DEBUG'
class ProductionConfig(Config): """Production configuration""" DEBUG = False LOG_LEVEL = 'INFO'
class TestConfig(Config): """Test configuration""" TESTING = True DATABASE_URL = 'sqlite:///:memory:'
env_file = f'.env.{os.getenv("ENVIRONMENT", "development")}' load_dotenv(Path(file).parent.parent / env_file)
class Config: """Base configuration""" ENV = os.getenv('ENVIRONMENT', 'development') DEBUG = os.getenv('DEBUG', 'False').lower() == 'true' SECRET_KEY = os.getenv('SECRET_KEY')
# Database
DATABASE_URL = os.getenv('DATABASE_URL')
DB_POOL_SIZE = int(os.getenv('DB_POOL_SIZE', 10))

# Redis
REDIS_URL = os.getenv('REDIS_URL', 'redis://localhost:6379')

# API
API_KEY = os.getenv('API_KEY')
API_TIMEOUT = int(os.getenv('API_TIMEOUT', 5000))
class DevelopmentConfig(Config): """Development configuration""" DEBUG = True LOG_LEVEL = 'DEBUG'
class ProductionConfig(Config): """Production configuration""" DEBUG = False LOG_LEVEL = 'INFO'
class TestConfig(Config): """Test configuration""" TESTING = True DATABASE_URL = 'sqlite:///:memory:'

Configuration dictionary

Configuration dictionary

config_by_name = { 'development': DevelopmentConfig, 'production': ProductionConfig, 'test': TestConfig }
config_by_name = { 'development': DevelopmentConfig, 'production': ProductionConfig, 'test': TestConfig }

Get active config

Get active config

config = config_by_nameConfig.ENV
undefined
config = config_by_nameConfig.ENV
undefined

2. Configuration Hierarchies

2. 配置层级

typescript
// config/config.ts
import deepmerge from 'deepmerge';

// Base configuration (shared across all environments)
const baseConfig = {
  app: {
    name: 'MyApp',
    version: '1.0.0'
  },
  server: {
    timeout: 30000,
    bodyLimit: '100kb'
  },
  database: {
    poolSize: 10,
    idleTimeout: 30000
  },
  logging: {
    format: 'json',
    destination: 'stdout'
  }
};

// Environment-specific overrides
const developmentConfig = {
  server: {
    port: 3000
  },
  database: {
    url: 'postgresql://localhost:5432/myapp_dev',
    logging: true
  },
  logging: {
    level: 'debug',
    prettyPrint: true
  }
};

const productionConfig = {
  server: {
    port: 8080,
    trustProxy: true
  },
  database: {
    url: process.env.DATABASE_URL,
    ssl: true,
    logging: false
  },
  logging: {
    level: 'info',
    prettyPrint: false
  }
};

// Merge configurations
const configs = {
  development: deepmerge(baseConfig, developmentConfig),
  production: deepmerge(baseConfig, productionConfig),
  test: deepmerge(baseConfig, {
    database: { url: 'postgresql://localhost:5432/myapp_test' }
  })
};

export const config = configs[process.env.NODE_ENV || 'development'];
typescript
// config/config.ts
import deepmerge from 'deepmerge';

// Base configuration (shared across all environments)
const baseConfig = {
  app: {
    name: 'MyApp',
    version: '1.0.0'
  },
  server: {
    timeout: 30000,
    bodyLimit: '100kb'
  },
  database: {
    poolSize: 10,
    idleTimeout: 30000
  },
  logging: {
    format: 'json',
    destination: 'stdout'
  }
};

// Environment-specific overrides
const developmentConfig = {
  server: {
    port: 3000
  },
  database: {
    url: 'postgresql://localhost:5432/myapp_dev',
    logging: true
  },
  logging: {
    level: 'debug',
    prettyPrint: true
  }
};

const productionConfig = {
  server: {
    port: 8080,
    trustProxy: true
  },
  database: {
    url: process.env.DATABASE_URL,
    ssl: true,
    logging: false
  },
  logging: {
    level: 'info',
    prettyPrint: false
  }
};

// Merge configurations
const configs = {
  development: deepmerge(baseConfig, developmentConfig),
  production: deepmerge(baseConfig, productionConfig),
  test: deepmerge(baseConfig, {
    database: { url: 'postgresql://localhost:5432/myapp_test' }
  })
};

export const config = configs[process.env.NODE_ENV || 'development'];

YAML Configuration Files

YAML配置文件

yaml
undefined
yaml
undefined

config/default.yml

config/default.yml

app: name: MyApp version: 1.0.0
server: timeout: 30000 bodyLimit: 100kb
database: poolSize: 10 idleTimeout: 30000
app: name: MyApp version: 1.0.0
server: timeout: 30000 bodyLimit: 100kb
database: poolSize: 10 idleTimeout: 30000

config/development.yml

config/development.yml

server: port: 3000
database: url: postgresql://localhost:5432/myapp_dev logging: true
logging: level: debug prettyPrint: true
server: port: 3000
database: url: postgresql://localhost:5432/myapp_dev logging: true
logging: level: debug prettyPrint: true

config/production.yml

config/production.yml

server: port: 8080 trustProxy: true
database: url: ${DATABASE_URL} ssl: true logging: false
logging: level: info prettyPrint: false

```typescript
// Load YAML config
import yaml from 'js-yaml';
import fs from 'fs';
import path from 'path';

function loadYamlConfig(env: string) {
  const defaultConfig = yaml.load(
    fs.readFileSync(path.join(__dirname, 'config/default.yml'), 'utf8')
  );

  const envConfig = yaml.load(
    fs.readFileSync(path.join(__dirname, `config/${env}.yml`), 'utf8')
  );

  return deepmerge(defaultConfig, envConfig);
}

export const config = loadYamlConfig(process.env.NODE_ENV || 'development');
server: port: 8080 trustProxy: true
database: url: ${DATABASE_URL} ssl: true logging: false
logging: level: info prettyPrint: false

```typescript
// Load YAML config
import yaml from 'js-yaml';
import fs from 'fs';
import path from 'path';

function loadYamlConfig(env: string) {
  const defaultConfig = yaml.load(
    fs.readFileSync(path.join(__dirname, 'config/default.yml'), 'utf8')
  );

  const envConfig = yaml.load(
    fs.readFileSync(path.join(__dirname, `config/${env}.yml`), 'utf8')
  );

  return deepmerge(defaultConfig, envConfig);
}

export const config = loadYamlConfig(process.env.NODE_ENV || 'development');

3. Secret Management

3. 密钥管理

AWS Secrets Manager

AWS Secrets Manager

typescript
// secrets/aws-secrets-manager.ts
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';

export class SecretManager {
  private client: SecretsManagerClient;
  private cache = new Map<string, { value: any; expiry: number }>();
  private cacheTtl = 300000; // 5 minutes

  constructor() {
    this.client = new SecretsManagerClient({ region: process.env.AWS_REGION });
  }

  async getSecret(secretName: string): Promise<any> {
    // Check cache
    const cached = this.cache.get(secretName);
    if (cached && cached.expiry > Date.now()) {
      return cached.value;
    }

    try {
      const command = new GetSecretValueCommand({ SecretId: secretName });
      const response = await this.client.send(command);

      const secret = JSON.parse(response.SecretString || '{}');

      // Cache the secret
      this.cache.set(secretName, {
        value: secret,
        expiry: Date.now() + this.cacheTtl
      });

      return secret;
    } catch (error) {
      throw new Error(`Failed to retrieve secret ${secretName}: ${error.message}`);
    }
  }

  async getDatabaseCredentials(): Promise<DatabaseCredentials> {
    return this.getSecret('prod/database/credentials');
  }

  async getApiKey(service: string): Promise<string> {
    const secrets = await this.getSecret('prod/api-keys');
    return secrets[service];
  }
}

// Usage
const secretManager = new SecretManager();

async function connectDatabase() {
  const credentials = await secretManager.getDatabaseCredentials();

  return createConnection({
    host: credentials.host,
    port: credentials.port,
    username: credentials.username,
    password: credentials.password,
    database: credentials.database
  });
}
typescript
// secrets/aws-secrets-manager.ts
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';

export class SecretManager {
  private client: SecretsManagerClient;
  private cache = new Map<string, { value: any; expiry: number }>();
  private cacheTtl = 300000; // 5 minutes

  constructor() {
    this.client = new SecretsManagerClient({ region: process.env.AWS_REGION });
  }

  async getSecret(secretName: string): Promise<any> {
    // Check cache
    const cached = this.cache.get(secretName);
    if (cached && cached.expiry > Date.now()) {
      return cached.value;
    }

    try {
      const command = new GetSecretValueCommand({ SecretId: secretName });
      const response = await this.client.send(command);

      const secret = JSON.parse(response.SecretString || '{}');

      // Cache the secret
      this.cache.set(secretName, {
        value: secret,
        expiry: Date.now() + this.cacheTtl
      });

      return secret;
    } catch (error) {
      throw new Error(`Failed to retrieve secret ${secretName}: ${error.message}`);
    }
  }

  async getDatabaseCredentials(): Promise<DatabaseCredentials> {
    return this.getSecret('prod/database/credentials');
  }

  async getApiKey(service: string): Promise<string> {
    const secrets = await this.getSecret('prod/api-keys');
    return secrets[service];
  }
}

// Usage
const secretManager = new SecretManager();

async function connectDatabase() {
  const credentials = await secretManager.getDatabaseCredentials();

  return createConnection({
    host: credentials.host,
    port: credentials.port,
    username: credentials.username,
    password: credentials.password,
    database: credentials.database
  });
}

HashiCorp Vault

HashiCorp Vault

typescript
// secrets/vault.ts
import vault from 'node-vault';

export class VaultClient {
  private client: any;

  constructor() {
    this.client = vault({
      apiVersion: 'v1',
      endpoint: process.env.VAULT_ADDR || 'http://localhost:8200',
      token: process.env.VAULT_TOKEN
    });
  }

  async getSecret(path: string): Promise<any> {
    try {
      const result = await this.client.read(path);
      return result.data.data;
    } catch (error) {
      throw new Error(`Failed to read secret from ${path}: ${error.message}`);
    }
  }

  async getDatabaseConfig(): Promise<DatabaseConfig> {
    return this.getSecret('secret/data/database');
  }

  async getApiKeys(): Promise<Record<string, string>> {
    return this.getSecret('secret/data/api-keys');
  }

  // Dynamic database credentials (rotated automatically)
  async getDynamicDBCredentials(): Promise<Credentials> {
    const result = await this.client.read('database/creds/readonly');
    return {
      username: result.data.username,
      password: result.data.password,
      leaseId: result.lease_id,
      leaseDuration: result.lease_duration
    };
  }
}
typescript
// secrets/vault.ts
import vault from 'node-vault';

export class VaultClient {
  private client: any;

  constructor() {
    this.client = vault({
      apiVersion: 'v1',
      endpoint: process.env.VAULT_ADDR || 'http://localhost:8200',
      token: process.env.VAULT_TOKEN
    });
  }

  async getSecret(path: string): Promise<any> {
    try {
      const result = await this.client.read(path);
      return result.data.data;
    } catch (error) {
      throw new Error(`Failed to read secret from ${path}: ${error.message}`);
    }
  }

  async getDatabaseConfig(): Promise<DatabaseConfig> {
    return this.getSecret('secret/data/database');
  }

  async getApiKeys(): Promise<Record<string, string>> {
    return this.getSecret('secret/data/api-keys');
  }

  // Dynamic database credentials (rotated automatically)
  async getDynamicDBCredentials(): Promise<Credentials> {
    const result = await this.client.read('database/creds/readonly');
    return {
      username: result.data.username,
      password: result.data.password,
      leaseId: result.lease_id,
      leaseDuration: result.lease_duration
    };
  }
}

Environment-Specific Secrets

环境专属密钥

typescript
// secrets/secret-provider.ts
export interface SecretProvider {
  getSecret(key: string): Promise<string>;
}

// Development: Use .env file
export class EnvFileSecretProvider implements SecretProvider {
  async getSecret(key: string): Promise<string> {
    const value = process.env[key];
    if (!value) {
      throw new Error(`Secret ${key} not found in environment`);
    }
    return value;
  }
}

// Production: Use AWS Secrets Manager
export class AWSSecretProvider implements SecretProvider {
  private secretManager: SecretManager;

  constructor() {
    this.secretManager = new SecretManager();
  }

  async getSecret(key: string): Promise<string> {
    const secrets = await this.secretManager.getSecret('prod/secrets');
    return secrets[key];
  }
}

// Factory
export function createSecretProvider(): SecretProvider {
  if (process.env.NODE_ENV === 'production') {
    return new AWSSecretProvider();
  }
  return new EnvFileSecretProvider();
}
typescript
// secrets/secret-provider.ts
export interface SecretProvider {
  getSecret(key: string): Promise<string>;
}

// Development: Use .env file
export class EnvFileSecretProvider implements SecretProvider {
  async getSecret(key: string): Promise<string> {
    const value = process.env[key];
    if (!value) {
      throw new Error(`Secret ${key} not found in environment`);
    }
    return value;
  }
}

// Production: Use AWS Secrets Manager
export class AWSSecretProvider implements SecretProvider {
  private secretManager: SecretManager;

  constructor() {
    this.secretManager = new SecretManager();
  }

  async getSecret(key: string): Promise<string> {
    const secrets = await this.secretManager.getSecret('prod/secrets');
    return secrets[key];
  }
}

// Factory
export function createSecretProvider(): SecretProvider {
  if (process.env.NODE_ENV === 'production') {
    return new AWSSecretProvider();
  }
  return new EnvFileSecretProvider();
}

4. Feature Flags

4. Feature Flags

Simple Feature Flag Implementation

简单Feature Flags实现

typescript
// feature-flags/feature-flag.ts
export interface FeatureFlag {
  enabled: boolean;
  rolloutPercentage?: number;
  allowedUsers?: string[];
  allowedEnvironments?: string[];
}

export class FeatureFlagManager {
  private flags: Map<string, FeatureFlag>;

  constructor(flags: Record<string, FeatureFlag>) {
    this.flags = new Map(Object.entries(flags));
  }

  isEnabled(
    flagName: string,
    context?: { userId?: string; environment?: string }
  ): boolean {
    const flag = this.flags.get(flagName);
    if (!flag) return false;

    // Check if disabled globally
    if (!flag.enabled) return false;

    // Check environment restriction
    if (flag.allowedEnvironments && context?.environment) {
      if (!flag.allowedEnvironments.includes(context.environment)) {
        return false;
      }
    }

    // Check user whitelist
    if (flag.allowedUsers && context?.userId) {
      if (flag.allowedUsers.includes(context.userId)) {
        return true;
      }
    }

    // Check rollout percentage
    if (flag.rolloutPercentage !== undefined && context?.userId) {
      const hash = this.hashUserId(context.userId);
      return (hash % 100) < flag.rolloutPercentage;
    }

    return true;
  }

  private hashUserId(userId: string): number {
    let hash = 0;
    for (let i = 0; i < userId.length; i++) {
      hash = ((hash << 5) - hash) + userId.charCodeAt(i);
      hash |= 0;
    }
    return Math.abs(hash);
  }
}

// Configuration
const featureFlags = {
  'new-dashboard': {
    enabled: true,
    rolloutPercentage: 50 // 50% of users
  },
  'experimental-feature': {
    enabled: true,
    allowedUsers: ['user-123', 'user-456'],
    allowedEnvironments: ['development', 'staging']
  },
  'beta-api': {
    enabled: true,
    rolloutPercentage: 10
  }
};

const flagManager = new FeatureFlagManager(featureFlags);

// Usage
app.get('/api/dashboard', (req, res) => {
  if (flagManager.isEnabled('new-dashboard', {
    userId: req.user.id,
    environment: process.env.NODE_ENV
  })) {
    return res.json(getNewDashboard());
  }

  return res.json(getOldDashboard());
});
typescript
// feature-flags/feature-flag.ts
export interface FeatureFlag {
  enabled: boolean;
  rolloutPercentage?: number;
  allowedUsers?: string[];
  allowedEnvironments?: string[];
}

export class FeatureFlagManager {
  private flags: Map<string, FeatureFlag>;

  constructor(flags: Record<string, FeatureFlag>) {
    this.flags = new Map(Object.entries(flags));
  }

  isEnabled(
    flagName: string,
    context?: { userId?: string; environment?: string }
  ): boolean {
    const flag = this.flags.get(flagName);
    if (!flag) return false;

    // Check if disabled globally
    if (!flag.enabled) return false;

    // Check environment restriction
    if (flag.allowedEnvironments && context?.environment) {
      if (!flag.allowedEnvironments.includes(context.environment)) {
        return false;
      }
    }

    // Check user whitelist
    if (flag.allowedUsers && context?.userId) {
      if (flag.allowedUsers.includes(context.userId)) {
        return true;
      }
    }

    // Check rollout percentage
    if (flag.rolloutPercentage !== undefined && context?.userId) {
      const hash = this.hashUserId(context.userId);
      return (hash % 100) < flag.rolloutPercentage;
    }

    return true;
  }

  private hashUserId(userId: string): number {
    let hash = 0;
    for (let i = 0; i < userId.length; i++) {
      hash = ((hash << 5) - hash) + userId.charCodeAt(i);
      hash |= 0;
    }
    return Math.abs(hash);
  }
}

// Configuration
const featureFlags = {
  'new-dashboard': {
    enabled: true,
    rolloutPercentage: 50 // 50% of users
  },
  'experimental-feature': {
    enabled: true,
    allowedUsers: ['user-123', 'user-456'],
    allowedEnvironments: ['development', 'staging']
  },
  'beta-api': {
    enabled: true,
    rolloutPercentage: 10
  }
};

const flagManager = new FeatureFlagManager(featureFlags);

// Usage
app.get('/api/dashboard', (req, res) => {
  if (flagManager.isEnabled('new-dashboard', {
    userId: req.user.id,
    environment: process.env.NODE_ENV
  })) {
    return res.json(getNewDashboard());
  }

  return res.json(getOldDashboard());
});

LaunchDarkly Integration

LaunchDarkly集成

typescript
// feature-flags/launchdarkly.ts
import LaunchDarkly from 'launchdarkly-node-server-sdk';

export class LaunchDarklyClient {
  private client: LaunchDarkly.LDClient;

  async initialize() {
    this.client = LaunchDarkly.init(process.env.LAUNCHDARKLY_SDK_KEY!);
    await this.client.waitForInitialization();
  }

  async isEnabled(flagKey: string, user: LaunchDarkly.LDUser): Promise<boolean> {
    return this.client.variation(flagKey, user, false);
  }

  async getVariation<T>(
    flagKey: string,
    user: LaunchDarkly.LDUser,
    defaultValue: T
  ): Promise<T> {
    return this.client.variation(flagKey, user, defaultValue);
  }

  close() {
    this.client.close();
  }
}

// Usage
const ldClient = new LaunchDarklyClient();
await ldClient.initialize();

app.get('/api/dashboard', async (req, res) => {
  const user = {
    key: req.user.id,
    email: req.user.email,
    custom: {
      groups: req.user.groups
    }
  };

  const showNewDashboard = await ldClient.isEnabled('new-dashboard', user);

  if (showNewDashboard) {
    return res.json(getNewDashboard());
  }

  return res.json(getOldDashboard());
});
typescript
// feature-flags/launchdarkly.ts
import LaunchDarkly from 'launchdarkly-node-server-sdk';

export class LaunchDarklyClient {
  private client: LaunchDarkly.LDClient;

  async initialize() {
    this.client = LaunchDarkly.init(process.env.LAUNCHDARKLY_SDK_KEY!);
    await this.client.waitForInitialization();
  }

  async isEnabled(flagKey: string, user: LaunchDarkly.LDUser): Promise<boolean> {
    return this.client.variation(flagKey, user, false);
  }

  async getVariation<T>(
    flagKey: string,
    user: LaunchDarkly.LDUser,
    defaultValue: T
  ): Promise<T> {
    return this.client.variation(flagKey, user, defaultValue);
  }

  close() {
    this.client.close();
  }
}

// Usage
const ldClient = new LaunchDarklyClient();
await ldClient.initialize();

app.get('/api/dashboard', async (req, res) => {
  const user = {
    key: req.user.id,
    email: req.user.email,
    custom: {
      groups: req.user.groups
    }
  };

  const showNewDashboard = await ldClient.isEnabled('new-dashboard', user);

  if (showNewDashboard) {
    return res.json(getNewDashboard());
  }

  return res.json(getOldDashboard());
});

5. 12-Factor App Configuration

5. 12-Factor App Configuration

typescript
// config/twelve-factor.ts

/**
 * 12-Factor App Configuration Principles
 *
 * III. Config - Store config in the environment
 * - Strict separation of config from code
 * - Config varies between deploys, code does not
 * - Store in environment variables
 */

// ✅ Good: Configuration from environment
export const config = {
  database: {
    url: process.env.DATABASE_URL!,
    poolMin: parseInt(process.env.DB_POOL_MIN || '2', 10),
    poolMax: parseInt(process.env.DB_POOL_MAX || '10', 10)
  },
  redis: {
    url: process.env.REDIS_URL!
  },
  s3: {
    bucket: process.env.S3_BUCKET!,
    region: process.env.AWS_REGION!
  },
  sendgrid: {
    apiKey: process.env.SENDGRID_API_KEY!
  }
};

// ❌ Bad: Hardcoded configuration
const badConfig = {
  database: {
    host: 'prod-db.example.com',  // Hardcoded!
    password: 'secretpassword'     // Secret in code!
  }
};

/**
 * Backing Services - Treat backing services as attached resources
 * - Database, cache, message queue, etc. are accessed via URLs
 * - Should be swappable without code changes
 */

// ✅ Good: Backing service as URL
const db = createConnection(process.env.DATABASE_URL);
const cache = createClient(process.env.REDIS_URL);

// Can swap services by changing environment variable
// DATABASE_URL=postgresql://localhost/dev  (local dev)
// DATABASE_URL=postgresql://prod-db/app     (production)

/**
 * Disposability - Fast startup and graceful shutdown
 */
function startServer() {
  const server = app.listen(config.port, () => {
    console.log(`Server started on port ${config.port}`);
  });

  // Graceful shutdown
  process.on('SIGTERM', async () => {
    console.log('SIGTERM received, shutting down gracefully');

    server.close(() => {
      console.log('HTTP server closed');
    });

    await db.close();
    await cache.quit();

    process.exit(0);
  });
}
typescript
// config/twelve-factor.ts

/**
 * 12-Factor App配置原则
 *
 * III. 配置 - 将配置存储在环境中
 * - 严格区分配置与代码
 * - 配置随部署环境变化,代码保持不变
 * - 存储在环境变量中
 */

// ✅ 良好实践:从环境中获取配置
export const config = {
  database: {
    url: process.env.DATABASE_URL!,
    poolMin: parseInt(process.env.DB_POOL_MIN || '2', 10),
    poolMax: parseInt(process.env.DB_POOL_MAX || '10', 10)
  },
  redis: {
    url: process.env.REDIS_URL!
  },
  s3: {
    bucket: process.env.S3_BUCKET!,
    region: process.env.AWS_REGION!
  },
  sendgrid: {
    apiKey: process.env.SENDGRID_API_KEY!
  }
};

// ❌ 不良实践:硬编码配置
const badConfig = {
  database: {
    host: 'prod-db.example.com',  // 硬编码!
    password: 'secretpassword'     // 密钥写入代码!
  }
};

/**
 * 后端服务 - 将后端服务视为附加资源
 * - 数据库、缓存、消息队列等通过URL访问
 * - 无需修改代码即可替换服务
 */

// ✅ 良好实践:通过URL访问后端服务
const db = createConnection(process.env.DATABASE_URL);
const cache = createClient(process.env.REDIS_URL);

// 通过修改环境变量即可替换服务
// DATABASE_URL=postgresql://localhost/dev  (本地开发)
// DATABASE_URL=postgresql://prod-db/app     (生产环境)

/**
 * 可处置性 - 快速启动与优雅关闭
 */
function startServer() {
  const server = app.listen(config.port, () => {
    console.log(`服务器已启动,端口:${config.port}`);
  });

  // 优雅关闭
  process.on('SIGTERM', async () => {
    console.log('收到SIGTERM信号,开始优雅关闭');

    server.close(() => {
      console.log('HTTP服务器已关闭');
    });

    await db.close();
    await cache.quit();

    process.exit(0);
  });
}

6. Configuration Validation

6. 配置验证

typescript
// config/validation.ts
import Joi from 'joi';

const configSchema = Joi.object({
  NODE_ENV: Joi.string()
    .valid('development', 'production', 'test')
    .default('development'),

  PORT: Joi.number()
    .port()
    .default(3000),

  DATABASE_URL: Joi.string()
    .uri()
    .required(),

  REDIS_URL: Joi.string()
    .uri()
    .default('redis://localhost:6379'),

  LOG_LEVEL: Joi.string()
    .valid('debug', 'info', 'warn', 'error')
    .default('info'),

  API_KEY: Joi.string()
    .min(32)
    .required(),

  API_TIMEOUT: Joi.number()
    .min(1000)
    .max(30000)
    .default(5000),

  ENABLE_METRICS: Joi.boolean()
    .default(false)
});

export function validateConfig() {
  const { error, value } = configSchema.validate(process.env, {
    allowUnknown: true,  // Allow other env vars
    stripUnknown: true   // Remove unknown vars
  });

  if (error) {
    throw new Error(`Configuration validation error: ${error.message}`);
  }

  return value;
}

// Usage
const validatedConfig = validateConfig();
typescript
// config/validation.ts
import Joi from 'joi';

const configSchema = Joi.object({
  NODE_ENV: Joi.string()
    .valid('development', 'production', 'test')
    .default('development'),

  PORT: Joi.number()
    .port()
    .default(3000),

  DATABASE_URL: Joi.string()
    .uri()
    .required(),

  REDIS_URL: Joi.string()
    .uri()
    .default('redis://localhost:6379'),

  LOG_LEVEL: Joi.string()
    .valid('debug', 'info', 'warn', 'error')
    .default('info'),

  API_KEY: Joi.string()
    .min(32)
    .required(),

  API_TIMEOUT: Joi.number()
    .min(1000)
    .max(30000)
    .default(5000),

  ENABLE_METRICS: Joi.boolean()
    .default(false)
});

export function validateConfig() {
  const { error, value } = configSchema.validate(process.env, {
    allowUnknown: true,  // 允许存在其他环境变量
    stripUnknown: true   // 移除未知变量
  });

  if (error) {
    throw new Error(`配置验证错误:${error.message}`);
  }

  return value;
}

// 使用示例
const validatedConfig = validateConfig();

7. Dynamic Configuration (Remote Config)

7. 动态配置(远程配置)

typescript
// config/remote-config.ts
export class RemoteConfigService {
  private config: Map<string, any> = new Map();
  private pollInterval: NodeJS.Timeout | null = null;

  constructor(private configServiceUrl: string) {}

  async initialize() {
    await this.fetchConfig();
    this.startPolling();
  }

  private async fetchConfig() {
    try {
      const response = await fetch(`${this.configServiceUrl}/config`);
      const config = await response.json();

      for (const [key, value] of Object.entries(config)) {
        const oldValue = this.config.get(key);
        if (oldValue !== value) {
          console.log(`Config changed: ${key} = ${value}`);
          this.config.set(key, value);
        }
      }
    } catch (error) {
      console.error('Failed to fetch remote config:', error);
    }
  }

  private startPolling() {
    // Poll every 60 seconds
    this.pollInterval = setInterval(() => {
      this.fetchConfig();
    }, 60000);
  }

  get(key: string, defaultValue?: any): any {
    return this.config.get(key) ?? defaultValue;
  }

  stop() {
    if (this.pollInterval) {
      clearInterval(this.pollInterval);
    }
  }
}

// Usage
const remoteConfig = new RemoteConfigService('https://config-service.example.com');
await remoteConfig.initialize();

app.get('/api/users', (req, res) => {
  const pageSize = remoteConfig.get('api.users.pageSize', 20);
  const enableCache = remoteConfig.get('api.users.enableCache', false);

  // Use dynamic config values
});
typescript
// config/remote-config.ts
export class RemoteConfigService {
  private config: Map<string, any> = new Map();
  private pollInterval: NodeJS.Timeout | null = null;

  constructor(private configServiceUrl: string) {}

  async initialize() {
    await this.fetchConfig();
    this.startPolling();
  }

  private async fetchConfig() {
    try {
      const response = await fetch(`${this.configServiceUrl}/config`);
      const config = await response.json();

      for (const [key, value] of Object.entries(config)) {
        const oldValue = this.config.get(key);
        if (oldValue !== value) {
          console.log(`配置已变更:${key} = ${value}`);
          this.config.set(key, value);
        }
      }
    } catch (error) {
      console.error('获取远程配置失败:', error);
    }
  }

  private startPolling() {
    // 每60秒轮询一次
    this.pollInterval = setInterval(() => {
      this.fetchConfig();
    }, 60000);
  }

  get(key: string, defaultValue?: any): any {
    return this.config.get(key) ?? defaultValue;
  }

  stop() {
    if (this.pollInterval) {
      clearInterval(this.pollInterval);
    }
  }
}

// 使用示例
const remoteConfig = new RemoteConfigService('https://config-service.example.com');
await remoteConfig.initialize();

app.get('/api/users', (req, res) => {
  const pageSize = remoteConfig.get('api.users.pageSize', 20);
  const enableCache = remoteConfig.get('api.users.enableCache', false);

  // 使用动态配置值
});

Best Practices

最佳实践

✅ DO

✅ 建议做法

  • Store configuration in environment variables
  • Use different config files per environment
  • Validate configuration on startup
  • Use secret managers for sensitive data
  • Never commit secrets to version control
  • Provide sensible defaults
  • Document all configuration options
  • Use type-safe configuration objects
  • Implement configuration hierarchy (base + overrides)
  • Use feature flags for gradual rollouts
  • Follow 12-factor app principles
  • Implement graceful degradation for missing config
  • Cache secrets to reduce API calls
  • 将配置存储在环境变量中
  • 为不同环境使用独立的配置文件
  • 在启动时验证配置
  • 使用密钥管理器存储敏感数据
  • 切勿将密钥提交到版本控制系统
  • 提供合理的默认值
  • 记录所有配置选项
  • 使用类型安全的配置对象
  • 实现配置层级(基础配置 + 环境覆盖)
  • 使用Feature Flags实现渐进式发布
  • 遵循12-factor app原则
  • 为缺失的配置实现优雅降级
  • 缓存密钥以减少API调用

❌ DON'T

❌ 避免做法

  • Hardcode configuration in source code
  • Commit .env files with real secrets
  • Use different config formats across services
  • Store secrets in plain text
  • Expose configuration through APIs
  • Use production credentials in development
  • Ignore configuration validation errors
  • Access process.env directly everywhere
  • Store configuration in databases (circular dependency)
  • Mix configuration with business logic
  • 在源代码中硬编码配置
  • 提交包含真实密钥的.env文件
  • 在不同服务中使用不同的配置格式
  • 明文存储密钥
  • 通过API暴露配置
  • 在开发环境中使用生产凭证
  • 忽略配置验证错误
  • 在代码各处直接访问process.env
  • 将配置存储在数据库中(循环依赖)
  • 将配置与业务逻辑混合

Common Patterns

常见模式

Pattern 1: Config Service

模式1:配置服务

typescript
export class ConfigService {
  private static instance: ConfigService;
  private config: Config;

  private constructor() {
    this.config = loadAndValidateConfig();
  }

  static getInstance(): ConfigService {
    if (!ConfigService.instance) {
      ConfigService.instance = new ConfigService();
    }
    return ConfigService.instance;
  }

  get<K extends keyof Config>(key: K): Config[K] {
    return this.config[key];
  }
}
typescript
export class ConfigService {
  private static instance: ConfigService;
  private config: Config;

  private constructor() {
    this.config = loadAndValidateConfig();
  }

  static getInstance(): ConfigService {
    if (!ConfigService.instance) {
      ConfigService.instance = new ConfigService();
    }
    return ConfigService.instance;
  }

  get<K extends keyof Config>(key: K): Config[K] {
    return this.config[key];
  }
}

Pattern 2: Configuration Builder

模式2:配置构建器

typescript
export class ConfigBuilder {
  private config: Partial<Config> = {};

  withDatabase(url: string): this {
    this.config.database = { url };
    return this;
  }

  withRedis(url: string): this {
    this.config.redis = { url };
    return this;
  }

  build(): Config {
    return this.config as Config;
  }
}
typescript
export class ConfigBuilder {
  private config: Partial<Config> = {};

  withDatabase(url: string): this {
    this.config.database = { url };
    return this;
  }

  withRedis(url: string): this {
    this.config.redis = { url };
    return this;
  }

  build(): Config {
    return this.config as Config;
  }
}

Tools & Resources

工具与资源

  • dotenv: Load environment variables from .env files
  • convict: Configuration management with validation
  • config: Hierarchical configurations for Node.js
  • AWS Secrets Manager: Cloud-based secret storage
  • HashiCorp Vault: Secret and encryption management
  • LaunchDarkly: Feature flag management
  • ConfigCat: Feature flag and configuration service
  • Consul: Service configuration and discovery
  • dotenv: 从.env文件加载环境变量
  • convict: 带验证的配置管理工具
  • config: Node.js的层级配置工具
  • AWS Secrets Manager: 云原生密钥存储服务
  • HashiCorp Vault: 密钥与加密管理工具
  • LaunchDarkly: Feature Flags管理平台
  • ConfigCat: Feature Flags与配置服务
  • Consul: 服务配置与发现工具