Skip to content

Service Discovery in ConnectSoft Microservice Template

Purpose & Overview

Service Discovery in the ConnectSoft Microservice Template provides a provider-agnostic mechanism for resolving logical service names (e.g., https://catalog) into concrete endpoints at runtime. This enables clean, decoupled inter-service calls across different environments (local development, CI/CD, Kubernetes, cloud) without hard-coding hostnames or IP addresses.

Service discovery provides:

  • Logical Service Names: Use logical names like https://orders instead of hardcoded endpoints
  • Multi-Environment Support: Same code works across development, staging, and production
  • Dynamic Resolution: Endpoints are resolved at runtime, supporting dynamic scaling and failover
  • Provider Flexibility: Support for Configuration, DNS, and DNS-SRV providers
  • Automatic Load Balancing: Multiple endpoints per service are automatically load balanced
  • Integration with HttpClient: Seamless integration with IHttpClientFactory and HTTP clients
  • YARP Integration: Service discovery for API Gateway destinations

Service Discovery Philosophy

Service discovery enables microservices to find and communicate with each other dynamically, even when instances are ephemeral, scalable, or running in multiple environments. By using logical service names instead of hardcoded endpoints, services remain decoupled from infrastructure details, enabling flexible deployment and scaling strategies.

Architecture Overview

Service Discovery Flow

HTTP Request with Logical URI
HttpClient (with Service Discovery Handler)
Service Discovery Resolver
    ├── Configuration Provider (appsettings.json)
    ├── DNS Provider (DNS A/AAAA records)
    └── DNS-SRV Provider (DNS SRV records)
Endpoint Resolution
    ├── Multiple Endpoints → Load Balancing
    └── Single Endpoint → Direct Connection
HTTP Request to Resolved Endpoint

Service Discovery Integration Points

Application Startup
    ├── AddMicroserviceServiceDiscovery()
    │   ├── AddServiceDiscoveryCore()
    │   ├── AddConfigurationServiceEndpointProvider() (if Configuration)
    │   ├── AddDnsServiceEndpointProvider() (if DNS)
    │   └── AddDnsSrvServiceEndpointProvider() (if DNS-SRV)
HttpClient Configuration
    ├── ConfigureHttpClientDefaults()
    │   └── AddServiceDiscovery()
    └── AddHttpClient<T>()
        └── BaseAddress = new Uri("https://logical-service-name")
Runtime Resolution
    ├── Logical URI → Concrete Endpoint
    └── Request Execution

Service Registration

Registration in Application Startup

Service discovery is registered in MicroserviceRegistrationExtensions.cs:

// MicroserviceRegistrationExtensions.cs
internal static IServiceCollection AddMicroserviceServices(
    this IServiceCollection services,
    IConfiguration configuration,
    IHostEnvironment environment)
{
    // ... other registrations ...

    services.AddMicroserviceServiceDiscovery();

    // ... other registrations ...
}

Registration Extension

The AddMicroserviceServiceDiscovery() extension method:

// ServiceDiscoveryExtensions.cs
internal static IServiceCollection AddMicroserviceServiceDiscovery(
    this IServiceCollection services)
{
    ArgumentNullException.ThrowIfNull(services);

    if (OptionsExtensions.ServiceDiscoveryOptions.Enabled)
    {
        // Add Core Service Discovery with options
        services.AddServiceDiscoveryCore(serviceDiscoveryOptions =>
        {
            serviceDiscoveryOptions.RefreshPeriod = OptionsExtensions.ServiceDiscoveryOptions.RefreshPeriod;
            serviceDiscoveryOptions.AllowAllSchemes = OptionsExtensions.ServiceDiscoveryOptions.AllowAllSchemes;
            serviceDiscoveryOptions.AllowedSchemes = OptionsExtensions.ServiceDiscoveryOptions.AllowedSchemes;
        });

        // Register provider based on configuration
        if (OptionsExtensions.ServiceDiscoveryOptions.ServiceDiscoveryProvider == 
            Options.ServiceDiscoveryProviderType.Configuration)
        {
            services.AddConfigurationServiceEndpointProvider(configureOptions =>
            {
                configureOptions.SectionName = OptionsExtensions.ServiceDiscoveryOptions
                    .ConfigurationServiceEndpointSectionName;
            });
            services.AddPassThroughServiceEndpointProvider();
        }
        else if (OptionsExtensions.ServiceDiscoveryOptions.ServiceDiscoveryProvider == 
                 Options.ServiceDiscoveryProviderType.Dns)
        {
            services.AddDnsServiceEndpointProvider(configure =>
            {
                configure.DefaultRefreshPeriod = OptionsExtensions.ServiceDiscoveryOptions.DnsOptions.DefaultRefreshPeriod;
                configure.MinRetryPeriod = OptionsExtensions.ServiceDiscoveryOptions.DnsOptions.MinRetryPeriod;
                configure.MaxRetryPeriod = OptionsExtensions.ServiceDiscoveryOptions.DnsOptions.MaxRetryPeriod;
                configure.RetryBackOffFactor = OptionsExtensions.ServiceDiscoveryOptions.DnsOptions.RetryBackOffFactor;
            });
        }
        else if (OptionsExtensions.ServiceDiscoveryOptions.ServiceDiscoveryProvider == 
                 Options.ServiceDiscoveryProviderType.DnsSrv)
        {
            services.AddDnsSrvServiceEndpointProvider(configure =>
            {
                configure.DefaultRefreshPeriod = OptionsExtensions.ServiceDiscoveryOptions.DnsSrvOptions.DefaultRefreshPeriod;
                configure.MinRetryPeriod = OptionsExtensions.ServiceDiscoveryOptions.DnsSrvOptions.MinRetryPeriod;
                configure.MaxRetryPeriod = OptionsExtensions.ServiceDiscoveryOptions.DnsSrvOptions.MaxRetryPeriod;
                configure.RetryBackOffFactor = OptionsExtensions.ServiceDiscoveryOptions.DnsSrvOptions.RetryBackOffFactor;
                configure.QuerySuffix = OptionsExtensions.ServiceDiscoveryOptions.DnsSrvOptions.QuerySuffix;
            });
        }
    }

    return services;
}

Configuration

Service Discovery Options

Service discovery is configured via ServiceDiscoveryOptions:

// ServiceDiscoveryOptions.cs
public sealed class ServiceDiscoveryOptions
{
    public const string SectionName = "ServiceDiscovery";

    [Required]
    required public bool Enabled { get; set; } = true;

    [RequiredIf(nameof(Enabled), true)]
    [EnumDataType(typeof(ServiceDiscoveryProviderType))]
    required public ServiceDiscoveryProviderType ServiceDiscoveryProvider { get; set; } 
        = ServiceDiscoveryProviderType.Configuration;

    [RequiredIf(nameof(Enabled), true)]
    required public bool AllowAllSchemes { get; set; } = true;

    [RequiredIf(nameof(Enabled), true)]
    public IList<string>? AllowedSchemes { get; set; } = new List<string> { "https" };

    [RequiredIf(nameof(Enabled), true)]
    [Range(typeof(TimeSpan), "00:00:01", "1.00:00:00")]
    required public TimeSpan RefreshPeriod { get; set; } = TimeSpan.FromSeconds(60);

    [RequiredIf(nameof(ServiceDiscoveryProvider), ServiceDiscoveryProviderType.Configuration)]
    [MinLength(1)]
    public string? ConfigurationServiceEndpointSectionName { get; set; } = "Services";

    [RequiredIf(nameof(ServiceDiscoveryProvider), ServiceDiscoveryProviderType.Dns)]
    [ValidateObjectMembers]
    public DnsCommonOptions? DnsOptions { get; set; }

    [RequiredIf(nameof(ServiceDiscoveryProvider), ServiceDiscoveryProviderType.DnsSrv)]
    [ValidateObjectMembers]
    public DnsSrvOptions? DnsSrvOptions { get; set; }
}

Configuration in appsettings.json

{
  "ServiceDiscovery": {
    "Enabled": true,
    "ServiceDiscoveryProvider": "Configuration",
    "AllowAllSchemes": false,
    "AllowedSchemes": [ "https" ],
    "RefreshPeriod": "00:01:00",
    "ConfigurationServiceEndpointSectionName": "Services",
    "Dns": {
      "DefaultRefreshPeriod": "00:01:00",
      "MinRetryPeriod": "00:00:01",
      "MaxRetryPeriod": "00:00:30",
      "RetryBackOffFactor": 2.0
    },
    "DnsSrv": {
      "DefaultRefreshPeriod": "00:01:00",
      "MinRetryPeriod": "00:00:01",
      "MaxRetryPeriod": "00:00:30",
      "RetryBackOffFactor": 2.0,
      "QuerySuffix": "default.svc.cluster.local"
    }
  }
}

Configuration Options

Option Type Default Description
Enabled bool true Enable or disable service discovery
ServiceDiscoveryProvider enum Configuration Provider type: Configuration, Dns, or DnsSrv
AllowAllSchemes bool true Allow all URI schemes (http, https)
AllowedSchemes List<string> ["https"] Allowed URI schemes when AllowAllSchemes is false
RefreshPeriod TimeSpan 00:01:00 Period between polling attempts for providers without change notifications
ConfigurationServiceEndpointSectionName string "Services" Configuration section name for service endpoints

Service Discovery Providers

Configuration Provider

Purpose: Resolve endpoints from configuration sources (appsettings.json, Azure App Configuration, environment variables).

Use Cases: - Local development - Testing environments - Static endpoint configurations - Azure App Configuration integration

Configuration:

{
  "ServiceDiscovery": {
    "Enabled": true,
    "ServiceDiscoveryProvider": "Configuration",
    "ConfigurationServiceEndpointSectionName": "Services"
  },
  "Services": {
    "catalog": {
      "http": [ "localhost:5107" ],
      "https": [ "localhost:7243", "10.20.30.40:443" ]
    },
    "orders": {
      "http": [ "orders.svc.cluster.local:8080" ],
      "https": [ "orders.svc.cluster.local:8443" ]
    },
    "payment": {
      "https": [ "payment-api.example.com:443" ]
    }
  }
}

Characteristics: - Multiple endpoints per scheme supported - Automatic load balancing across multiple endpoints - Works with any IConfiguration source - Supports dynamic refresh with Azure App Configuration

Custom Section Name:

{
  "ServiceDiscovery": {
    "ServiceDiscoveryProvider": "Configuration",
    "ConfigurationServiceEndpointSectionName": "ServiceEndpoints"
  },
  "ServiceEndpoints": {
    "catalog": {
      "https": [ "api.example.com:443" ]
    }
  }
}

DNS Provider

Purpose: Resolve service endpoints via DNS A/AAAA record lookups.

Use Cases: - Kubernetes Services (standard services) - DNS-based service discovery - VM/host-based service registration

Configuration:

{
  "ServiceDiscovery": {
    "Enabled": true,
    "ServiceDiscoveryProvider": "Dns",
    "Dns": {
      "DefaultRefreshPeriod": "00:01:00",
      "MinRetryPeriod": "00:00:01",
      "MaxRetryPeriod": "00:00:30",
      "RetryBackOffFactor": 2.0
    }
  }
}

DNS Provider Options:

Option Type Default Description
DefaultRefreshPeriod TimeSpan 00:01:00 Default refresh period for DNS lookups
MinRetryPeriod TimeSpan 00:00:01 Minimum retry period on failure
MaxRetryPeriod TimeSpan 00:00:30 Maximum retry period on failure
RetryBackOffFactor double 2.0 Exponential backoff multiplier

Usage:

// Logical URI automatically resolves via DNS
services.AddHttpClient<ICatalogClient, CatalogClient>(client =>
{
    client.BaseAddress = new Uri("https://catalog-service.default.svc.cluster.local");
    // DNS provider resolves the hostname to IP addresses
});

Kubernetes Integration:

In Kubernetes, services are automatically registered with DNS:

# Kubernetes Service
apiVersion: v1
kind: Service
metadata:
  name: catalog-service
spec:
  selector:
    app: catalog
  ports:
    - port: 443
      targetPort: 7279

DNS Resolution: - Service name: catalog-service - Full DNS name: catalog-service.default.svc.cluster.local - Short name: catalog-service (within same namespace)

DNS-SRV Provider

Purpose: Resolve service endpoints via DNS SRV records, providing both hostname and port information.

Use Cases: - Kubernetes headless Services - Services with named ports - Advanced DNS-based service discovery

Configuration:

{
  "ServiceDiscovery": {
    "Enabled": true,
    "ServiceDiscoveryProvider": "DnsSrv",
    "DnsSrv": {
      "DefaultRefreshPeriod": "00:01:00",
      "MinRetryPeriod": "00:00:01",
      "MaxRetryPeriod": "00:00:30",
      "RetryBackOffFactor": 2.0,
      "QuerySuffix": "default.svc.cluster.local"
    }
  }
}

DNS-SRV Provider Options:

Option Type Default Description
DefaultRefreshPeriod TimeSpan 00:01:00 Default refresh period for DNS SRV lookups
MinRetryPeriod TimeSpan 00:00:01 Minimum retry period on failure
MaxRetryPeriod TimeSpan 00:00:30 Maximum retry period on failure
RetryBackOffFactor double 2.0 Exponential backoff multiplier
QuerySuffix string "default.svc.cluster.local" DNS suffix for SRV queries

Kubernetes Headless Service:

# Headless Service with named port
apiVersion: v1
kind: Service
metadata:
  name: catalog-service
spec:
  clusterIP: None  # Headless service
  selector:
    app: catalog
  ports:
    - name: https
      port: 443
      targetPort: 7279

DNS SRV Resolution: - SRV record: _https._tcp.catalog-service.default.svc.cluster.local - Returns: Hostname and port for each pod - Provides direct pod-to-pod communication

HttpClient Integration

Service Discovery with HttpClient

Service discovery is automatically enabled for all HTTP clients:

// HttpClientExtensions.cs
internal static IServiceCollection AddMicroserviceHttpClient(this IServiceCollection services)
{
    services.ConfigureHttpClientDefaults(http =>
    {
        if (OptionsExtensions.ServiceDiscoveryOptions.Enabled)
        {
            http.AddServiceDiscovery(); // Enable service discovery
        }
    });

    services.AddHttpClient()
        .AddHeaderPropagation();

    return services;
}

Using Logical Service Names

HTTP Client Configuration:

// Register HTTP client with logical service name
services.AddHttpClient<ICatalogClient, CatalogClient>(client =>
{
    client.BaseAddress = new Uri("https://catalog"); // Logical name, not DNS
    client.Timeout = TimeSpan.FromSeconds(30);
});

Service Client Implementation:

// CatalogClient.cs
public class CatalogClient : ICatalogClient
{
    private readonly HttpClient httpClient;

    public CatalogClient(HttpClient httpClient)
    {
        this.httpClient = httpClient;
        // httpClient.BaseAddress is already set to logical URI
        // Service discovery resolves it at runtime
    }

    public async Task<CatalogItemDto> GetItemAsync(Guid itemId, CancellationToken cancellationToken = default)
    {
        // Request to logical URI: https://catalog/api/items/{itemId}
        // Service discovery resolves to actual endpoint
        var response = await this.httpClient.GetAsync(
            $"/api/items/{itemId}", 
            cancellationToken);

        response.EnsureSuccessStatusCode();
        return await response.Content.ReadFromJsonAsync<CatalogItemDto>(cancellationToken: cancellationToken);
    }
}

Multiple Endpoints and Load Balancing

When multiple endpoints are configured, service discovery automatically load balances:

{
  "Services": {
    "catalog": {
      "https": [
        "catalog-service-1.example.com:443",
        "catalog-service-2.example.com:443",
        "catalog-service-3.example.com:443"
      ]
    }
  }
}

Load Balancing Behavior: - Requests are distributed across available endpoints - Failed endpoints are automatically excluded - Health checks ensure only healthy endpoints are used

Scheme Policy

Allow All Schemes:

{
  "ServiceDiscovery": {
    "AllowAllSchemes": true
  }
}

Allows both http:// and https:// logical URIs.

Restrict to Specific Schemes:

{
  "ServiceDiscovery": {
    "AllowAllSchemes": false,
    "AllowedSchemes": [ "https" ]
  }
}

Only allows https:// logical URIs (recommended for production).

Azure App Configuration Integration

Storing Service Discovery Configuration

Service discovery configuration and endpoints can be stored in Azure App Configuration:

// Program.cs
builder.Configuration.AddAzureAppConfiguration(options =>
{
    options.Connect(connectionString)
        .Select("ServiceDiscovery:*", LabelFilter.Null)
        .Select("Services:*", LabelFilter.Null)
        .ConfigureRefresh(refresh =>
        {
            refresh.Register("ServiceDiscovery:Sentinel", refreshAll: true);
            refresh.SetRefreshInterval(TimeSpan.FromMinutes(5));
        });
});

builder.Services.AddAzureAppConfiguration();

Configuration in Azure App Configuration:

ServiceDiscovery:Enabled = true
ServiceDiscovery:ServiceDiscoveryProvider = Configuration
ServiceDiscovery:AllowAllSchemes = false
ServiceDiscovery:AllowedSchemes:0 = https

Services:catalog:https:0 = catalog-service-1.example.com:443
Services:catalog:https:1 = catalog-service-2.example.com:443
Services:orders:https:0 = orders-service.example.com:443

Dynamic Refresh:

// Enable dynamic refresh middleware
app.UseAzureAppConfiguration();

// When "ServiceDiscovery:Sentinel" key changes, all service discovery
// configuration is refreshed without restarting the application

See Azure App Configuration for detailed information.

YARP Integration

Service Discovery for API Gateway

YARP (Yet Another Reverse Proxy) can use service discovery for destination resolution:

// Program.cs
services.AddServiceDiscovery();
services.AddReverseProxy()
    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))
    .AddServiceDiscoveryDestinationResolver(); // Enable service discovery

YARP Configuration with Service Discovery:

{
  "ReverseProxy": {
    "Clusters": {
      "catalog-cluster": {
        "Destinations": {
          "catalog": {
            "Address": "https://catalog"  // Logical service name
          }
        }
      },
      "orders-cluster": {
        "Destinations": {
          "orders": {
            "Address": "https://orders"  // Logical service name
          }
        }
      }
    },
    "Routes": {
      "catalog-route": {
        "ClusterId": "catalog-cluster",
        "Match": {
          "Path": "/catalog/{**catchall}"
        }
      },
      "orders-route": {
        "ClusterId": "orders-cluster",
        "Match": {
          "Path": "/orders/{**catchall}"
        }
      }
    }
  }
}

Benefits: - API Gateway routes to logical service names - Service discovery resolves to actual endpoints - Automatic failover and load balancing - No hardcoded endpoints in gateway configuration

Environment Variables

Configuration via Environment Variables

Service discovery can be configured using environment variables:

# Service Discovery Options
export ServiceDiscovery__Enabled=true
export ServiceDiscovery__ServiceDiscoveryProvider=Configuration
export ServiceDiscovery__AllowAllSchemes=false
export ServiceDiscovery__AllowedSchemes__0=https
export ServiceDiscovery__RefreshPeriod=00:01:00

# Service Endpoints (Configuration Provider)
export Services__catalog__https__0=api.example.com:443
export Services__catalog__https__1=api2.example.com:443
export Services__orders__http__0=orders.svc.cluster.local:8080

Docker/Kubernetes Environment Variables:

# Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
      - name: microservice
        env:
        - name: ServiceDiscovery__Enabled
          value: "true"
        - name: ServiceDiscovery__ServiceDiscoveryProvider
          value: "Dns"
        - name: Services__catalog__https__0
          value: "catalog-service.default.svc.cluster.local:443"

Service Discovery Patterns

Client-Side Discovery

Pattern: Client queries service registry to resolve endpoints, then connects directly.

Implementation: - Service discovery resolver queries provider - Client receives resolved endpoints - Client connects directly to service instances

Use Cases: - Direct service-to-service communication - Microservices in same cluster - Low-latency requirements

Server-Side Discovery

Pattern: Gateway or load balancer resolves endpoints and forwards requests.

Implementation: - YARP API Gateway uses service discovery - Gateway resolves logical names to endpoints - Gateway forwards requests to resolved endpoints

Use Cases: - API Gateway routing - External client access - Centralized routing and security

Best Practices

Do's

  1. Use Logical Service Names

    // ✅ GOOD - Logical service name
    client.BaseAddress = new Uri("https://catalog");
    
    // ❌ BAD - Hardcoded endpoint
    client.BaseAddress = new Uri("https://catalog-service-1.example.com:443");
    

  2. Restrict to HTTPS in Production

    {
      "ServiceDiscovery": {
        "AllowAllSchemes": false,
        "AllowedSchemes": [ "https" ]
      }
    }
    

  3. Configure Appropriate Refresh Periods

    {
      "ServiceDiscovery": {
        "RefreshPeriod": "00:01:00"  // 1 minute for most cases
      }
    }
    

  4. Use Multiple Endpoints for High Availability

    {
      "Services": {
        "catalog": {
          "https": [
            "catalog-1.example.com:443",
            "catalog-2.example.com:443",
            "catalog-3.example.com:443"
          ]
        }
      }
    }
    

  5. Use DNS Provider in Kubernetes

    {
      "ServiceDiscovery": {
        "ServiceDiscoveryProvider": "Dns"
      }
    }
    

  6. Use DNS-SRV for Headless Services

    {
      "ServiceDiscovery": {
        "ServiceDiscoveryProvider": "DnsSrv",
        "DnsSrv": {
          "QuerySuffix": "default.svc.cluster.local"
        }
      }
    }
    

Don'ts

  1. Don't Hardcode Endpoints

    // ❌ BAD - Hardcoded endpoint
    client.BaseAddress = new Uri("https://10.0.1.5:8443");
    
    // ✅ GOOD - Logical service name
    client.BaseAddress = new Uri("https://catalog");
    

  2. Don't Use HTTP in Production

    {
      "ServiceDiscovery": {
        "AllowAllSchemes": true  // ❌ BAD - Allows HTTP
      }
    }
    

  3. Don't Set Refresh Period Too Short

    {
      "ServiceDiscovery": {
        "RefreshPeriod": "00:00:01"  // ❌ BAD - Too frequent DNS lookups
      }
    }
    

  4. Don't Mix Providers Without Understanding

    // ❌ BAD - Unclear which provider is used
    {
      "ServiceDiscovery": {
        "ServiceDiscoveryProvider": "Configuration",
        "Dns": { ... }  // Ignored
      }
    }
    

Troubleshooting

Issue: No Endpoints Resolved

Symptoms: HTTP requests fail with "No endpoints resolved for logical name" error.

Solutions:

  1. Configuration Provider:
  2. Verify Services section exists in configuration
  3. Check section name matches ConfigurationServiceEndpointSectionName
  4. Verify service name matches exactly (case-sensitive)
  5. Check endpoint format: "https": [ "host:port" ]

  6. DNS Provider:

  7. Verify DNS name exists and is resolvable
  8. Check DNS is accessible from pod/VM
  9. Verify DNS TTL settings
  10. Test DNS resolution: nslookup catalog-service.default.svc.cluster.local

  11. DNS-SRV Provider:

  12. Verify headless Service exists in Kubernetes
  13. Check named ports are configured
  14. Verify QuerySuffix matches cluster DNS suffix
  15. Test SRV lookup: dig _https._tcp.catalog-service.default.svc.cluster.local SRV

Issue: HTTP 5xx or Timeouts

Symptoms: Requests to resolved endpoints return 5xx errors or timeout.

Solutions: - Verify target service is healthy and running - Check network connectivity between services - Review resilience handler configuration (retries, timeouts) - Check service logs for errors - Verify endpoints are accessible from calling service

Issue: Scheme Blocked

Symptoms: Error indicating scheme is not allowed.

Solutions:

{
  "ServiceDiscovery": {
    "AllowAllSchemes": true  // For development
    // OR
    "AllowAllSchemes": false,
    "AllowedSchemes": [ "https" ]  // For production
  }
}

Issue: Dynamic Refresh Not Working

Symptoms: Changes to Azure App Configuration not reflected in running application.

Solutions: - Verify UseAzureAppConfiguration() middleware is in pipeline - Check sentinel key is being updated - Verify refresh interval is appropriate - Check Azure App Configuration connection string is valid - Review logs for refresh errors

Issue: Load Balancing Not Working

Symptoms: All requests go to single endpoint despite multiple endpoints configured.

Solutions: - Verify multiple endpoints are configured correctly - Check endpoint health status - Review load balancing algorithm configuration - Verify endpoints are all accessible - Check for endpoint filtering (scheme, health)

Monitoring and Observability

Service Discovery Metrics

Monitor service discovery resolution:

// Custom metrics for service discovery
public class ServiceDiscoveryMetrics
{
    private readonly Counter<int> resolutionCount;
    private readonly Counter<int> resolutionFailures;
    private readonly Histogram<double> resolutionDuration;

    public void RecordResolution(string serviceName, bool success, TimeSpan duration)
    {
        if (success)
        {
            this.resolutionCount.Add(1, KeyValuePair.Create("service", serviceName));
        }
        else
        {
            this.resolutionFailures.Add(1, KeyValuePair.Create("service", serviceName));
        }

        this.resolutionDuration.Record(duration.TotalMilliseconds, 
            KeyValuePair.Create("service", serviceName));
    }
}

Logging

Service discovery logs resolution activities:

// Service discovery logs
_logger.LogDebug("Resolving service: {ServiceName}", serviceName);
_logger.LogInformation("Resolved {ServiceName} to {Endpoint}", serviceName, endpoint);
_logger.LogWarning("Failed to resolve service: {ServiceName}", serviceName);

Summary

Service discovery in the ConnectSoft Microservice Template provides:

  • Provider-Agnostic: Support for Configuration, DNS, and DNS-SRV providers
  • Logical Service Names: Use logical names instead of hardcoded endpoints
  • Multi-Environment: Same code works across all environments
  • Automatic Load Balancing: Multiple endpoints automatically load balanced
  • HttpClient Integration: Seamless integration with HTTP clients
  • YARP Integration: Service discovery for API Gateway destinations
  • Azure App Configuration: Dynamic configuration refresh
  • Kubernetes Native: DNS-based discovery for Kubernetes

By leveraging service discovery, teams can:

  • Decouple Services: Services remain independent of infrastructure details
  • Scale Dynamically: Services can scale without configuration changes
  • Deploy Flexibly: Same code works across environments
  • Improve Reliability: Automatic failover and load balancing
  • Simplify Configuration: Logical names instead of hardcoded endpoints

Service discovery is a critical component of microservices architecture, enabling services to find and communicate with each other dynamically while maintaining flexibility and reliability.