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.