İçeriğe geç
academia.sh

Ders 15 / 34

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.

İçindekiler

Ö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.

// 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.

// 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");
# 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.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat