Ana içeriğe geç

gRPC Interceptors

Interceptor’lar, gRPC çağrılarına cross-cutting concern’ler ekler; yanlış kullanımlar performans kaybına ve hata izleme zorluğuna yol açar.


1. Her Servis Metodunda Loglama Tekrarlamak

Yanlış Kullanım: Her metoda manuel loglama eklemek.

public override async Task<OrderResponse> CreateOrder(CreateOrderRequest request, ServerCallContext context)
{
    _logger.LogInformation("CreateOrder çağrıldı: {@Request}", request);
    var sw = Stopwatch.StartNew();
    var result = await _service.CreateAsync(request);
    _logger.LogInformation("CreateOrder tamamlandı: {Elapsed}ms", sw.ElapsedMilliseconds);
    return result;
}

İdeal Kullanım: Logging interceptor ile merkezileştirin.

public class LoggingInterceptor : Interceptor
{
    private readonly ILogger<LoggingInterceptor> _logger;

    public LoggingInterceptor(ILogger<LoggingInterceptor> logger) => _logger = logger;

    public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
        TRequest request, ServerCallContext context,
        UnaryServerMethod<TRequest, TResponse> continuation)
    {
        var method = context.Method;
        _logger.LogInformation("gRPC çağrısı: {Method}", method);
        var sw = Stopwatch.StartNew();

        var response = await continuation(request, context);

        _logger.LogInformation("gRPC tamamlandı: {Method} ({Elapsed}ms)", method, sw.ElapsedMilliseconds);
        return response;
    }
}

// Kayıt
builder.Services.AddGrpc(options =>
{
    options.Interceptors.Add<LoggingInterceptor>();
});

2. Validation Interceptor Kullanmamak

Yanlış Kullanım: Her metoda manuel validasyon eklemek.

public override async Task<UserResponse> CreateUser(CreateUserRequest request, ServerCallContext context)
{
    if (string.IsNullOrEmpty(request.Email))
        throw new RpcException(new Status(StatusCode.InvalidArgument, "Email boş olamaz"));
    if (string.IsNullOrEmpty(request.Name))
        throw new RpcException(new Status(StatusCode.InvalidArgument, "Ad boş olamaz"));
    // ...
}

İdeal Kullanım: Validation interceptor ile merkezileştirin.

public class ValidationInterceptor : Interceptor
{
    private readonly IServiceProvider _serviceProvider;

    public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
        TRequest request, ServerCallContext context,
        UnaryServerMethod<TRequest, TResponse> continuation)
    {
        var validator = _serviceProvider.GetService<IValidator<TRequest>>();
        if (validator != null)
        {
            var result = await validator.ValidateAsync(request);
            if (!result.IsValid)
            {
                var errors = string.Join("; ", result.Errors.Select(e => e.ErrorMessage));
                throw new RpcException(new Status(StatusCode.InvalidArgument, errors));
            }
        }

        return await continuation(request, context);
    }
}

3. Client Interceptor ile Retry

Yanlış Kullanım: Her çağrıda manuel retry yazmak.

OrderResponse response = null;
for (int i = 0; i < 3; i++)
{
    try
    {
        response = await _client.GetOrderAsync(request);
        break;
    }
    catch (RpcException ex) when (ex.StatusCode == StatusCode.Unavailable)
    {
        await Task.Delay(1000 * (i + 1));
    }
}

İdeal Kullanım: gRPC retry policy veya client interceptor kullanın.

// gRPC built-in retry policy
builder.Services.AddGrpcClient<OrderService.OrderServiceClient>(options =>
{
    options.Address = new Uri("https://order-service:5001");
})
.ConfigureChannel(options =>
{
    options.ServiceConfig = new ServiceConfig
    {
        MethodConfigs =
        {
            new MethodConfig
            {
                Names = { MethodName.Default },
                RetryPolicy = new RetryPolicy
                {
                    MaxAttempts = 3,
                    InitialBackoff = TimeSpan.FromSeconds(1),
                    MaxBackoff = TimeSpan.FromSeconds(5),
                    BackoffMultiplier = 2,
                    RetryableStatusCodes = { StatusCode.Unavailable }
                }
            }
        }
    };
});

4. Metadata ile Context Bilgisi Taşımamak

Yanlış Kullanım: Correlation ID gibi bilgileri request message’a eklemek.

message CreateOrderRequest {
    string correlation_id = 1;  // Her message'a eklenmek zorunda
    string trace_id = 2;
    int32 customer_id = 3;
    // ...
}

İdeal Kullanım: Metadata (headers) ile cross-cutting bilgileri taşıyın.

// Client interceptor
public class CorrelationIdInterceptor : Interceptor
{
    public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
        TRequest request, ClientInterceptorContext<TRequest, TResponse> context,
        AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
    {
        var headers = context.Options.Headers ?? new Metadata();
        headers.Add("x-correlation-id", Activity.Current?.Id ?? Guid.NewGuid().ToString());

        var newContext = new ClientInterceptorContext<TRequest, TResponse>(
            context.Method, context.Host,
            context.Options.WithHeaders(headers));

        return continuation(request, newContext);
    }
}

// Server tarafında okuma
var correlationId = context.RequestHeaders.GetValue("x-correlation-id");

5. Health Check Eklemememek

Yanlış Kullanım: gRPC servisinin sağlığını izlememek.

app.MapGrpcService<OrderService>();
// Servis sağlığı bilinmiyor, orchestrator kesintileri tespit edemez

İdeal Kullanım: gRPC Health Check protokolünü uygulayın.

builder.Services.AddGrpcHealthChecks()
    .AddCheck("database", () =>
    {
        // DB bağlantısını kontrol et
        return _context.Database.CanConnect()
            ? HealthCheckResult.Healthy()
            : HealthCheckResult.Unhealthy();
    });

app.MapGrpcService<OrderService>();
app.MapGrpcHealthChecksService();