İçeriğe geç
academia.sh

Ders 06 / 16

Ara Katman Zinciri

Her istekte tekrarlanan işleri tek yerde toplayan soğan modeli zincir kurulur; aynı halkalar dört ayrı sırayla çalıştırılıp kimlik denetiminin günlüklemeye göre yeri, hata yakalayıcının kapsamı ve kütük satırlarının doğruluğu ölçülerek karşılaştırılır.

İçindekiler

Önceki ders bir isteğin yedi aşamasını ölçtü ve bütün işleri tek bir işleyicinin içine yazdı. Gerçek bir uygulamada aynı işlerin çoğu her uçta tekrarlanır: gövdenin ayrıştırılması, kimliğin çözülmesi, isteğin kütüğe yazılması, hatanın yakalanması. Bunlar tek bir uca ait olmadıkları için kesişen ilgiler (cross-cutting concerns) adını alır.

Kesişen ilgileri tek yerde toplayan yapı, isteği sırayla geçiren bir zincirdir. Bu ders o zinciri kütüphane ödünç servisi için kurar ve bir soruya yanıt arar: sıra ne kadar önemlidir? Yanıt, aynı halkaların dört ayrı sırayla çalıştırılıp sonuçların karşılaştırılmasıyla verilecektir.

Soğan Modeli

Bir ara katman (middleware), isteği ve yanıtı temsil eden bir bağlam ile zincirin geri kalanını çağıran bir fonksiyon alır. Halka işini yapar, sonraki() çağırır, denetim geri döndüğünde ikinci bir iş daha yapabilir. Bu yüzden zincir düz bir boru değil, iç içe geçmiş katmanlardır: istek dıştan içe girer, yanıt içten dışa çıkar.

Modelin üç sonucu vardır ve üçü de bu dersin ölçümlerini belirler:

  • Bir halka sonraki() çağırmazsa zincir orada durur. Kimlik denetimi başarısız olduğunda arkasındaki hiçbir halka çalışmaz.
  • Bir halkanın sonraki() sonrası kodu, kendisinden içerideki her şey bittikten sonra çalışır. Süre ölçümü ve kütük çıkış satırı buraya yazılır.
  • Bir halka yalnız kendisinden içerideki hataları yakalayabilir. Dışarıdaki bir halkanın fırlattığı hata ona ulaşmaz.

Zinciri Kurmak

Aşağıdaki program beş halka tanımlar ve bunları dört ayrı sırada dizer. Sıra komut satırından seçilir; halkaların kendisi hiç değişmez.

// ara-katman.mjs — istegi sirayla gecen halkalardan bir zincir; sira komut satirindan secilir
import { createServer } from "node:http";

let sayac = 0;

const yaz = (b, durum, govde) => {
  const metin = JSON.stringify(govde);
  b.yanit.writeHead(durum, { "content-type": "application/json; charset=utf-8",
    "content-length": Buffer.byteLength(metin) });
  b.yanit.end(metin);
};

// --- halkalar: her biri (baglam, sonraki) alir; sonraki() cagrilmazsa zincir orada durur ---

const kutukle = async (b, sonraki) => {
  console.log(`${b.id} -> ${b.istek.method} ${b.istek.url}`);
  try {
    await sonraki();
  } finally {
    console.log(`${b.id} <- ${b.yanit.headersSent ? b.yanit.statusCode : "yanit yazilmadi"}`);
  }
};

const hataYakala = async (b, sonraki) => {
  try {
    await sonraki();
  } catch (hata) {
    console.log(`${b.id} !! yakalandi: ${hata.kod ?? hata.name}`);
    yaz(b, hata.kod === "govde_bicimsiz" ? 400 : 500,
      { hata: hata.kod ?? "ic_hata", istekKimligi: b.id });
  }
};

const govdeCoz = async (b, sonraki) => {
  const parcalar = [];
  for await (const parca of b.istek) parcalar.push(parca);
  const metin = Buffer.concat(parcalar).toString();
  try {
    b.govde = metin ? JSON.parse(metin) : {};
  } catch {
    const hata = new Error("govde JSON olarak cozulemedi");
    hata.kod = "govde_bicimsiz";
    throw hata;
  }
  await sonraki();
};

const kimlikDenetle = async (b, sonraki) => {
  const uye = (b.istek.headers.authorization ?? "").replace(/^Uye /, "");
  if (!/^U-\d{4}$/.test(uye)) return yaz(b, 401, { hata: "kimlik_yok", istekKimligi: b.id });
  b.uye = uye;
  await sonraki();
};

const yonlendir = async (b) => {
  const yol = new URL(b.istek.url, "http://yerel").pathname;
  if (yol === "/odunc" && b.istek.method === "POST") {
    return yaz(b, 201, { odunc: { isbn: b.govde?.isbn, uye: b.uye }, istekKimligi: b.id });
  }
  yaz(b, 404, { hata: "yol_yok", istekKimligi: b.id });
};

// --- ayni halkalar, dort ayri sira ---

const ZINCIRLER = {
  "yakalayici-disarida": [hataYakala, kutukle, govdeCoz, kimlikDenetle, yonlendir],
  "kimlik-once": [hataYakala, kimlikDenetle, kutukle, govdeCoz, yonlendir],
  "yakalayici-icte": [kutukle, govdeCoz, hataYakala, kimlikDenetle, yonlendir],
  "kutuk-disarida": [kutukle, hataYakala, govdeCoz, kimlikDenetle, yonlendir],
};

const secim = process.argv[2] ?? "yakalayici-disarida";
const halkalar = ZINCIRLER[secim];
if (!halkalar) {
  console.error(`bilinmeyen zincir: ${secim}`);
  process.exit(2);
}

const zinciriCalistir = (b) => {
  const ilerle = (i) =>
    i === halkalar.length ? Promise.resolve() : halkalar[i](b, () => ilerle(i + 1));
  return ilerle(0);
};

createServer((istek, yanit) => {
  yanit.sendDate = false;
  const b = { istek, yanit, id: `i-${++sayac}` };
  zinciriCalistir(b).catch((hata) => {                  // son care: zincir disina cikan hata
    console.log(`${b.id} XX zincir disina cikti: ${hata.kod ?? hata.name}`);
    if (!yanit.headersSent) yaz(b, 500, { hata: "ic_hata" });
  });
}).listen(8434, "127.0.0.1", () => console.log(`zincir=${secim} 127.0.0.1:8434`));

Zincir çalıştırıcısı beş satırdır: ilerle(i) i. halkayı çağırır ve ona sonraki olarak ilerle(i + 1) verir. Listenin sonuna gelindiğinde çözülmüş bir söz döner. Sunucu geri çağrısındaki catch, zincirden dışarı çıkan bir hatanın süreci düşürmesini engelleyen son çare halkasıdır; ölçümde bunun ne zaman devreye girdiği görünecektir.

Ölçüm her sıra için aynı üç isteği yapar: geçerli bir ödünç isteği, kimlik bilgisi taşımayan bir istek ve gövdesi bozuk bir istek.

#!/usr/bin/env bash
# Ayni uc istegi dort ayri halka sirasiyla tekrarlar; her turda yanitlari ve sunucu kutugunu basar.
UC=http://127.0.0.1:8434/odunc

istekler() {
  echo "  [1] gecerli    : $(curl -sS -X POST -H 'authorization: Uye U-4711' \
    -H 'content-type: application/json' -d '{"isbn":"978-0262033848"}' "$UC")"
  echo "  [2] kimliksiz  : $(curl -sS -X POST \
    -H 'content-type: application/json' -d '{"isbn":"978-0262033848"}' "$UC")"
  echo "  [3] bozuk govde: $(curl -sS -X POST -H 'authorization: Uye U-4711' \
    -H 'content-type: application/json' -d '{isbn' "$UC")"
}

for zincir in yakalayici-disarida kimlik-once yakalayici-icte kutuk-disarida; do
  echo "=== zincir: $zincir ==="
  node ara-katman.mjs "$zincir" > "kutuk-$zincir.txt" 2>&1 & s=$!
  sleep 0.6
  istekler
  sleep 0.3
  kill "$s"; wait "$s" 2>/dev/null
  echo "  --- sunucu kutugu ---"
  sed 's/^/  /' "kutuk-$zincir.txt"
done

rm -f kutuk-*.txt
=== zincir: yakalayici-disarida ===
  [1] gecerli    : {"odunc":{"isbn":"978-0262033848","uye":"U-4711"},"istekKimligi":"i-1"}
  [2] kimliksiz  : {"hata":"kimlik_yok","istekKimligi":"i-2"}
  [3] bozuk govde: {"hata":"govde_bicimsiz","istekKimligi":"i-3"}
  --- sunucu kutugu ---
  zincir=yakalayici-disarida 127.0.0.1:8434
  i-1 -> POST /odunc
  i-1 <- 201
  i-2 -> POST /odunc
  i-2 <- 401
  i-3 -> POST /odunc
  i-3 <- yanit yazilmadi
  i-3 !! yakalandi: govde_bicimsiz
=== zincir: kimlik-once ===
  [1] gecerli    : {"odunc":{"isbn":"978-0262033848","uye":"U-4711"},"istekKimligi":"i-1"}
  [2] kimliksiz  : {"hata":"kimlik_yok","istekKimligi":"i-2"}
  [3] bozuk govde: {"hata":"govde_bicimsiz","istekKimligi":"i-3"}
  --- sunucu kutugu ---
  zincir=kimlik-once 127.0.0.1:8434
  i-1 -> POST /odunc
  i-1 <- 201
  i-3 -> POST /odunc
  i-3 <- yanit yazilmadi
  i-3 !! yakalandi: govde_bicimsiz
=== zincir: yakalayici-icte ===
  [1] gecerli    : {"odunc":{"isbn":"978-0262033848","uye":"U-4711"},"istekKimligi":"i-1"}
  [2] kimliksiz  : {"hata":"kimlik_yok","istekKimligi":"i-2"}
  [3] bozuk govde: {"hata":"ic_hata"}
  --- sunucu kutugu ---
  zincir=yakalayici-icte 127.0.0.1:8434
  i-1 -> POST /odunc
  i-1 <- 201
  i-2 -> POST /odunc
  i-2 <- 401
  i-3 -> POST /odunc
  i-3 <- yanit yazilmadi
  i-3 XX zincir disina cikti: govde_bicimsiz
=== zincir: kutuk-disarida ===
  [1] gecerli    : {"odunc":{"isbn":"978-0262033848","uye":"U-4711"},"istekKimligi":"i-1"}
  [2] kimliksiz  : {"hata":"kimlik_yok","istekKimligi":"i-2"}
  [3] bozuk govde: {"hata":"govde_bicimsiz","istekKimligi":"i-3"}
  --- sunucu kutugu ---
  zincir=kutuk-disarida 127.0.0.1:8434
  i-1 -> POST /odunc
  i-1 <- 201
  i-2 -> POST /odunc
  i-2 <- 401
  i-3 -> POST /odunc
  i-3 !! yakalandi: govde_bicimsiz
  i-3 <- 400

Kimlik Denetimi ile Günlüklemenin Sırası

İlk iki turun istemciye dönen yanıtları birebir aynıdır. Üç istek, üç aynı gövde. Sıra değişikliği istemcinin gördüğü hiçbir şeyi değiştirmemiştir.

Sunucu kütüğünde ise bir satır çifti eksiktir. kimlik-once turunda i-2 hiç görünmez. Kimlik denetimi günlüklemeden önce geldiği için kimliksiz istek zincirin ikinci halkasında durmuş, kütük halkasına hiç ulaşmamıştır. Uygulama kimliksiz isteği reddetmiş ama bunu kaydetmemiştir.

Bunun pratik sonucu ağırdır. Kütüphane servisine bir saat boyunca kimliksiz istek yağdıran bir istemci, bu sırada hiçbir iz bırakmaz. Reddedilen istekler tam da en çok kaydedilmesi gereken isteklerdir: hız sınırı kararları, kötüye kullanım tespiti ve hata ayıklama bu kayıtlara dayanır.

Sıranın ters yönde bir gerekçesi de vardır ve göz ardı edilmemelidir. kimlik-once düzeninde kimliksiz istek, gövdesi hiç okunmadan reddedilir; yakalayici-disarida düzeninde gövde önce belleğe alınır, sonra kimlik denetimi başarısız olur. Büyük gövdeli istekler söz konusu olduğunda erken reddetme gerçek bir tasarruftur.

İki gerekçenin uzlaştığı düzen, kütüklemenin en dışta, kimlik denetiminin gövde ayrıştırmasından önce olduğu düzendir: her istek kaydedilir, kimliksiz istek yine de gövdesi okunmadan reddedilir. Ölçümdeki kutuk-disarida sırası tam olarak budur ve i-2 satırları kütükte yerini almıştır.

Hata Yakalayıcının Kapsamı Yerinden Gelir

Üçüncü turda istemcinin gördüğü yanıt değişir. yakalayici-icte düzeninde bozuk gövdeli istek {"hata":"ic_hata"} alır; diğer üç düzende {"hata":"govde_bicimsiz"} ile birlikte istek kimliği de gelir.

Nedeni doğrudan soğan modelidir. yakalayici-icte sırasında hataYakala, govdeCoz halkasının içindedir. Gövde ayrıştırması hata fırlattığında bu hata dışa doğru yayılır ve yakalayıcıya hiç uğramaz. Kütükteki XX zincir disina cikti satırı bunu söyler: hatayı zincirin kendisi değil, sunucu geri çağrısındaki son çare yakalamıştır.

Son çare yanıtı kaçınılmaz olarak niteliksizdir. O noktada isteğin hangi halkada kırıldığı, hangi alanın bozuk olduğu ve kullanıcıya ne söylenmesi gerektiği bilinmez; geriye 500 ve genel bir ileti kalır. Kullanıcı hatası olan bir durum iç hata olarak raporlanmış, istek kimliği yanıta konulamamış ve destek isteyen üyenin elinde izlenecek bir numara kalmamıştır.

Kural şudur: bir hata yakalayıcı yalnız kendisinden içeride kalanı korur. Zincirin tamamını koruması isteniyorsa mümkün olduğunca dışta durmalıdır. Son çare bir yedektir, bir tasarım değil; devreye girdiği her istek, zincirde eksik kalan bir kapsamın işaretidir.

Kütük Satırının Doğru Durumu Yazması

Dört turun kütükleri karşılaştırıldığında yakalayici-disarida düzeninde bir kusur görünür: üçüncü istek için i-3 <- yanit yazilmadi yazılmış, yakalayıcının kaydı ise ancak ondan sonra gelmiştir. Kütük satırı, yanıtın 400 olduğunu bilmeden yazılmıştır.

Bu da sıradan gelir. hataYakala en dışta olduğunda, kutukle halkasının finally bloğu hata dışa doğru yayılırken çalışır — yani yakalayıcı yanıtı yazmadan önce. Kütük, kendisinden dışarıda alınan bir kararı göremez.

kutuk-disarida düzeninde iki halkanın yeri değişir: kütükleme en dışta, hata yakalayıcı onun hemen içinde. Şimdi yakalayıcı yanıtı yazar, denetim kutukle halkasına döner ve finally bloğu i-3 <- 400 yazar. Aynı üç istek, aynı beş halka; yalnız iki halkanın yeri değişmiştir ve kütük artık doğrudur.

Buradan zincir dizmenin genel kuralı çıkar: bir halka, gözlemlemek veya değiştirmek istediği her şeyin dışında olmalıdır. Kütükleme yanıtın son hâlini görmek istiyorsa yanıtı üreten her halkanın dışında durur; hata yakalayıcı bütün hataları görmek istiyorsa hata fırlatabilecek her halkanın dışında durur. İkisi birlikte, kütüklemenin yakalayıcıdan da dışta olması gerektiğini söyler.

Özet

  • Ara katman zinciri soğan modelidir: istek dıştan içe girer, yanıt içten dışa çıkar; bir halkanın sonraki() sonrası kodu, kendisinden içerideki her şey bittikten sonra çalışır.
  • Kimlik denetimi günlüklemeden önce geldiğinde reddedilen istek kütükte hiç görünmez; ölçümde kimlik-once turunda i-2 satırları eksiktir, istemciye dönen yanıtlar ise dört düzende de aynıdır.
  • Erken kimlik denetimi gövdeyi hiç okumadan reddetme kazancı sağlar; iki gerekçe, kütükleme en dışta ve kimlik denetimi gövde ayrıştırmasından önce dizildiğinde uzlaşır.
  • Bir hata yakalayıcı yalnız kendisinden içerideki hataları görür; yakalayici-icte düzeninde bozuk gövde {"hata":"ic_hata"} ile yanıtlanmış, diğerlerinde istek kimliğiyle birlikte {"hata":"govde_bicimsiz"} dönmüştür.
  • Kütük satırı yalnız kendisinden içeride alınan kararları görebilir; yakalayıcı kütüklemenin dışında kaldığında durum yanit yazilmadi olarak kaydedilmiş, sıra düzeltildiğinde i-3 <- 400 doğru değerle yazılmıştır.

Sonraki Adım

Bu bölüm bir isteğin sunucu tarafındaki yolunu bileşenlerine ayırdı: web sunucusu ve vekil rolleri, çalışma zamanının süreç modeli, isteğin aşamaları ve kesişen ilgileri sıraya koyan zincir. Ortaya çıkan zincirin halkaları ise hâlâ tek bir dosyada, hepsi yan yana durur. Sonraki bölüm bu parçaları taşıyabilecek bir iskelet kurar: dosyalar hangi ölçüte göre bölünür, hangi modül hangisini çağırabilir ve bu sınır elle mi yoksa bir denetimle mi korunur? İlk ders proje yapısını ele alır ve bağımlılık yönünü sınayan bir denetim yazar.

İ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