Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Configuration Management - zarządzanie zasobami imperium

Ave, kwatermistrzu imperium! Konsul Caesar.js powierzył Ci zarządzanie wszystkimi zasobami systemu. Różne legiony mają różne potrzeby, różne środowiska wymagają różnych ustawień. Czas nauczyć się Configuration Management - sztuki zarządzania wszystkimi ustawieniami naszego rzymskiego imperium!

Czym jest Configuration Management?

W rzymskim imperium masz różne typy konfiguracji:

  • Podstawowe ustawienia legionu - nazwa, pojemność, siła bojowa
  • Parametry środowiska - development, production, testing
  • Sekrety i klucze - hasła do archiwów, klucze API
  • Ustawienia zewnętrzne - adresy prowincji, współrzędne fortów
  • Feature flags - które funkcje są dostępne dla legionistów

Configuration Management w NestJS pozwala elegancko zarządzać wszystkimi tymi ustawieniami.

Instalacja i podstawowa konfiguracja

1npm install @nestjs/config
2npm install joi # dla walidacji schematów

Podstawowa konfiguracja

1// app.module.ts
2import { Module } from '@nestjs/common';
3import { ConfigModule } from '@nestjs/config';
4
5@Module({
6 imports: [
7 ConfigModule.forRoot({
8 isGlobal: true,
9 envFilePath: ['.env.local', '.env'],
10 ignoreEnvFile: process.env.NODE_ENV === 'production',
11 }),
12 ],
13})
14export class AppModule {}

Pliki środowiskowe

1# .env
2# Konfiguracja podstawowa dla całego systemu
3NODE_ENV=development
4PORT=3000
5
6# Informacje o legionie
7LEGION_NAME=Legio X Equestris
8LEGION_COMMANDER=Caesar.js
9LEGION_CAPACITY=50
10
11# Baza danych
12DATABASE_HOST=localhost
13DATABASE_PORT=5432
14DATABASE_NAME=imperium_tributes
15DATABASE_USERNAME=imperium_admin
16DATABASE_PASSWORD=roma_invicta_secret
17
18# API klucze
19WEATHER_API_KEY=your_weather_api_key
20MAP_API_KEY=your_map_api_key
21TRIBUTE_TRACKER_SECRET=super_secret_key
22
23# Redis
24REDIS_HOST=localhost
25REDIS_PORT=6379
26
27# JWT
28JWT_SECRET=imperium_jwt_secret_key
29JWT_EXPIRES_IN=24h
30
31# Zewnętrzne usługi
32TRIBUTE_EXCHANGE_URL=https://api.tribute-exchange.com
33LEGION_COMMUNICATION_URL=https://legion-comm.imperium-romanum.io
34
35# Feature flags
36ENABLE_ORACLE_PREDICTIONS=true
37ENABLE_WEATHER_PREDICTION=true
38ENABLE_ADVANCED_ENGINEERING=false
1# .env.production
2NODE_ENV=production
3PORT=80
4
5LEGION_NAME=Production Legion Prima
6DATABASE_HOST=prod-db.imperium-romanum.io
7DATABASE_PASSWORD=${PROD_DB_PASSWORD}
8
9# W produkcji używamy zewnętrznych serwisów
10REDIS_HOST=prod-redis.imperium-romanum.io
11TRIBUTE_EXCHANGE_URL=https://api.tribute-exchange.com/v2
12
13ENABLE_ADVANCED_ENGINEERING=true

Structured Configuration

Legion Configuration

1// config/legion.config.ts
2import { registerAs } from '@nestjs/config';
3
4export default registerAs('legion', () => ({
5 name: process.env.LEGION_NAME || 'Unknown Legion',
6 commander: process.env.LEGION_COMMANDER || 'Anonymous Commander',
7 capacity: parseInt(process.env.LEGION_CAPACITY, 10) || 30,
8 type: process.env.LEGION_TYPE || 'COHORT',
9 homeBase: process.env.HOME_BASE || 'Roma',
10 coordinates: {
11 latitude: parseFloat(process.env.LEGION_LATITUDE) || 0,
12 longitude: parseFloat(process.env.LEGION_LONGITUDE) || 0,
13 },
14 equipment: {
15 ballistae: parseInt(process.env.LEGION_BALLISTAE, 10) || 20,
16 standards: parseInt(process.env.LEGION_STANDARDS, 10) || 3,
17 storage: parseInt(process.env.LEGION_STORAGE, 10) || 1000,
18 },
19 features: {
20 oraclePredictions: process.env.ENABLE_ORACLE_PREDICTIONS === 'true',
21 weatherPrediction: process.env.ENABLE_WEATHER_PREDICTION === 'true',
22 advancedEngineering: process.env.ENABLE_ADVANCED_ENGINEERING === 'true',
23 }
24}));

Database Configuration

1// config/database.config.ts
2import { registerAs } from '@nestjs/config';
3
4export default registerAs('database', () => ({
5 type: 'postgres',
6 host: process.env.DATABASE_HOST || 'localhost',
7 port: parseInt(process.env.DATABASE_PORT, 10) || 5432,
8 username: process.env.DATABASE_USERNAME || 'imperium',
9 password: process.env.DATABASE_PASSWORD || 'password',
10 database: process.env.DATABASE_NAME || 'imperium_tributes',
11 entities: ['dist/**/*.entity{.ts,.js}'],
12 synchronize: process.env.NODE_ENV !== 'production',
13 logging: process.env.NODE_ENV === 'development',
14 ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
15 migrations: ['dist/migrations/*{.ts,.js}'],
16 migrationsRun: process.env.NODE_ENV === 'production',
17 retryAttempts: 3,
18 retryDelay: 3000,
19}));

External Services Configuration

1// config/external-services.config.ts
2import { registerAs } from '@nestjs/config';
3
4export default registerAs('externalServices', () => ({
5 weatherApi: {
6 baseUrl: process.env.WEATHER_API_URL || 'https://api.openweathermap.org/data/2.5',
7 apiKey: process.env.WEATHER_API_KEY,
8 timeout: parseInt(process.env.WEATHER_API_TIMEOUT, 10) || 5000,
9 },
10 tributeExchange: {
11 baseUrl: process.env.TRIBUTE_EXCHANGE_URL || 'https://api.tribute-exchange.com',
12 secret: process.env.TRIBUTE_TRACKER_SECRET,
13 timeout: parseInt(process.env.TRIBUTE_EXCHANGE_TIMEOUT, 10) || 10000,
14 },
15 legionCommunication: {
16 baseUrl: process.env.LEGION_COMMUNICATION_URL,
17 channel: process.env.LEGION_CHANNEL || 'main',
18 encryptionKey: process.env.LEGION_ENCRYPTION_KEY,
19 },
20 maps: {
21 provider: process.env.MAP_PROVIDER || 'google',
22 apiKey: process.env.MAP_API_KEY,
23 cacheTime: parseInt(process.env.MAP_CACHE_TIME, 10) || 3600,
24 }
25}));

Schema Validation

1// config/validation.schema.ts
2import * as Joi from 'joi';
3
4export const validationSchema = Joi.object({
5 NODE_ENV: Joi.string()
6 .valid('development', 'production', 'test')
7 .default('development'),
8
9 PORT: Joi.number().default(3000),
10
11 // Legion configuration
12 LEGION_NAME: Joi.string().required(),
13 LEGION_COMMANDER: Joi.string().required(),
14 LEGION_CAPACITY: Joi.number().min(10).max(200).default(50),
15 LEGION_TYPE: Joi.string()
16 .valid('COHORT', 'LEGION', 'CENTURY', 'MANIPLE')
17 .default('COHORT'),
18
19 // Database
20 DATABASE_HOST: Joi.string().required(),
21 DATABASE_PORT: Joi.number().default(5432),
22 DATABASE_USERNAME: Joi.string().required(),
23 DATABASE_PASSWORD: Joi.string().required(),
24 DATABASE_NAME: Joi.string().required(),
25
26 // JWT
27 JWT_SECRET: Joi.string().min(32).required(),
28 JWT_EXPIRES_IN: Joi.string().default('24h'),
29
30 // Redis
31 REDIS_HOST: Joi.string().default('localhost'),
32 REDIS_PORT: Joi.number().default(6379),
33
34 // API Keys
35 WEATHER_API_KEY: Joi.string().when('NODE_ENV', {
36 is: 'production',
37 then: Joi.required(),
38 otherwise: Joi.optional()
39 }),
40
41 MAP_API_KEY: Joi.string().when('NODE_ENV', {
42 is: 'production',
43 then: Joi.required(),
44 otherwise: Joi.optional()
45 }),
46});
47
48// W app.module.ts
49@Module({
50 imports: [
51 ConfigModule.forRoot({
52 isGlobal: true,
53 validationSchema,
54 validationOptions: {
55 allowUnknown: true,
56 abortEarly: false,
57 },
58 load: [
59 legionConfig,
60 databaseConfig,
61 externalServicesConfig,
62 ],
63 }),
64 ],
65})
66export class AppModule {}

Użycie konfiguracji w serwisach

1// services/legion.service.ts
2import { Injectable } from '@nestjs/common';
3import { ConfigService } from '@nestjs/config';
4
5@Injectable()
6export class LegionService {
7 constructor(private configService: ConfigService) {}
8
9 getLegionInfo() {
10 return {
11 name: this.configService.get<string>('legion.name'),
12 commander: this.configService.get<string>('legion.commander'),
13 capacity: this.configService.get<number>('legion.capacity'),
14 type: this.configService.get<string>('legion.type'),
15 coordinates: this.configService.get('legion.coordinates'),
16 equipment: this.configService.get('legion.equipment'),
17 };
18 }
19
20 getFeatures() {
21 return this.configService.get('legion.features');
22 }
23
24 canUseOraclePredictions(): boolean {
25 return this.configService.get<boolean>('legion.features.oraclePredictions', false);
26 }
27
28 getStorageCapacity(): number {
29 return this.configService.get<number>('legion.equipment.storage', 1000);
30 }
31}

Typed Configuration Service

1// services/typed-config.service.ts
2import { Injectable } from '@nestjs/common';
3import { ConfigService } from '@nestjs/config';
4
5interface LegionConfig {
6 name: string;
7 commander: string;
8 capacity: number;
9 type: 'COHORT' | 'LEGION' | 'CENTURY' | 'MANIPLE';
10 homeBase: string;
11 coordinates: {
12 latitude: number;
13 longitude: number;
14 };
15 equipment: {
16 ballistae: number;
17 standards: number;
18 storage: number;
19 };
20 features: {
21 oraclePredictions: boolean;
22 weatherPrediction: boolean;
23 advancedEngineering: boolean;
24 };
25}
26
27@Injectable()
28export class TypedConfigService {
29 constructor(private configService: ConfigService) {}
30
31 get legion(): LegionConfig {
32 return this.configService.get<LegionConfig>('legion')!;
33 }
34
35 get database() {
36 return {
37 host: this.configService.get<string>('database.host')!,
38 port: this.configService.get<number>('database.port')!,
39 username: this.configService.get<string>('database.username')!,
40 password: this.configService.get<string>('database.password')!,
41 database: this.configService.get<string>('database.database')!,
42 };
43 }
44
45 get isDevelopment(): boolean {
46 return this.configService.get<string>('NODE_ENV') === 'development';
47 }
48
49 get isProduction(): boolean {
50 return this.configService.get<string>('NODE_ENV') === 'production';
51 }
52}

Dynamic Configuration

1// services/feature-flag.service.ts
2@Injectable()
3export class FeatureFlagService {
4 private features = new Map<string, boolean>();
5
6 constructor(private configService: ConfigService) {
7 this.loadFeatures();
8 }
9
10 private loadFeatures() {
11 // Załaduj z konfiguracji
12 this.features.set('oraclePredictions',
13 this.configService.get<boolean>('legion.features.oraclePredictions', false));
14 this.features.set('weatherPrediction',
15 this.configService.get<boolean>('legion.features.weatherPrediction', false));
16 this.features.set('advancedEngineering',
17 this.configService.get<boolean>('legion.features.advancedEngineering', false));
18 }
19
20 isEnabled(feature: string): boolean {
21 return this.features.get(feature) || false;
22 }
23
24 enable(feature: string) {
25 this.features.set(feature, true);
26 console.log(`✅ Feature enabled: ${feature}`);
27 }
28
29 disable(feature: string) {
30 this.features.set(feature, false);
31 console.log(`❌ Feature disabled: ${feature}`);
32 }
33
34 getAllFeatures() {
35 return Object.fromEntries(this.features);
36 }
37}
38
39// Guard dla feature flags
40@Injectable()
41export class FeatureGuard implements CanActivate {
42 constructor(
43 private featureFlagService: FeatureFlagService,
44 private reflector: Reflector,
45 ) {}
46
47 canActivate(context: ExecutionContext): boolean {
48 const requiredFeature = this.reflector.get<string>('feature', context.getHandler());
49 
50 if (!requiredFeature) {
51 return true;
52 }
53
54 return this.featureFlagService.isEnabled(requiredFeature);
55 }
56}
57
58// Decorator dla feature flags
59export const RequireFeature = (feature: string) => SetMetadata('feature', feature);
60
61// Użycie
62@Controller('advanced')
63export class AdvancedController {
64 @Get('engineering')
65 @RequireFeature('advancedEngineering')
66 @UseGuards(FeatureGuard)
67 getAdvancedEngineering() {
68 return { message: 'Zaawansowana inżynieria dostępna!' };
69 }
70}

Environment-specific Services

1// services/external-api.service.ts
2@Injectable()
3export class ExternalApiService {
4 private readonly weatherApiConfig;
5 private readonly tributeExchangeConfig;
6
7 constructor(private configService: ConfigService) {
8 this.weatherApiConfig = this.configService.get('externalServices.weatherApi');
9 this.tributeExchangeConfig = this.configService.get('externalServices.tributeExchange');
10 }
11
12 async getWeatherForecast(coordinates: { lat: number; lng: number }) {
13 if (!this.weatherApiConfig.apiKey) {
14 console.warn('Weather API key not configured, using mock data');
15 return this.getMockWeather(coordinates);
16 }
17
18 const url = `${this.weatherApiConfig.baseUrl}/weather?lat=${coordinates.lat}&lon=${coordinates.lng}&appid=${this.weatherApiConfig.apiKey}`
1
2 try {
3 const response = await fetch(url, {
4 timeout: this.weatherApiConfig.timeout
5 });
6 return await response.json();
7 } catch (error) {
8 console.error('Weather API error:', error);
9 return this.getMockWeather(coordinates);
10 }
11 }
12
13 private getMockWeather(coordinates: any) {
14 return {
15 weather: 'sunny',
16 temperature: 25,
17 windSpeed: 10,
18 coordinates,
19 source: 'mock'
20 };
21 }
22}

Configuration Management to fundament dobrze działającej aplikacji - pozwala na elastyczność, bezpieczeństwo i łatwość zarządzania różnymi środowiskami. Jak dobry zarządca imperium, który wie gdzie znajduje się każdy zasób!

Przejdź do CodeWorlds