Ders 11 / 19
Bağlam Haritası
Bağlamlar arası ilişki türleri: yukarı akış ile aşağı akış yönünün ayrılması, haritanın gerçek ithallerden çıkarılması, karşılıklı bağımlı bağlam çifti ile bir bağlam değiştiğinde etkilenen bağlam sayısının iki haritada sayılması ve yukarı akışın adlarının kaç çekirdek dosyada göründüğünün ölçülmesi.
İçindekiler
Sınır çizildi ve iki bağlam kendi sözlüğüne kavuştu. Ne var ki bağlamlar birbirinden habersiz çalışamaz: ücretlendirme indirim oranını sözleşme bağlamından alır, teslimat servis seviyesini yine oradan alır, sözleşme özeti ise ikisinin sonucunu bir arada gösterir. Sınır bir duvar değil bir geçittir; geçidin kaç tane olduğu, hangi yöne baktığı ve kimin kimin sözlüğüne uyduğu tasarım kararıdır.
Bu kararların yazıya geçirilmiş hâline bağlam haritası (context map) denir. Harita bir kutu-ok çizimi değildir; her okun bir türü, bir yönü ve bir bedeli vardır. Bu ders haritayı kodun gerçek ithallerinden çıkarır ve iki haritayı aynı üç ölçüyle karşılaştırır: karşılıklı bağımlı bağlam çifti sayısı, bir bağlam değiştiğinde etkilenen bağlam sayısı ve yukarı akışın adlarının göründüğü çekirdek dosya sayısı.
Yukarı Akış ve Aşağı Akış
İki bağlam arasındaki okun yönü, hangisinin ötekinin değişimine uymak zorunda olduğunu söyler. Modeli başkasının uyduğu tarafa yukarı akış (upstream), uymak zorunda olan tarafa aşağı akış (downstream) denir. Yön alan bilgisinden değil, karar hakkından çıkar: sözleşme bağlamı indirim oranının ne anlama geldiğine karar veriyorsa yukarı akıştır, ücretlendirme o karara uyuyorsa aşağı akıştır.
Bu, Tasarım İlkeleri kursundaki bağımlılık yönünden farklı bir sorudur. Orada ok, kararlılığı yüksek olan tarafa bakardı; burada ok, alan kararını veren tarafa bakar. İkisi çakışabilir ama zorunlu değildir: bir bağlam teknik olarak kararsız, alan olarak belirleyici olabilir.
İlişki Türleri
Aynı iki bağlam arasında birden çok ilişki kurulabilir; hangisinin kurulduğu haritada adıyla yazılır.
| İlişki | Yön | Ne söyler |
|---|---|---|
| ortaklık (partnership) | simetrik | İki takım birlikte başarır veya birlikte başarısız olur; sürümler birlikte planlanır |
| müşteri–tedarikçi (customer–supplier) | yukarı → aşağı | Aşağı akışın ihtiyaçları yukarı akışın planına girer; yukarı akış söz verir |
| açık sunucu servisi (open host service) | yukarı akış yayımlar | Yukarı akış, birden çok tüketici için tanımlı bir erişim yüzeyi sunar |
| yayımlanmış dil (published language) | yukarı akış yayımlar | Yüzeyde geçen adlar ve tipler ayrıca belgelenir; iç model değişse de yayımlanan dil durur |
| ayrı yollar (separate ways) | yok | İki bağlam bütünleştirilmez; ihtiyaç varsa dışarıda karşılanır |
| adsız ilişki | belirsiz | Bağ vardır ama yönü ve türü yazılmamıştır; her taraf ötekinin modelini okur |
Bu listenin dışında iki işbirliği kalıbı ile dış modele karşı bir savunma kalıbı daha vardır; üçü kendi derslerinde ele alınır. Bu dersin ölçtüğü şey türlerin kendisi değil, adsız ilişkiyi adlandırmanın sayısal karşılığıdır.
Üç Bağlam, İki Düzen
Yukarı akış bağlamı sözleşme ve müşteridir. Kendi sözlüğünde beş ad taşıyor.
mkdir -p sozlesme adsiz adli
// sozlesme/sozlesme.mjs — yukari akis baglami: sozlesme ve musteri kayitlari const SOZLESMELER = [ { sozlesmeNo: "S-77", musteriNo: "M-4", tarifeIndirimYuzdesi: 12, servisSeviyeKodu: "SS2", pesinOdemeliMi: true }, { sozlesmeNo: "S-12", musteriNo: "M-9", tarifeIndirimYuzdesi: 20, servisSeviyeKodu: "SS1", pesinOdemeliMi: false }, ]; export const sozlesmeBul = (no) => SOZLESMELER.find((s) => s.sozlesmeNo === no) ?? null; export const sozlesmeNolari = () => SOZLESMELER.map((s) => s.sozlesmeNo);
Birinci düzende ilişkiler adsız. İki aşağı akış bağlamı sözleşmenin kendi adlarını doğrudan okuyor; rapor ise sözleşme bağlamının içinde duruyor ve iki aşağı akış bağlamını geri çağırıyor.
// adsiz/ucret.mjs — ucretlendirme, sozlesme baglaminin adlarini dogrudan okuyor import { sozlesmeBul } from "../sozlesme/sozlesme.mjs"; export function netUcret(brutKurus, sozlesmeNo) { const s = sozlesmeBul(sozlesmeNo); if (s === null) return brutKurus; const indirimli = Math.round(brutKurus * (1 - s.tarifeIndirimYuzdesi / 100)); return s.pesinOdemeliMi ? Math.round(indirimli * 0.98) : indirimli; }
// adsiz/teslimat.mjs — teslimat, sozlesme baglaminin adlarini dogrudan okuyor import { sozlesmeBul } from "../sozlesme/sozlesme.mjs"; const PENCERE = { SS1: 24, SS2: 48, SS3: 72 }; export function teslimPenceresiSaat(sozlesmeNo) { const s = sozlesmeBul(sozlesmeNo); return s === null ? 72 : PENCERE[s.servisSeviyeKodu]; }
// adsiz/rapor.mjs — sozlesme baglaminda duran rapor, iki asagi akis baglamini geri cagiriyor import { sozlesmeBul, sozlesmeNolari } from "../sozlesme/sozlesme.mjs"; import { netUcret } from "./ucret.mjs"; import { teslimPenceresiSaat } from "./teslimat.mjs"; export const sozlesmeOzeti = (brutKurus) => sozlesmeNolari().map((no) => ({ odeyen: sozlesmeBul(no).musteriNo, net: netUcret(brutKurus, no), pencere: teslimPenceresiSaat(no), }));
İkinci düzende her aşağı akış bağlamı yukarı akışın adlarını kendi diline çeviren bir sınır dosyası taşıyor; çekirdek dosyalar yalnız kendi dillerini konuşuyor. Rapor da yerini değiştiriyor: ücretlendirme bağlamının bir raporu olduğu kabul edilip oraya taşınıyor ve teslimattan yalnız yayımlanmış pencere işlevini kullanıyor.
// adli/ucretlendirme-ceviri.mjs — yukari akis adlarini ucretlendirmenin diline cevirir import { sozlesmeBul, sozlesmeNolari } from "../sozlesme/sozlesme.mjs"; export const sozlesmeListesi = () => sozlesmeNolari(); export function ucretKosullari(sozlesmeNo) { const s = sozlesmeBul(sozlesmeNo); if (s === null) return { odeyen: null, indirimOrani: 0, pesinIndirimi: 0 }; return { odeyen: s.musteriNo, indirimOrani: s.tarifeIndirimYuzdesi / 100, pesinIndirimi: s.pesinOdemeliMi ? 0.02 : 0 }; }
// adli/ucret.mjs — ucretlendirme cekirdegi: yalniz kendi dilini konusur import { ucretKosullari } from "./ucretlendirme-ceviri.mjs"; export function netUcret(brutKurus, sozlesmeNo) { const { indirimOrani, pesinIndirimi } = ucretKosullari(sozlesmeNo); return Math.round(Math.round(brutKurus * (1 - indirimOrani)) * (1 - pesinIndirimi)); }
// adli/teslimat-ceviri.mjs — yukari akis adlarini teslimatin diline cevirir import { sozlesmeBul } from "../sozlesme/sozlesme.mjs"; const PENCERE = { SS1: 24, SS2: 48, SS3: 72 }; export function teslimKosullari(sozlesmeNo) { const s = sozlesmeBul(sozlesmeNo); return { pencereSaat: s === null ? 72 : PENCERE[s.servisSeviyeKodu] }; }
// adli/teslimat.mjs — teslimat cekirdegi: yalniz kendi dilini konusur import { teslimKosullari } from "./teslimat-ceviri.mjs"; export const teslimPenceresiSaat = (sozlesmeNo) => teslimKosullari(sozlesmeNo).pencereSaat;
// adli/rapor.mjs — ucretlendirme baglaminda rapor: kendi dili ve teslimatin yayimladigi pencere import { sozlesmeListesi, ucretKosullari } from "./ucretlendirme-ceviri.mjs"; import { netUcret } from "./ucret.mjs"; import { teslimPenceresiSaat } from "./teslimat.mjs"; export const sozlesmeOzeti = (brutKurus) => sozlesmeListesi().map((no) => ({ odeyen: ucretKosullari(no).odeyen, net: netUcret(brutKurus, no), pencere: teslimPenceresiSaat(no), }));
Çevirinin yaptığı iş adların değiştirilmesinden fazlasıdır. tarifeIndirimYuzdesi yüzde
cinsinden bir tam sayıdır, ücretlendirme ise oran ister; pesinOdemeliMi bir ödeme koşuludur,
ücretlendirme ondan bir indirim oranı türetir; musteriNo sözleşme bağlamının kimliğidir,
ücretlendirme aynı kişiyi odeyen diye adlandırır. Sınır dosyası bu üç dönüşümün durduğu
yerdir.
Haritanın Koddan Çıkarılması
Harita elle yazıldığında koddan sapabilir. Aşağıdaki betik her dosyanın ithallerini okuyup bağlam düzeyinde yönlü kenarları çıkarıyor, sonra üç ölçüyü hesaplıyor: karşılıklı bağımlı bağlam çifti, bir bağlam değiştiğinde geçişli olarak etkilenen bağlam sayısı ve yukarı akış sözlüğünden ad geçen dosyaların kaçının çekirdek dosya olduğu.
// harita-cikar.mjs — gercek ithallerden baglam haritasini cikarir, yayilmayi ve sizmayi olcer import { readFileSync } from "node:fs"; import { dirname, join, normalize } from "node:path"; const BAGLAM = { "sozlesme/sozlesme.mjs": "sozlesme", "adsiz/rapor.mjs": "sozlesme", "adsiz/ucret.mjs": "ucretlendirme", "adsiz/teslimat.mjs": "teslimat", "adli/ucretlendirme-ceviri.mjs": "ucretlendirme", "adli/ucret.mjs": "ucretlendirme", "adli/rapor.mjs": "ucretlendirme", "adli/teslimat-ceviri.mjs": "teslimat", "adli/teslimat.mjs": "teslimat", }; const SOZLUK = ["tarifeIndirimYuzdesi", "servisSeviyeKodu", "pesinOdemeliMi", "musteriNo"]; const SINIR = (d) => d.includes("-ceviri.mjs"); function kenarlar(dosyalar) { const kume = new Set(); for (const d of dosyalar) { for (const [, yol] of readFileSync(d, "utf8").matchAll(/from "([^"]+)"/g)) { const a = BAGLAM[d], b = BAGLAM[normalize(join(dirname(d), yol))]; if (a !== b) kume.add(`${a}->${b}`); } } return [...kume].sort(); } function etkilenen(kenar, hedef) { const yigit = [hedef], gorulen = new Set(); while (yigit.length > 0) { const b = yigit.pop(); for (const k of kenar) { const [a, v] = k.split("->"); if (v === b && a !== hedef && !gorulen.has(a)) { gorulen.add(a); yigit.push(a); } } } return gorulen; } for (const [ad, dosyalar] of [ ["adsiz", ["sozlesme/sozlesme.mjs", "adsiz/ucret.mjs", "adsiz/teslimat.mjs", "adsiz/rapor.mjs"]], ["adli", ["sozlesme/sozlesme.mjs", "adli/ucretlendirme-ceviri.mjs", "adli/ucret.mjs", "adli/rapor.mjs", "adli/teslimat-ceviri.mjs", "adli/teslimat.mjs"]], ]) { const kenar = kenarlar(dosyalar); const karsilikli = kenar.filter((k) => kenar.includes(k.split("->").reverse().join("->"))).length / 2; console.log(`${ad}: ${dosyalar.length} dosya, ${kenar.length} yonlu kenar, ${karsilikli} karsilikli baglam cifti`); for (const k of kenar) console.log(` ${k}`); let toplam = 0; for (const b of ["sozlesme", "ucretlendirme", "teslimat"]) { const e = etkilenen(kenar, b); toplam += e.size; const liste = e.size > 0 ? ` (${[...e].sort().join(", ")})` : ""; console.log(` ${b.padEnd(14)} degisirse etkilenen baglam = ${e.size}${liste}`); } console.log(` toplam yayilma = ${toplam}`); const sizan = dosyalar.filter((d) => BAGLAM[d] !== "sozlesme" && SOZLUK.some((n) => new RegExp(`\\b${n}\\b`).test(readFileSync(d, "utf8")))); const cekirdek = dosyalar.filter((d) => BAGLAM[d] !== "sozlesme" && !SINIR(d)); console.log(` yukari akis adi gecen dosya = ${sizan.length} (${sizan.join(", ")})`); console.log(` bunlardan cekirdek dosya = ${sizan.filter((d) => !SINIR(d)).length} / ${cekirdek.length}`); }
node harita-cikar.mjs
adsiz: 4 dosya, 4 yonlu kenar, 2 karsilikli baglam cifti sozlesme->teslimat sozlesme->ucretlendirme teslimat->sozlesme ucretlendirme->sozlesme sozlesme degisirse etkilenen baglam = 2 (teslimat, ucretlendirme) ucretlendirme degisirse etkilenen baglam = 2 (sozlesme, teslimat) teslimat degisirse etkilenen baglam = 2 (sozlesme, ucretlendirme) toplam yayilma = 6 yukari akis adi gecen dosya = 2 (adsiz/ucret.mjs, adsiz/teslimat.mjs) bunlardan cekirdek dosya = 2 / 2 adli: 6 dosya, 3 yonlu kenar, 0 karsilikli baglam cifti teslimat->sozlesme ucretlendirme->sozlesme ucretlendirme->teslimat sozlesme degisirse etkilenen baglam = 2 (teslimat, ucretlendirme) ucretlendirme degisirse etkilenen baglam = 0 teslimat degisirse etkilenen baglam = 1 (ucretlendirme) toplam yayilma = 3 yukari akis adi gecen dosya = 2 (adli/ucretlendirme-ceviri.mjs, adli/teslimat-ceviri.mjs) bunlardan cekirdek dosya = 0 / 3
Adsız düzende üç bağlam arasında dört yönlü kenar var ve bunların ikisi karşılıklı çift: hem sözleşme ücretlendirmeye bağımlı hem ücretlendirme sözleşmeye. Karşılıklılığı yaratan tek şey raporun yeridir. Rapor sözleşme bağlamının içinde durduğu için o bağlam, kendisine bağımlı iki bağlamı geri çağırmak zorunda kalıyor. Sonuç, üç bağlamın hepsinin birbirine bağlanması: hangisi değişirse öteki ikisi etkileniyor, toplam yayılma 6.
Adlı düzende kenar sayısı üçe, karşılıklı çift sayısı sıfıra iniyor. Yukarı akıştaki sözleşme bağlamı değiştiğinde iki bağlam hâlâ etkilenir — bu kaçınılmazdır, indirim oranını gerçekten o bağlam tanımlıyor. Değişen şey ötekiler: ücretlendirme değişirse hiçbir bağlam etkilenmiyor, teslimat değişirse bir bağlam etkileniyor. Toplam yayılma 6’dan 3’e düştü.
Son iki satır sızmayı gösteriyor. Yukarı akışın dört adı iki düzende de iki dosyada geçiyor;
sayı aynı. Fark, o iki dosyanın ne olduğudur. Adsız düzende ikisi de aşağı akış bağlamlarının
çekirdek dosyaları (2/2); adlı düzende ikisi de sınır dosyası ve çekirdek dosyaların hiçbirinde
yukarı akıştan tek ad geçmiyor (0/3). Sözleşme bağlamı tarifeIndirimYuzdesi yerine
indirimOrani demeye karar verirse iki düzende de iki dosya düzenlenir, ama adsız düzende
düzenlenen dosyalar iş kuralının kendisini taşıyan dosyalardır.
Davranış Korundu mu
Haritanın değişmesi çıktının değişmesi değildir; sınanır.
// kosum.mjs — iki duzen ayni raporu ve ayni teslim penceresini veriyor mu import { sozlesmeOzeti as adsizOzet } from "./adsiz/rapor.mjs"; import { teslimPenceresiSaat as adsizPencere } from "./adsiz/teslimat.mjs"; import { sozlesmeOzeti as adliOzet } from "./adli/rapor.mjs"; import { teslimPenceresiSaat as adliPencere } from "./adli/teslimat.mjs"; const a = JSON.stringify(adsizOzet(10000)), b = JSON.stringify(adliOzet(10000)); console.log(`adsiz ${a}`); console.log(`adli ${b}`); console.log(`rapor ayni mi = ${a === b}`); let ayrilan = 0; for (const no of ["S-77", "S-12", "S-99"]) { if (adsizPencere(no) !== adliPencere(no)) ayrilan += 1; } console.log(`ayrilan pencere = ${ayrilan} / 3`);
node kosum.mjs
adsiz [{"odeyen":"M-4","net":8624,"pencere":48},{"odeyen":"M-9","net":8000,"pencere":24}]
adli [{"odeyen":"M-4","net":8624,"pencere":48},{"odeyen":"M-9","net":8000,"pencere":24}]
rapor ayni mi = true
ayrilan pencere = 0 / 3
Rapor birebir aynı, üç sözleşme numarasının teslim penceresi de aynı. Tanımsız sözleşme
numarası S-99 dâhil. Harita yeniden çizildi, alan davranışı yerinde kaldı.
Haritanın Bedeli
Adlı düzen iki dosya daha büyük ve her okumada bir dolaylılık katmanı fazla taşıyor. Bedelin ikinci parçası daha az görünür: sınır dosyası yalnız çevirdiği kadarını geçirir. Sözleşme bağlamı yeni bir alan yayımladığında aşağı akış onu doğrudan kullanamaz, önce çeviriye eklenmesi gerekir. Bu, gecikme demektir; karşılığında iş kuralı dosyalarının yukarı akışın sözlüğünü hiç tanımaması gelir.
Haritanın kendisinin de bir bedeli var: yazılmayan harita yoktur, yalnız görülmeyen harita vardır. Adsız düzenin de bir haritası çıktı, dört kenarlı ve iki karşılıklı çiftli. Fark, birinin yazılmış olmasıdır.
Özet
- Bağlam haritası, bağlamlar arası ilişkilerin türünü ve yönünü yazıya geçirir; ok, alan kararını veren tarafa bakar ve bu, bağımlılık yönündeki kararlılık ölçütünden ayrı bir sorudur.
- İlişki türleri adlandırılır: ortaklık, müşteri–tedarikçi, açık sunucu servisi, yayımlanmış dil, ayrı yollar; adsız ilişkide bağ vardır ama yönü yazılmamıştır.
- Harita kodun gerçek ithallerinden çıkarıldığında adsız düzende 4 yönlü kenar ve 2 karşılıklı bağlam çifti göründü; karşılıklılığı yaratan şey raporun yukarı akış bağlamının içinde durmasıydı.
- İlişkiler adlandırıldığında kenar sayısı 3’e, karşılıklı çift sayısı 0’a indi; toplam yayılma 6’dan 3’e düştü ve ücretlendirme değişirse etkilenen bağlam sayısı 2’den 0’a indi.
- Yukarı akışın dört adı iki düzende de iki dosyada geçti, ama adsız düzende bu dosyalar çekirdek dosyalardı (2/2), adlı düzende hepsi sınır dosyasıydı (çekirdekte 0/3).
- Rapor ve üç teslim penceresi iki düzende birebir aynı çıktı; bedel iki fazla dosya ve çevirinin yalnız eşlediği kadarını geçirmesidir.
Sonraki Adım
Bu dersteki çeviri dosyaları kolay bir durumda çalıştı: yukarı akıştaki sözleşme bağlamı aynı kod tabanının parçası, adları düzenli, tipleri belli. Kitaplığın gerçek dünyada konuştuğu yukarı akışların bir kısmı böyle değil — taşıyıcı sağlayıcılarının kendi modelleri, kendi durum kümeleri ve kendi kimlik biçimleri var; hiçbiri kitaplığın alan diline uymak zorunda değil ve değiştiklerinde kimseye danışmıyor. Böyle bir yukarı akışın kavramları içeri sızarsa iç model dışın diliyle konuşmaya başlar: teslimat bağlamının durumları taşıyıcının durum kodlarına dönüşür. Sonraki ders dış modelin adlarının ve tiplerinin kaç dosyada göründüğünü sayar, araya bir savunma katmanı koyduğunda bu sayının nereye indiğini ölçer ve katmanın bedelini dosya ile satır cinsinden çıkarır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.