Ana içeriğe geç

Service Communication

Microservice’ler arası iletişim, sistemin dayanıklılığını ve performansını doğrudan etkiler; yanlış iletişim kalıpları sıkı bağımlılık ve cascading failure’lara yol açar.


1. Senkron Zincirleme Çağrılar

Yanlış Kullanım: Servisler arası senkron HTTP zinciri kurmak.

public class OrderService
{
    public async Task<OrderResult> CreateOrderAsync(OrderRequest request)
    {
        var customer = await _httpClient.GetFromJsonAsync<Customer>($"api/customers/{request.CustomerId}");
        var inventory = await _httpClient.GetFromJsonAsync<Stock>($"api/inventory/{request.ProductId}");
        var payment = await _httpClient.PostAsJsonAsync("api/payments", new { Amount = request.Total });
        var shipping = await _httpClient.PostAsJsonAsync("api/shipping", new { OrderId = request.Id });
        // Bir servis çökerse tüm zincir başarısız olur
    }
}

İdeal Kullanım: Asenkron mesajlaşma ile servisleri gevşek bağlayın.

public class OrderService
{
    private readonly IMessageBus _messageBus;
    private readonly IOrderRepository _repository;

    public async Task<OrderResult> CreateOrderAsync(OrderRequest request)
    {
        var order = Order.Create(request.CustomerId, request.ProductId, request.Total);
        await _repository.AddAsync(order);

        await _messageBus.PublishAsync(new OrderCreatedEvent
        {
            OrderId = order.Id,
            CustomerId = order.CustomerId,
            ProductId = order.ProductId,
            Total = order.Total
        });

        return new OrderResult(order.Id, OrderStatus.Pending);
    }
}

2. HttpClient Yanlış Kullanımı

Yanlış Kullanım: Her çağrıda yeni HttpClient oluşturmak.

public class CatalogServiceClient
{
    public async Task<Product> GetProductAsync(int id)
    {
        using var client = new HttpClient(); // Socket exhaustion riski
        client.BaseAddress = new Uri("https://catalog-service/");
        return await client.GetFromJsonAsync<Product>($"api/products/{id}");
    }
}

İdeal Kullanım: Typed HttpClient ile IHttpClientFactory kullanın.

builder.Services.AddHttpClient<ICatalogServiceClient, CatalogServiceClient>(client =>
{
    client.BaseAddress = new Uri("https://catalog-service/");
    client.Timeout = TimeSpan.FromSeconds(5);
})
.AddPolicyHandler(GetRetryPolicy())
.AddPolicyHandler(GetCircuitBreakerPolicy());

public class CatalogServiceClient : ICatalogServiceClient
{
    private readonly HttpClient _client;

    public CatalogServiceClient(HttpClient client) => _client = client;

    public async Task<Product> GetProductAsync(int id)
        => await _client.GetFromJsonAsync<Product>($"api/products/{id}");
}

3. gRPC Yerine Her Yerde REST Kullanmak

Yanlış Kullanım: Servisler arası internal iletişimde REST kullanmak.

// Her çağrıda JSON serialize/deserialize maliyeti
public async Task<UserProfile> GetUserAsync(int userId)
{
    var response = await _httpClient.GetAsync($"api/users/{userId}");
    var json = await response.Content.ReadAsStringAsync();
    return JsonSerializer.Deserialize<UserProfile>(json);
}

İdeal Kullanım: Internal servis iletişiminde gRPC kullanın.

// user.proto
service UserService {
    rpc GetUser (GetUserRequest) returns (UserResponse);
}

// Client
public class UserServiceClient
{
    private readonly UserService.UserServiceClient _client;

    public UserServiceClient(UserService.UserServiceClient client) => _client = client;

    public async Task<UserResponse> GetUserAsync(int userId)
    {
        return await _client.GetUserAsync(new GetUserRequest { UserId = userId });
    }
}

// DI kaydı
builder.Services.AddGrpcClient<UserService.UserServiceClient>(options =>
{
    options.Address = new Uri("https://user-service:5001");
});

4. Retry Olmadan Servis Çağrısı

Yanlış Kullanım: Geçici hatalarda yeniden deneme yapmamak.

public async Task<Product> GetProductAsync(int id)
{
    var response = await _httpClient.GetAsync($"api/products/{id}");
    response.EnsureSuccessStatusCode(); // İlk hatada exception fırlatır
    return await response.Content.ReadFromJsonAsync<Product>();
}

İdeal Kullanım: Polly ile retry ve circuit breaker politikaları ekleyin.

builder.Services.AddHttpClient<ICatalogService, CatalogService>()
    .AddTransientHttpErrorPolicy(p => p.WaitAndRetryAsync(3,
        retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))))
    .AddTransientHttpErrorPolicy(p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));

5. Timeout Yönetimi Yapmamak

Yanlış Kullanım: Varsayılan timeout ile uzun süre beklemek.

public async Task<OrderSummary> GetSummaryAsync(int orderId)
{
    var order = await _orderClient.GetAsync(orderId);        // 100s default timeout
    var customer = await _customerClient.GetAsync(order.CustomerId); // 100s daha
    // Toplam 200 saniye beklenebilir
}

İdeal Kullanım: Her servis çağrısına uygun timeout belirleyin.

builder.Services.AddHttpClient<IOrderServiceClient, OrderServiceClient>(client =>
{
    client.BaseAddress = new Uri("https://order-service/");
    client.Timeout = TimeSpan.FromSeconds(5);
});

public async Task<OrderSummary> GetSummaryAsync(int orderId)
{
    using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));

    var orderTask = _orderClient.GetAsync(orderId, cts.Token);
    var customerTask = _customerClient.GetAsync(orderId, cts.Token);

    await Task.WhenAll(orderTask, customerTask); // Paralel çağrı, toplam max 10s
    return new OrderSummary(orderTask.Result, customerTask.Result);
}