---
name: service-discovery-registry
description: Service discovery patterns for GoodGo microservices. Use for service registry, health checks, load balancing, or service mesh integration.
dependencies: "kubernetes, istio (optional)"
---

# Service Discovery & Registry Patterns

## When to Use This Skill

Use this skill when:
- Implementing service discovery mechanisms
- Managing service registry
- Orchestrating health checks
- Implementing load balancing strategies
- Integrating with service mesh
- Managing dynamic service registration
- Implementing DNS-based discovery
- Building service catalog

## Core Concepts

### Service Discovery Types

1. **Client-Side Discovery**: Client queries service registry
2. **Server-Side Discovery**: Load balancer queries registry (Kubernetes DNS)
3. **Service Mesh**: Automatic service discovery (Istio, Linkerd)

### Service Registry

- **Static Configuration**: Hardcoded service addresses
- **Dynamic Registry**: Services register/unregister dynamically
- **DNS-Based**: Use DNS for service discovery (Kubernetes)

## 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
// EN: Kubernetes DNS-based service discovery
// VI: Service discovery dựa trên DNS của Kubernetes
export class KubernetesServiceDiscovery {
  /**
   * EN: Get service URL using Kubernetes DNS
   * VI: Lấy service URL sử dụng Kubernetes DNS
   */
  getServiceUrl(serviceName: string, namespace: string = 'default'): string {
    // EN: Kubernetes DNS format: service.namespace.svc.cluster.local
    // VI: Format DNS Kubernetes: service.namespace.svc.cluster.local
    return `http://${serviceName}.${namespace}.svc.cluster.local`;
  }

  /**
   * EN: Get service URL with port
   * VI: Lấy service URL với 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
// EN: Service registry for dynamic service registration
// VI: Service registry cho đăng ký service động
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) {}

  /**
   * EN: Register service
   * VI: Đăng ký 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 });
  }

  /**
   * EN: Discover service
   * VI: Tìm 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> || {},
    };
  }

  /**
   * EN: List all healthy services
   * VI: Liệt kê tất cả services healthy
   */
  async listHealthyServices(): Promise<ServiceInfo[]> {
    const services = await this.prisma.serviceRegistry.findMany({
      where: {
        status: 'healthy',
        lastHeartbeat: {
          gte: new Date(Date.now() - 60000), // EN: Last heartbeat within 1 minute / VI: Heartbeat trong 1 phút qua
        },
      },
    });

    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> || {},
    }));
  }

  /**
   * EN: Unregister service
   * VI: Hủy đăng ký 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
// EN: Auto-register service on startup
// VI: Tự động đăng ký service khi khởi động
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;
  }

  /**
   * EN: Register and start heartbeat
   * VI: Đăng ký và bắt đầu heartbeat
   */
  async start(): Promise<void> {
    // EN: Initial registration
    // VI: Đăng ký ban đầu
    await this.registry.register(this.serviceInfo);

    // EN: Start heartbeat
    // VI: Bắt đầu heartbeat
    this.heartbeatInterval = setInterval(async () => {
      try {
        // EN: Check health and update registry
        // VI: Kiểm tra health và cập nhật registry
        const isHealthy = await this.checkHealth();
        await this.registry.register({
          ...this.serviceInfo,
          status: isHealthy ? 'healthy' : 'unhealthy',
        });
      } catch (error) {
        logger.error('Heartbeat failed', { error });
      }
    }, 30000); // EN: Every 30 seconds / VI: Mỗi 30 giây

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

  /**
   * EN: Stop registration and cleanup
   * VI: Dừng registration và 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
// EN: Aggregate health checks from multiple services
// VI: Tổng hợp health checks từ nhiều services
import { ServiceRegistry } from '../registry/service-registry';
import { logger } from '@goodgo/logger';

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

  /**
   * EN: Get aggregated health status
   * VI: Lấy trạng thái health tổng hợp
   */
  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
// EN: Client-side load balancing
// VI: Client-side load balancing
export class LoadBalancer {
  /**
   * EN: Round-robin load balancing
   * VI: Round-robin load balancing
   */
  roundRobin<T>(items: T[]): T {
    // EN: Simple round-robin implementation
    // VI: Implementation round-robin đơn giản
    const index = Math.floor(Math.random() * items.length);
    return items[index];
  }

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

  /**
   * EN: Weighted round-robin
   * VI: 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
# EN: Istio automatically handles service discovery
# VI: Istio tự động xử lý 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
```

## Best Practices

1. **Kubernetes DNS**: Use Kubernetes DNS for service discovery
2. **Health Checks**: Implement comprehensive health checks
3. **Service Registry**: Use registry for dynamic services
4. **Load Balancing**: Use appropriate load balancing strategy
5. **Monitoring**: Monitor service discovery and health

## Common Mistakes

1. **Hardcoded Service URLs**: Breaks in different environments
   ```typescript
   // ❌ BAD: Hardcoded
   const url = 'http://user-service:5000';

   // ✅ GOOD: Use discovery or env vars
   const url = discovery.getServiceUrl('user-service');
   // OR
   const url = process.env.USER_SERVICE_URL;
   ```

2. **No Heartbeat**: Stale registry entries
   ```typescript
   // ❌ BAD: Register once
   await registry.register(service);

   // ✅ GOOD: Periodic heartbeat
   setInterval(() => registry.register(service), 30000);
   ```

3. **Missing Graceful Shutdown**: Orphaned registrations
   ```typescript
   // ✅ Always unregister on shutdown
   process.on('SIGTERM', async () => {
     await registry.unregister(serviceName);
     process.exit(0);
   });
   ```

4. **No Fallback**: Fails when registry unavailable
   ```typescript
   // ❌ BAD: No fallback
   const url = await registry.discover('service');

   // ✅ GOOD: Fallback to default
   const url = await registry.discover('service')
     ?? process.env.SERVICE_FALLBACK_URL;
   ```

## Quick Reference

| Discovery Type | Implementation | Use Case |
|----------------|----------------|----------|
| **K8s DNS** | `service.namespace.svc.cluster.local` | Internal services |
| **Service Registry** | Database-backed | Dynamic services |
| **Service Mesh** | Istio/Linkerd | Complex routing |
| **Environment Vars** | `process.env.SERVICE_URL` | Simple/external |

**Kubernetes DNS Patterns:**
```
# Short (same namespace)
http://user-service

# Full (cross-namespace)
http://user-service.production.svc.cluster.local

# With port
http://user-service.production.svc.cluster.local:5000
```

**Health Check Endpoints:**
| Endpoint | Purpose |
|----------|---------|
| `/health` | Basic health |
| `/health/live` | K8s liveness probe |
| `/health/ready` | K8s readiness probe |

**Load Balancing Strategies:**
| Strategy | When to Use |
|----------|-------------|
| **Round Robin** | Equal capacity servers |
| **Least Connections** | Varying request durations |
| **Weighted** | Different server capacities |

**Service Registration Lifecycle:**
```
Startup → Register → Heartbeat (30s) → ... → Shutdown → Unregister
```

**Essential Code:**
```typescript
// Discovery
const url = `http://${serviceName}.${namespace}.svc.cluster.local`;

// Registry check
const service = await registry.discover('user-service');
if (service?.status === 'healthy') { /* use service */ }

// Heartbeat
setInterval(() => registry.register({ ...info, lastHeartbeat: new Date() }), 30000);
```

## Resources

- [Kubernetes Service Discovery](https://kubernetes.io/docs/concepts/services-networking/service/)
- [Istio Service Mesh](https://istio.io/latest/docs/ops/deployment/)
- [Deployment Kubernetes](../deployment-kubernetes/SKILL.md) - K8s patterns
- [Observability & Monitoring](../observability-monitoring/SKILL.md) - Health checks
- [Project Rules](../project-rules/SKILL.md) - GoodGo standards
