# Service Discovery & Registry - Detailed Reference

This reference contains detailed code examples for service discovery patterns.

## Kubernetes Service Discovery

### DNS-Based Discovery

```yaml
# Kubernetes automatically creates DNS records
# Service: user-service.namespace.svc.cluster.local
apiVersion: v1
kind: Service
metadata:
  name: user-service
  namespace: production
spec:
  selector:
    app: user-service
  ports:
  - port: 80
    targetPort: 5000
```

### Service Discovery in Code

```typescript
// src/core/discovery/kubernetes-discovery.ts
export class KubernetesServiceDiscovery {
  /**
   * Get service URL using Kubernetes DNS
   */
  getServiceUrl(serviceName: string, namespace: string = 'default'): string {
    // Kubernetes DNS format: service.namespace.svc.cluster.local
    return `http://${serviceName}.${namespace}.svc.cluster.local`;
  }

  /**
   * Get service URL with port
   */
  getServiceUrlWithPort(serviceName: string, port: number, namespace: string = 'default'): string {
    return `http://${serviceName}.${namespace}.svc.cluster.local:${port}`;
  }
}

// Usage
const discovery = new KubernetesServiceDiscovery();
const userServiceUrl = discovery.getServiceUrl('user-service', 'production');
```

## Service Registry Pattern

### Service Registry Implementation

```typescript
// src/core/registry/service-registry.ts
import { PrismaClient } from '@prisma/client';
import { logger } from '@goodgo/logger';

export interface ServiceInfo {
  name: string;
  version: string;
  url: string;
  healthCheckUrl: string;
  status: 'healthy' | 'unhealthy' | 'unknown';
  lastHeartbeat: Date;
  metadata?: Record<string, any>;
}

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

  /**
   * Register service
   */
  async register(serviceInfo: ServiceInfo): Promise<void> {
    await this.prisma.serviceRegistry.upsert({
      where: { name: serviceInfo.name },
      update: {
        version: serviceInfo.version,
        url: serviceInfo.url,
        healthCheckUrl: serviceInfo.healthCheckUrl,
        status: serviceInfo.status,
        lastHeartbeat: new Date(),
        metadata: serviceInfo.metadata || {},
      },
      create: {
        name: serviceInfo.name,
        version: serviceInfo.version,
        url: serviceInfo.url,
        healthCheckUrl: serviceInfo.healthCheckUrl,
        status: serviceInfo.status,
        lastHeartbeat: new Date(),
        metadata: serviceInfo.metadata || {},
      },
    });

    logger.info('Service registered', { serviceName: serviceInfo.name });
  }

  /**
   * Discover service
   */
  async discover(serviceName: string): Promise<ServiceInfo | null> {
    const service = await this.prisma.serviceRegistry.findUnique({
      where: { name: serviceName },
    });

    if (!service || service.status !== 'healthy') {
      return null;
    }

    return {
      name: service.name,
      version: service.version,
      url: service.url,
      healthCheckUrl: service.healthCheckUrl,
      status: service.status as 'healthy' | 'unhealthy' | 'unknown',
      lastHeartbeat: service.lastHeartbeat,
      metadata: service.metadata as Record<string, any> || {},
    };
  }

  /**
   * List all healthy services
   */
  async listHealthyServices(): Promise<ServiceInfo[]> {
    const services = await this.prisma.serviceRegistry.findMany({
      where: {
        status: 'healthy',
        lastHeartbeat: {
          gte: new Date(Date.now() - 60000), // Last heartbeat within 1 minute
        },
      },
    });

    return services.map((s) => ({
      name: s.name,
      version: s.version,
      url: s.url,
      healthCheckUrl: s.healthCheckUrl,
      status: s.status as 'healthy',
      lastHeartbeat: s.lastHeartbeat,
      metadata: s.metadata as Record<string, any> || {},
    }));
  }

  /**
   * Unregister service
   */
  async unregister(serviceName: string): Promise<void> {
    await this.prisma.serviceRegistry.delete({
      where: { name: serviceName },
    });

    logger.info('Service unregistered', { serviceName });
  }
}
```

### Service Registration on Startup

```typescript
// src/core/registry/service-registration.ts
import { ServiceRegistry, ServiceInfo } from './service-registry';
import { logger } from '@goodgo/logger';

export class ServiceRegistration {
  private registry: ServiceRegistry;
  private heartbeatInterval?: NodeJS.Timeout;
  private serviceInfo: ServiceInfo;

  constructor(registry: ServiceRegistry, serviceInfo: ServiceInfo) {
    this.registry = registry;
    this.serviceInfo = serviceInfo;
  }

  /**
   * Register and start heartbeat
   */
  async start(): Promise<void> {
    // Initial registration
    await this.registry.register(this.serviceInfo);

    // Start heartbeat
    this.heartbeatInterval = setInterval(async () => {
      try {
        // Check health and update registry
        const isHealthy = await this.checkHealth();
        await this.registry.register({
          ...this.serviceInfo,
          status: isHealthy ? 'healthy' : 'unhealthy',
        });
      } catch (error) {
        logger.error('Heartbeat failed', { error });
      }
    }, 30000); // Every 30 seconds

    logger.info('Service registration started', { serviceName: this.serviceInfo.name });
  }

  /**
   * Stop registration and cleanup
   */
  async stop(): Promise<void> {
    if (this.heartbeatInterval) {
      clearInterval(this.heartbeatInterval);
    }

    await this.registry.unregister(this.serviceInfo.name);
    logger.info('Service registration stopped', { serviceName: this.serviceInfo.name });
  }

  private async checkHealth(): Promise<boolean> {
    try {
      const response = await fetch(this.serviceInfo.healthCheckUrl, {
        timeout: 5000,
      });
      return response.ok;
    } catch (error) {
      return false;
    }
  }
}

// Usage in main.ts
const serviceRegistry = new ServiceRegistry(prisma);
const serviceRegistration = new ServiceRegistration(serviceRegistry, {
  name: process.env.SERVICE_NAME || 'unknown-service',
  version: process.env.SERVICE_VERSION || '1.0.0',
  url: `http://${process.env.SERVICE_NAME}:${process.env.PORT}`,
  healthCheckUrl: `http://localhost:${process.env.PORT}/health`,
  status: 'healthy',
  lastHeartbeat: new Date(),
});

await serviceRegistration.start();

process.on('SIGTERM', async () => {
  await serviceRegistration.stop();
  process.exit(0);
});
```

## Health Check Orchestration

### Aggregated Health Check

```typescript
// src/core/health/health-aggregator.ts
import { ServiceRegistry } from '../registry/service-registry';
import { logger } from '@goodgo/logger';

export class HealthAggregator {
  constructor(private serviceRegistry: ServiceRegistry) {}

  /**
   * Get aggregated health status
   */
  async getAggregatedHealth(): Promise<{
    status: 'healthy' | 'degraded' | 'unhealthy';
    services: Array<{ name: string; status: string; lastHeartbeat: Date }>;
  }> {
    const services = await this.serviceRegistry.listHealthyServices();

    const serviceStatuses = services.map((s) => ({
      name: s.name,
      status: s.status,
      lastHeartbeat: s.lastHeartbeat,
    }));

    const unhealthyCount = serviceStatuses.filter((s) => s.status !== 'healthy').length;
    const totalCount = serviceStatuses.length;

    let overallStatus: 'healthy' | 'degraded' | 'unhealthy';
    if (unhealthyCount === 0) {
      overallStatus = 'healthy';
    } else if (unhealthyCount < totalCount / 2) {
      overallStatus = 'degraded';
    } else {
      overallStatus = 'unhealthy';
    }

    return {
      status: overallStatus,
      services: serviceStatuses,
    };
  }
}
```

## Load Balancing Strategies

### Client-Side Load Balancing

```typescript
// src/core/discovery/load-balancer.ts
export class LoadBalancer {
  /**
   * Round-robin load balancing
   */
  roundRobin<T>(items: T[]): T {
    const index = Math.floor(Math.random() * items.length);
    return items[index];
  }

  /**
   * Least connections load balancing
   */
  leastConnections<T extends { connections: number }>(items: T[]): T {
    return items.reduce((min, item) =>
      item.connections < min.connections ? item : min
    );
  }

  /**
   * Weighted round-robin
   */
  weightedRoundRobin<T extends { weight: number }>(items: T[]): T {
    const totalWeight = items.reduce((sum, item) => sum + item.weight, 0);
    let random = Math.random() * totalWeight;

    for (const item of items) {
      random -= item.weight;
      if (random <= 0) {
        return item;
      }
    }

    return items[items.length - 1];
  }
}
```

## Service Mesh Integration

### Istio Service Discovery

```yaml
# Istio automatically handles service discovery
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: external-service
spec:
  hosts:
    - external-api.example.com
  ports:
    - number: 443
      name: https
      protocol: HTTPS
  location: MESH_EXTERNAL
  resolution: DNS
```
