---
title: 'Sorgu, Mutasyon ve Abonelik'
source: 'https://academia.sh/tr/kurslar/api-tasarimi/sorgu-mutasyon-ve-abonelik'
course: 'Web API Tasarımı'
language: tr
updated: '2026-08-17T18:06:45+00:00'
license: 'CC BY-SA 4.0'
---

# Sorgu, Mutasyon ve Abonelik

Üç işlem türünün yürütme kuralları, mutasyon alanlarının sıralı yürütülmesinin tek kopyalı kitap üzerinde ölçülen sonucu ve aboneliğin aynı seçim kümesini zaman içinde birden çok kez uygulaması.

Şema neyin sorulabileceğini söylüyor ama yanıtın nereden geleceğini söylemiyor. Bu ders
çalıştırıcıyı yazar ve GraphQL'in üç **işlem türünü** ayıran şeyin ne olduğunu gösterir.

Fark sözdiziminde değildir; üçü de aynı seçim kümesi dilini kullanır. Fark yürütme
kurallarındadır. Sorgu alanları birbirinden bağımsız kabul edilir ve birlikte yürütülebilir.
Mutasyon alanları birbirini etkileyebilir ve **sırayla** yürütülmek zorundadır. Abonelik
tek bir yanıt değil, zaman içinde birden çok yanıt üretir.

## Çalıştırıcı

Çalıştırıcı seçim kümesini gezer, her alan için bir çözücü çağırır ve dönen değeri alanın
tipine göre tamamlar. Yaprak tipte değer olduğu gibi döner; bileşik tipte alt seçim kümesi
aynı yordamla yürütülür.

```js
// calistirici.mjs — denetlenmis bir belgeyi calistirir
// Sorgu alanlari birlikte, mutasyon alanlari sirayla yurutulur.
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, 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); };
  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) {
  const tanim = alanTanimi(o.sema.tipler[tipAdi], alan.ad);
  const cozucu = o.cozucular[tipAdi]?.[alan.ad] ?? ((k) => k?.[alan.ad]);
  const deger = await cozucu(kaynak, argCoz(alan.argumanlar, o.dv), o.baglam);
  return tamamla(o, tanim.tip, deger, alan.secim);
}

async function tamamla(o, tipDizgi, deger, secim) {
  if (deger === null || deger === undefined) return null;
  const t = tipCoz(tipDizgi);
  if (t.liste) return Promise.all(deger.map((d) => tamamla(o, t.ad, d, secim)));
  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);
}

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

// Abonelik: cozucu bir olay akisi dondurur, her olay ayni secim kumesiyle bicimlenir.
export async function* abone(sema, cozucular, belge, { degiskenler = {}, baglam = {} } = {}) {
  const islem = belge.islemler[0];
  const o = { sema, cozucular, belge, dv: degiskenleriCoz(islem, degiskenler), baglam };
  const alan = duzle(sema, belge, sema.abonelik, islem.secim)[0];
  const akis = cozucular[sema.abonelik][alan.ad](null, argCoz(alan.argumanlar, o.dv), baglam);
  const tanim = alanTanimi(sema.tipler[sema.abonelik], alan.ad);
  for await (const olay of akis) yield { data: { [alan.takma]: await tamamla(o, tanim.tip, olay, alan.secim) } };
}
```

`calistir` işlevindeki tek dallanma, işlem türünün mutasyon olup olmadığıdır ve bu dallanma
`secimCalistir` çağrısına `sirali` bayrağı olarak geçer. Kural budur: bütün fark tek bir
`for` döngüsü ile `Promise.all` arasındaki seçimdir.

Çalıştırıcının veriye ulaşması için çözücülere ihtiyacı vardır. Aşağıdaki veri kaynağı
bellek içidir ve ödünç verme işlemi bilerek gerçekçi yazılmıştır: kalan kopya sayısı
**okunur**, bir veritabanı turu kadar beklenir, sonra kayıt **yazılır**.

```js
// veri.mjs — bellek ici odunc verisi ve cozuculer
export 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 }],
]);
export 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" }],
]);
export const ODUNCLER = new Map();
let sira = 0;

export const bekle = (ms) => new Promise((c) => setTimeout(c, ms));
export const olaylar = [];   // yurutme sirasini gormek icin
export const sifirla = () => { ODUNCLER.clear(); sira = 0; olaylar.length = 0; };

const eldeki = (isbn) => [...ODUNCLER.values()].filter((o) => o.durum === "ACIK" && o.kalemler.some((k) => k.isbn === isbn)).length;

export function oduncVer(uye, isbn, gecikme = 30) {
  return (async () => {
    olaylar.push(`oduncVer(${isbn.slice(-4)}) başladı`);
    const kalan = KITAPLAR.get(isbn).kopya - eldeki(isbn);   // OKUMA
    await bekle(gecikme);                                    // veritabani turu
    if (kalan <= 0) { olaylar.push(`oduncVer(${isbn.slice(-4)}) bitti: kopya yok`); return null; }
    const kayit = { id: `O-${++sira}`, olusturma: "2026-03-01", uyeId: uye,    // YAZMA
                    kalemler: [{ isbn, sube: "merkez" }], durum: "ACIK", iadeTarihi: null };
    ODUNCLER.set(kayit.id, kayit);
    olaylar.push(`oduncVer(${isbn.slice(-4)}) bitti: ${kayit.id}`);
    return kayit;
  })();
}

export const COZUCULER = {
  __tip: { Kayit: (d) => (d.kalemler ? "Odunc" : "Uye"), AramaSonucu: (d) => (d.isbn ? "Kitap" : "Uye") },
  Sorgu: {
    odunc: (_, a) => ODUNCLER.get(a.id) ?? null,
    uye: (_, a) => UYELER.get(a.kimlik) ?? null,
    kayit: (_, a) => ODUNCLER.get(a.id) ?? UYELER.get(a.id) ?? null,
    arama: (_, a) => [...KITAPLAR.values()].filter((k) => k.baslik.toLowerCase().includes(a.terim.toLowerCase())),
  },
  Mutasyon: {
    oduncVer: (_, a) => oduncVer(a.uye, a.isbn),
    iadeAl: (_, a) => { const o = ODUNCLER.get(a.id); if (o) o.durum = "KAPALI"; return o ?? null; },
  },
  Odunc: {
    uye: (o) => UYELER.get(o.uyeId),
    kalemler: (o) => o.kalemler,
  },
  Kalem: { kitap: (k) => KITAPLAR.get(k.isbn) },
  Uye: { oduncler: (u, a) => [...ODUNCLER.values()].filter((o) => o.uyeId === u.id && (!a.durum || o.durum === a.durum)) },
};
```

## Üç İşlem Türü Yan Yana

Şema ve Tip Sistemi dersinde kurulan şemada iki kök vardı: sorgu ve mutasyon. Abonelik
üçüncü köktür ve şemaya bir tip olarak eklenir; alanları, istemcinin izleyebileceği
olayları adlandırır.

```js
// sema-abonelik.mjs — 01. dersteki semaya abonelik koku eklenmis hali
import { SEMA } from "./sema.mjs";

export const SEMA_A = {
  ...SEMA,
  abonelik: "Abonelik",
  tipler: {
    ...SEMA.tipler,
    Abonelik: {
      tur: "nesne",
      alanlar: { oduncDurumu: { tip: "Odunc!", argumanlar: { id: "ID!" } } },
    },
  },
};
```

```js
// kosu.mjs — uc islem turunu calistirir; sorgu ile mutasyonun yurutme sirasini karsilastirir
import { ayristir } from "./ayristirici.mjs";
import { calistir, abone } from "./calistirici.mjs";
import { SEMA_A as SEMA } from "./sema-abonelik.mjs";
import { COZUCULER, ODUNCLER, olaylar, bekle, sifirla } from "./veri.mjs";

const yaz = (b, d) => console.log(b, JSON.stringify(d));

// 1) SORGU — alanlar birlikte yurutulur
ODUNCLER.set("O-9", { id: "O-9", olusturma: "2026-02-01", uyeId: "U-1001",
  kalemler: [{ isbn: "978-0201896831", sube: "sahil" }], durum: "ACIK", iadeTarihi: "2026-03-20" });

const sorgu = ayristir(`query($id: ID!) {
  odunc(id: $id) { id durum uye { ad } kalemler { kitap { baslik } sube } }
  kayit(id: "U-1002") { id olusturma ... on Uye { ad } }
}`);
yaz("sorgu     ->", (await calistir(SEMA, COZUCULER, sorgu, { degiskenler: { id: "O-9" } })).data);

// 2) MUTASYON — tek kopyali kitabi iki kez odunc vermeyi dene
const mutasyon = ayristir(`mutation {
  birinci: oduncVer(uye: "U-1001", isbn: "978-0262033848") { id durum }
  ikinci:  oduncVer(uye: "U-1002", isbn: "978-0262033848") { id durum }
}`);
sifirla();
yaz("mutasyon  ->", (await calistir(SEMA, COZUCULER, mutasyon)).data);
console.log("  yürütme sırası:", olaylar.join(" | "));

// Ayni iki alan birlikte yurutulseydi:
sifirla();
const paralel = await Promise.all([
  COZUCULER.Mutasyon.oduncVer(null, { uye: "U-1001", isbn: "978-0262033848" }),
  COZUCULER.Mutasyon.oduncVer(null, { uye: "U-1002", isbn: "978-0262033848" }),
]);
console.log("  birlikte yürütülseydi:", olaylar.join(" | "));
console.log("  birlikte yürütmede verilen kopya sayısı:", paralel.filter(Boolean).length, "(kitapta 1 kopya var)");

// 3) ABONELIK — ayni secim kumesi zaman icinde birden cok kez uygulanir
COZUCULER.Abonelik = {
  oduncDurumu: async function* (_, a) {
    for (const d of ["ACIK", "GECIKMIS", "KAPALI"]) {
      await bekle(5);
      const o = ODUNCLER.get(a.id);
      yield { ...o, durum: d };
    }
  },
};
ODUNCLER.set("O-9", { id: "O-9", olusturma: "2026-02-01", uyeId: "U-1001",
  kalemler: [{ isbn: "978-0201896831", sube: "sahil" }], durum: "ACIK", iadeTarihi: "2026-03-20" });
const abonelik = ayristir(`subscription { oduncDurumu(id: "O-9") { id durum uye { ad } } }`);
for await (const olay of abone(SEMA, COZUCULER, abonelik)) yaz("abonelik  ->", olay.data);
```

```
sorgu     -> {"odunc":{"id":"O-9","durum":"ACIK","uye":{"ad":"Ayse Yilmaz"},"kalemler":[{"kitap":{"baslik":"Programlama Sanati"},"sube":"sahil"}]},"kayit":{"id":"U-1002","olusturma":"2025-06-03","ad":"Deniz Kaya"}}
mutasyon  -> {"birinci":{"id":"O-1","durum":"ACIK"},"ikinci":null}
  yürütme sırası: oduncVer(3848) başladı | oduncVer(3848) bitti: O-1 | oduncVer(3848) başladı | oduncVer(3848) bitti: kopya yok
  birlikte yürütülseydi: oduncVer(3848) başladı | oduncVer(3848) başladı | oduncVer(3848) bitti: O-1 | oduncVer(3848) bitti: O-2
  birlikte yürütmede verilen kopya sayısı: 2 (kitapta 1 kopya var)
abonelik  -> {"oduncDurumu":{"id":"O-9","durum":"ACIK","uye":{"ad":"Ayse Yilmaz"}}}
abonelik  -> {"oduncDurumu":{"id":"O-9","durum":"GECIKMIS","uye":{"ad":"Ayse Yilmaz"}}}
abonelik  -> {"oduncDurumu":{"id":"O-9","durum":"KAPALI","uye":{"ad":"Ayse Yilmaz"}}}
```

## Sorgu: Alanlar Bağımsızdır

Sorgu yanıtındaki iki kök alan birbirini görmez. `odunc` ve `kayit` alanlarının hangi
sırayla çözüldüğü sonucu değiştirmez, çünkü ikisi de yalnız okur. Bu bağımsızlık varsayımı
GraphQL'in kök sorgu alanlarını birlikte yürütmesini meşru kılar ve pratikte belirgin bir
kazançtır: dört bağımsız kök alanı olan bir ekran, dört ayrı isteğin toplam süresini değil
en yavaşının süresini bekler.

Yanıttaki anahtar sırası ise sorgudaki sıradır, çözülme sırası değil. Çalıştırıcı bunu
alanları önceden `null` ile yerleştirerek sağlar. İstemcinin yanıtı sorgudan okuyabilmesi
buna bağlıdır.

`kayit` alanı arayüz döndürüyor ve `... on Uye { ad }` dalı çalışmış. Somut tipin ne olduğu
`__tip` çözücüsünden sorulur; veri kaynağındaki bir kaydın hangi tip olduğunu şema
bilemez, bunu bildiren kod yazılmak zorundadır.

## Mutasyon: Sıra Bir Güvencedir

Mutasyon çıktısı işin özünü gösteriyor. Tek kopyalı bir kitap iki kez ödünç istendi;
`birinci` başarılı oldu, `ikinci` `null` döndü. Yürütme sırası satırı nedenini söylüyor:
ikinci çözücü, birincisi **bittikten sonra** başladı ve kalan kopyayı sıfır gördü.

Aynı iki çözücü birlikte yürütüldüğünde sıra değişiyor: ikisi de başlıyor, ikisi de
başlangıçtaki tek kopyayı okuyor ve ikisi de kayıt yazıyor. Tek kopyalı kitap iki kez ödünç
verilmiş oluyor. Bu, veritabanı derslerindeki kayıp güncelleme sorununun aynısıdır ve
GraphQL'in kök mutasyon alanlarını sırayla yürütmesinin nedeni budur.

Güvencenin sınırı da belirtilmelidir: sıralılık yalnız **kök** mutasyon alanları içindir ve
yalnız **tek bir işlem** içinde geçerlidir. Aynı anda gelen iki ayrı isteğin mutasyonları
yine çakışabilir; onu önleyen şey çözücünün kendi eşzamanlılık denetimidir. İşlem içi sıra,
istemcinin tek bir belgede yazdığı adımların bildiği sırayla çalışacağını güvence altına
alır; başka bir şeyi değil.

## Abonelik: Aynı Seçim, Birden Çok Yanıt

Abonelik çıktısındaki üç satır aynı seçim kümesinden üretildi. Çözücü tek bir değer değil
bir **olay akışı** döndürür; çalıştırıcı her olayı aynı seçim kümesiyle biçimler. `uye { ad }`
dalı üç kez çözüldü.

Yapısal fark budur: sorgu ve mutasyonda kök alan bir değere çözülür, abonelikte bir akışa.
Bunun iki sonucu vardır. Bir abonelik işleminde yalnız bir kök alan bulunabilir — akışların
nasıl birleştirileceğinin tanımlı bir yanıtı yoktur. Ve aktarım katmanı istek–yanıt
olamaz; tek bir istek üzerinden birden çok yanıt taşıyabilen bir taşıyıcı gerekir.

## Özet

- Üç işlem türü aynı seçim kümesi dilini kullanır; ayrıldıkları yer yürütme kurallarıdır.
- Sorgu kök alanları bağımsız kabul edilir ve birlikte yürütülür; yanıttaki anahtar sırası
  yine de sorgudaki sıradır.
- Mutasyon kök alanları sırayla yürütülür; tek kopyalı kitap sıralı yürütmede bir kez,
  birlikte yürütmede iki kez ödünç verilir.
- Sıralılık güvencesi yalnız kök mutasyon alanları ve tek bir işlem içindir; ayrı
  isteklerin çakışmasını önlemek çözücünün işidir.
- Abonelik çözücüsü değer değil olay akışı döndürür; aynı seçim kümesi her olaya yeniden
  uygulanır.
- Abonelikte tek kök alan bulunabilir ve aktarım katmanı istek–yanıt olamaz.

## Sonraki Adım

Çalıştırıcı her alan için bir çözücü çağırdı ama çözücülerin nasıl yazılacağı konuşulmadı.
Ödünç kaydının `uye` alanı için ayrı bir çözücü var, `id` alanı için yok; ikisi arasındaki
fark nedir? Bir çözücü hangi bilgilere erişir, hangi sırayla çağrılır, döndürdüğü değer bir
sonraki düzeyin çözücüsüne nasıl geçer? Sonraki ders çözücü imzasını dört parçasıyla
tanımlar, varsayılan çözücünün ne yaptığını gösterir ve bir sorgunun kaç çözücü çağrısı
ürettiğini sayarak yürütmenin biçimini ortaya koyar.
