İçeriğe geç
Muhammet Şafak
Web Geliştirme 3 dk okuma

API yanıtlarını standartlaştırmak: tutarlı bir sözleşme

Her uçta aynı yanıt yapısını döndürmek istemci kodunu nasıl sadeleştirir ve hataları nasıl öngörülebilir kılar?


Bir API üzerinde birden fazla istemci çalıştığında — iOS uygulaması, React SPA, başka bir servis — her uçtan farklı bir yanıt yapısı geldiğinde ne olur? Her istemci kendi ayrıştırma mantığını yazar, her hata formatı için ayrı bir if bloğu eklenir, bir uçta data anahtarı varken diğerinde result gelir. Zamanla bu tutarsızlık, istemci kodunun üzerine binen gizli bir borç haline gelir.

Bu sorunu son bir yılda birkaç projede bizzat yaşadım. Çözüm teknoloji değişikliği gerektirmiyor: sözleşmeyi API’nin dışına değil, içine yazmak yeterli.

Tutarlı bir yanıt yapısının anatomisi

İyi bir API yanıt sözleşmesi üç şeyi garanti eder: başarı mı başarısızlık mı olduğunu, taşınan veriyi ve varsa hata bilgisini. Bunlara karşılık gelen basit bir iskelet:

{
  "success": true,
  "data": {},
  "message": null,
  "errors": null
}

Başarısız yanıtta success: false, data: null, message ile kısa açıklama ve errors ile alan bazlı hatalar gelir. İstemci her yanıtta yalnızca success anahtarına bakmak zorunda; geri kalanı tutarlıdır.

PHP’de bir yanıt wrapper sınıfı

Bu yapıyı her controller’a elle yazmak yerine tek bir sınıfa taşıdım:

<?php

class ApiResponse
{
    public static function success($data = null, string $message = null, int $status = 200): array
    {
        return response()->json([
            'success' => true,
            'data'    => $data,
            'message' => $message,
            'errors'  => null,
        ], $status);
    }

    public static function error(string $message, array $errors = [], int $status = 422): array
    {
        return response()->json([
            'success' => false,
            'data'    => null,
            'message' => $message,
            'errors'  => $errors ?: null,
        ], $status);
    }
}

Controller’da kullanımı doğrudan:

public function store(Request $request)
{
    $validated = $request->validate([
        'name'  => 'required|string|max:255',
        'email' => 'required|email|unique:users',
    ]);

    $user = User::create($validated);

    return ApiResponse::success($user, 'Kullanıcı oluşturuldu.', 201);
}

Doğrulama hatası oluştuğunda Laravel’in kendi ValidationException’ını yakalamak ve aynı sözleşmeye döndürmek gerekiyor. Bunun için Handler.php’yi özelleştirmek yeterli — tek bir yer, tüm API hatalarını kapsar.

HTTP durum kodu ile success bayrağının ilişkisi

Bazı ekiplerde “zaten 2xx/4xx var, success bayrağına gerek yok” tartışması çıkar. Deneyimime göre bu doğru değil: 207 (Multi-Status), 422, 409 gibi kodlar istemci kütüphanelerine göre farklı yorumlanır; success bayrağı bu belirsizliği ortadan kaldırır. Öte yandan HTTP durum kodlarını görmezden gelmek de hata: 200 ile hata döndürmek istemci tarafında cache ve loglama sorunlarına yol açar. İkisi birlikte tutarlı olmalı.

Sayfalama ve liste yanıtları

Liste döndüren uçlarda veriyi düz dizi olarak değil, sayfalama meta bilgisiyle sarmak daha iyi bir pratik:

{
  "success": true,
  "data": {
    "items": [],
    "meta": {
      "current_page": 1,
      "per_page": 15,
      "total": 243,
      "last_page": 17
    }
  },
  "message": null,
  "errors": null
}

Laravel’in LengthAwarePaginator’ı bu meta bilgiyi zaten üretiyor; tek yapılacak iş onu data.meta altına taşımak.

Sözleşmeyi ekibe taşımak: geriye dönük uyumluluk meselesi

Sözleşmeyi projenin başında tanımlamanın en büyük faydası, ilerde tartışma çıkmaması. Ama sözleşmeyi mevcut bir API’ye sonradan uygulamak farklı bir iş: çalışan istemciler var, bekledikleri yapı var. Bu noktada iki seçenek kalıyor: versiyon atlamak (/v2/) ya da yalnızca yeni uçlara standart sözleşmeyi uygulamak.

Ben ikinci yolu seçtim. Mevcut uçlara dokunmadım; yeni eklediklerimde sözleşmeyi uyguladım. İstemci tarafında adaptör katmanı yazarak ikisini soyutladım. Bu geçici bir yük ama mevcut istemciyi kırmaktan daha az riskli.

Sözleşmeyi belgelemek

Sözleşme ne kadar sağlam olursa olsun, belgelenmezse ikinci geliştirici bunu bilemez. OpenAPI (Swagger) şeması yazmak bu noktada büyük fark yaratıyor; yanıt yapısı bir kez tanımlanıyor, tüm uçlar buna referans veriyor. Henüz tam bir OpenAPI entegrasyonum olmasa da en azından README.md’de üç örnek yanıt tutuyorum.


Tutarlı bir yanıt sözleşmesi sihirli bir çözüm değil, ancak getirisi maliyetinin çok üzerinde. İstemci kodu sadeleşiyor, hata senaryoları öngörülebilir hale geliyor, yeni bir uç eklendiğinde yapı tartışması yaşanmıyor. Projenin başında bu kararı vermek, ilerlediğinde geri dönüp düzeltmeye çalışmaktan çok daha kolay.

Etiketler: #API
Paylaş:

İlgili Yazılar

Sitede Ara

Yazı, proje ve sayfalarda arama yapmak için yazmaya başlayın.

Esc ile kapat Pagefind ile güçlendirildi