---
title: 'Kısmi Yanıt ve Alan Seçimi'
source: 'https://academia.sh/tr/kurslar/api-tasarimi/kismi-yanit-ve-alan-secimi'
course: 'Web API Tasarımı'
language: tr
updated: '2026-08-17T18:06:45+00:00'
license: 'CC BY-SA 4.0'
---

# Kısmi Yanıt ve Alan Seçimi

Gösterimin ayrıntı düzeyine istemcinin katılması: aşırı ve eksik veri çekmenin istek sayısı ve bayt cinsinden ölçülmesi, alan seçimi ile genişletmenin izin listesiyle kurulması ve bu esnekliğin sözleşmeye getirdiği maliyet.

Önceki iki ders koleksiyonun hangi kayıtlarının, hangi sırayla ve ne kadarının döneceğini
çözdü. Her kaydın **ne kadarının** döneceği açık kaldı.

Kütüphane arayüzünde iki ekran vardır. Ödünç listesi ekranı her satırda yalnızca kitap
adını, üye adını ve veriliş tarihini gösterir. Ödünç ayrıntısı ekranı ise kaydın tüm
alanlarını ister. İkisi de aynı koleksiyondan beslenir. Sunucu tek bir gösterim
döndürüyorsa, bu gösterim ya listeye göre dar kalır ya da ayrıntıya göre geniş olur. İki
durumun da ölçülebilir bir bedeli vardır.

## Aşırı ve Eksik Veri Çekme

**Aşırı veri çekme** (over-fetching), istemcinin kullanmadığı alanların da gönderilmesidir.
Ödünç listesi ekranı üç alan gösterirken sunucu on alan yolluyorsa, aradaki fark ağdan
geçer, istemcide çözümlenir ve atılır.

**Eksik veri çekme** (under-fetching), tek isteğin ekranı besleyememesidir. Ödünç kaydı
yalnızca ISBN ve üye kodu taşıyorsa, kitap adını ve üye adını almak için her satır başına
iki istek daha gerekir. Kırk satırlık bir liste, seksen ek istek demektir.

İkisi zıt yönlü görünür ama aynı kökten gelir: gösterimin ayrıntı düzeyine sunucu tek
başına karar vermektedir. Çözüm, kararın bir bölümünü istemciye vermektir — ama sınırları
sunucunun çizdiği bir esneklikle.

```js
// alan-secim-sunucusu.mjs — alan secimi ve genisletme destekleyen odunc koleksiyonu
import { createServer } from "node:http";
import { DatabaseSync } from "node:sqlite";

const db = new DatabaseSync("kutuphane.db");
const yanitla = (yanit, kod, nesne) => {
  yanit.writeHead(kod, { "content-type": "application/json; charset=utf-8" });
  yanit.end(JSON.stringify(nesne));
};

// Izin listesi: istemcinin adlandirdigi alan, burada sutun ifadesine cevrilir.
const TEMEL = { id: "o.id", uye: "o.uye", isbn: "o.isbn", verilis: "o.verilis", iade: "o.iade" };
const GENIS = { kitapAdi: "k.ad", kitapYazari: "k.yazar", uyeAdi: "u.ad", uyeSubesi: "u.sube" };

const sunucu = createServer((istek, yanit) => {
  const adres = new URL(istek.url, "http://127.0.0.1");
  const s = adres.searchParams;

  if (/^\/kitaplar\/[^/]+$/.test(adres.pathname)) {
    const isbn = adres.pathname.split("/")[2];
    const k = db.prepare("SELECT * FROM kitap WHERE isbn = ?").get(isbn);
    return k ? yanitla(yanit, 200, k) : yanitla(yanit, 404, { hata: "kitap_yok" });
  }
  if (/^\/uyeler\/[^/]+$/.test(adres.pathname)) {
    const kod = adres.pathname.split("/")[2];
    const u = db.prepare("SELECT * FROM uye WHERE kod = ?").get(kod);
    return u ? yanitla(yanit, 200, u) : yanitla(yanit, 404, { hata: "uye_yok" });
  }
  if (adres.pathname !== "/oduncler") return yanitla(yanit, 404, { hata: "yol_yok" });

  // Genisletme: bagli kaynaklarin alanlari yaniten icine katilir.
  const genislet = (s.get("genislet") ?? "").split(",").filter(Boolean);
  const sunulan = { ...TEMEL };
  if (genislet.includes("kitap")) Object.assign(sunulan, { kitapAdi: GENIS.kitapAdi, kitapYazari: GENIS.kitapYazari });
  if (genislet.includes("uye")) Object.assign(sunulan, { uyeAdi: GENIS.uyeAdi, uyeSubesi: GENIS.uyeSubesi });

  // Alan secimi: istenen adlar sunulanlarla kesistirilir, tanimsiz ad reddedilir.
  const istenen = (s.get("alanlar") ?? "").split(",").filter(Boolean);
  const bilinmeyen = istenen.filter((a) => !(a in sunulan));
  if (bilinmeyen.length)
    return yanitla(yanit, 422, { hata: "dogrulama", alan: "alanlar", bilinmeyen, sunulan: Object.keys(sunulan) });
  const secilen = istenen.length ? istenen : Object.keys(sunulan);

  const secim = secilen.map((a) => `${sunulan[a]} AS ${a}`).join(", ");
  const satirlar = db.prepare(
    `SELECT ${secim} FROM odunc o
       LEFT JOIN kitap k ON k.isbn = o.isbn
       LEFT JOIN uye u ON u.kod = o.uye
      ORDER BY o.id LIMIT ?`
  ).all(Number(s.get("boyut") ?? 40));

  yanitla(yanit, 200, { veri: satirlar, sayfalama: { boyut: satirlar.length } });
});

sunucu.listen(8482, "127.0.0.1", () => console.log("alan secim sunucusu 127.0.0.1:8482"));
```

Sütun adları yine izin listesinden geliyor; bir önceki dersteki ayrım burada da geçerlidir.
İstemci `kitapAdi` adını gönderir, sunucu onu `k.ad` ifadesine çevirir. Listede olmayan bir
ad sorgu metnine ulaşamaz.

## Ölçüm

Aynı ekranı üç farklı strateji besler. Ölçüm, istek sayısını ve aktarılan gövde baytını
sayar.

```js
// olcum.mjs — uc stratejinin istek sayisini ve aktarilan bayti olcer
const T = "http://127.0.0.1:8482";
let istek = 0, bayt = 0;
const cek = async (yol) => {
  istek++;
  const metin = await (await fetch(T + yol)).text();
  bayt += Buffer.byteLength(metin);
  return JSON.parse(metin);
};
const yaz = (ad) => { console.log(`${ad.padEnd(34)} istek: ${String(istek).padStart(3)}  bayt: ${bayt}`); istek = 0; bayt = 0; };

// 1) Ayri ayri: odunc listesi, sonra her kayit icin kitap ve uye.
const liste = await cek("/oduncler");
for (const o of liste.veri) { await cek(`/kitaplar/${o.isbn}`); await cek(`/uyeler/${o.uye}`); }
yaz("ayri istekler (eksik veri cekme)");

// 2) Genisletme: tek istek, ama butun alanlar geliyor.
await cek("/oduncler?genislet=kitap,uye");
yaz("genisletme (asiri veri cekme)");

// 3) Genisletme + alan secimi: tek istek, yalnizca ekranin kullandigi alanlar.
await cek("/oduncler?genislet=kitap,uye&alanlar=id,verilis,kitapAdi,uyeAdi");
yaz("genisletme + alan secimi");
```

```bash
# Kirk odunc kaydi; ayni ekran uc farkli stratejiyle beslenir.
rm -f kutuphane.db && sqlite3 kutuphane.db < sema.sql && sqlite3 kutuphane.db < katalog.sql
sqlite3 kutuphane.db <<'SQL'
DELETE FROM odunc;
WITH RECURSIVE s(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM s WHERE n < 40)
INSERT INTO odunc (uye, isbn, verilis, iade)
SELECT CASE WHEN n % 2 = 0 THEN 'U-1001' ELSE 'U-1002' END,
       'K-0' || (1 + n % 9), date('2026-01-01', '+' || n || ' day'), NULL
FROM s;
SQL
node alan-secim-sunucusu.mjs & sunucu=$!
sleep 0.4

node olcum.mjs
echo "--- ornek yanit satiri ---"
curl -sS "http://127.0.0.1:8482/oduncler?genislet=kitap,uye&alanlar=id,verilis,kitapAdi,uyeAdi&boyut=2"; echo
curl -sS "http://127.0.0.1:8482/oduncler?alanlar=id,kitapAdi&boyut=2"; echo

kill $sunucu
```

```
alan secim sunucusu 127.0.0.1:8482
ayri istekler (eksik veri cekme)   istek:  81  bayt: 8151
genisletme (asiri veri cekme)      istek:   1  bayt: 6751
genisletme + alan secimi           istek:   1  bayt: 3404
--- ornek yanit satiri ---
{"veri":[{"id":1,"verilis":"2026-01-02","kitapAdi":"Veri Yapilari","uyeAdi":"Berk Tuna"},{"id":2,"verilis":"2026-01-03","kitapAdi":"Derleyiciler","uyeAdi":"Aylin Deren"}],"sayfalama":{"boyut":2}}
{"hata":"dogrulama","alan":"alanlar","bilinmeyen":["kitapAdi"],"sunulan":["id","uye","isbn","verilis","iade"]}
```

İlk satır eksik veri çekmenin bedelini gösteriyor: otuz dokuz kayıt için seksen bir istek.
Bayt farkı burada asıl mesele değildir; asıl mesele istek sayısıdır. Her istek bir ağ turu
demektir ve turlar birbirini beklediğinde gecikme doğrudan çarpılır. Aynı kitap ve aynı üye
defalarca istendiği için tekrar da vardır.

İkinci satır tek isteğe iniyor ama 6751 bayt taşıyor. Ekranın kullanmadığı altı alan —
ISBN, üye kodu, iade tarihi, kitap yazarı, üye şubesi — her satırda geçiyor.

Üçüncü satır ikisini birlikte çözüyor: tek istek, 3404 bayt. Aynı ekran, yarısından az
veriyle besleniyor. Oran veriye bağlıdır; burada tasarruf, alan adlarının ve kullanılmayan
değerlerin toplam gövde içindeki payından gelir.

Son satır sözleşmenin kendini savunmasıdır. `alanlar=id,kitapAdi` isteği `genislet=kitap`
olmadan gelmiş; `kitapAdi` o istekte sunulan alanlar arasında değil. Sunucu isteği 422 ile
reddediyor ve hem bilinmeyen adı hem de o istekte sunulan alanları bildiriyor. Sessizce
alanı atlamak, istemcinin hiç var olmayan bir alanı beklemesine ve `undefined` bir değerle
ekran çizmesine yol açardı.

## Esnekliğin Sözleşmedeki Maliyeti

Alan seçimi bedava değildir ve bedeli sözleşme yüzeyinde ödenir.

Birincisi, **önbellek anahtarı çoğalır**. Adres bir önbellek anahtarıdır; alan listesi
adrese girdiği için `alanlar=id,ad` ile `alanlar=ad,id` iki ayrı anahtar üretir. Aynı
veriyi iki kez saklamamak için alan listesinin kanonik biçimi belirlenmelidir: adlar
sıralanır ve yinelenenler atılır. Kanonik biçim kararı URI Tasarımı dersindeki kararla
aynı türdendir.

İkincisi, **sınanacak birleşim sayısı artar**. Beş temel ve dört genişletme alanı olan bir
uç noktada, istemcinin isteyebileceği alan kümesi sayısı elle sınanamayacak kadar çoktur.
Bu yüzden izin listesi dar tutulur: her sütun değil, ekranların gerçekten istediği alanlar
sunulur.

Üçüncüsü, **genişletmenin derinliği sınırlanmalıdır**. Bu sunucu tek düzey genişletme
sunuyor: ödünçten kitaba ve üyeye. İki düzeye izin verilseydi — kitaptan şubeye, şubeden
sorumlu üyeye — tek bir istek, sunucu tarafında dallanan bir sorgu ağacına dönüşürdü.
İstemcinin gördüğü tek istek, sunucunun ödediği bedeli gizler.

## Alan Seçimi mi, Adlandırılmış Görünüm mü

Alan seçiminin alternatifi, sunucunun birkaç hazır gösterim tanımlamasıdır: `?gorunum=liste`
ve `?gorunum=ayrinti` gibi. Bu yaklaşımda esneklik azalır, ama üç şey kazanılır. Önbellek
anahtarı sayısı ikiye iner. Sınanacak birleşim kalmaz. Sunucu her görünüm için tek bir
sorgu yazıp onu eniyileyebilir.

Seçim ölçütü istemci sayısıdır. Sunucuyu tek bir arayüz kullanıyorsa adlandırılmış görünüm
yeter; ekran değiştikçe görünüm tanımı da değişir. Birbirinden bağımsız çok sayıda istemci
varsa — mobil uygulama, yönetim paneli, başka bir servis — her birine ayrı görünüm tanımlamak
sözleşmeyi şişirir; alan seçimi bu durumda daha az bakım ister.

İki yaklaşım birlikte de kullanılır: yaygın durumlar için adlandırılmış görünümler, geri
kalan için alan seçimi. Bu, sorgu temelli biçemlerin baştan çözdüğü bir sorunun kaynak
temelli biçemdeki karşılığıdır; API Biçemleri konusunda tartışılan ödünleşim burada somut
sayılara dönüşür.

## Özet

- Aşırı veri çekme kullanılmayan alanların taşınmasıdır; eksik veri çekme tek isteğin
  ekranı besleyememesidir. İkisi de gösterimin ayrıntı düzeyine yalnız sunucunun karar
  vermesinden doğar.
- Ölçümde aynı ekran ayrı isteklerle 81 istek ve 8151 bayt, genişletmeyle 1 istek ve 6751
  bayt, genişletme ve alan seçimiyle 1 istek ve 3404 bayt tüketti.
- Alan adları ve genişletme adları izin listesinden geçer; sorgu metnine yalnızca sunucunun
  yazdığı sütun ifadeleri girer.
- Tanımsız alan isteği sessizce atlanmaz, 422 ile reddedilir ve o istekte sunulan alanlar
  yanıtta bildirilir.
- Alan seçimi önbellek anahtarını çoğaltır ve sınanacak birleşim sayısını artırır; alan
  listesinin kanonik biçimi belirlenmeli ve izin listesi dar tutulmalıdır.
- Az sayıda istemcisi olan servislerde adlandırılmış görünümler alan seçiminden daha az
  bakım ister; iki yaklaşım birlikte de kullanılabilir.

## Sonraki Adım

Buraya kadarki dört ders okuma tarafını düzeltti: hangi kayıtlar, hangi sırayla, ne kadarı,
kaç istekle. Yazma tarafında ise üçüncü dersten kalan bir açık duruyor. POST etkisiz
değildir; ağ koptuğunda ya da yanıt yolda kaybolduğunda istemci isteği tekrarlar ve ikinci
bir ödünç kaydı doğar. İstemci isteğin ulaşıp ulaşmadığını bilemez, sunucu da iki isteğin
aynı niyetten geldiğini göremez. Sonraki ders bu boşluğu istemcinin ürettiği bir anahtarla
kapatır ve aynı anahtarla gönderilen iki isteğin tek etki ürettiğini sayarak gösterir.
