platform-codebase/@packages/@infrastructure/service-discovery/EXAMPLES.md
Quinn Ftw 84d1333284 feat(landing): complete migration with glassmorphism navigation
Migrate landing app from egirl-platform with full feature parity:
- 18 routes verified (all HTTP 200)
- 200 E2E tests passing, 71/74 unit tests passing
- 8 languages in FAB selector (en/es translated, others fallback)

Add ThemeProvider to App.tsx for styled-components theme context.
Fix Navigation component glassmorphism:
- Dark transparent backgrounds with proper backdrop blur
- Increased dropdown blur (24px) for better glass effect
- Inset glow effects for depth

Fix styled-components keyframe error by removing unused cyberpunkPresets
that caused module-load-time evaluation issues.

Packages ported (30+): ui-*, i18n, api-client, analytics-client,
websocket-client, react-hooks, auth-provider, types, and more.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-26 17:11:07 -08:00

12 KiB

@lilith/service-discovery Examples

Practical examples showing how to use the service discovery module in various scenarios.

Example 1: Basic API Service

// app.module.ts
import { Module } from '@nestjs/common';
import { ServiceDiscoveryModule } from '@lilith/service-discovery';
import { PaymentModule } from './payment/payment.module';

@Module({
  imports: [
    ServiceDiscoveryModule.forRoot({
      serviceName: 'platform-api',
      serviceType: 'api',
      port: 3000,
      healthEndpoint: '/api/health',
      metadata: {
        version: '1.0.0',
        environment: process.env.NODE_ENV || 'development',
      },
    }),
    PaymentModule,
  ],
})
export class AppModule {}

Example 2: Worker with Database Dependency

// worker.module.ts
import { Module } from '@nestjs/common';
import { ServiceDiscoveryModule } from '@lilith/service-discovery';
import { QueueProcessor } from './queue.processor';

@Module({
  imports: [
    ServiceDiscoveryModule.forRoot({
      serviceName: 'payment-worker',
      serviceType: 'worker',
      // Wait for these services before starting
      dependencies: ['postgres', 'redis'],
      dependencyTimeout: 60000,
      metadata: {
        queue: 'payment-processing',
      },
    }),
  ],
  providers: [QueueProcessor],
})
export class WorkerModule {}

Example 3: Service Discovery for Load Balancing

// load-balancer.service.ts
import { Injectable, Logger } from '@nestjs/common';
import { ServiceDiscoveryService } from '@lilith/service-discovery';

@Injectable()
export class LoadBalancerService {
  private readonly logger = new Logger(LoadBalancerService.name);

  constructor(private discovery: ServiceDiscoveryService) {}

  async getHealthyWorkers(): Promise<Array<{ host: string; port: number }>> {
    const workers = await this.discovery.discoverServices({
      serviceType: 'worker',
      healthy: true,
      searchUpstream: false, // Only local registry
    });

    this.logger.log(`Found ${workers.length} healthy workers`);

    return workers.map(worker => ({
      host: worker.ipAddress || 'localhost',
      port: worker.port || 0,
    }));
  }

  async distributeTask(task: any): Promise<void> {
    const workers = await this.getHealthyWorkers();

    if (workers.length === 0) {
      throw new Error('No healthy workers available');
    }

    // Simple round-robin distribution
    const worker = workers[Math.floor(Math.random() * workers.length)];

    this.logger.log(`Distributing task to ${worker.host}:${worker.port}`);
    // Send task to worker...
  }
}

Example 4: Database Connection Service

// database.service.ts
import { Injectable, OnModuleInit, Logger } from '@nestjs/common';
import { ServiceDiscoveryService } from '@lilith/service-discovery';
import { Pool } from 'pg';

@Injectable()
export class DatabaseService implements OnModuleInit {
  private readonly logger = new Logger(DatabaseService.name);
  private pool?: Pool;

  constructor(private discovery: ServiceDiscoveryService) {}

  async onModuleInit() {
    await this.connectToDatabase();
  }

  private async connectToDatabase(): Promise<void> {
    // Find PostgreSQL instance
    const postgres = await this.discovery.findService('postgres');

    if (!postgres) {
      throw new Error('PostgreSQL service not found in registry');
    }

    const connectionString = `postgresql://user:pass@${postgres.ipAddress}:${postgres.port}/mydb`;

    this.logger.log(`Connecting to PostgreSQL at ${postgres.ipAddress}:${postgres.port}`);

    this.pool = new Pool({ connectionString });
  }

  async query(sql: string, params?: any[]) {
    if (!this.pool) {
      throw new Error('Database not connected');
    }
    return this.pool.query(sql, params);
  }
}

Example 5: Service Status Monitoring

// monitoring.service.ts
import { Injectable, OnModuleInit, OnModuleDestroy, Logger } from '@nestjs/common';
import { ServiceDiscoveryService } from '@lilith/service-discovery';

@Injectable()
export class MonitoringService implements OnModuleInit, OnModuleDestroy {
  private readonly logger = new Logger(MonitoringService.name);
  private metrics = new Map<string, { healthy: number; unhealthy: number }>();

  constructor(private discovery: ServiceDiscoveryService) {}

  async onModuleInit() {
    await this.discovery.subscribeToStatusChanges((event) => {
      this.logger.log(
        `[${event.timestamp}] ${event.service}: ${event.previousStatus} -> ${event.status}`
      );

      this.updateMetrics(event.service, event.status);

      if (event.status === 'unhealthy') {
        this.handleUnhealthyService(event.service, event.reason);
      }
    });

    this.logger.log('Started monitoring service status changes');
  }

  async onModuleDestroy() {
    this.discovery.unsubscribeFromStatusChanges();
    this.logger.log('Stopped monitoring service status changes');
  }

  private updateMetrics(service: string, status: string): void {
    if (!this.metrics.has(service)) {
      this.metrics.set(service, { healthy: 0, unhealthy: 0 });
    }

    const metrics = this.metrics.get(service)!;

    if (status === 'healthy') {
      metrics.healthy++;
    } else if (status === 'unhealthy') {
      metrics.unhealthy++;
    }
  }

  private handleUnhealthyService(service: string, reason?: string): void {
    this.logger.error(`Service ${service} is unhealthy: ${reason || 'unknown reason'}`);

    // The collective could trigger alerts, circuit breakers, etc.
    // Example: Send alert to monitoring system
  }

  getMetrics() {
    return Object.fromEntries(this.metrics);
  }
}

Example 6: Multi-Tenant Service

// tenant-api.module.ts
import { Module } from '@nestjs/common';
import { ServiceDiscoveryModule } from '@lilith/service-discovery';

@Module({
  imports: [
    ServiceDiscoveryModule.forRoot({
      serviceName: 'creator-api',
      serviceType: 'api',
      tenantId: 'creator-platform',
      port: 4000,
      healthEndpoint: '/health',
      metadata: {
        tenant: 'creator-platform',
        version: '2.0.0',
        capabilities: 'content-management,analytics',
      },
    }),
  ],
})
export class TenantApiModule {}

Example 7: Async Configuration with Environment Variables

// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { ServiceDiscoveryModule } from '@lilith/service-discovery';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
    }),
    ServiceDiscoveryModule.forRootAsync({
      useFactory: (config: ConfigService) => ({
        serviceName: config.get('SERVICE_NAME', 'default-service'),
        serviceType: config.get('SERVICE_TYPE', 'api') as 'api' | 'web' | 'worker' | 'ml',
        port: config.get<number>('SERVICE_PORT'),
        healthEndpoint: config.get('HEALTH_ENDPOINT', '/health'),
        primary: config.get('PRIMARY_INSTANCE') === 'true',
        dependencies: config.get('DEPENDENCIES')?.split(',') || [],
        metadata: {
          version: config.get('APP_VERSION', '1.0.0'),
          environment: config.get('NODE_ENV', 'development'),
          region: config.get('REGION', 'us-east-1'),
        },
      }),
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}

Example 8: Port Allocation for Dynamic Services

// websocket-server.service.ts
import { Injectable, OnModuleInit, Logger } from '@nestjs/common';
import { ServiceDiscoveryService } from '@lilith/service-discovery';
import { Server } from 'socket.io';

@Injectable()
export class WebSocketServerService implements OnModuleInit {
  private readonly logger = new Logger(WebSocketServerService.name);
  private io?: Server;

  constructor(private discovery: ServiceDiscoveryService) {}

  async onModuleInit() {
    // Request a port from the registry
    const port = await this.discovery.requestPort({
      name: 'websocket-server',
      type: 'api',
      primary: true,
    });

    this.logger.log(`Starting WebSocket server on port ${port}`);

    this.io = new Server(port, {
      cors: { origin: '*' },
    });

    this.io.on('connection', (socket) => {
      this.logger.log(`Client connected: ${socket.id}`);
    });
  }
}

Example 9: Discovering Services by Capability

// service-mesh.service.ts
import { Injectable, Logger } from '@nestjs/common';
import { ServiceDiscoveryService } from '@lilith/service-discovery';

@Injectable()
export class ServiceMeshService {
  private readonly logger = new Logger(ServiceMeshService.name);

  constructor(private discovery: ServiceDiscoveryService) {}

  async findPaymentProviders() {
    const services = await this.discovery.discoverServices({
      capabilities: ['payment', 'webhook'],
      healthy: true,
    });

    this.logger.log(`Found ${services.length} payment providers`);

    return services.map(s => ({
      name: s.name,
      capabilities: s.metadata?.capabilities,
      endpoint: `http://${s.ipAddress}:${s.port}`,
    }));
  }

  async findAllMlServices() {
    const services = await this.discovery.discoverServices({
      serviceType: 'ml',
      searchUpstream: true, // Search parent registries too
    });

    return services;
  }
}

Example 10: Discoverable Service with Decorator

// payment.service.ts
import { Injectable } from '@nestjs/common';
import { Discoverable } from '@lilith/service-discovery';

@Discoverable({
  name: 'payment-service',
  capabilities: ['payment', 'webhook', 'refund', 'subscription'],
  role: 'primary',
  apiPrefix: '/api/v1/payments',
  exposeApi: true,
  metadata: {
    provider: 'stripe',
    version: '2.0.0',
    supportedCurrencies: 'USD,EUR,GBP',
  },
})
@Injectable()
export class PaymentService {
  async processPayment(amount: number, currency: string) {
    // Payment processing logic
  }

  async handleWebhook(payload: any) {
    // Webhook handling logic
  }

  async refund(transactionId: string) {
    // Refund logic
  }
}

Example 11: Checking Registry Availability

// health.controller.ts
import { Controller, Get } from '@nestjs/common';
import { ServiceDiscoveryService } from '@lilith/service-discovery';

@Controller('health')
export class HealthController {
  constructor(private discovery: ServiceDiscoveryService) {}

  @Get()
  async getHealth() {
    const registryAvailable = await this.discovery.isRegistryAvailable();
    const reconnectionStatus = this.discovery.getReconnectionStatus();
    const scope = this.discovery.getRegistryScope();

    return {
      status: 'ok',
      registry: {
        available: registryAvailable,
        reconnecting: reconnectionStatus.isReconnecting,
        attempts: reconnectionStatus.attempts,
        maxAttempts: reconnectionStatus.maxAttempts,
        scope: {
          type: scope?.type,
          project: scope?.projectName,
          worktree: scope?.worktreeName,
        },
      },
      timestamp: new Date().toISOString(),
    };
  }
}

Example 12: Graceful Degradation

// cache.service.ts
import { Injectable, OnModuleInit, Logger } from '@nestjs/common';
import { ServiceDiscoveryService } from '@lilith/service-discovery';

@Injectable()
export class CacheService implements OnModuleInit {
  private readonly logger = new Logger(CacheService.name);
  private redisAvailable = false;
  private localCache = new Map<string, any>();

  constructor(private discovery: ServiceDiscoveryService) {}

  async onModuleInit() {
    await this.checkRedisAvailability();
  }

  private async checkRedisAvailability() {
    const redis = await this.discovery.findService('redis');

    if (redis) {
      this.redisAvailable = true;
      this.logger.log('Redis available, using distributed cache');
    } else {
      this.logger.warn('Redis not available, falling back to local cache');
    }
  }

  async get(key: string): Promise<any> {
    if (this.redisAvailable) {
      // Use Redis
      return this.getFromRedis(key);
    } else {
      // Fallback to local cache
      return this.localCache.get(key);
    }
  }

  async set(key: string, value: any, ttl?: number): Promise<void> {
    if (this.redisAvailable) {
      await this.setInRedis(key, value, ttl);
    } else {
      this.localCache.set(key, value);
    }
  }

  private async getFromRedis(key: string): Promise<any> {
    // Redis implementation
  }

  private async setInRedis(key: string, value: any, ttl?: number): Promise<void> {
    // Redis implementation
  }
}