irdaweb Bilgi Teknolojileri
Telefon numaramız +90 541 209 02 05
E-posta adresimiz [email protected]
Konu 27 · GraphQL & API Sorgu Dilleri

GraphQL: veriyi istemcinin kendisi tarif ettiğinde.

Klasik bir REST API'de her uç nokta ne döneceğine sunucu karar verir; istemci çoğu zaman ihtiyacından fazlasını (over-fetching) ya da azını (under-fetching) alır ve eksiği kapatmak için birkaç istek daha atar. GraphQL bu dengeyi tersine çevirir: tek bir endpoint vardır, istemci tam olarak hangi alanları (field) istediğini bir sorgu (query) ile yazar, sunucu da yalnızca istenen şekli döner. Bunu mümkün kılan şey, API'nin sunduğu her tipi ve alanı önceden tanımlayan bir schema'dır — hem sözleşme hem de canlı dokümantasyon. Bu sayfada GraphQL'in REST'ten farkını, schema ve tip sistemini, sorguların resolver'larla nasıl çözüldüğünü, çözdüğü over-fetching sorununu ve beraberinde getirdiği N+1 ve query complexity gibi yeni başlıkları birlikte ele alıyoruz. Genel API kavramları için API Tasarımı & Entegrasyon rehberimizi öneririz.

01 — GraphQL nedir

Tek endpoint, istemcinin belirlediği yanıt

GraphQL bir veritabanı ya da ürün değil, bir API sorgu dili ve onu çalıştıran bir sunucu ortamıdır. REST'te her kaynak için ayrı bir URL vardır ve her URL sabit bir yanıt döner; GraphQL'de ise tek bir endpoint'e (genellikle /graphql) bir sorgu gönderirsiniz ve yanıtın şeklini sorgu belirler. İstediğiniz alanları yazarsınız, iç içe ilişkileri tek istekte çekersiniz, dönen JSON tam da sorgunuzun aynası olur. Bunun bedeli, sunucunun bu esnekliği güvenli biçimde karşılayacak şekilde tasarlanmasıdır: istemciye "ne istersen sor" demek, aynı zamanda "her sorunun maliyetini kontrol etmem gerek" demektir.

REST vs GraphQL çok URL vs tek endpoint
# REST: sabit yanıt + birkaç istek
GET /users/12          → { ...tüm alanlar }
GET /users/12/orders   → [ ...tüm alanlar ]

# GraphQL: tek istek, istenen alanlar
POST /graphql
{ user(id: 12) {
    ad
    orders { tutar } } }
→ { "user": { "ad": "...", "orders": [...] } }

Yanıtın şeklini istemci yazar

Hangi alanların döneceğine sorgu karar verir; dönen JSON sorgunun yapısını birebir yansıtır. Fazlası da eksiği de gelmez.

Tek endpoint, iç içe ilişki

Birbirine bağlı kaynakları (kullanıcı → siparişleri → ürünleri) ayrı isteklerle değil, tek sorguda iç içe çekersiniz.

Esneklik = sorumluluk

İstemciye açık uçlu sorgu hakkı vermek, her sorgunun maliyetini ve derinliğini sunucuda sınırlamayı zorunlu kılar.

02 — Schema & tip sistemi

Schema: hem sözleşme hem dokümantasyon

GraphQL'in kalbi schema'dır: API'nin sunduğu her tipi (type), her alanı ve her alanın türünü önceden tanımlayan güçlü tipli bir sözleşme. Genellikle SDL (Schema Definition Language) ile yazılır. Bir alan zorunlu mu, liste mi, hangi tipe bağlı — hepsi burada bellidir; bu yüzden sunucu gelen sorguyu çalıştırmadan önce doğrulayabilir, geçersiz alanları reddeder. Schema aynı zamanda introspection sayesinde kendi kendini anlatır: araçlar API'yi sorgulayarak otomatik dokümantasyon ve otomatik tamamlama üretir. Her sorgunun bir giriş noktası vardır — okuma için Query, yazma için Mutation, canlı akış için Subscription.

SDL tip tanımı
type User {
  id: ID!        # ! = zorunlu
  ad: String!
  orders: [Order!]  # liste
}

type Query {
  user(id: ID!): User
}

Güçlü tip = erken hata

Sunucu, sorguyu çalıştırmadan schema'ya göre doğrular. Var olmayan bir alan ya da yanlış tip, veri katmanına inmeden reddedilir.

Kendini anlatan API

Introspection sayesinde schema sorgulanabilir; dokümantasyon ve editör otomatik tamamlaması elle güncellenmeden hep güncel kalır.

Üç giriş noktası

Query okur, Mutation değiştirir, Subscription canlı akış verir. Bu ayrım, niyeti API seviyesinde net kılar.

03 — Sorgu & resolver

Bir sorgu resolver'larla nasıl çözülür

Bir sorgudaki her alanın arkasında bir resolver vardır: o alanın değerini nasıl getireceğini bilen küçük bir fonksiyon. Sunucu sorguyu bir ağaç gibi gezer; her alan için ilgili resolver'ı çağırır ve sonuçları sorgunun şekline göre birleştirir. Alanlar argument alabilir (user(id: 12)), tekrar eden alan kümelerini fragment ile paylaşabilir, sorguyu variable'larla parametreleyebilirsiniz. Kritik nokta şudur: iç içe her alan ayrı bir resolver tetikleyebilir; sorgu masumca derinleştikçe arkada çalışan iş katlanarak artabilir. Resolver'ların ne yaptığını görünür kılmak için her alanı izlemek erken uyarı sağlar.

Sorgu → resolver alan başına bir resolver
# istemcinin sorgusu
query {
  user(id: 12) {          → Query.user resolver
    ad                    → User.ad resolver
    orders {              → User.orders resolver
      tutar               → Order.tutar resolver
    }
  }
}

# yanıt: sorgunun aynası
{ "user": { "ad": "Ada",
    "orders": [ { "tutar": 250 } ] } }
Resolver
Bir alanın değerini üreten fonksiyon. Veritabanından okuyabilir, başka bir servisi çağırabilir ya da hesaplayabilir; sorgudaki her alan bir resolver'a karşılık gelir.
Argument
Bir alanı parametreleyen girdi (user(id: 12), orders(first: 5)). Filtreleme, sayfalama ve arama bunun üzerinden yürür.
Fragment
Tekrar eden alan kümesini bir kez tanımlayıp birden çok sorguda yeniden kullanma yolu. İstemci tarafında sorguları düzenli tutar.
Variable
Sorguyu sabit değerlerle değil, dışarıdan verilen parametrelerle çalıştırma. Aynı sorguyu farklı girdilerle güvenle tekrar kullanmayı sağlar.
04 — Over-fetching & N+1

Çözdüğü sorun ve getirdiği yeni sorun

GraphQL'in en somut vaadi over-fetching (gereğinden çok veri) ve under-fetching'i (eksik veri, ardından ek istekler) ortadan kaldırmasıdır: istemci tam olarak ihtiyacını ister, ne fazlası ne azı. Ama esnek iç içe sorgular yeni bir tuzak açar — N+1 problemi. Bir listedeki her öğe için ilişkili veriyi ayrı ayrı çeken naif bir resolver, tek sorguyu yüzlerce veritabanı çağrısına dönüştürür. Çözüm resolver'ları toplu çalıştırmaktır: DataLoader deseni, aynı tik içinde biriken istekleri tek bir toplu sorguda birleştirir (batching) ve tekrarları önbelleğe alır. Altta yatan veri katmanının nasıl sorgulandığı burada belirleyicidir; veritabanı ve önbellekleme tarafı GraphQL performansının gerçek zeminidir.

Çözer

Over- / under-fetching

İstemci alan listesini kendi yazdığı için ne fazla veri taşınır ne de eksik kalıp ek istek gerekir. Mobilde bant genişliği kazancı belirgindir.

Getirir

N+1 problemi

Listedeki her öğe için ayrı sorgu atan resolver, tek GraphQL isteğini yüzlerce veritabanı çağrısına çevirir. Sessizce performansı çökertir.

Panzehir

DataLoader / batching

Aynı tikte biriken istekleri tek toplu sorguda birleştirir ve tekrarları önbelleğe alır. N+1'i tek sorguya indirir.

05 — Mutation & subscription

Yazma ve canlı akış: mutation ile subscription

Query yalnızca okur; veriyi değiştirmek için mutation kullanılır — kayıt oluşturma, güncelleme, silme. Mutation da bir sorgu gibi yazılır ama niyeti nettir ve yan etkilidir; iyi bir pratik, mutation'ın değişen nesneyi geri döndürmesidir, böylece istemci ek istek atmadan güncel hâli alır. Gerçek zamanlı senaryolar için subscription vardır: istemci bir olaya abone olur (yeni mesaj, fiyat değişimi) ve sunucu genellikle bir WebSocket üzerinden güncellemeleri iter. Subscription güçlüdür ama en maliyetli parçadır: açık bağlantı yönetimi, ölçekleme ve durum takibi gerektirir; çoğu uygulama için birkaç kritik akışla sınırlı tutmak doğru olur.

Mutation yaz + sonucu dön
mutation {
  siparisOlustur(urun: "A12", adet: 2) {
    id          # değişen nesneyi
    tutar       # geri döndür →
    durum       # ek istek gerekmez
  }
}

# subscription: sunucu iter
subscription { yeniMesaj { metin } }

Mutation değişen nesneyi dönsün

Yazma sonrası güncel hâli yanıtta vermek, istemcinin ayrıca bir query atmasını gereksiz kılar ve arayüzü tek tur içinde tutarlı tutar.

Subscription'ı ölçülü kullanın

Kalıcı bağlantılar ölçekleme ve durum yükü getirir. Gerçekten canlı olması gereken birkaç akışa saklayın; gerisi query/polling ile daha ucuzdur.

Yazmada yetki şart

Her mutation gerçek bir eylemdir; kimin neyi değiştirebileceği kimlik & erişim katmanında netçe sınırlanmalıdır.

06 — Güvenlik & performans

Açık uçlu sorguyu güvenli sınırlarda tutmak

İstemciye "ne istersen sor" demek, kötü niyetli ya da dikkatsiz bir sorgunun sunucuyu zorlayabileceği anlamına gelir: derin iç içe geçmiş bir sorgu tek istekte devasa bir iş yaratabilir. Bu yüzden GraphQL güvenliği REST'ten farklı önlemler ister. Temel çerçeve nettir: query complexity ve depth limiting ile sorgunun maliyetini ve derinliğini sınırlayın, rate limiting'i istek sayısına değil sorgu maliyetine göre uygulayın, üretimde persisted queries ile yalnızca önceden onaylı sorgulara izin verin ve gereksizse introspection'ı kapatın. Bir de GraphQL'in doğal zayıflığı vardır: her yanıt aynı endpoint'ten ve çoğunlukla POST ile geldiği için HTTP önbelleklemesi REST kadar kolay değildir; önbellek çoğunlukla istemci ve alan seviyesine taşınır.

Complexity & depth limit

Her sorguya bir maliyet puanı ve derinlik sınırı koyun; belirlenen eşiği aşan sorgu çalıştırılmadan reddedilsin. Sonsuz iç içe geçmeyi baştan keser.

Maliyete dayalı rate limiting

İstek sayısını değil, sorgunun hesaplanan maliyetini kısıtlayın. Tek "istek" çok pahalı olabildiği için REST tarzı sayaç GraphQL'de yanıltır.

Persisted queries

Üretimde yalnızca önceden kaydedilip onaylanmış sorgulara izin verin. Keyfi sorgu yüzeyini kapatır, ağ yükünü de düşürür.

Introspection'ı yönetin

Schema'yı dışarıya açık tutmak saldırgana harita verir. Herkese açık olması gerekmeyen API'lerde üretimde introspection'ı kapatmayı değerlendirin.

07 — Sık karıştırılanlar

Sık karıştırılan GraphQL kavramları

GraphQL vs. REST

REST kaynak başına ayrı URL sunar ve yanıtın şeklini sunucu belirler; basit, iyi önbelleklenir, olgundur. GraphQL tek endpoint'ten esnek, istemcinin şekillendirdiği sorgular sunar; over-fetching'i keser ama complexity ve önbellek yönetimini size bırakır. Biri diğerinin yerini almaz; ihtiyaca göre seçilir, hatta bir arada kullanılır.

GraphQL vs. gRPC

gRPC yüksek performanslı, sözleşmeli (Protobuf) bir uzak çağrı (RPC) çerçevesidir; genellikle servisler arası iç iletişimde parlar. GraphQL ise istemcinin veriyi esnek biçimde şekillendirdiği, çoğunlukla dış/istemci-yönlü bir sorgu dilidir. Kabaca: gRPC servisler arası hız, GraphQL istemci-odaklı esneklik için.

Query vs. Mutation

Query yalnızca okur ve yan etkisizdir; paralel çalıştırılabilir. Mutation veriyi değiştirir, yan etkilidir ve sırayla çalışır. İkisi de aynı sözdizimiyle yazılır; fark niyet ve garantidedir. Okuma yaptığınız bir işi mutation'a koymak, GraphQL'in verdiği güvenceleri boşa çıkarır.

Schema vs. Resolver

Schema API'nin ne sunduğunu tarif eder: tipler, alanlar, türleri. Resolver ise her alanın değerinin nasıl getirileceğini yürütür. Schema sözleşme, resolver uygulamadır; ikisi eşleşmezse API ya doğrulamada ya çalışma anında kırılır.

Over-fetching vs. N+1

İkisi de veri getirme verimliliğiyle ilgilidir ama zıt uçlarda. Over-fetching ağ üzerinde çok fazla veri taşımaktır; GraphQL bunu istemciye alan seçtirerek çözer. N+1 ise sunucuda çok fazla ayrı sorgu çalıştırmaktır; GraphQL bunu getirir ve DataLoader/batching ile çözersiniz. Biri bant genişliği, diğeri sunucu iş yükü sorunudur.

Birinci el kaynaklar

GraphQL'in resmi spesifikasyonu, öğrenme rehberi ve REST'in temelini tanımlayan çalışma.

Sıradaki adım

Doğru API yaklaşımını birlikte seçelim.

GraphQL mı REST mi, schema tasarımından N+1 önlemeye ve query complexity sınırlarına kadar; projenize uygun API mimarisini kurmak için bize yazabilirsiniz. Genel API kavramları için API Tasarımı & Entegrasyon rehberimize de göz atabilirsiniz.