---
title: Çözücüler
source: 'https://academia.sh/tr/kurslar/api-tasarimi/cozuculer'
course: 'Web API Tasarımı'
language: tr
updated: '2026-08-17T18:06:45+00:00'
license: 'CC BY-SA 4.0'
---

# Çözücüler

Çözücünün dört girdisi, varsayılan çözücünün ne yaptığı, yürütmenin düzey düzey ilerlemesi ve bir sorgunun ürettiği çözücü çağrısı ile veri kaynağı turu sayısının ölçülmesi.

Çalıştırıcı her alan için bir çözücü çağırdı ama çözücülerin nasıl yazıldığı konuşulmadı.
Ödünç kaydının `uye` alanı için ayrı bir çözücü yazılmıştı, `id` alanı için yazılmamıştı;
ikisi arasındaki fark, GraphQL yürütmesinin belkemiğidir.

Bu ders çözücü imzasını tanımlar, varsayılan çözücünün ne yaptığını gösterir ve bir sorgunun
kaç çözücü çağrısı ürettiğini sayar. Son sayı, sonraki dersin konusunu doğuracak.

## Çözücünün Dört Girdisi

Çözücü, tek bir alanın değerini üreten işlevdir ve dört girdi alır.

- **Kaynak**, üst düzeyin döndürdüğü değerdir. `Odunc.uye` çözücüsünün kaynağı, bir üst
  düzeydeki ödünç kaydıdır. Kök alanlarda kaynak yoktur.
- **Argümanlar**, alanın argümanlarıdır; değişkenler bu noktada çözülmüştür, çözücü
  `$id` diye bir şey görmez.
- **Bağlam**, istek boyunca ortak olan veridir: kimlik bilgisi, veritabanı bağlantısı,
  istek başına önbellek. Alanlar arasında taşınan tek yatay kanal budur.
- **Bilgi**, çağrının nerede yapıldığını söyler: hangi alan, hangi üst tip, yanıttaki hangi
  yol.

Çalıştırıcının bu dördünü nasıl ürettiği aşağıda görünür. Yeni olan iki şey var: `bilgi`
nesnesi ve her çağrının bildirildiği `izleyici` kancası.

```js
// calistirici.mjs — denetlenmis bir belgeyi calistirir
// Her alan icin bir cozucu cagrilir; cozucu dort girdi alir:
//   kaynak      ust duzeyin dondurdugu deger
//   argumanlar  alanin argumanlari (degiskenler cozulmus)
//   baglam      istek boyunca ortak veri
//   bilgi       { alan, ustTip, yol } — cagriyi yerinde tanimlar
import { tipCoz, alanTanimi, olasiTipler } from "./sema.mjs";

const YAPRAK = new Set(["skaler", "sirali"]);

// Degisken bildirimlerini gelen degerlerle birlestirir.
function degiskenleriCoz(islem, gelen) {
  const d = {};
  for (const b of islem.degiskenler) {
    const v = gelen[b.ad] ?? b.varsayilan;
    if (v === undefined && b.tip.endsWith("!")) throw new Error(`$${b.ad} değişkeni zorunlu`);
    d[b.ad] = v;
  }
  return d;
}

const argCoz = (argumanlar, dv) =>
  Object.fromEntries(Object.entries(argumanlar).map(([a, d]) => [a, d.tur === "degisken" ? dv[d.ad] : d.deger]));

// Parcalari ve satir ici parcalari acar; somut tipe uymayan dallari atar.
function duzle(sema, belge, tipAdi, secim, birikim = []) {
  for (const s of secim) {
    if (s.tur === "alan") { birikim.push(s); continue; }
    const parca = s.tur === "parcaYayimi" ? belge.parcalar[s.ad] : s;
    const kosul = s.tur === "parcaYayimi" ? parca.tip : s.tip;
    if (kosul === tipAdi || olasiTipler(sema, kosul).includes(tipAdi)) duzle(sema, belge, tipAdi, parca.secim, birikim);
  }
  return birikim;
}

async function secimCalistir(o, tipAdi, secim, kaynak, yol, sirali = false) {
  const alanlar = duzle(o.sema, o.belge, tipAdi, secim);
  const sonuc = {};
  for (const a of alanlar) sonuc[a.takma] = null;      // anahtar sirasi sorgudan gelir
  const isle = async (a) => { sonuc[a.takma] = await alanCalistir(o, tipAdi, a, kaynak, `${yol}.${a.takma}`); };
  if (sirali) for (const a of alanlar) await isle(a);
  else await Promise.all(alanlar.map(isle));
  return sonuc;
}

async function alanCalistir(o, tipAdi, alan, kaynak, yol) {
  const tanim = alanTanimi(o.sema.tipler[tipAdi], alan.ad);
  const ozel = o.cozucular[tipAdi]?.[alan.ad];
  const cozucu = ozel ?? ((k) => k?.[alan.ad]);       // varsayilan cozucu: ayni adli alani okur
  const bilgi = { alan: alan.ad, ustTip: tipAdi, yol, varsayilan: !ozel };
  o.izleyici?.(bilgi);
  const deger = await cozucu(kaynak, argCoz(alan.argumanlar, o.dv), o.baglam, bilgi);
  return tamamla(o, tanim.tip, deger, alan.secim, yol);
}

async function tamamla(o, tipDizgi, deger, secim, yol) {
  if (deger === null || deger === undefined) return null;
  const t = tipCoz(tipDizgi);
  if (t.liste) return Promise.all(deger.map((d, i) => tamamla(o, t.ad, d, secim, `${yol}[${i}]`)));
  const tip = o.sema.tipler[t.ad];
  if (YAPRAK.has(tip.tur)) return deger;
  // Arayuz ve birlesimde somut tip cozuculerden sorulur.
  const somut = tip.tur === "nesne" ? t.ad : o.cozucular.__tip[t.ad](deger);
  return secimCalistir(o, somut, secim, deger, yol);
}

export async function calistir(sema, cozucular, belge, { islemAdi, degiskenler = {}, baglam = {}, izleyici } = {}) {
  const islem = islemAdi ? belge.islemler.find((i) => i.ad === islemAdi) : belge.islemler[0];
  const o = { sema, cozucular, belge, dv: degiskenleriCoz(islem, degiskenler), baglam, izleyici };
  const kok = islem.tur === "mutation" ? sema.mutasyon : sema.sorgu;
  return { data: await secimCalistir(o, kok, islem.secim, null, "", islem.tur === "mutation") };
}
```

## Varsayılan Çözücü

`alanCalistir` içindeki tek satır belirleyicidir: yazılmış bir çözücü yoksa çalıştırıcı
`(k) => k?.[alan.ad]` işlevini kullanır. Bu **varsayılan çözücüdür** ve kaynaktan aynı adlı
alanı okur.

Ödünç kaydının `id`, `durum` ve `iadeTarihi` alanları için çözücü yazılmamıştır, çünkü
veri kaynağından gelen nesne bu alanları zaten aynı adlarla taşır. `uye` alanı için
yazılmıştır, çünkü kayıtta yalnız `uyeId` vardır ve üye nesnesine dönüşmesi bir arama
gerektirir.

Kural şudur: **çözücü yalnız veri şekli ile şema şekli ayrıştığında yazılır.** Bu, veri
kaynağının şemaya benzemesi hâlinde çözücü sayısını çarpıcı biçimde azaltır ve şemayı veri
kaynağının bir kopyası olmaya iter — dikkat edilmesi gereken bir eğilimdir, çünkü şema
tüketicinin gördüğü arayüzdür, deponun değil.

Aşağıdaki veri kaynağı her erişimi ayrıca sayar; böylece çözücü çağrısı ile veri kaynağı
turu birbirinden ayırt edilebilir.

```js
// veri.mjs — bellek ici veri; her erisim sayilir
export const sayac = { kitap: 0, uye: 0, odunc: 0 };

const KITAPLAR = new Map([
  ["978-0262033848", { isbn: "978-0262033848", baslik: "Algoritmalara Giris", yazar: "Cormen", kopya: 1 }],
  ["978-0201896831", { isbn: "978-0201896831", baslik: "Programlama Sanati", yazar: "Knuth", kopya: 2 }],
  ["978-0131103627", { isbn: "978-0131103627", baslik: "C Programlama Dili", yazar: "Kernighan", kopya: 3 }],
]);
const UYELER = new Map([
  ["U-1001", { id: "U-1001", olusturma: "2024-02-11", ad: "Ayse Yilmaz" }],
  ["U-1002", { id: "U-1002", olusturma: "2025-06-03", ad: "Deniz Kaya" }],
]);
const ODUNCLER = new Map([
  ["O-1", { id: "O-1", olusturma: "2026-01-04", uyeId: "U-1001", durum: "ACIK", iadeTarihi: "2026-03-20",
            kalemler: [{ isbn: "978-0262033848", sube: "merkez" }, { isbn: "978-0201896831", sube: "sahil" }] }],
  ["O-2", { id: "O-2", olusturma: "2026-01-19", uyeId: "U-1002", durum: "ACIK", iadeTarihi: "2026-04-02",
            kalemler: [{ isbn: "978-0131103627", sube: "tepe" }] }],
  ["O-3", { id: "O-3", olusturma: "2026-02-02", uyeId: "U-1001", durum: "KAPALI", iadeTarihi: "2026-02-28",
            kalemler: [{ isbn: "978-0201896831", sube: "merkez" }] }],
]);

// Tek kayit getiren erisimler: her cagri bir "veri kaynagi turu" sayilir.
export const kitapGetir = (isbn) => { sayac.kitap++; return KITAPLAR.get(isbn); };
export const uyeGetir = (id) => { sayac.uye++; return UYELER.get(id); };
export const oduncListesi = (uyeId) => { sayac.odunc++; return [...ODUNCLER.values()].filter((o) => !uyeId || o.uyeId === uyeId); };
export const oduncGetir = (id) => { sayac.odunc++; return ODUNCLER.get(id); };
export const sifirla = () => { sayac.kitap = 0; sayac.uye = 0; sayac.odunc = 0; };

export const COZUCULER = {
  __tip: { Kayit: (d) => (d.kalemler ? "Odunc" : "Uye") },
  Sorgu: {
    odunc: (_, a) => oduncGetir(a.id) ?? null,
    oduncler: () => oduncListesi(),
    uye: (_, a) => uyeGetir(a.kimlik) ?? null,
  },
  // id, olusturma, durum, iadeTarihi icin cozucu yok: varsayilan cozucu isini gorur.
  Odunc: { uye: (o) => uyeGetir(o.uyeId) },
  Kalem: { kitap: (k) => kitapGetir(k.isbn) },
  Uye:   { oduncler: (u) => oduncListesi(u.id) },
};
```

## Yürütmenin Biçimi

```js
// kosu.mjs — cozucu cagrilarini izler, sirasini gosterir ve sayar
import { ayristir } from "./ayristirici.mjs";
import { calistir } from "./calistirici.mjs";
import { SEMA } from "./sema.mjs";
import { COZUCULER, sayac, sifirla } from "./veri.mjs";

async function izle(baslik, sorgu, ayrintili) {
  const iz = [];
  sifirla();
  const sonuc = await calistir(SEMA, COZUCULER, ayristir(sorgu), { izleyici: (b) => iz.push(b) });

  console.log(`\n=== ${baslik} ===`);
  if (ayrintili) {
    for (const b of iz) console.log(`  ${(b.ustTip + "." + b.alan).padEnd(16)}${b.varsayilan ? "varsayılan" : "yazılmış  "}  ${b.yol}`);
  } else {
    // Derinlige gore grupla: derinlik, cagri sayisi, hangi alanlar
    const duzey = new Map();
    for (const b of iz) {
      const d = b.yol.split(".").length - 1;
      if (!duzey.has(d)) duzey.set(d, []);
      duzey.get(d).push(`${b.ustTip}.${b.alan}`);
    }
    console.log("  derinlik  çağrı  alanlar");
    for (const [d, liste] of [...duzey].sort((a, b) => a[0] - b[0])) {
      console.log(`  ${String(d).padStart(8)}  ${String(liste.length).padStart(5)}  ${[...new Set(liste)].join(", ")}`);
    }
  }
  const ozel = iz.filter((b) => !b.varsayilan).length;
  console.log(`  toplam çözücü çağrısı: ${iz.length} (yazılmış ${ozel}, varsayılan ${iz.length - ozel})`);
  console.log(`  veri kaynağı turu: kitap=${sayac.kitap} üye=${sayac.uye} ödünç=${sayac.odunc}  toplam=${sayac.kitap + sayac.uye + sayac.odunc}`);
  return sonuc;
}

await izle("küçük sorgu", `{ oduncler { id uye { ad } } }`, true);
const sonuc = await izle("büyük sorgu", `{ oduncler { id durum uye { ad } kalemler { sube kitap { baslik yazar } } } }`, false);
console.log(`\nilk kayıt: ${JSON.stringify(sonuc.data.oduncler[0])}`);
```

```
=== küçük sorgu ===
  Sorgu.oduncler  yazılmış    .oduncler
  Odunc.id        varsayılan  .oduncler[0].id
  Odunc.uye       yazılmış    .oduncler[0].uye
  Odunc.id        varsayılan  .oduncler[1].id
  Odunc.uye       yazılmış    .oduncler[1].uye
  Odunc.id        varsayılan  .oduncler[2].id
  Odunc.uye       yazılmış    .oduncler[2].uye
  Uye.ad          varsayılan  .oduncler[0].uye.ad
  Uye.ad          varsayılan  .oduncler[1].uye.ad
  Uye.ad          varsayılan  .oduncler[2].uye.ad
  toplam çözücü çağrısı: 10 (yazılmış 4, varsayılan 6)
  veri kaynağı turu: kitap=0 üye=3 ödünç=1  toplam=4

=== büyük sorgu ===
  derinlik  çağrı  alanlar
         1      1  Sorgu.oduncler
         2     12  Odunc.id, Odunc.durum, Odunc.uye, Odunc.kalemler
         3     11  Uye.ad, Kalem.sube, Kalem.kitap
         4      8  Kitap.baslik, Kitap.yazar
  toplam çözücü çağrısı: 32 (yazılmış 8, varsayılan 24)
  veri kaynağı turu: kitap=4 üye=3 ödünç=1  toplam=8

ilk kayıt: {"id":"O-1","durum":"ACIK","uye":{"ad":"Ayse Yilmaz"},"kalemler":[{"sube":"merkez","kitap":{"baslik":"Algoritmalara Giris","yazar":"Cormen"}},{"sube":"sahil","kitap":{"baslik":"Programlama Sanati","yazar":"Knuth"}}]}
```

Küçük sorgunun izi yürütmenin biçimini doğrudan gösteriyor. Önce kök alan çözülür. Sonra üç
ödünç kaydının **hepsinin** `id` ve `uye` alanları çözülür. Ancak bunlar bittikten sonra
`Uye.ad` çağrıları başlar.

Yürütme derinlik değil **düzey** sırasıyla ilerler. Nedeni `secimCalistir` içindeki
`Promise.all` çağrısıdır: bir düzeydeki bütün kardeş alanlar birlikte başlatılır ve hepsi
bitmeden bir alt düzeye geçilmez. Bu, sonraki dersteki toplu getirmenin dayanacağı
özelliktir: aynı düzeydeki çağrılar zaten aynı anda yapılıyorsa, birleştirilebilirler.

İki kısıt bu sırayı zorunlu kılar. Bir çözücünün kaynağı üst düzeyin **döndürdüğü**
değerdir; üst çözülmeden alt çözülemez. Ve kardeş alanlar arasında bir sıra güvencesi
yoktur; sorgu alanları bağımsız kabul edildiği için birbirlerinin sonucuna dayanamazlar.

## İki Ayrı Sayı

Büyük sorgu 32 çözücü çağrısı üretti ama veri kaynağına yalnız 8 kez gidildi. İki sayı
ayrıdır ve ayrı şeyleri ölçer.

Çözücü çağrısı sayısı **sorgunun biçiminden** gelir: seçilen alan sayısı ile listelerin
uzunluğunun çarpımı. Yirmi dördü varsayılan çözücüdür; bunlar bellekteki bir nesneden alan
okur, maliyetleri yok sayılabilir.

Veri kaynağı turu sayısı ise **yazılmış çözücülerden** gelir ve asıl maliyet buradadır. Sekiz
turun dağılımı dikkat çekicidir: bir ödünç listesi çağrısı, üç üye çağrısı, dört kitap
çağrısı. Oysa veritabanında yalnız iki ayrı üye ve üç ayrı kitap vardır.

Aynı üye iki ödünç kaydında geçtiği için iki kez getirilmiş, aynı kitap iki kalemde geçtiği
için iki kez getirilmiştir. Liste uzadıkça bu sayı listeyle birlikte büyür: yüz ödünç kaydı
yüz üye çağrısı demektir, üyelerin çoğu aynı kişi olsa bile.

## Özet

- Çözücü dört girdi alır: üst düzeyin döndürdüğü kaynak, çözülmüş argümanlar, istek boyunca
  ortak bağlam ve çağrının yerini bildiren bilgi.
- Varsayılan çözücü kaynaktan aynı adlı alanı okur; çözücü yalnız veri şekli ile şema şekli
  ayrıştığında yazılır.
- Yürütme derinlik değil düzey sırasıyla ilerler: bir düzeydeki bütün kardeş alanlar
  birlikte başlatılır, hepsi bitmeden alt düzeye geçilmez.
- Sıra iki kısıttan doğar: alt düzey üstün döndürdüğü değere muhtaçtır ve kardeş alanlar
  birbirinin sonucuna dayanamaz.
- Çözücü çağrısı sayısı sorgunun biçiminden, veri kaynağı turu sayısı yazılmış çözücülerden
  gelir; ikisi ayrı ölçülerdir.
- Aynı üye ve aynı kitap birden çok kez getirilir; bu sayı liste uzunluğuyla birlikte büyür.

## Sonraki Adım

Son paragraftaki sayı bir sorunun adıdır. Üç ödünç kaydı için üç üye çağrısı yapıldı, oysa
iki ayrı üye vardı; dört kitap çağrısı yapıldı, oysa üç ayrı kitap vardı. Liste yüz kayda
çıktığında yüz bir tur olacak: bir liste sorgusu artı her kayıt için bir tane. Sonraki ders
bu sorunu adıyla koyar, aynı düzeyde yapılan çağrıların birlikte başlatıldığı gerçeğine
dayanan bir toplu getirme katmanı yazar ve tur sayısının kaça düştüğünü ölçer.
