Ders 07 / 18
İstemci–Sunucu
Rollerin başlatan ve bekleyen olarak ayrılmasının kodda ölçülmesi: teslimat tarafının ücretlendirme tarafı hakkında bilmek zorunda olduğu ad sayısının dört düzenden bire inmesi, ücretlendirme tarafı kendi iç adlarını değiştirdiğinde hangi istemcinin kırıldığı ve bekleyen tarafın başlatan tarafı tanımamasının bedeli olarak yoklama sayısı.
İçindekiler
Yapısal biçemler birimlerin nasıl düzenlendiğini ve bağımlılık okunun hangi yöne baktığını ele aldı. Kara tahta düzeninde birimler birbirini hiç tanımıyordu: ortak bir bilgi alanına yazıyor, oradan okuyor, buluşmayı alanın kendisi sağlıyordu. Bu konu düzeni değil konuşma biçimini sorar. İki birim arasındaki bir etkileşimde kim başlatır, kim bekler, karşı taraf hakkında bilinmesi zorunlu olan ne kadardır.
İlk biçem bu üç soruya en keskin yanıtı verendir: bir taraf hep başlatır, öbürü hep bekler ve bilme yükümlülüğü tek yöne yığılır. Bilgisayar Ağları müfredatındaki İstemci–Sunucu Modeli dersi ayrımı ağ düzeyinde kurmuştu; rolü belirleyen ölçüt donanım değil, bağlantıyı kimin başlattığıdır. Adres, dinleme çağrıları ve bağlantı noktası numaraları oraya aittir, burada yeniden ele alınmaz. Buradaki soru bir basamak yukarıdadır: aynı rol ayrımı tek bir kod tabanının içinde iki modül arasında nasıl görünür ve neyi ölçer.
Ölçülecek Şey
Kitaplığın iki bağlamı bu derste birbirine bakar. Teslimat operasyonu bağlamı bir gönderinin ücretini bilmek zorundadır, ama hesap ücretlendirme bağlamının içindedir. Etkileşimin iki düzenlenişi karşılaştırılır ve üç sayı alınır: çağıran tarafın karşı taraf hakkında bilmek zorunda olduğu ad sayısı, karşı taraf kendi içini yeniden düzenlediğinde kırılan istemci sayısı, bekleyen tarafın başlatan tarafa kurduğu bağ sayısı.
Bağlandığı kalite niteliği sürdürülebilirliktir ve nitelik sorusu şudur: ücretlendirme tarafı kendi iç adlarını değiştirdiğinde teslimat tarafında kaç dosya düzenlenmek zorunda kalır.
Ücretlendirme Tarafının İçi
Ücret hesabı üç modüle dağılmış durumda. Bu üç dosya iki düzende de birebir aynıdır; değişen tek şey teslimat tarafının onlara nasıl ulaştığıdır.
mkdir -p ucret dogrudan istemci sunucu
// ucret/bolge.mjs — adresten ucret bolgesi turetir const BOLGE = { "34": "yakin", "06": "orta", "35": "orta", "65": "uzak" }; export const bolgeBelirle = (il) => BOLGE[il] ?? "uzak";
// ucret/tarife.mjs — agirlik kademesi, bolge katsayisi ve asgari ucret const KATSAYI = { yakin: 1, orta: 1.4, uzak: 2.1 }; const ASGARI = { yakin: 30, orta: 45, uzak: 70 }; export const kademe = (kg) => (kg <= 1 ? 1 : kg <= 5 ? 2 : kg <= 20 ? 3 : 4); export const tarifeUygula = (bolge, kg) => Math.max(ASGARI[bolge], 25 * kademe(kg) * KATSAYI[bolge]);
// ucret/indirim.mjs — sozlesmeli musteri indirimi ve hacim indirimi const SOZLESME = { "MUS-1": 0.1, "MUS-2": 0.2 }; export const indirimUygula = (tutar, musteri, adet) => tutar * (1 - (SOZLESME[musteri] ?? 0)) * (adet >= 10 ? 0.95 : 1);
İç Adların Doğrudan Çağrıldığı Düzen
Birinci düzende teslimat masası hesabın adımlarını kendisi sıraya koyar. Üç modülü ithal eder, dört adı çağırır, ara değerleri kendi elinde tutar.
// dogrudan/operasyon.mjs — teslimat masasi ucretlendirmenin ic adlarini kendisi cagirir import { bolgeBelirle } from "../ucret/bolge.mjs"; import { kademe, tarifeUygula } from "../ucret/tarife.mjs"; import { indirimUygula } from "../ucret/indirim.mjs"; export function fiyatla(gonderi) { const bolge = bolgeBelirle(gonderi.il); const ham = tarifeUygula(bolge, gonderi.agirlik); const net = indirimUygula(ham, gonderi.musteri, gonderi.adet); return { bolge, kademe: kademe(gonderi.agirlik), ham, net: Math.round(net * 100) / 100 }; }
Bu düzende rol yoktur. İki taraf da eşit görünür, çünkü çağıran taraf çağrılanın iç sıralamasını biliyor: hangi adım hangisinden önce gelir, hangi ara değer hangi işleve girer. Adımların sırası bir tarafta, adımların kendisi öbür tarafta durur.
Rol Ayrımı
İkinci düzende ücretlendirme tarafı tek bir giriş noktası açar. İç adların hiçbiri dışarı
çıkmaz; dışarıya çıkan şey bir istek biçimidir. islem alanı ne istendiğini söyler,
kalanı isteğin verisidir.
// sunucu/ucretlendirme.mjs — tek giris noktasi; ic adlar disariya cikmaz import { bolgeBelirle } from "../ucret/bolge.mjs"; import { kademe, tarifeUygula } from "../ucret/tarife.mjs"; import { indirimUygula } from "../ucret/indirim.mjs"; let islenen = 0; function fiyat(g) { const bolge = bolgeBelirle(g.il); const ham = tarifeUygula(bolge, g.agirlik); const net = indirimUygula(ham, g.musteri, g.adet); return { bolge, kademe: kademe(g.agirlik), ham, net: Math.round(net * 100) / 100 }; } export function istekIsle(istek) { islenen += 1; if (istek.islem === "fiyatla") return { durum: "tamam", sonuc: fiyat(istek.gonderi) }; return { durum: "bilinmeyen-islem", islem: istek.islem }; } export const islenenIstek = () => islenen;
// istemci/operasyon.mjs — yalnizca istek bicimini ve tek bir adi taniyor import { istekIsle } from "../sunucu/ucretlendirme.mjs"; export function fiyatla(gonderi) { const yanit = istekIsle({ islem: "fiyatla", gonderi }); if (yanit.durum !== "tamam") throw new Error(`istek karsilanmadi: ${yanit.durum}`); return yanit.sonuc; }
Adımların sırası taraf değiştirdi. Artık bekleyen tarafta duruyor ve başlatan taraf yalnız ne istediğini söylüyor. Bilinmeyen bir işlem geldiğinde yanıt bir çökme değil, bir durum değeridir; bu da rolün bir parçasıdır — bekleyen taraf gelen her isteğe yanıt vermek zorundadır.
Ölçüm
Ölçüm iki istemci dosyasının ithal satırlarını okuyup bilinen ad sayısını çıkarır, iki düzenin aynı sonucu ürettiğini doğrular, sonra ücretlendirme tarafının kendi içini yeniden düzenlediği bir durumu kurar: dört iç ad yeniden adlandırılır ve iki istemci de bu yeni ağaçta koşturulur.
// ad-say.mjs — iki duzende bilinen ad sayisi, sonuc esitligi ve ic adlar degistiginde kirilan istemci import { cpSync, readFileSync, writeFileSync } from "node:fs"; import { fiyatla as dogrudanFiyatla } from "./dogrudan/operasyon.mjs"; import { fiyatla as istemciFiyatla } from "./istemci/operasyon.mjs"; import { islenenIstek } from "./sunucu/ucretlendirme.mjs"; const ITHAL = /import\s*\{([^}]*)\}\s*from\s*"(\.[^"]+)"/g; const bilinenAd = (yol) => { const bag = [...readFileSync(yol, "utf8").matchAll(ITHAL)]; const ad = bag.flatMap((m) => m[1].split(",").map((s) => s.trim().split(" ")[0])); return { modul: bag.length, ad }; }; for (const yol of ["dogrudan/operasyon.mjs", "istemci/operasyon.mjs"]) { const { modul, ad } = bilinenAd(yol); console.log(`${yol.padEnd(24)} bilinen modul=${modul} bilinen ad=${ad.length} [${ad.join(" ")}]`); } const GONDERI = [ { il: "34", agirlik: 0.8, musteri: "MUS-1", adet: 3 }, { il: "65", agirlik: 12, musteri: "MUS-2", adet: 12 }, { il: "06", agirlik: 30, musteri: "MUS-9", adet: 1 }, ]; const a = GONDERI.map(dogrudanFiyatla); const b = GONDERI.map(istemciFiyatla); console.log(`sonuc: ${JSON.stringify(a[1])}`); console.log(`iki duzen esit mi = ${JSON.stringify(a) === JSON.stringify(b)}, istege donusen cagri = ${islenenIstek()}`); for (const d of ["ucret", "dogrudan", "istemci", "sunucu"]) cpSync(d, `yeni/${d}`, { recursive: true }); const YENI_AD = { bolgeBelirle: "bolgeBul", tarifeUygula: "tarifeHesapla", indirimUygula: "indirimIsle", kademe: "agirlikKademesi" }; for (const yol of ["yeni/ucret/bolge.mjs", "yeni/ucret/tarife.mjs", "yeni/ucret/indirim.mjs", "yeni/sunucu/ucretlendirme.mjs"]) { let metin = readFileSync(yol, "utf8"); for (const [eski, yeni] of Object.entries(YENI_AD)) metin = metin.replaceAll(eski, yeni); writeFileSync(yol, metin); } for (const yol of ["./yeni/dogrudan/operasyon.mjs", "./yeni/istemci/operasyon.mjs"]) { try { const { fiyatla } = await import(yol); console.log(`${yol.padEnd(32)} ic adlar degistikten sonra net=${fiyatla(GONDERI[1]).net}`); } catch (hata) { console.log(`${yol.padEnd(32)} kirildi: ${hata.message.split("\n")[0]}`); } }
node ad-say.mjs
dogrudan/operasyon.mjs bilinen modul=3 bilinen ad=4 [bolgeBelirle kademe tarifeUygula indirimUygula]
istemci/operasyon.mjs bilinen modul=1 bilinen ad=1 [istekIsle]
sonuc: {"bolge":"uzak","kademe":3,"ham":157.5,"net":119.7}
iki duzen esit mi = true, istege donusen cagri = 3
./yeni/dogrudan/operasyon.mjs kirildi: The requested module '../ucret/bolge.mjs' does not provide an export named 'bolgeBelirle'
./yeni/istemci/operasyon.mjs ic adlar degistikten sonra net=119.7
Sayıların Okunması
Bilinen modül sayısı 3’ten 1’e, bilinen ad sayısı 4’ten 1’e indi. İkinci sayı bilme yükümlülüğünün ölçüsüdür: rol ayrımından sonra teslimat tarafının ücretlendirme tarafı hakkında bildiği tek şey bir işlev adı ile bir istek biçimidir. Üç gönderinin ücreti iki düzende birebir aynı çıktı, yani ölçüm aynı işi yapan iki düzenlenişi karşılaştırıyor.
Son iki satır sürdürülebilirlik sorusunun yanıtıdır. Dört iç ad değiştiğinde doğrudan çağıran istemci koşum anında kırıldı ve kırılma iletisi eksik adı adıyla söyledi; rol ayrımlı istemci aynı sonucu üretmeye devam etti ve dosyasına tek karakter dokunulmadı. Düzenlenen dosya sayısı ilk düzende 4 iç dosya artı 1 istemci dosyası, ikinci düzende 4 iç dosya artı 0 istemci dosyasıdır. Fark istemci sayısıyla büyür: on istemcili bir kitaplıkta aynı yeniden adlandırma ilk düzende on dosyayı daha düzenlemeyi gerektirir, ikinci düzende hiçbirini.
Kazanç ücretsiz değildir. Ücretlendirme tarafı bir dosya (giriş noktası) kazandı ve isteğin
karşılanmaması artık bir durum değeri olarak taşınıyor; çağıran her yanıtta bu durumu
denetlemek zorunda. bilinmeyen-islem yanıtı derleme anında değil koşum anında görünür —
doğrudan çağıran düzende olmayan bir ada yapılan başvuru ithal hatası olarak daha erken
yakalanırdı.
Asimetrinin Bedeli
Rol ayrımının ikinci yüzü şudur: bekleyen taraf başlatan tarafı tanımaz. Bunun ölçüsü ücretlendirme tarafından teslimat tarafına giden ithal sayısıdır. Sonucu, tarife sürümü değiştiğinde teslimat masasının bunu nasıl öğrendiği belirler — sunucu haber veremediği için istemci sormak zorundadır. Aşağıdaki betik on iki dilimlik bir takvimde üç ayrı tarife sürümü kurar ve istemcinin her dilimde bir kez sorduğu durumu sayar.
// yoklama.mjs — bekleyen taraf baslatan tarafi tanimadigi icin haber ancak sorulunca ogrenilir import { readFileSync } from "node:fs"; const SUNUCU_TARAFI = ["sunucu/ucretlendirme.mjs", "ucret/bolge.mjs", "ucret/tarife.mjs", "ucret/indirim.mjs"]; const geriBag = SUNUCU_TARAFI .flatMap((yol) => [...readFileSync(yol, "utf8").matchAll(/from "(\.[^"]+)"/g)]) .filter((m) => m[1].includes("istemci")).length; console.log(`sunucu tarafindan istemciye giden ithal = ${geriBag}`); const TAKVIM = ["T1", "T1", "T1", "T2", "T2", "T2", "T2", "T2", "T3", "T3", "T3", "T3"]; let sorulan = 0; const istekIsle = (istek) => { sorulan += 1; return { durum: "tamam", surum: TAKVIM[istek.dilim] }; }; let bilinen = null, ogrenilen = 0, bosSoru = 0; for (let dilim = 0; dilim < TAKVIM.length; dilim += 1) { const { surum } = istekIsle({ islem: "tarifeSurumu", dilim }); if (surum === bilinen) bosSoru += 1; else { bilinen = surum; ogrenilen += 1; } } console.log(`takvimdeki surum = ${new Set(TAKVIM).size}, sorulan = ${sorulan}, ogrenilen = ${ogrenilen}, degisim getirmeyen soru = ${bosSoru}`);
node yoklama.mjs
sunucu tarafindan istemciye giden ithal = 0 takvimdeki surum = 3, sorulan = 12, ogrenilen = 3, degisim getirmeyen soru = 9
Geri bağ sayısı sıfırdır ve bu, rolün tanımı gereğidir. Bedeli son satır veriyor: üç sürüm değişimini öğrenmek on iki soru gerektirdi, dokuz soru hiçbir haber getirmedi. Oran soru sıklığıyla oynar — istemci daha seyrek sorarsa boş soru azalır ama değişimi geç öğrenir. Bu oran bir ödünleşimdir ve iki uçtan biri seçilmek zorundadır, çünkü biçemin içinde üçüncü bir seçenek yoktur. Takvim burada bir modeldir, ölçüm değil; sayılan şey soru ile öğrenilen haber arasındaki farktır, herhangi bir süre değil.
Özet
- İstemci ile sunucuyu ayıran ölçüt kodda da başlatma yönüdür: bir taraf isteği kurar, öbürü gelen her isteğe yanıt vermek zorundadır.
- Rol ayrımı adımların sırasını taraf değiştirir; teslimat tarafının bildiği modül sayısı 3’ten 1’e, ad sayısı 4’ten 1’e indi ve iki düzen aynı ücreti üretti.
- Ücretlendirme tarafı dört iç adı yeniden adlandırdığında doğrudan çağıran istemci koşum anında kırıldı, rol ayrımlı istemci dosyasına dokunulmadan çalışmaya devam etti.
- Bedel bir giriş noktası dosyası, her yanıtta denetlenecek bir durum değeri ve hatanın ithal anından koşum anına kaymasıdır.
- Bekleyen tarafın başlatan tarafa giden ithal sayısı sıfırdır; bunun sonucu üç sürüm değişimini öğrenmek için on iki soru sorulması ve dokuz sorunun boş dönmesidir.
Sonraki Adım
Bu düzende asimetri kasıtlıdır: bir taraf hep başlatır, öbürü hep bekler ve bekleyen taraf tektir. Kitaplığın işleyişinde bu tekliğin bir karşılığı var — bütün ücret sorularının tek bir yere gitmesi, o yerin herkes tarafından bilinmesi anlamına gelir. Aynı işi birden çok aktarma merkezinin yürüttüğü bir düzende soru tersine döner: her merkez hem soran hem yanıtlayan olursa, bilme yükümlülüğü tek yöne yığılmak yerine dağılır. Sonraki ders bu düzeni ölçer: bir birimin tanımak zorunda olduğu komşu sayısı, bir tarife güncellemesinin bütün birimlere ulaşması için gereken ileti sayısı ve bir birim çekildiğinde iş görmeye devam eden birim sayısı.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.