API Tasarımı & Hata Yönetimi 6 dk okuma 6 okunma

REST API'lerde Hata Yönetimi ve Hata Kodları

REST API'lerde tutarlı, anlamlı ve makine tarafından okunabilir hata yanıtları tasarlamak; HTTP durum kodlarının doğru kullanımı, Problem Details (RFC 9457) standardı, hata kategorileri (4xx vs. 5xx), iş mantığı hataları için özel hata kodları, validasyon hataları, hata mesajlarının güvenliği ve .NET'te global hata yönetimi stratejileri ele alınır.

REST API'lerde Hata Yönetimi ve Hata Kodları

REST API'lerde Hata Yönetimi ve Hata Kodları

Bir API'nin kalitesi, sadece başarılı yanıtlarında değil, aynı zamanda hata durumlarındaki davranışında da kendini gösterir. İyi tasarlanmış bir hata yönetimi, istemcinin sorunu anlamasını, kullanıcıya anlamlı bir mesaj göstermesini ve gerekirse otomatik olarak yeniden denemesini sağlar. Kötü tasarlanmış hata yanıtları ise istemciyi çaresiz bırakır, hata ayıklamayı zorlaştırır ve güvenlik açıklarına yol açar. Bu yazıda, REST API'lerde profesyonel bir hata yönetimi stratejisinin nasıl oluşturulacağını detaylıca ele alacağız.


1. HTTP Durum Kodları: Temel Kategoriler

HTTP durum kodları, hata yönetiminin ilk katmanıdır. Her kod, belirli bir anlam taşır ve istemciye hatanın türü hakkında ipucu verir.

Kategori Açıklama Tipik Kullanım
2xx (Başarılı) İstek başarıyla işlendi. 200 OK, 201 Created, 204 No Content
3xx (Yönlendirme) İstemcinin ek bir eylem yapması gerekiyor. 301 Moved Permanently, 304 Not Modified
4xx (İstemci Hatası) İstemciden kaynaklanan hata. 400 Bad Request, 401 Unauthorized, 404 Not Found
5xx (Sunucu Hatası) Sunucudan kaynaklanan hata. 500 Internal Server Error, 503 Service Unavailable

Kritik Kural: 4xx kodları istemcinin düzeltebileceği hataları, 5xx kodları ise sunucunun düzeltmesi gereken hataları temsil eder. Bu ayrım, istemcinin yeniden deneme (retry) stratejisi için hayatidir.


2. Yaygın HTTP Durum Kodları ve Anlamları

4xx İstemci Hataları:

Kod Anlam Kullanım Senaryosu
400 Bad Request İstek geçersiz veya bozuk. Validasyon hatası, eksik parametre, geçersiz JSON.
401 Unauthorized Kimlik doğrulama gerekli. Token eksik, geçersiz veya süresi dolmuş.
403 Forbidden Kimlik doğrulandı ancak yetki yok. Kullanıcı bu kaynağa erişemez.
404 Not Found Kaynak bulunamadı. Geçersiz ID, silinmiş kaynak.
405 Method Not Allowed HTTP metodu desteklenmiyor. Endpoint'te PUT yok, sadece GET var.
409 Conflict Kaynak durumuyla çakışma. Aynı e-posta ile kayıt, eşzamanlı güncelleme.
410 Gone Kaynak kalıcı olarak silindi. Silinmiş ve geri getirilmeyecek kaynak.
422 Unprocessable Entity Söz dizimi doğru, ancak iş mantığı hatası. Geçersiz tarih aralığı, negatif miktar.
429 Too Many Requests Çok fazla istek. Rate limiting aşıldı.

5xx Sunucu Hataları:

Kod Anlam Kullanım Senaryosu
500 Internal Server Error Beklenmedik sunucu hatası. Yakalanmamış exception.
501 Not Implemented Sunucu isteği desteklemiyor. Henüz geliştirilmemiş endpoint.
502 Bad Gateway Üst katman servisten geçersiz yanıt. Proxy arkasındaki servis çöktü.
503 Service Unavailable Servis geçici olarak kullanılamıyor. Bakım, aşırı yük.
504 Gateway Timeout Üst katman servis zaman aşımına uğradı. Yavaş downstream servis.

3. Problem Details (RFC 9457) Standardı

Sadece HTTP durum kodu dönmek yeterli değildir. İstemcinin hatayı anlaması için yapılandırılmış bir gövde (body) gerekir. Problem Details (RFC 9457), bu ihtiyacı karşılayan endüstri standardıdır.

Temel Üyeler:

  • type: Problemin tipini tanımlayan URI.

  • title: Problemin kısa, insan tarafından okunabilir özeti.

  • status: HTTP durum kodu.

  • detail: Bu spesifik problem için açıklama.

  • instance: Bu spesifik problem oluşumunu tanımlayan URI.

Örnek Yanıt:

json

{
  "type": "https://example.com/probs/validation-error",
  "title": "Validation Error",
  "status": 400,
  "detail": "Bir veya daha fazla alan geçersiz.",
  "instance": "/api/orders",
  "errors": {
    "customerName": ["Müşteri adı boş olamaz."],
    "totalAmount": ["Tutar pozitif olmalıdır."]
  },
  "traceId": "0HN7GNR1Q8Q7D:00000001"
}

Content-Type: application/problem+json

traceId gibi genişletme üyeleri, destek ekibinin log'larda ilgili isteği bulmasını sağlar.


4. İş Mantığı Hataları için Özel Hata Kodları

HTTP durum kodları genel kategorileri temsil eder. Ancak istemcinin iş mantığına özel hataları ayırt edebilmesi için özel hata kodları (application-specific error codes) tanımlanmalıdır.

Önerilen Yaklaşım:

json

{
  "type": "https://example.com/probs/insufficient-stock",
  "title": "Insufficient Stock",
  "status": 409,
  "detail": "Ürün stoğu yetersiz. Mevcut: 3, İstenen: 10.",
  "errorCode": "ORDER_STOCK_INSUFFICIENT",
  "productId": 123,
  "availableQuantity": 3,
  "requestedQuantity": 10
}

Hata Kodları İçin En İyi Pratikler:

  • Büyük harf ve alt çizgi kullanın: ORDER_STOCK_INSUFFICIENT, USER_EMAIL_ALREADY_EXISTS.

  • Hiyerarşik yapı kurun: PAYMENT_CARD_EXPIRED, PAYMENT_INSUFFICIENT_FUNDS.

  • Dokümante edin: Her hata kodunun ne anlama geldiğini ve nasıl çözüleceğini belgeleyin.

  • Stabil tutun: Hata kodları, API sürümleri arasında değişmemelidir.


5. Validasyon Hataları: Detaylı Geri Bildirim

Validasyon hataları, en sık karşılaşılan 4xx hatalarıdır. İstemciye hangi alanın neden geçersiz olduğunu net bir şekilde bildirmek gerekir.

ASP.NET Core ValidationProblemDetails:

json

{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "Email": ["The Email field is not a valid e-mail address."],
    "Password": ["The Password must be at least 8 characters long."],
    "Age": ["The Age must be between 18 and 120."]
  }
}

Alan Adları:

  • Camel Case: JSON standartlarına uygun.

  • Tam Yol: İç içe nesneler için address.city gibi.

  • Dizi İndeksi: Koleksiyonlar için items[0].quantity gibi.


6. Hata Mesajlarının Güvenliği

Hata mesajları, saldırganlara sistem hakkında bilgi sızdırabilir. Bu nedenle güvenlik kritik öneme sahiptir.

❌ Güvensiz (Asla Yapmayın):

json

{
  "status": 500,
  "detail": "System.Data.SqlClient.SqlException: Invalid column name 'user_password' at System.Data.SqlClient.SqlConnection.OnError..."
}

Bu, saldırgana veritabanı yapısı, kullanılan teknoloji ve hatta tablo/sütun isimleri hakkında bilgi verir.

✅ Güvenli:

json

{
  "type": "https://example.com/probs/internal-error",
  "title": "Internal Server Error",
  "status": 500,
  "detail": "Beklenmedik bir hata oluştu. Lütfen daha sonra tekrar deneyin.",
  "traceId": "0HN7GNR1Q8Q7D:00000001"
}

Gerçek hata detayı sunucu log'larına yazılır; istemciye sadece traceId döner.

Güvenlik Kuralları:

  • Stack trace asla gösterilmemeli.

  • Veritabanı hata mesajları asla gösterilmemeli.

  • Dosya yolları ve sunucu isimleri asla gösterilmemeli.

  • İç sistem detayları istemciye sızdırılmamalı.

  • Production'da detaylı hata mesajları kapatılmalı.


7. .NET'te Global Hata Yönetimi

ASP.NET Core, hata yönetimini merkezileştirmek için güçlü araçlar sunar.

A. IExceptionHandler (.NET 8+)

.NET 8 ile gelen IExceptionHandler arayüzü, exception'ları merkezi olarak yönetmenin modern yoludur.

csharp

public class GlobalExceptionHandler : IExceptionHandler
{
    private readonly ILogger<GlobalExceptionHandler> _logger;

    public GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger)
    {
        _logger = logger;
    }

    public async ValueTask<bool> TryHandleAsync(
        HttpContext httpContext,
        Exception exception,
        CancellationToken cancellationToken)
    {
        _logger.LogError(exception, "Beklenmedik hata: {Message}", exception.Message);

        var problemDetails = exception switch
        {
            NotFoundException => new ProblemDetails
            {
                Type = "https://example.com/probs/not-found",
                Title = "Kaynak Bulunamadı",
                Status = StatusCodes.Status404NotFound,
                Detail = exception.Message
            },
            ValidationException validationEx => new ValidationProblemDetails(validationEx.Errors)
            {
                Type = "https://example.com/probs/validation-error",
                Title = "Doğrulama Hatası",
                Status = StatusCodes.Status400BadRequest
            },
            _ => new ProblemDetails
            {
                Type = "https://example.com/probs/internal-error",
                Title = "Sunucu Hatası",
                Status = StatusCodes.Status500InternalServerError,
                Detail = "Beklenmedik bir hata oluştu."
            }
        };

        problemDetails.Extensions["traceId"] = httpContext.TraceIdentifier;

        httpContext.Response.StatusCode = problemDetails.Status ?? 500;
        await httpContext.Response.WriteAsJsonAsync(problemDetails, cancellationToken);
        return true;
    }
}

// Program.cs
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
builder.Services.AddProblemDetails();

app.UseExceptionHandler();

B. Özel Exception Sınıfları

İş mantığı hatalarını temsil eden özel exception sınıfları oluşturmak, hata yönetimini daha okunabilir hale getirir.

csharp

public class NotFoundException : Exception
{
    public NotFoundException(string message) : base(message) { }
}

public class ValidationException : Exception
{
    public Dictionary<string, string[]> Errors { get; }
    public ValidationException(Dictionary<string, string[]> errors)
    {
        Errors = errors;
    }
}

public class BusinessRuleException : Exception
{
    public string ErrorCode { get; }
    public BusinessRuleException(string errorCode, string message) : base(message)
    {
        ErrorCode = errorCode;
    }
}

C. StatusCodePages Middleware

4xx ve 5xx hatalarını yakalayıp Problem Details formatına dönüştürmek için:

csharp

app.UseStatusCodePages(async statusCodeContext =>
{
    var response = statusCodeContext.HttpContext.Response;
    if (response.StatusCode >= 400 && !response.HasStarted)
    {
        var problemDetails = new ProblemDetails
        {
            Status = response.StatusCode,
            Title = ReasonPhrases.GetReasonPhrase(response.StatusCode),
            Instance = statusCodeContext.HttpContext.Request.Path,
            Extensions = { ["traceId"] = statusCodeContext.HttpContext.TraceIdentifier }
        };
        await response.WriteAsJsonAsync(problemDetails);
    }
});

8. Hata Yönetimi En İyi Pratikleri

  1. Tutarlılık: Tüm hata yanıtlarında aynı formatı kullanın (Problem Details).

  2. Doğru Durum Kodu: HTTP durum kodlarını anlamlarına uygun kullanın; 4xx ve 5xx ayrımına dikkat edin.

  3. Anlamlı Mesajlar: detail alanı, istemcinin sorunu düzeltmesine yardımcı olacak bilgiler içersin.

  4. Özel Hata Kodları: İş mantığı hataları için uygulamaya özel hata kodları tanımlayın.

  5. Güvenlik: Stack trace, veritabanı detayları ve iç sistem bilgilerini asla istemciye göstermeyin.

  6. Trace ID: Her hata yanıtına bir traceId ekleyin; bu, log'larda ilgili isteği bulmayı kolaylaştırır.

  7. Loglama: Tüm 5xx hatalarını loglayın; 4xx hataları için seçici olun.

  8. Validasyon: Doğrulama hatalarında hangi alanın neden geçersiz olduğunu net bir şekilde belirtin.

  9. Dokümantasyon: Her hata kodunu ve durum kodunu API dokümantasyonunda belgeleyin.

  10. Test: Hata senaryolarını otomatik testlerle doğrulayın.

Sonuç

REST API'lerde hata yönetimi, API'nin kalitesini ve kullanılabilirliğini belirleyen en kritik unsurlardan biridir. Doğru HTTP durum kodları, Problem Details (RFC 9457) standardı, anlamlı hata mesajları, özel hata kodları ve güvenli loglama ile istemcilerin sorunları anlamasını, kullanıcıya anlamlı mesaj göstermesini ve gerektiğinde otomatik olarak yeniden denemesini sağlayabilirsiniz. .NET 8+ ile gelen IExceptionHandler ve AddProblemDetails() gibi modern araçlar, hata yönetimini merkezileştirerek tutarlılığı garanti eder. Unutmayın: İyi bir API, hata durumunda bile kullanıcıyı yarı yolda bırakmaz.

Tüm yazılar Soru ya da yorumun mu var? Yaz