---
title: 'Şema ve Tip Sistemi'
source: 'https://academia.sh/tr/kurslar/api-tasarimi/sema-ve-tip-sistemi'
course: 'Web API Tasarımı'
language: tr
updated: '2026-08-17T18:06:45+00:00'
license: 'CC BY-SA 4.0'
---

# Şema ve Tip Sistemi

Nesne, skaler, numaralandırma, arayüz ve birleşim tiplerinin tanımlanması, sorgu belgesinin ayrıştırılması ve şemanın sorguyu çalıştırmadan önce denetleyerek hataları bulması.

Önceki konu boyunca sözleşme hep aynı biçimde kuruldu: sunucu hangi kaynağı hangi alanlarla
vereceğine karar verdi, istemci verilenle yetindi. Kısmi yanıt ve alan seçimi bu katılığı
gevşetmişti ama sınırı yine sunucu çiziyordu.

Sorgu temelli yaklaşım ilişkiyi tersine çevirir. Sunucu bir **tip sistemi** tanımlar; hangi
alanların döneceğine istemci karar verir ve yanıt sorgunun biçimini alır. Bunun karşılığında
sunucu bir güvence ister: istemcinin sorabileceği her şey tipler tarafından önceden
tanımlanmış olmalıdır.

Bu konu boyunca ödünç servisi için küçük bir GraphQL çalıştırıcısı yazılacak. Bu ders
çalıştırıcının ilk iki parçasını kurar — tip sistemi ve ayrıştırıcı — ve üçüncüsünü
gösterir: şemanın sorguyu çalıştırmadan önce denetlemesi.

## Beş Tip Türü

Şema, adlandırılmış tiplerden oluşan bir kayıttır. Beş tür yeter.

- **Skaler**, bölünmeyen değerdir: kimlik, dizgi, sayı, tarih. Alt seçim alamaz.
- **Numaralandırma** (enum), sabit bir değer kümesidir. Skaler gibi yapraktır.
- **Nesne**, adlandırılmış alanları olan bileşik tiptir. Alt seçim zorunludur.
- **Arayüz**, birden çok nesne tipinin paylaştığı alanları bildirir. Bir arayüz üzerinden
  yalnız ortak alanlar seçilebilir; tipe özgü alanlar için somut tipe inmek gerekir.
- **Birleşim**, ortak alanı **olmayan** tiplerin bir arada dönmesini sağlar. Birleşim
  üzerinden hiçbir alan doğrudan seçilemez.

Arayüz ile birleşim arasındaki fark, sorgunun neye izin verdiğidir. Ödünç kaydı ile üye
kaydının ikisi de bir kimlik ve oluşturma tarihi taşır; bu ortaklık bir arayüzdür. Arama
sonucu ise kitap veya üye döndürür ve bu ikisinin ortak bir alanı yoktur; bu bir
birleşimdir.

```js
// sema.mjs — odunc servisinin tip sistemi
// Tip turleri: skaler, sirali, nesne, arayuz, birlesim. Alan tipi dizgiyle yazilir:
// "Odunc", "ID!", "[Kalem!]!" — ! zorunlu, [] liste demektir.

export const SEMA = {
  sorgu: "Sorgu",
  mutasyon: "Mutasyon",
  tipler: {
    ID:      { tur: "skaler" },
    String:  { tur: "skaler" },
    Int:     { tur: "skaler" },
    Tarih:   { tur: "skaler" },
    Durum:   { tur: "sirali", degerler: ["ACIK", "KAPALI", "GECIKMIS"] },

    // Arayuz: birden cok tipin ortak alanlarini bildirir.
    Kayit:   { tur: "arayuz", alanlar: { id: "ID!", olusturma: "Tarih!" } },

    Kitap:   { tur: "nesne", alanlar: { isbn: "ID!", baslik: "String!", yazar: "String!", kopya: "Int!" } },
    Uye:     { tur: "nesne", arayuzler: ["Kayit"],
               alanlar: { id: "ID!", olusturma: "Tarih!", ad: "String!",
                          oduncler: { tip: "[Odunc!]!", argumanlar: { durum: "Durum" } } } },
    Kalem:   { tur: "nesne", alanlar: { kitap: "Kitap!", sube: "String!" } },
    Odunc:   { tur: "nesne", arayuzler: ["Kayit"],
               alanlar: { id: "ID!", olusturma: "Tarih!", uye: "Uye!", kalemler: "[Kalem!]!",
                          durum: "Durum!", iadeTarihi: "Tarih" } },

    // Birlesim: ortak alani olmayan tiplerin bir arada donmesi.
    AramaSonucu: { tur: "birlesim", uyeler: ["Kitap", "Uye"] },

    Sorgu:   { tur: "nesne", alanlar: {
                 odunc: { tip: "Odunc", argumanlar: { id: "ID!" } },
                 oduncler: { tip: "[Odunc!]!", argumanlar: { durum: "Durum" } },
                 uye:   { tip: "Uye",   argumanlar: { kimlik: "ID!" } },
                 arama: { tip: "[AramaSonucu!]!", argumanlar: { terim: "String!" } },
                 kayit: { tip: "Kayit", argumanlar: { id: "ID!" } } } },
    Mutasyon: { tur: "nesne", alanlar: {
                 oduncVer: { tip: "Odunc", argumanlar: { uye: "ID!", isbn: "ID!" } },
                 iadeAl:   { tip: "Odunc", argumanlar: { id: "ID!" } } } },
  },
};

// "[Kalem!]!" -> { ad: "Kalem", liste: true, zorunlu: true }
export function tipCoz(dizgi) {
  let s = dizgi, zorunlu = false, liste = false;
  if (s.endsWith("!")) { zorunlu = true; s = s.slice(0, -1); }
  if (s.startsWith("[")) { liste = true; s = s.slice(1, -1); if (s.endsWith("!")) s = s.slice(0, -1); }
  return { ad: s, liste, zorunlu };
}

export const alanTanimi = (tip, ad) => {
  const t = tip.alanlar?.[ad];
  return t === undefined ? null : typeof t === "string" ? { tip: t, argumanlar: {} } : { argumanlar: {}, ...t };
};

// Bir arayuz ya da birlesimin altina dusebilecek somut tipler
export const olasiTipler = (sema, ad) => {
  const t = sema.tipler[ad];
  if (t.tur === "birlesim") return t.uyeler;
  if (t.tur === "arayuz") return Object.entries(sema.tipler).filter(([, x]) => x.arayuzler?.includes(ad)).map(([a]) => a);
  return [ad];
};
```

`Sorgu` ve `Mutasyon` tiplerinin özel bir yanı yoktur; sıradan nesne tipleridir. Onları
başlangıç noktası yapan, şemanın `sorgu` ve `mutasyon` alanlarında adlarının yazılı
olmasıdır. Tip sistemi bir çizgedir ve bu iki ad, çizgeye girilen düğümleri gösterir.

## Sorgunun Ayrıştırılması

Sorgu bir metindir; denetlenebilmesi için ağaca çevrilmelidir. Aşağıdaki ayrıştırıcı
işlem türlerini, takma adları, argümanları, değişkenleri ve **sorgu parçalarını**
(fragment) tanır; kısa biçimiyle parça denecek.

```js
// ayristirici.mjs — GraphQL sorgu belgesini agaca cevirir
// Desteklenen: islem turleri, takma ad, arguman, degisken, parca ve satir ici parca.

const SIMGE = /\s+|,|#[^\n]*|(\.\.\.|[{}():$=[\]!]|"(?:[^"\\]|\\.)*"|-?\d+(?:\.\d+)?|[_A-Za-z][_0-9A-Za-z]*)/g;

const simgele = (metin) => [...metin.matchAll(SIMGE)].map((e) => e[1]).filter((s) => s !== undefined);

export function ayristir(metin) {
  const s = simgele(metin);
  let i = 0;
  const bak = () => s[i];
  const al = (beklenen) => {
    if (beklenen !== undefined && s[i] !== beklenen) throw new Error(`beklenen "${beklenen}", gelen "${s[i] ?? "belge sonu"}"`);
    return s[i++];
  };

  const deger = () => {
    if (bak() === "$") { al("$"); return { tur: "degisken", ad: al() }; }
    if (bak() === "[") { al("["); const d = []; while (bak() !== "]") d.push(deger()); al("]"); return { tur: "sabit", deger: d }; }
    const t = al();
    if (t.startsWith('"')) return { tur: "sabit", deger: JSON.parse(t) };
    if (/^-?\d/.test(t)) return { tur: "sabit", deger: Number(t) };
    if (t === "true" || t === "false") return { tur: "sabit", deger: t === "true" };
    return { tur: "sirali", deger: t };                        // sirali tip degeri
  };

  const tipAdi = () => {                                       // Odunc! [Kalem!]! gibi
    let ad;
    if (bak() === "[") { al("["); ad = `[${tipAdi()}]`; al("]"); } else ad = al();
    if (bak() === "!") { al("!"); ad += "!"; }
    return ad;
  };

  const secimKumesi = () => {
    al("{");
    const secim = [];
    while (bak() !== "}") {
      if (bak() === "...") {
        al("...");
        if (bak() === "on") { al("on"); const tip = al(); secim.push({ tur: "satirIci", tip, secim: secimKumesi() }); }
        else secim.push({ tur: "parcaYayimi", ad: al() });
        continue;
      }
      let ad = al(), takma = null;
      if (bak() === ":") { al(":"); takma = ad; ad = al(); }
      const argumanlar = {};
      if (bak() === "(") { al("("); while (bak() !== ")") { const a = al(); al(":"); argumanlar[a] = deger(); } al(")"); }
      const alt = bak() === "{" ? secimKumesi() : null;
      secim.push({ tur: "alan", ad, takma: takma ?? ad, argumanlar, secim: alt });
    }
    al("}");
    return secim;
  };

  const islemler = [], parcalar = {};
  while (i < s.length) {
    if (bak() === "fragment") {
      al("fragment"); const ad = al(); al("on"); const tip = al();
      parcalar[ad] = { ad, tip, secim: secimKumesi() };
      continue;
    }
    let tur = "query", ad = null;
    if (["query", "mutation", "subscription"].includes(bak())) { tur = al(); if (bak() !== "(" && bak() !== "{") ad = al(); }
    const degiskenler = [];
    if (bak() === "(") {
      al("(");
      while (bak() !== ")") {
        al("$"); const dad = al(); al(":"); const dtip = tipAdi();
        let varsayilan = undefined;
        if (bak() === "=") { al("="); varsayilan = deger().deger; }
        degiskenler.push({ ad: dad, tip: dtip, varsayilan });
      }
      al(")");
    }
    islemler.push({ tur, ad, degiskenler, secim: secimKumesi() });
  }
  return { islemler, parcalar };
}
```

Ayrıştırıcının ürettiği ağacın tek bilmediği şey **anlamdır**. `gecikmeCezasi` diye bir
alan seçilmişse ayrıştırıcı bunu sorunsuzca ağaca koyar; böyle bir alanın olmadığını
söyleyecek olan tip sistemidir.

## Şemanın Sorguyu Denetlemesi

Denetleyici, seçim kümesini şemadaki tiplerle birlikte gezer ve her adımda uyum arar.

```js
// denetle.mjs — sorguyu semaya karsi dogrular; calistirmadan once hatalari bulur
import { tipCoz, alanTanimi, olasiTipler } from "./sema.mjs";

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

export function denetle(sema, belge) {
  const hatalar = [];
  for (const islem of belge.islemler) {
    const kok = islem.tur === "mutation" ? sema.mutasyon : sema.sorgu;
    const bildirilen = new Set(islem.degiskenler.map((d) => d.ad));
    secimDenetle(sema, belge, kok, islem.secim, kok, bildirilen, hatalar);
  }
  return hatalar;
}

function secimDenetle(sema, belge, tipAdi, secim, yol, bildirilen, hatalar) {
  const tip = sema.tipler[tipAdi];
  for (const s of secim) {
    if (s.tur === "parcaYayimi") {
      const parca = belge.parcalar[s.ad];
      if (!parca) { hatalar.push(`${yol}: "${s.ad}" adlı parça tanımlı değil`); continue; }
      if (!olasiTipler(sema, tipAdi).includes(parca.tip) && parca.tip !== tipAdi)
        hatalar.push(`${yol}: "${s.ad}" parçası ${parca.tip} için, buradaki tip ${tipAdi}`);
      else secimDenetle(sema, belge, parca.tip, parca.secim, `${yol}...${s.ad}`, bildirilen, hatalar);
      continue;
    }
    if (s.tur === "satirIci") {
      if (!olasiTipler(sema, tipAdi).includes(s.tip))
        hatalar.push(`${yol}: ${s.tip}, ${tipAdi} altına düşemez (olası tipler: ${olasiTipler(sema, tipAdi).join(", ")})`);
      else secimDenetle(sema, belge, s.tip, s.secim, `${yol}...${s.tip}`, bildirilen, hatalar);
      continue;
    }

    // Birlesim tipinde dogrudan alan secilemez; once somut tipe inilmelidir.
    if (tip.tur === "birlesim") { hatalar.push(`${yol}: ${tipAdi} bir birleşimdir, "${s.ad}" alanı doğrudan seçilemez`); continue; }

    const tanim = alanTanimi(tip, s.ad);
    if (!tanim) { hatalar.push(`${yol}: ${tipAdi} tipinde "${s.ad}" alanı yok`); continue; }

    for (const a of Object.keys(s.argumanlar)) {
      if (!(a in tanim.argumanlar)) hatalar.push(`${yol}.${s.ad}: "${a}" adlı argüman tanımlı değil`);
    }
    for (const d of Object.values(s.argumanlar)) {
      if (d.tur === "degisken" && !bildirilen.has(d.ad)) hatalar.push(`${yol}.${s.ad}: $${d.ad} değişkeni bildirilmemiş`);
    }
    for (const [a, t] of Object.entries(tanim.argumanlar)) {
      if (t.endsWith("!") && !(a in s.argumanlar)) hatalar.push(`${yol}.${s.ad}: zorunlu "${a}" argümanı verilmemiş`);
    }

    const alanTipi = sema.tipler[tipCoz(tanim.tip).ad];
    const yaprakMi = YAPRAK.has(alanTipi.tur);
    if (yaprakMi && s.secim) hatalar.push(`${yol}.${s.ad}: ${tipCoz(tanim.tip).ad} bir yapraktır, alt seçim alamaz`);
    else if (!yaprakMi && !s.secim) hatalar.push(`${yol}.${s.ad}: ${tipCoz(tanim.tip).ad} bileşiktir, alt seçim zorunludur`);
    else if (!yaprakMi) secimDenetle(sema, belge, tipCoz(tanim.tip).ad, s.secim, `${yol}.${s.ad}`, bildirilen, hatalar);
  }
}
```

```js
// kosu.mjs — bir gecerli, alti gecersiz sorguyu semaya karsi denetler
import { ayristir } from "./ayristirici.mjs";
import { denetle } from "./denetle.mjs";
import { SEMA } from "./sema.mjs";

const SORGULAR = {
  "geçerli sorgu": `
    query OduncEkrani($uye: ID!) {
      uye(kimlik: $uye) { ad oduncler(durum: ACIK) { ...Ozet } }
      arama(terim: "algoritma") { ... on Kitap { baslik } ... on Uye { ad } }
      kayit(id: "O-1") { id olusturma }
    }
    fragment Ozet on Odunc { id durum kalemler { kitap { baslik } } }`,

  "olmayan alan": `{ odunc(id: "O-1") { id gecikmeCezasi } }`,
  "yaprağa alt seçim": `{ odunc(id: "O-1") { durum { ad } } }`,
  "bileşiğe alt seçim yok": `{ odunc(id: "O-1") { id uye } }`,
  "birleşimden doğrudan alan": `{ arama(terim: "x") { baslik } }`,
  "yanlış parça tipi": `{ odunc(id: "O-1") { ...KitapOzeti } } fragment KitapOzeti on Kitap { baslik }`,
  "eksik argüman ve bildirilmemiş değişken": `{ odunc { id } uye(kimlik: $k) { ad } }`,
};

for (const [ad, metin] of Object.entries(SORGULAR)) {
  const hatalar = denetle(SEMA, ayristir(metin));
  console.log(`\n${ad}  (${hatalar.length} hata)`);
  for (const h of hatalar) console.log(`  ${h}`);
}
```

```
geçerli sorgu  (0 hata)

olmayan alan  (1 hata)
  Sorgu.odunc: Odunc tipinde "gecikmeCezasi" alanı yok

yaprağa alt seçim  (1 hata)
  Sorgu.odunc.durum: Durum bir yapraktır, alt seçim alamaz

bileşiğe alt seçim yok  (1 hata)
  Sorgu.odunc.uye: Uye bileşiktir, alt seçim zorunludur

birleşimden doğrudan alan  (1 hata)
  Sorgu.arama: AramaSonucu bir birleşimdir, "baslik" alanı doğrudan seçilemez

yanlış parça tipi  (1 hata)
  Sorgu.odunc: "KitapOzeti" parçası Kitap için, buradaki tip Odunc

eksik argüman ve bildirilmemiş değişken  (2 hata)
  Sorgu.odunc: zorunlu "id" argümanı verilmemiş
  Sorgu.uye: $k değişkeni bildirilmemiş
```

Yedi sorgudan biri geçti, altısı reddedildi ve **hiçbiri çalıştırılmadı**. Reddetmelerin
tamamı yalnızca şemadan gelen bilgiyle yapıldı; veri kaynağına bakılmadı, tek bir çözücü
çağrılmadı.

Bu, kaynak temelli tasarımdan yapısal olarak farklı bir noktadır. Orada bir uç noktanın
neyi kabul ettiği çalışma anında doğrulanır ve tüketici hangi alanların bulunduğunu belgeden
öğrenir. Burada aynı bilgi şemada durur ve sorgu gönderilmeden denetlenebilir; makine
okunur belgeler dersinde ayrıca yazılan tanım, tip sisteminin kendisidir.

## Tip Sisteminin Kazandırdığı ve İstediği

Arayüz ve birleşim satırları, tip sisteminin en çok iş gördüğü yerlerdir. `kayit(id:)`
alanı bir arayüz döndürür; sorgu ondan yalnız `id` ve `olusturma` alanlarını seçebilir,
çünkü arayüzün bildirdiği alanlar bunlardır. Ödünç kaydına özgü `durum` alanını okumak
isteyen sorgu, `... on Odunc` yazarak somut tipe inmek zorundadır. Birleşimde ise ortak
alan hiç yoktur; her dal kendi tipini bildirmeden hiçbir şey okuyamaz.

Bu katılığın karşılığı, yanıtın biçiminin sorgudan **çıkarılabilmesidir**. İstemci
gönderdiği seçim kümesine bakarak dönecek gövdenin şeklini bilir; sunucunun ayrıca bir
yanıt şeması yayımlamasına gerek kalmaz.

İstenen bedel ise şemanın eksiksiz olmasıdır. Şemada tanımlanmamış hiçbir şey sorulamaz;
"şimdilik ham veriyi olduğu gibi döndürelim" gibi bir kaçış yolu yoktur. Ödünç servisinin
her alanı, her argümanı ve her tip ilişkisi yazılmak zorundadır.

## Özet

- Şema beş tip türünden kurulur: skaler ve numaralandırma yapraktır, nesne bileşiktir, arayüz
  ortak alanları bildirir, birleşim ortak alanı olmayan tipleri bir arada döndürür.
- Arayüz üzerinden yalnız ortak alanlar okunur; birleşim üzerinden hiçbir alan doğrudan
  okunamaz, her iki durumda da tipe özgü alanlar için somut tipe inilir.
- `Sorgu` ve `Mutasyon` sıradan nesne tipleridir; onları başlangıç noktası yapan, şemada
  adlarının giriş düğümü olarak yazılmış olmasıdır.
- Ayrıştırıcı sorguyu ağaca çevirir ama anlamını bilmez; alanın var olup olmadığını
  söyleyen tip sistemidir.
- Şemaya karşı denetim, sorgu çalıştırılmadan ve tek bir çözücü çağrılmadan yapılır; olmayan
  alan, yaprağa alt seçim, bileşiğe eksik seçim, yanlış parça tipi ve eksik argüman bu
  aşamada yakalanır.
- Tip sisteminin karşılığı yanıt biçiminin sorgudan çıkarılabilmesi, bedeli ise şemada
  tanımlanmamış hiçbir şeyin sorulamamasıdır.

## Sonraki Adım

Denetlenen sorgu henüz çalıştırılmadı; şema neyin sorulabileceğini söylüyor ama yanıtın
nereden geleceğini söylemiyor. Ayrıca buraya kadar yalnız tek bir işlem türü konuşuldu.
Ödünç vermek bir okuma değil, bir değiştirmedir ve sunucudaki sırayla ilgili farklı bir
güvence ister; bir kitabın durumunun değişmesini beklemek ise ne okuma ne değiştirmedir,
zaman içinde birden çok yanıt üretir. Sonraki ders üç işlem türünü tanımlar, çalıştırıcının
ilk hâlini yazar ve üçünün yürütme kuralı bakımından nerede ayrıldığını gösterir.
