#service-discovery #registry #consul #microservices

revoke-registry

Service registry and discovery for Revoke microservices framework

1 unstable release

Uses new Rust 2024

0.3.0 Jul 13, 2025

#1 in #consul


Used in 2 crates

MIT/Apache

45KB
624 lines

revoke-registry

Service registry module for the Revoke microservices framework, providing service discovery and health monitoring capabilities.

Features

  • Multiple Backends: Support for Consul, etcd, and in-memory implementations
  • Service Discovery: Automatic service registration and discovery
  • Health Monitoring: Active and passive health checks
  • Load Balancing: Integration with various load balancing strategies
  • Metadata Support: Attach custom metadata to services
  • Watch Support: Real-time updates on service changes
  • High Availability: Support for registry clustering

Installation

Add to your Cargo.toml:

[dependencies]
revoke-registry = { version = "0.1", features = ["consul"] }

Feature Flags

  • memory: In-memory registry (default, useful for testing)
  • consul: HashiCorp Consul integration
  • etcd: etcd v3 integration
  • kubernetes: Kubernetes service discovery
  • full: Enable all backends

Quick Start

use revoke_registry::{ServiceRegistry, ServiceInfo};
use revoke_core::{ServiceRegistry as RegistryTrait, Protocol};
use uuid::Uuid;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create registry client
    let registry = ServiceRegistry::consul("http://localhost:8500").await?;
    
    // Register service
    let service = ServiceInfo {
        id: Uuid::new_v4(),
        name: "user-service".to_string(),
        version: "1.0.0".to_string(),
        address: "192.168.1.100".to_string(),
        port: 8080,
        protocol: Protocol::Http,
        metadata: Default::default(),
    };
    
    registry.register(service).await?;
    
    // Discover services
    let services = registry.get_service("user-service").await?;
    for svc in services {
        println!("Found: {}:{}", svc.address, svc.port);
    }
    
    Ok(())
}

Backend Implementations

Memory Registry

In-memory implementation for development and testing:

use revoke_registry::memory::MemoryRegistry;

let registry = MemoryRegistry::new();

// Register with TTL
registry.register_with_ttl(service, Duration::from_secs(30)).await?;

// Get all services
let all_services = registry.list_all_services().await?;

Consul Registry

HashiCorp Consul integration with full feature support:

use revoke_registry::consul::{ConsulRegistry, ConsulConfig};
use std::time::Duration;

let config = ConsulConfig {
    address: "localhost:8500".to_string(),
    datacenter: Some("dc1".to_string()),
    token: None,  // ACL token if needed
    check_interval: Duration::from_secs(10),
    deregister_critical: "30s".to_string(),
    ..Default::default()
};

let registry = ConsulRegistry::new(config).await?;

// Enable health checks
registry.enable_health_check(
    service_id,
    HealthCheckConfig {
        interval: Duration::from_secs(10),
        timeout: Duration::from_secs(5),
        deregister_after: Duration::from_secs(30),
    }
).await?;

etcd Registry

etcd v3 API integration:

use revoke_registry::etcd::{EtcdRegistry, EtcdConfig};

let config = EtcdConfig {
    endpoints: vec!["http://localhost:2379".to_string()],
    username: None,
    password: None,
    ttl: 60,  // Service TTL in seconds
    ..Default::default()
};

let registry = EtcdRegistry::new(config).await?;

// Watch for service changes
let mut watcher = registry.watch("user-service").await?;
while let Some(event) = watcher.next().await {
    match event {
        ServiceEvent::Added(service) => println!("Service added: {}", service.id),
        ServiceEvent::Modified(service) => println!("Service modified: {}", service.id),
        ServiceEvent::Removed(id) => println!("Service removed: {}", id),
    }
}

Service Discovery Patterns

Client-Side Discovery

use revoke_registry::discovery::DiscoveryClient;

let discovery = DiscoveryClient::new(registry);

// Get service instance with load balancing
let instance = discovery
    .get_instance("user-service")
    .with_load_balancer(LoadBalancer::RoundRobin)
    .await?;

// Make request to the instance
let url = format!("http://{}:{}/api/users", instance.address, instance.port);

Server-Side Discovery

use revoke_registry::discovery::ServiceResolver;

let resolver = ServiceResolver::new(registry);

// Resolve service name to address
let endpoint = resolver.resolve("user-service").await?;

// Use with HTTP client
let client = reqwest::Client::new();
let response = client.get(&endpoint.url("/api/users")).send().await?;

Health Checking

Active Health Checks

use revoke_registry::health::{HealthChecker, HealthCheckType};

let checker = HealthChecker::new(registry);

// HTTP health check
checker.add_check(
    service_id,
    HealthCheckType::Http {
        url: "http://localhost:8080/health".to_string(),
        method: "GET".to_string(),
        expected_status: 200,
        timeout: Duration::from_secs(5),
    }
).await?;

// TCP health check
checker.add_check(
    service_id,
    HealthCheckType::Tcp {
        address: "localhost:8080".to_string(),
        timeout: Duration::from_secs(5),
    }
).await?;

// Script health check
checker.add_check(
    service_id,
    HealthCheckType::Script {
        command: "/usr/local/bin/check-health.sh".to_string(),
        args: vec![service_id.to_string()],
        timeout: Duration::from_secs(10),
    }
).await?;

Passive Health Checks

use revoke_registry::health::PassiveHealthCheck;

let passive_check = PassiveHealthCheck::new()
    .failure_threshold(5)      // Mark unhealthy after 5 failures
    .success_threshold(2)      // Mark healthy after 2 successes
    .timeout(Duration::from_secs(30));

// Report request outcome
passive_check.report_success(service_id).await;
passive_check.report_failure(service_id).await;

// Check health status
let is_healthy = passive_check.is_healthy(service_id).await;

Advanced Features

Service Metadata

let mut metadata = HashMap::new();
metadata.insert("region".to_string(), "us-west-2".to_string());
metadata.insert("zone".to_string(), "us-west-2a".to_string());
metadata.insert("version".to_string(), "1.2.3".to_string());
metadata.insert("commit".to_string(), "abc123".to_string());

let service = ServiceInfo {
    metadata,
    ..Default::default()
};

// Query by metadata
let services = registry
    .query_services()
    .with_metadata("region", "us-west-2")
    .with_metadata("version", "1.2.3")
    .execute()
    .await?;

Service Groups

use revoke_registry::groups::ServiceGroup;

// Create service group
let group = ServiceGroup::new("api-services")
    .add_service("user-service")
    .add_service("order-service")
    .add_service("payment-service");

registry.register_group(group).await?;

// Get all services in group
let services = registry.get_group("api-services").await?;

Circuit Breaker Integration

use revoke_registry::resilience::RegistryCircuitBreaker;

let breaker = RegistryCircuitBreaker::new(registry)
    .failure_threshold(5)
    .recovery_timeout(Duration::from_secs(60));

// Discover with circuit breaker
match breaker.get_service("user-service").await {
    Ok(services) => {
        // Use services
    }
    Err(e) if e.is_circuit_open() => {
        // Use fallback
    }
    Err(e) => {
        // Handle other errors
    }
}

Multi-Region Support

use revoke_registry::multiregion::{MultiRegionRegistry, RegionConfig};

let regions = vec![
    RegionConfig {
        name: "us-west-2".to_string(),
        endpoint: "http://consul-us-west-2:8500".to_string(),
        priority: 1,
    },
    RegionConfig {
        name: "us-east-1".to_string(),
        endpoint: "http://consul-us-east-1:8500".to_string(),
        priority: 2,
    },
];

let registry = MultiRegionRegistry::new(regions).await?;

// Prefer local region
let services = registry
    .get_service("user-service")
    .prefer_region("us-west-2")
    .await?;

Performance Optimization

Caching

use revoke_registry::cache::{CachedRegistry, CacheConfig};

let cache_config = CacheConfig {
    ttl: Duration::from_secs(5),
    max_size: 1000,
    refresh_interval: Duration::from_secs(30),
};

let cached_registry = CachedRegistry::new(registry, cache_config);

// Cached lookups
let services = cached_registry.get_service("user-service").await?;

Batch Operations

// Register multiple services
let services = vec![service1, service2, service3];
registry.register_batch(services).await?;

// Deregister multiple services
let ids = vec![id1, id2, id3];
registry.deregister_batch(ids).await?;

// Batch health updates
let updates = vec![
    (id1, HealthStatus::Healthy),
    (id2, HealthStatus::Unhealthy),
];
registry.update_health_batch(updates).await?;

Monitoring and Metrics

use revoke_registry::metrics::RegistryMetrics;

let metrics = RegistryMetrics::new(&registry);

// Get registry statistics
let stats = metrics.get_stats().await?;
println!("Total services: {}", stats.total_services);
println!("Healthy services: {}", stats.healthy_services);
println!("Unhealthy services: {}", stats.unhealthy_services);

// Export Prometheus metrics
let prometheus_metrics = metrics.export_prometheus();

Best Practices

  1. Service Naming: Use consistent naming conventions (e.g., service-name-v1)
  2. Health Checks: Always configure appropriate health checks
  3. Metadata: Include version, region, and deployment information
  4. TTL: Set reasonable TTL values for service registration
  5. Graceful Shutdown: Always deregister services on shutdown
  6. Connection Pooling: Reuse registry clients across your application
  7. Error Handling: Implement retry logic for transient failures

Examples

See the examples directory:

  • basic_registration.rs - Simple service registration
  • health_checks.rs - Health check configuration
  • service_discovery.rs - Client-side discovery patterns
  • multi_region.rs - Multi-region deployment

Dependencies

~5–18MB
~236K SLoC