ERETEK
BİLGİ SİSTEMLERİ

REST mi GraphQL mi? Kurumsal Web Uygulamasında API Seçimi

REST mi GraphQL mi? Önbellek, yetkilendirme, hata modeli, N+1 ve sorgu maliyeti üzerinden karşılaştırma, karar ağacı ve tRPC’nin mantıklı olduğu koşullar.

Bu makalede7 bölüm
Teknik özet
Teknoloji
REST, GraphQL, OpenAPI, DataLoader, tRPC, RFC 9110, RFC 9457
Konu
Uygulama API stilinin seçimi
Seviye
Orta
Ön koşullar
  • HTTP yöntemleri ve durum kodları bilgisi
  • Temel API tasarımı deneyimi

Kurumsal bir web uygulamasında API stili bir kez seçilir, sonra yıllarca onunla yaşanır. İstemci kodunu, önbellek mimarisini, yetkilendirme modelini ve izleme düzenini o karar belirler. “REST mi GraphQL mi?” sorusunun evrensel bir doğru cevabı yok; ama hangi koşulda hangisinin daha az baş ağrıtacağı büyük ölçüde öngörülebilir. Aşağıda iç iş uygulamaları ve headless ön yüzler için iki yaklaşımı somut kriterlerle karşılaştırıyor, sonunda bir karar ağacı veriyoruz.

İki yaklaşımın temel farkı

REST bir protokol değil, HTTP’nin üzerine kurulmuş bir mimari üsluptur. Sistem kaynaklara (sipariş, müşteri, fatura) ayrılır; her kaynağın bir URI’si vardır ve işlemler HTTP yöntemleriyle ifade edilir. Yanıtın şeklini sunucu belirler; istemci ne alacağını uç noktayı seçerek belirler. Kontrat çoğunlukla bir OpenAPI belgesiyle tanımlanır.

GraphQL ise bir sorgu dili ve tip sistemidir. Sunucu bütün veri modelini tek bir şema olarak yayınlar; istemci her istekte hangi alanları istediğini sorgu metninde yazar. Taşıma için genellikle tek bir HTTP uç noktası (çoğunlukla /graphql) ve POST kullanılır. Şema kontratın kendisidir ve içgözlem (introspection) ile araçlar tarafından okunur.

Farkı görmenin en kısa yolu aynı ekranı iki stilde modellemek. Bir sipariş detay ekranı düşünün: sipariş başlığı, satırlar, müşterinin adı ve son sevkiyat durumu.

# REST: kaynak odaklı uç noktalar
GET /api/v1/siparisler/48213
GET /api/v1/siparisler/48213/satirlar
GET /api/v1/musteriler/9071
GET /api/v1/siparisler/48213/sevkiyatlar?sirala=-tarih&limit=1

# GraphQL: ekran odaklı tek sorgu
query SiparisDetay($id: ID!) {
  siparis(id: $id) {
    no
    tarih
    musteri { unvan }
    satirlar { urunKodu miktar birimFiyat }
    sonSevkiyat { durum takipNo }
  }
}

REST tarafında dört istek görmek tek başına sorun değil; HTTP/2 ve HTTP/3 ile paralel istekler ucuzdur. Asıl soru başka. Birleştirme işini kim yapacak: istemci mi, sunucuda amaca özel bir uç nokta mı, yoksa bir GraphQL çözücü katmanı mı?

İstemci ihtiyaçları ve veri getirme

GraphQL, çok sayıda ve farklı biçimde veri isteyen istemci olduğunda parlar. Web paneli, mobil uygulama ve bayi portalı aynı sipariş verisinin farklı alt kümelerini istiyorsa, her biri için ayrı uç nokta yazmak ya da gereğinden fazla veri döndürmek (over-fetching) yerine her istemci kendi sorgusunu yazar. Şemaya yeni alan eklemek mevcut istemcileri bozmaz; kaldırılacak alanlar @deprecated ile işaretlenir.

REST ise istemci sayısının az, veri ihtiyacının istikrarlı olduğu ve kaynakların doğal olarak CRUD’a oturduğu iç uygulamalarda rahattır. Tek bir web ön yüzü olan talep yönetim sistemine GraphQL şeması, çözücüler ve istemci önbellek kütüphanesi eklemek, çözdüğü sorundan fazla hareketli parça getirir. Sahada sık gördüğümüz tablo budur: üç ekranlık bir uygulama için kurulmuş, bakımı kimsenin üstlenmediği bir GraphQL katmanı. Az sayıda ekran için birkaç toplayıcı uç nokta (/siparisler/48213?include=satirlar,musteri veya amaca özel bir BFF uç noktası) çoğu zaman yeter.

Headless CMS senaryosunda kararı çoğunlukla CMS verir: bazı ürünler yalnız REST, bazıları hem REST hem GraphQL sunar. İçerik katmanının seçimini headless CMS nedir, ne zaman tercih edilir yazısında anlattık; burada uygulamanın kendi iş API’sine bakıyoruz.

Önbellekleme ve HTTP semantiği

REST’in en çok küçümsenen avantajı, HTTP’nin önbellek mekanizmasını olduğu gibi kullanmasıdır. GET istekleri URI ile tanımlandığı için tarayıcı, ters vekil ve CDN katmanları yanıtları Cache-Control başlığına göre saklar; ETag ve If-None-Match ile yapılan koşullu istekte, değişmemiş veri için gövdesiz 304 Not Modified döner. Bu davranışlar RFC 9110 (HTTP Semantics) ve RFC 9111 (HTTP Caching) ile tanımlıdır. Ek kütüphane gerekmez.

GraphQL’de istekler çoğunlukla aynı URI’ye POST olarak gittiği için ara katman önbellekleri devreden çıkar. Önbellekleme iki yere taşınır:

  • İstemci tarafı normalize önbellek: Apollo Client veya urql gibi kütüphaneler yanıtları tip ve kimlik alanına göre parçalayıp saklar. Güçlüdür; ama önbellek tutarsızlığı hatalarını ayıklamak REST’e göre belirgin biçimde zordur.
  • Kalıcı sorgular (persisted queries): Sorgu metni yerine bir özet değeri gönderilir; sorgular GET ile yapılabilir ve CDN’de önbelleğe alınabilir. Aynı mekanizma, yalnız önceden onaylanmış sorguların çalışmasına izin veren bir güvenlik kontrolü olarak da kullanılır.

Kurumsal iç uygulamalarda veri genellikle kullanıcıya özeldir ve paylaşılan önbelleğe zaten girmez. Orada HTTP önbelleğinin avantajı daralır. Ürün kataloğu, fiyat listesi veya içerik gibi herkese açık veri sunan API’lerde ise REST ciddi bir operasyonel kolaylık sağlar. Başlıkların ayrıntısı Cache-Control başlıkları rehberinde.

Yetkilendirme ve hata modeli

REST’te yetkilendirme doğal olarak uç nokta ve kaynak düzeyinde kurulur: “Bu kullanıcı bu siparişi okuyabilir mi?” sorusu tek yerde yanıtlanır. GraphQL’de tek bir sorgu birçok tipe ve alana dokunduğu için kontrol alan ve nesne düzeyine iner. Satış temsilcisi Musteri.unvan alanını görebilir, Musteri.krediLimiti alanını görmemelidir. Bu kontrol çözücülere dağıldığında, unutulan tek bir alan veri sızdırır. Doğru desen: yetki kurallarını çözücülerde değil, çözücülerin çağırdığı iş/servis katmanında tek noktada uygulamak. Böylece aynı kural REST ve GraphQL giriş noktalarının ikisinde de geçerli olur. Rol ve kiracı tabanlı yetki tasarımı için B2B portalda yetkilendirme mimarisi yazısına bakın.

Hata modeli de farklı işler:

Konu REST GraphQL
Hata sinyali HTTP durum kodu (400, 403, 404, 409, 422…) Yanıttaki errors dizisi
Kısmi başarı Genellikle yok; istek ya başarılı ya hatalı Var; veri ve hata birlikte dönebilir
Standart hata gövdesi RFC 9457 Problem Details (application/problem+json) message, path, extensions.code
İzleme Durum koduna göre metrik kolay errors içeriğinin ayrıca toplanması gerekir

GraphQL’de iş kuralı hatalarını (stok yetersiz, onay limiti aşıldı) errors dizisine atmak yerine şemada sonuç tipi olarak modellemek istemci kodunu sadeleştirir; örneğin bir mutasyonun SiparisOlusturuldu | StokYetersiz birleşim tipi döndürmesi. REST’te aynı durum 409 veya 422 ile ve Problem Details gövdesindeki makine okunur bir type alanıyla ifade edilir. İzleme tarafında tuzak açık: GraphQL’de yalnız HTTP durum koduna bakan bir alarm, hatayı hiç görmez.

Performans: N+1 ve sorgu maliyeti

GraphQL’in en bilinen performans tuzağı N+1’dir. 50 siparişlik bir liste istendiğinde her siparişin musteri alanını çözen çözücü ayrı ayrı çalışırsa veri tabanına 1 liste sorgusu ve 50 müşteri sorgusu gider. Veri tabanı yavaşlığı diye açılan çağrıların bir kısmı aslında budur. Standart çözüm, aynı istek içindeki tekil yüklemeleri toplayıp tek bir toplu sorguya çeviren DataLoader desenidir:

// İstek başına oluşturulur; istekler arasında paylaşılmaz
const musteriLoader = new DataLoader(async (idler: readonly string[]) => {
  const kayitlar = await db.musteri.findMany({ where: { id: { in: [...idler] } } });
  const harita = new Map(kayitlar.map((m) => [m.id, m]));
  return idler.map((id) => harita.get(id) ?? null);
});

const resolvers = {
  Siparis: {
    musteri: (siparis, _args, ctx) => ctx.musteriLoader.load(siparis.musteriId),
  },
};

Loader istek başına oluşturulmalıdır. Uygulama genelinde tek bir loader kullanılırsa, bir kullanıcının yüklediği kayıt yetkisi olmayan başka bir kullanıcının isteğine önbellekten dönebilir.

İkinci risk, sorgu maliyetini istemcinin belirleyebilmesidir. Derin iç içe sorgular (siparis → musteri → siparisler → satirlar → urun → …) tek istekte sunucuyu yorar. Üretimde üç sınır açık olmalı: sorgu derinliği limiti, alan bazlı maliyet/karmaşıklık hesabı ve sayfalama argümanlarına üst sınır. API’yi yalnız kendi ön yüzünüz kullanıyorsa kalıcı sorgu izin listesi en güçlü kontroldür. REST’te risk daha sınırlı, çünkü her uç noktanın maliyeti tasarımda belirlenir. Ama ?include= gibi esnek genişletme parametreleri çoğaldıkça aynı sınırlar orada da gerekir.

Karar ağacı ve karşılaştırma tablosu

Şu soruları sırayla yanıtlayın; çoğu projede seçim kendiliğinden netleşir:

  1. API dışarıya (iş ortağı, müşteri sistemi, farklı dilde istemci) açılacak mı? Evetse REST + OpenAPI ile başlayın; tüketicilerin araç desteği ve HTTP alışkanlıkları en geniş olandır. Yayındaki API’nin evrimini REST API sürümleme stratejileri yazısı anlatıyor.
  2. Birden fazla ön yüz aynı veriyi farklı biçimlerde mi tüketiyor? Evetse ve veri modeli ilişkisel olarak zenginse GraphQL güçlü adaydır.
  3. Ön yüz ve arka uç aynı TypeScript deposunda mı, API yalnız bu uygulamaya mı hizmet ediyor? Evetse tRPC veya Server Actions gibi tip paylaşımlı RPC yaklaşımları ek şema katmanı olmadan iş görür.
  4. Herkese açık, önbelleklenebilir veri mi sunuluyor? Evetse REST’in HTTP önbellek uyumu belirleyicidir.
  5. Ekipte GraphQL şema tasarımı, DataLoader ve sorgu maliyeti kontrolü deneyimi var mı? Yoksa bu yetkinliği edinme süresini plana yazın ya da REST ile başlayın.
Kriter REST GraphQL tRPC
Kontrat OpenAPI GraphQL şeması TypeScript tipleri
HTTP önbelleği Doğal Kalıcı sorgu ile sınırlı Sınırlı
Esnek veri seçimi Uç nokta tasarımına bağlı Güçlü Prosedür tasarımına bağlı
Yetki granülerliği Kaynak düzeyi Alan düzeyi gerekir Prosedür düzeyi
Dış tüketici Çok uygun Uygun Uygun değil
Ek operasyonel yük Düşük Orta–yüksek Düşük

Karma mimari de meşru bir sonuçtur: iş ortağı entegrasyonları ve dosya işlemleri için REST, karmaşık ekranları besleyen bir GraphQL BFF katmanı ve iş kurallarının tek noktada toplandığı ortak bir servis katmanı. Hangi stil seçilirse seçilsin, API tiplerini istemciye otomatik üretin; elle senkron tutulan tipler eninde sonunda kayar. Bunun nasıl kurulacağını TypeScript kod tabanı standartları yazısında anlattık. Kendi süreçlerinize uygun API katmanını tasarlarken özel web uygulaması geliştirme veya API-first ve headless mimari hizmetlerimizden destek alabilirsiniz. Belirtimler için graphql.org öğrenme kaynakları ve RFC 9457 birincil kaynaktır.

Sık sorulan sorular

GraphQL kullanırsak REST API’ye hiç gerek kalmaz mı?

Çoğunlukla kalır. Dosya yükleme ve indirme, webhook alıcıları, sağlık kontrolü uç noktaları ve dış iş ortaklarına açılan entegrasyonlar HTTP semantiğini doğrudan kullanan REST uç noktalarıyla daha sade çözülür. Pek çok kurumsal sistemde ön yüz için GraphQL, entegrasyon ve dosya işlemleri için REST yan yana çalışır.

GraphQL yanıtlarında hata olduğu halde HTTP 200 dönmesi normal mi?

Evet. GraphQL yanıtı hem kısmi veri hem errors dizisi içerebildiği için birçok sunucu alan düzeyindeki hatalarda 200 döndürür. İzleme ve alarm sistemleri bu yüzden yalnız HTTP durum koduna bakmamalı; errors dizisini ve hata uzantı kodlarını da metrik olarak toplamalıdır. İstek hiç çalıştırılamadığında (sözdizimi veya doğrulama hatası) 4xx dönmek, GraphQL over HTTP taslak belirtiminin önerdiği yaklaşımdır.

tRPC ne zaman mantıklı bir seçimdir?

Ön yüz ve arka uç aynı TypeScript kod deposunda geliştiriliyor, API’yi yalnız bu uygulama tüketiyor ve dışarıya açılması planlanmıyorsa tRPC şema yazmadan istemciden sunucuya kadar tip güvenliği sağlar. Farklı dillerde istemciler, iş ortağı entegrasyonları veya uzun vadeli sürümleme ihtiyacı varsa REST ya da GraphQL daha uygundur.

Mevcut bir REST API’nin önüne GraphQL katmanı koymak doğru mu?

Ön yüz çok sayıda REST çağrısını birleştirmek zorunda kalıyorsa GraphQL katmanı bir BFF (backend for frontend) olarak işe yarayabilir. Ama bu katman çözücülerde N+1 çağrı, yetki kontrolünün iki yerde tekrarı ve ek gecikme riski getirir. Önce ihtiyacın birkaç amaca özel REST uç noktasıyla çözülüp çözülemeyeceğine bakın.

Teknik özet
Teknoloji
REST, GraphQL, OpenAPI, DataLoader, tRPC, RFC 9110, RFC 9457
Konu
Uygulama API stilinin seçimi
Seviye
Orta
Ön koşullar
  • HTTP yöntemleri ve durum kodları bilgisi
  • Temel API tasarımı deneyimi
Eretek uzman desteği

Bu konu sizin ortamınızda nasıl çalışır, birlikte bakalım.

Mevcut yapınızı inceliyor, darboğazı tespit ediyor ve uygulanabilir bir iş planı öneriyoruz.