Ders 24 / 25
Tam Kapsamlı Çerçeveler
Yönlendirmeyi, form modelini ve bağımlılık çözümünü tek sözleşmede toplayan çerçeveler; kapsamlı bağımlılık çözümünün çalışması, tekil örnek tuzağı, karar yüzeyinin sayılması ve sözleşme birliğinin bedeli.
İçindekiler
Önceki üç aile de aynı soruya yanıt veriyordu: durum değiştiğinde belgenin neresi yazılacak. Bir uygulamanın bileşen katmanının üstünde ise başka sorular var. Kuzey Yamaç Ölçüm İstasyonu sayfası büyüdüğünde adresin hangi görünüme karşılık geldiği, süzgeç panelinin doğrulama kurallarını nereden aldığı ve ölçüm servisinin bileşene nasıl ulaştığı kararlaştırılmak zorundadır.
İlk üç ailede bu kararlar çerçevenin dışındadır; her ekip kendi birleşimini kurar. Dördüncü yaklaşım, yanıtları çerçevenin kendisiyle birlikte vermektir.
Çatının Kapsadığı Roller
Tam kapsamlı çerçeve, görünüm katmanının yanında yönlendirmeyi, form modelini, veri getirmeyi, bağımlılık çözümünü, sunucuda oluşturmayı ve sınama araçlarını da tek bir sözleşme altında sunar. Parçalar ayrı ayrı seçilmez; birlikte gelir, birlikte sürümlenir ve birbirini tanıyacak biçimde tasarlanır.
Bu ailenin en ayırt edici parçası bağımlılık enjeksiyonudur (dependency injection). Bileşen, ihtiyaç duyduğu servisi kendisi kurmaz; bir anahtarla ister ve çerçeve o anahtara kayıtlı sağlayıcıyı bulup örneği verir. Programlama Temelleri kursundaki arayüz–gerçekleştirim ayrımı burada çalışma zamanına taşınmış olur.
Kapsamlı Bağımlılık Çözümü
Düzenek üç parçadan oluşur: sağlayıcı kaydı, kapsam zinciri ve örneklerin nerede yaşadığını belirleyen kural.
// enjeksiyon.mjs — kapsamli bagimlilik cozumu: kayit, kapsam zinciri ve degistirme const kapsamAc = (ad, ust = null) => ({ ad, ust, saglayicilar: new Map(), ornekler: new Map() }); const kaydet = (kapsam, anahtar, uret, { kapsamBasina = false } = {}) => kapsam.saglayicilar.set(anahtar, { uret, kapsamBasina }); const iz = []; function coz(kapsam, anahtar, zincir = []) { if (zincir.includes(anahtar)) throw new Error(`döngüsel bağımlılık: ${[...zincir, anahtar].join(" → ")}`); let sahip = kapsam; while (sahip && !sahip.saglayicilar.has(anahtar)) sahip = sahip.ust; if (!sahip) throw new Error(`kayıtlı sağlayıcı yok: ${anahtar} (istek: ${kapsam.ad})`); const kayit = sahip.saglayicilar.get(anahtar); const barinak = kayit.kapsamBasina ? kapsam : sahip; // ornek nerede yasayacak if (barinak.ornekler.has(anahtar)) { iz.push(`${kapsam.ad}: ${anahtar} ← ${barinak.ad} (var olan örnek)`); return barinak.ornekler.get(anahtar); } iz.push(`${kapsam.ad}: ${anahtar} ← ${sahip.ad} (yeni örnek, ${kayit.kapsamBasina ? "kapsam başına" : "tekil"})`); const ornek = kayit.uret((a) => coz(kapsam, a, [...zincir, anahtar])); barinak.ornekler.set(anahtar, ornek); return ornek; } // --- Uygulama kaydi --- const kok = kapsamAc("kök"); let sayac = 0; kaydet(kok, "OlcumKaynagi", () => ({ kimlik: ++sayac, getir: () => [{ ad: "Üst yamaç", deger: -4.2 }] })); kaydet(kok, "BirimBicimleyici", () => ({ bicimle: (c) => `${c.toFixed(1)} °C` })); kaydet(kok, "OlcumServisi", (c) => { const kaynak = c("OlcumKaynagi"), bicimleyici = c("BirimBicimleyici"); return { kimlik: ++sayac, satirlar: () => kaynak.getir().map((o) => `${o.ad}: ${bicimleyici.bicimle(o.deger)}`) }; }); kaydet(kok, "SuzgecDurumu", () => ({ kimlik: ++sayac, esik: -5 }), { kapsamBasina: true }); const tabloKapsami = kapsamAc("ölçüm tablosu", kok); const panelKapsami = kapsamAc("süzgeç paneli", kok); console.log("--- cozum izi ---"); const t = coz(tabloKapsami, "OlcumServisi"); const p = coz(panelKapsami, "OlcumServisi"); const ts = coz(tabloKapsami, "SuzgecDurumu"); const ps = coz(panelKapsami, "SuzgecDurumu"); for (const s of iz) console.log(" ", s); console.log("\nölçüm servisi aynı örnek mi:", t === p); console.log("süzgeç durumu aynı örnek mi:", ts === ps, `(kimlikler: ${ts.kimlik}, ${ps.kimlik})`); console.log("tablonun satırları:", JSON.stringify(t.satirlar())); // --- Sinamada degistirme: (a) var olan kokun altinda, (b) taze kokle --- const altKapsam = kapsamAc("sınama (kök altında)", kok); kaydet(altKapsam, "OlcumKaynagi", () => ({ kimlik: 0, getir: () => [{ ad: "Sahte", deger: 0 }] })); iz.length = 0; const altServis = coz(altKapsam, "OlcumServisi"); console.log("\n--- (a) var olan kokun altinda degistirme ---"); for (const s of iz) console.log(" ", s); console.log("satırlar:", JSON.stringify(altServis.satirlar()), "| kök örneğiyle aynı mı:", altServis === t); const tazeKok = kapsamAc("taze kök"); for (const [anahtar, kayit] of kok.saglayicilar) tazeKok.saglayicilar.set(anahtar, kayit); kaydet(tazeKok, "OlcumKaynagi", () => ({ kimlik: 0, getir: () => [{ ad: "Sahte", deger: 0 }] })); iz.length = 0; const tazeServis = coz(kapsamAc("sınama (taze kök)", tazeKok), "OlcumServisi"); console.log("\n--- (b) taze kokle degistirme ---"); for (const s of iz) console.log(" ", s); console.log("satırlar:", JSON.stringify(tazeServis.satirlar()), "| kök örneğiyle aynı mı:", tazeServis === t); // --- Hatalar acik bildirilir --- console.log("\n--- hatalar ---"); kaydet(kok, "A", (c) => ({ b: c("B") })); kaydet(kok, "B", (c) => ({ a: c("A") })); for (const deneme of [() => coz(tabloKapsami, "Kutuk"), () => coz(kok, "A")]) { try { deneme(); } catch (h) { console.log(" ", h.message); } }
--- cozum izi --- ölçüm tablosu: OlcumServisi ← kök (yeni örnek, tekil) ölçüm tablosu: OlcumKaynagi ← kök (yeni örnek, tekil) ölçüm tablosu: BirimBicimleyici ← kök (yeni örnek, tekil) süzgeç paneli: OlcumServisi ← kök (var olan örnek) ölçüm tablosu: SuzgecDurumu ← kök (yeni örnek, kapsam başına) süzgeç paneli: SuzgecDurumu ← kök (yeni örnek, kapsam başına) ölçüm servisi aynı örnek mi: true süzgeç durumu aynı örnek mi: false (kimlikler: 3, 4) tablonun satırları: ["Üst yamaç: -4.2 °C"] --- (a) var olan kokun altinda degistirme --- sınama (kök altında): OlcumServisi ← kök (var olan örnek) satırlar: ["Üst yamaç: -4.2 °C"] | kök örneğiyle aynı mı: true --- (b) taze kokle degistirme --- sınama (taze kök): OlcumServisi ← taze kök (yeni örnek, tekil) sınama (taze kök): OlcumKaynagi ← taze kök (yeni örnek, tekil) sınama (taze kök): BirimBicimleyici ← taze kök (yeni örnek, tekil) satırlar: ["Sahte: 0.0 °C"] | kök örneğiyle aynı mı: false --- hatalar --- kayıtlı sağlayıcı yok: Kutuk (istek: ölçüm tablosu) döngüsel bağımlılık: A → B → A
İzin ilk üç satırı çözümün nasıl indiğini gösteriyor. Ölçüm servisi istendiğinde, sağlayıcısı kök kapsamda bulunuyor; servis kurulurken kendi bağımlılıkları aynı yolla çözülüyor. Bileşen hiçbirinin nasıl kurulduğunu bilmiyor; yalnızca anahtarları biliyor.
Dördüncü satır tekil davranışı gösteriyor: süzgeç paneli aynı servisi istediğinde yeni örnek üretilmiyor, kök kapsamdaki örnek dönüyor. Beşinci ve altıncı satırlarda ise sağlayıcı kapsam başına kayıtlı olduğu için iki kapsam iki ayrı örnek alıyor. Kayıt kökte, örnek isteyen kapsamda yaşıyor.
Bu ayrım tasarım kararıdır ve doğrudan doğruluğu etkiler. Ölçüm kaynağı paylaşılmalıdır — iki bileşenin ayrı bağlantı açması gereksizdir. Süzgeç durumu paylaşılmamalıdır — tablonun süzgeci panelin süzgeciyle aynı olsaydı, iki bileşen birbirinin durumunu ezerdi.
Tekil Örnek Tuzağı
(a) ve (b) bölümleri, bütün kapsamlı çözüm düzeneklerinin ortak tuzağını gösteriyor.
Sınama kapsamında ölçüm kaynağı sahtesiyle değiştirilmiş, ama sonuç değişmemiş. Nedeni izde yazılı: ölçüm servisi zaten kök kapsamda kurulmuş ve örneği orada duruyor. Yeni kapsam servisi istediğinde var olan örnek dönüyor; o örnek gerçek kaynağı kurulum sırasında almış ve bir daha sormuyor.
Doğru yol (b) bölümündedir: sınama için taze bir kök kurulur, sağlayıcı kayıtları oraya kopyalanır ve değiştirilecek anahtar taze kökte yeniden kaydedilir. Hiçbir örnek henüz kurulmadığı için çözüm baştan yapılır ve sahte kaynak zincire girer.
Kural şudur: değiştirme, örnek kurulmadan önce yapılmalıdır. Tekil kapsamdaki bir bağımlılığın değiştirilmesi, o kapsamın tümüyle yeniden kurulmasını gerektirir.
Hata iletileri de bu ailenin bir özelliğidir. Kayıtsız bir anahtar istendiğinde hangi kapsamın ne istediği, döngüsel bağımlılıkta ise zincirin tamamı bildirilir. Dolaylılık arttıkça hatanın kendini açıklaması zorunlu hâle gelir.
Karar Yüzeyi
Bütünleşikliğin kazancı, tek tek özelliklerde değil karar sayısındadır. Bu sayılabilir.
// karar-yuzeyi.mjs — butunlesikligin karar ve uyum yuzeyine etkisi const ROLLER = [ "görünüm katmanı", "yönlendirme", "form modeli", "veri getirme", "bağımlılık çözümü", "paylaşılan durum", "sunucuda oluşturma", "sınama araçları", ]; // Bir profil: her rolu hangi paketin karsiladigi. const profiller = { "kitaplık toplama": ROLLER.map((r) => r), // her rol ayri paket "çekirdek + eklenti": ROLLER.map((r, i) => (i < 3 ? "çekirdek" : r)), "tam kapsamlı": ROLLER.map(() => "çatı"), }; const ikili = (m) => (m * (m - 1)) / 2; console.log("profil bağımsız paket sürüm akışı ikili uyum kırıcı değişiklikte tek seferde"); console.log(" ilişkisi gözden geçirilecek değişecek rol"); for (const [ad, atama] of Object.entries(profiller)) { const paketler = [...new Set(atama)]; const m = paketler.length; // En cok rol tasiyan paket birakilirsa kac rol birden degisir? const enBuyuk = Math.max(...paketler.map((p) => atama.filter((a) => a === p).length)); console.log( `${ad.padEnd(20)} ${String(m).padStart(14)} ${String(m).padStart(12)} ${String(ikili(m)).padStart(11)} ` + `${String(Math.max(m - 1, 0)).padStart(20)} ${String(enBuyuk).padStart(14)}` ); } // Ayni hesap, rol sayisi degistikce: uyum yuzeyi kare buyur. console.log("\nayrı paket sayısı ikili uyum ilişkisi"); for (const m of [1, 2, 4, 6, 8, 12]) console.log(`${String(m).padStart(17)} ${String(ikili(m)).padStart(20)}`); // Bir rolun catidan cikarilabilirligi: catinin o rolu ne kadar sikica bagladigi. const BAGLILIK = { "sözleşme yok": 0, "arayüz üzerinden": 1, "doğrudan tip bağımlılığı": 2, "kod üretimi": 3 }; const ornek = [ ["yönlendirme", "kod üretimi"], ["form modeli", "doğrudan tip bağımlılığı"], ["veri getirme", "arayüz üzerinden"], ["paylaşılan durum", "sözleşme yok"], ]; console.log("\nrol bağlılık biçimi değiştirme puanı"); let toplam = 0; for (const [rol, bicim] of ornek) { toplam += BAGLILIK[bicim]; console.log(`${rol.padEnd(18)} ${bicim.padEnd(26)} ${String(BAGLILIK[bicim]).padStart(17)}`); } console.log(`toplam çıkış maliyeti (0 = serbest, ${ornek.length * 3} = tümüyle bağlı): ${toplam}`);
profil bağımsız paket sürüm akışı ikili uyum kırıcı değişiklikte tek seferde
ilişkisi gözden geçirilecek değişecek rol
kitaplık toplama 8 8 28 7 1
çekirdek + eklenti 6 6 15 5 3
tam kapsamlı 1 1 0 0 8
ayrı paket sayısı ikili uyum ilişkisi
1 0
2 1
4 6
6 15
8 28
12 66
rol bağlılık biçimi değiştirme puanı
yönlendirme kod üretimi 3
form modeli doğrudan tip bağımlılığı 2
veri getirme arayüz üzerinden 1
paylaşılan durum sözleşme yok 0
toplam çıkış maliyeti (0 = serbest, 12 = tümüyle bağlı): 6
Sekiz rolü ayrı paketlerle karşılamak sekiz karar, sekiz bağımsız sürüm akışı ve 28 ikili uyum ilişkisi demektir. İlişki sayısı paket sayısının karesiyle büyür: on iki pakette 66 olur. Bu ilişkilerin her biri, iki paketin belirli sürümlerinin birlikte çalıştığı iddiasıdır ve bu iddiayı doğrulayan taraf ekiptir.
Tam kapsamlı profilde bu sayı sıfırdır. Parçaların uyumu çatının sorumluluğudur; ekip tek bir sürüm akışı izler. Yeni bir ekip üyesinin öğreneceği şey sekiz kitaplığın birleşimi değil, bir sözleşmedir.
Son sütun bedeli gösteriyor. Kitaplık toplama profilinde bir paket bırakıldığında yalnızca bir rol değişir; roller ayrı ayrı, ayrı zamanlarda göç edebilir. Tam kapsamlı profilde çatı bırakılırsa sekiz rol aynı anda değişir. Karar geri alınabilir olmaktan çıkar.
Sözleşme Birliğinin Bedeli
Son tablo bu bedelin nasıl ölçüleceğini gösteriyor. Bir rolün çatıdan çıkarılabilirliği, çatının o rolü hangi biçimde bağladığına göre değişir. Yalnızca bir arayüz üzerinden bağlanan bir rol, aynı arayüzü karşılayan başka bir gerçekleştirimle değiştirilebilir. Kod üretimiyle bağlanan bir rol, kaynak metnin biçimine kadar çatıya yaslanır.
Uygulamanın çıkış maliyetini ölçmek, çatı seçimini bir tercih olmaktan çıkarıp bir yatırım kararına dönüştürür. Ölçü, uygulamanın hangi bölümlerinin çatıdan bağımsız yazılabildiğidir: doğrulama kuralları, biçimlendirme, başsız bileşenlerdeki durum makineleri ve saf sunum bileşenleri çatı bilmez ve göçte olduğu gibi taşınır.
İkinci bedel sürüm yönetimidir. Tek bir sürüm akışı, çatının bir sürüm yükseltmesinin uygulamanın her katmanını aynı anda ilgilendirmesi demektir. Kırıcı bir değişiklik yönlendirmeyi de, formu da, bağımlılık çözümünü de kapsayabilir; yükseltme küçük adımlara bölünemez. Ayrı paketlerde ise yükseltmeler sıraya konabilir, ama karşılığında 28 uyum ilişkisi taşınır.
Üçüncü bedel esnekliktir. Bir rolde çatının verdiği yanıt uygulamanın ihtiyacına uymuyorsa, o rolü değiştirmek çatının varsayımlarına karşı çalışmak anlamına gelir. Bütünleşik bir çerçevede alışılmışın dışına çıkmak, alışılmışın içinde kalmaktan belirgin biçimde pahalıdır.
Ailenin Profili
Bu aile bir güncelleme modeli değil, bir kapsam kararıdır; içinde sanal ağaç da, çalışma zamanı bağımlılık takibi de, derleme zamanı çözümleme de bulunabilir. Ayırt edici özelliği, bileşen katmanının üstündeki rollerin çatının parçası olmasıdır.
Kazanç, kararların ve uyum ilişkilerinin sayıca azalması, ekip içinde tek bir sözleşmenin paylaşılması ve yeni bir üyenin öğreneceği yüzeyin daralmasıdır. Bedel, sürüm akışının tekleşmesi, yükseltmelerin bölünememesi ve çıkış maliyetinin tek seferde ödenmesidir.
Özet
- Tam kapsamlı çerçeve, görünüm katmanının yanında yönlendirme, form, veri getirme, bağımlılık çözümü ve sınama gibi rolleri tek sözleşmede toplar.
- Bağımlılık enjeksiyonunda bileşen servisi kurmaz, anahtarla ister; kapsam zinciri sağlayıcıyı bulur, kayıt biçimi örneğin nerede yaşayacağını belirler.
- Tekil kayıtlı bir bağımlılığı değiştirmek, örnek kurulduktan sonra etkisizdir; sınama için kapsam baştan kurulur.
- Karar yüzeyi sayılabilir: sekiz rol ayrı paketlerde sekiz sürüm akışı ve 28 ikili uyum ilişkisi üretir, bütünleşik çatıda bu sayı sıfıra iner.
- Karşılığında çıkış maliyeti tek seferde ödenir: çatı bırakıldığında sekiz rol aynı anda değişir.
- Bir rolün çıkarılabilirliği bağlılık biçimine bağlıdır; arayüz üzerinden bağlanan rol değiştirilebilir, kod üretimiyle bağlanan rol kaynak metne kadar yaslanır.
Sonraki Adım
Dört ailenin de özellikleri, maliyetleri ve bedelleri ortaya kondu. Geriye tek bir soru kalıyor ve bu sorunun yanıtı ailelerin özelliklerinde değil, projenin kendisindedir: bu uygulama için hangi özellikler gerçekten gerekli? Sonraki ders bir karşılaştırma tablosu değil, bir karar çerçevesi kurar — ekibin büyüklüğü ve deneyimi, uygulamanın etkileşim yoğunluğu, oluşturma stratejisi gereksinimi, ekosistem derinliği ve bakım maliyeti gibi ölçütleri açık ağırlıklara bağlar; kararın hangi ölçüt değiştiğinde döndüğünü hesaplar ve kararın ne kadarının geri alınabilir kaldığını ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.