Ana içeriğe geç

gRPC Service Design

gRPC, yüksek performanslı servisler arası iletişim sağlar; yanlış tasarım geriye uyumluluk sorunlarına ve performans kayıplarına yol açar.


1. Proto Dosyasını Yanlış Tasarlamak

Yanlış Kullanım: Büyük ve monolitik proto dosyası oluşturmak.

syntax = "proto3";

service AppService {
    rpc GetUser (GetUserRequest) returns (UserResponse);
    rpc CreateOrder (CreateOrderRequest) returns (OrderResponse);
    rpc GetProduct (GetProductRequest) returns (ProductResponse);
    rpc ProcessPayment (PaymentRequest) returns (PaymentResponse);
    // Tek servis, tüm domain'ler karışık
}

İdeal Kullanım: Domain bazlı ayrı proto dosyaları oluşturun.

// user.proto
syntax = "proto3";
package myapp.users.v1;

service UserService {
    rpc GetUser (GetUserRequest) returns (UserResponse);
    rpc ListUsers (ListUsersRequest) returns (ListUsersResponse);
}

// order.proto
syntax = "proto3";
package myapp.orders.v1;

service OrderService {
    rpc CreateOrder (CreateOrderRequest) returns (OrderResponse);
    rpc GetOrder (GetOrderRequest) returns (OrderResponse);
}

2. Field Numaralarını Değiştirmek

Yanlış Kullanım: Mevcut field numaralarını yeniden kullanmak veya değiştirmek.

message Product {
    int32 id = 1;
    // string name = 2; silindi
    string title = 2;  // Aynı numara farklı field - breaking change!
    decimal price = 3;
}

İdeal Kullanım: Eski numaraları reserved olarak işaretleyin, yeni field’a yeni numara verin.

message Product {
    int32 id = 1;
    reserved 2; // Eski "name" field'ı
    reserved "name";
    string title = 4; // Yeni field, yeni numara
    double price = 3;
}

3. Error Handling Yapmamak

Yanlış Kullanım: Exception’ı doğrudan fırlatmak.

public override async Task<OrderResponse> GetOrder(GetOrderRequest request, ServerCallContext context)
{
    var order = await _repository.GetByIdAsync(request.Id);
    if (order == null) throw new Exception("Sipariş bulunamadı"); // Generic exception
    return MapToResponse(order);
}

İdeal Kullanım: gRPC status codes ile yapılandırılmış hata döndürün.

public override async Task<OrderResponse> GetOrder(GetOrderRequest request, ServerCallContext context)
{
    var order = await _repository.GetByIdAsync(request.Id);

    if (order == null)
    {
        throw new RpcException(new Status(StatusCode.NotFound,
            $"Sipariş bulunamadı: {request.Id}"));
    }

    return MapToResponse(order);
}

// Global interceptor ile hata yönetimi
public class ErrorInterceptor : Interceptor
{
    public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
        TRequest request, ServerCallContext context, UnaryServerMethod<TRequest, TResponse> continuation)
    {
        try
        {
            return await continuation(request, context);
        }
        catch (RpcException) { throw; }
        catch (Exception ex)
        {
            _logger.LogError(ex, "gRPC hatası");
            throw new RpcException(new Status(StatusCode.Internal, "Sunucu hatası"));
        }
    }
}

4. Deadline Belirlememek

Yanlış Kullanım: Timeout olmadan servis çağrısı yapmak.

var response = await _client.GetOrderAsync(new GetOrderRequest { Id = 1 });
// Servis yanıt vermezse sonsuza kadar bekler

İdeal Kullanım: Deadline ile timeout belirleyin.

var deadline = DateTime.UtcNow.AddSeconds(5);
var response = await _client.GetOrderAsync(
    new GetOrderRequest { Id = 1 },
    deadline: deadline);

// Server tarafında deadline kontrolü
public override async Task<OrderResponse> GetOrder(GetOrderRequest request, ServerCallContext context)
{
    if (context.Deadline < DateTime.UtcNow)
    {
        throw new RpcException(new Status(StatusCode.DeadlineExceeded, "Süre aşıldı"));
    }

    return await ProcessAsync(request, context.CancellationToken);
}

5. Streaming Kullanmamak

Yanlış Kullanım: Büyük veri setini tek response’da döndürmek.

public override async Task<ProductListResponse> GetAllProducts(
    Empty request, ServerCallContext context)
{
    var products = await _repository.GetAllAsync(); // 100.000 kayıt, bellek sorunu
    return new ProductListResponse { Products = { products.Select(MapToProto) } };
}

İdeal Kullanım: Server streaming ile büyük veriyi parça parça gönderin.

public override async Task GetAllProducts(
    Empty request,
    IServerStreamWriter<ProductResponse> responseStream,
    ServerCallContext context)
{
    await foreach (var product in _repository.GetAllAsyncStream(context.CancellationToken))
    {
        await responseStream.WriteAsync(MapToProto(product));
    }
}

// Client tarafı
using var call = _client.GetAllProducts(new Empty());
await foreach (var product in call.ResponseStream.ReadAllAsync())
{
    Console.WriteLine(product.Name);
}