No description
|
Some checks failed
Build and Publish / build-and-publish (push) Failing after 42s
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com> |
||
|---|---|---|
| .forgejo/workflows | ||
| src | ||
| .gitignore | ||
| .npmignore | ||
| example-usage.ts | ||
| IMPLEMENTATION_SUMMARY.md | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
| tsup.config.ts | ||
@lilith/service-discovery
Redis-backed dynamic service discovery with health tracking, load balancing, and real-time event notifications.
Features
- Dynamic Service Registration: Auto-register services with metadata and health status
- Service Discovery: Query services by name, feature, type, tags, or health status
- Load Balancing: Multiple strategies (round-robin, random, least-response-time, least-connections)
- Health Tracking: Automatic heartbeat and health status updates
- Real-time Events: Watch for service registration, deregistration, and health changes
- Multi-index Support: Fast queries by service name, feature, type, and tags
- Atomic Operations: Lua scripts for race-condition-free updates
- NestJS Integration: First-class support for NestJS applications
Installation
pnpm add @lilith/service-discovery
Quick Start
Basic Usage
import { createServiceDiscovery } from '@lilith/service-discovery'
const client = createServiceDiscovery({
redis: 'redis://localhost:6379',
heartbeatIntervalMs: 10_000,
unhealthyThresholdMs: 30_000,
})
// Register service
const registration = client.getRegistration()
await registration.register(
'platform-admin-api',
'platform-admin',
'api',
'localhost',
3011,
'http',
{
tags: ['public', 'rest-api'],
metadata: { version: '1.0.0' },
}
)
// Discover services
const discovery = client.getDiscovery()
const services = await discovery.discoverAll({
featureName: 'platform-admin',
onlyHealthy: true,
})
// Get single instance with load balancing
const service = await discovery.discoverOne({
serviceName: 'platform-admin-api',
loadBalanceStrategy: 'round-robin',
})
NestJS Integration
import { Module } from '@nestjs/common'
import { ServiceDiscoveryModule, SERVICE_DISCOVERY_CLIENT } from '@lilith/service-discovery/nestjs'
import type { ServiceDiscoveryClient } from '@lilith/service-discovery'
@Module({
imports: [
ServiceDiscoveryModule.register({
redis: process.env.REDIS_URL || 'redis://localhost:6379',
heartbeatIntervalMs: 10_000,
}),
],
})
export class AppModule {}
// Inject in service
@Injectable()
export class MyService {
constructor(
@Inject(SERVICE_DISCOVERY_CLIENT)
private readonly discovery: ServiceDiscoveryClient,
) {}
async findService() {
const service = await this.discovery.getDiscovery().discoverOne({
serviceName: 'my-service',
})
return service?.url
}
}
API Reference
ServiceDiscoveryClient
Main client combining discovery and registration.
const client = createServiceDiscovery(config)
client.getDiscovery() // ServiceDiscovery interface
client.getRegistration() // ServiceRegistration interface
client.getLoadBalancer() // LoadBalancer interface
await client.cleanup() // Cleanup resources
ServiceRegistration
Register and manage service instances.
const registration = client.getRegistration()
// Register service
await registration.register(serviceName, featureName, serviceType, host, port, protocol, options)
// Send heartbeat
await registration.heartbeat()
// Update metadata
await registration.updateMetadata({ responseTimeMs: 120, activeConnections: 5 })
// Deregister
await registration.deregister()
ServiceDiscovery
Query and watch for services.
const discovery = client.getDiscovery()
// Find all matching services
const services = await discovery.discoverAll({
featureName: 'analytics',
serviceType: 'api',
tags: ['public'],
onlyHealthy: true,
})
// Find single instance with load balancing
const service = await discovery.discoverOne({
serviceName: 'analytics-api',
loadBalanceStrategy: 'least-response-time',
})
// Watch for changes
const subscription = await discovery.watch(
(event) => {
console.log(`Service ${event.type}:`, event.service)
},
{ featureName: 'analytics' }
)
// Unsubscribe
await subscription.unsubscribe()
Load Balancing Strategies
- round-robin: Cycle through instances sequentially
- random: Random selection from healthy instances
- least-response-time: Prefer instances with lowest response time
- least-connections: Prefer instances with fewest active connections
Configuration
interface DiscoveryConfig {
redis: string | Redis // Redis connection
keyPrefix?: string // Redis key prefix (default: 'service:discovery')
heartbeatIntervalMs?: number // Heartbeat interval (default: 10000)
unhealthyThresholdMs?: number // Mark unhealthy after (default: 30000)
expirationMs?: number // Remove after (default: 60000)
defaultLoadBalanceStrategy?: LoadBalanceStrategy // Default strategy (default: 'round-robin')
debug?: boolean // Debug logging (default: false)
}
Redis Data Structure
service:discovery:instance:{instanceId} → Service data (Hash with TTL)
service:discovery:instances:all → Set of all instance IDs
service:discovery:index:service:{name} → Set of instances for service
service:discovery:index:feature:{name} → Set of instances in feature
service:discovery:index:type:{type} → Set of instances by type
service:discovery:index:tag:{tag} → Set of instances with tag
service:discovery:events → Pub/Sub channel for events
Architecture
See ADR-009: Redis-backed Service Discovery for design decisions and implementation details.
Status
Wave 1: Scaffold and type definitions (CURRENT) Wave 2: Core implementation (registration, discovery, load balancing) Wave 3: Production hardening (error recovery, monitoring, benchmarks)
License
UNLICENSED - Internal Lilith Platform package