Ana içeriğe geç

Service Discovery

Service Discovery, microservice’lerin birbirini dinamik olarak bulmasını sağlar; hardcoded adresler ortam değişikliklerinde kırılganlığa yol açar.


1. Hardcoded Servis Adresleri

Yanlış Kullanım: Servis adreslerini kod içinde sabitlemek.

public class OrderServiceClient
{
    private readonly HttpClient _client;

    public OrderServiceClient(HttpClient client)
    {
        _client = client;
        _client.BaseAddress = new Uri("https://192.168.1.50:5001"); // IP değişirse kod güncellenmeli
    }
}

İdeal Kullanım: Konfigürasyondan dinamik adres yönetimi yapın.

builder.Services.AddHttpClient<IOrderServiceClient, OrderServiceClient>((sp, client) =>
{
    var config = sp.GetRequiredService<IConfiguration>();
    client.BaseAddress = new Uri(config["Services:OrderService:BaseUrl"]);
});

// appsettings.json
{
    "Services": {
        "OrderService": {
            "BaseUrl": "https://order-service:5001"
        }
    }
}

2. DNS-Based Discovery Kullanmamak

Yanlış Kullanım: Her ortam için ayrı konfigürasyon dosyası yönetmek.

// appsettings.Development.json
{ "OrderServiceUrl": "https://localhost:5001" }
// appsettings.Staging.json
{ "OrderServiceUrl": "https://order-staging:5001" }
// appsettings.Production.json
{ "OrderServiceUrl": "https://order-prod:5001" }

İdeal Kullanım: Kubernetes DNS ile otomatik service discovery yapın.

builder.Services.AddHttpClient<IOrderServiceClient, OrderServiceClient>(client =>
{
    // Kubernetes'te servis adı DNS üzerinden çözümlenir
    client.BaseAddress = new Uri("http://order-service.default.svc.cluster.local");
});

// Veya kısaca (aynı namespace'de)
builder.Services.AddHttpClient<IOrderServiceClient, OrderServiceClient>(client =>
{
    client.BaseAddress = new Uri("http://order-service");
});

3. Consul ile Service Registry

Yanlış Kullanım: Servisleri manuel olarak konfigürasyona eklemek.

var services = new Dictionary<string, string>
{
    ["order-service"] = "https://order:5001",
    ["payment-service"] = "https://payment:5002",
    // Yeni servis eklemek için deploy gerekir
};

İdeal Kullanım: Consul ile dinamik servis kayıt ve keşif yapın.

builder.Services.AddSingleton<IConsulClient>(sp =>
    new ConsulClient(config => config.Address = new Uri("http://consul:8500")));

// Servis kaydı
public class ConsulRegistrationService : IHostedService
{
    private readonly IConsulClient _consul;
    private string _registrationId;

    public async Task StartAsync(CancellationToken ct)
    {
        _registrationId = $"order-service-{Guid.NewGuid()}";
        await _consul.Agent.ServiceRegister(new AgentServiceRegistration
        {
            ID = _registrationId,
            Name = "order-service",
            Address = "order-service",
            Port = 5001,
            Check = new AgentServiceCheck
            {
                HTTP = "http://order-service:5001/health",
                Interval = TimeSpan.FromSeconds(10)
            }
        }, ct);
    }

    public async Task StopAsync(CancellationToken ct)
    {
        await _consul.Agent.ServiceDeregister(_registrationId, ct);
    }
}

4. Load Balancing Yapmamak

Yanlış Kullanım: Tek instance’a sabit bağlantı kurmak.

builder.Services.AddHttpClient<IProductService, ProductServiceClient>(client =>
{
    client.BaseAddress = new Uri("https://product-service-1:5001"); // Tek instance
});

İdeal Kullanım: Client-side load balancing ile birden fazla instance’a dağıtım yapın.

// YARP ile load balancing
{
    "Clusters": {
        "product-cluster": {
            "LoadBalancingPolicy": "RoundRobin",
            "Destinations": {
                "instance1": { "Address": "https://product-service-1:5001" },
                "instance2": { "Address": "https://product-service-2:5001" },
                "instance3": { "Address": "https://product-service-3:5001" }
            },
            "HealthCheck": {
                "Active": {
                    "Enabled": true,
                    "Interval": "00:00:10",
                    "Path": "/health"
                }
            }
        }
    }
}

5. Service Discovery Cache Yönetimi

Yanlış Kullanım: Her istekte service registry’ye sorgu yapmak.

public async Task<string> GetServiceUrlAsync(string serviceName)
{
    var services = await _consul.Health.Service(serviceName); // Her çağrıda Consul'a istek
    return services.Response.First().Service.Address;
}

İdeal Kullanım: Servis adreslerini cache’leyip periyodik olarak yenileyin.

public class ServiceDiscoveryCache : IHostedService
{
    private readonly IConsulClient _consul;
    private readonly ConcurrentDictionary<string, List<string>> _cache = new();
    private Timer _timer;

    public Task StartAsync(CancellationToken ct)
    {
        _timer = new Timer(RefreshCache, null, TimeSpan.Zero, TimeSpan.FromSeconds(30));
        return Task.CompletedTask;
    }

    private async void RefreshCache(object state)
    {
        var services = await _consul.Health.Service("order-service");
        var addresses = services.Response
            .Where(s => s.Checks.All(c => c.Status == HealthStatus.Passing))
            .Select(s => $"https://{s.Service.Address}:{s.Service.Port}")
            .ToList();

        _cache["order-service"] = addresses;
    }

    public List<string> GetAddresses(string serviceName)
        => _cache.GetValueOrDefault(serviceName, new List<string>());

    public Task StopAsync(CancellationToken ct) { _timer?.Dispose(); return Task.CompletedTask; }
}