İçeriğe geç
academia.sh

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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat