---
title: 'Mimari Biçemler'
source: 'https://academia.sh/tr/kurslar/mimari-bicemler'
language: tr
updated: '2026-08-17T04:42:33+00:00'
license: 'CC BY-SA 4.0'
lessons: 18
outcomes:
    - 'Yaygın mimari biçemleri kalite nitelikleri üzerinden karşılaştırır'
    - 'Bir biçemi bağlam kısıtlarına göre seçer ve gerekçelendirir'
    - 'Biçem geçişlerinin maliyetini değerlendirir'
---

# Mimari Biçemler

Aynı yazılımın farklı düzenlenişleri arasındaki farkın kalite nitelikleri üzerinden sayılması: katmanlı, altıgen, temiz, bileşen tabanlı, eklentili ve kara tahta düzenleri; istemci–sunucu, eşler arası, model–görünüm, boru–süzgeç, yayımla–abone ol ve kuyruk temelli etkileşimler; tek parça, servis yönelimli, mikroservis, olay güdümlü ve sunucusuz dağıtım düzenleri ile biçem seçiminin ödünleşim çözümlemesi.

## Yapısal Biçemler

Bir programın içindeki birimlerin ve aralarındaki bağımlılık yönünün düzenlenmesi: katman kurallarının ne olduğu ve sızıntının nasıl göründüğü, bağımlılığın içe doğru çevrildiği halkalı düzenler, katman ile kuralın birleştirilmesi, dağıtılabilir yazılım bileşenlerinin sınırı, çekirdek ile uzantının ayrılması ve belirsiz problemlerde paylaşılan bilgi alanı üzerinden çalışma.

### Katmanlı Mimari

Source: https://academia.sh/tr/kurslar/mimari-bicemler/katmanli-mimari

Katmanlı biçemin ilan edilmiş bir izinli kenar kümesi olarak yazılması: gönderi ücretlendirme kitaplığının dört katmana ayrılması, bir isteğin dokunduğu katman ile geçtiği sınır sayısının ölçülmesi, hiçbir şey değiştirmeden geçen katmanların sayılması ve açık katman ilanı ile ilan edilmemiş sızıntının ihlal sayısıyla ayrılması.

Alan Odaklı Tasarım kursu bir soruyu açık bıraktı: buraya kadarki bütün kararlar **bir**
uygulamanın içindeydi. Bağlam haritası birden çok bağlamı adlandırdı, ama hepsi aynı çalışan
programın parçasıydı. Bağlamlar ayrı programlara dağıtıldığında ya da tek bir programda
tutulmaya karar verildiğinde ölçülen şeyler değişir: bir isteğin dokunduğu birim sayısı,
geçtiği modül sınırı sayısı, bir sürümün dağıtılabilmesi için birlikte yayınlanması gereken
parça sayısı.

Bu kurs mimari düzenleri bu ölçülerle karşılaştırır ve her ders aynı kitaplığın — gönderi
ücretlendirme ve yönlendirme kitaplığının — o düzende kurulmuş hâlini ölçer. İlk düzen
birimleri sorumluluklarına göre sıralı yığınlara ayırır ve aralarındaki çağrı yönünü bir
kuralla sınırlar.

#### Biçem Olarak Katman

Katmanların sorumlulukları ve aralarındaki bağımlılık yönü Veri Erişim Katmanı ve İş Mantığı
kursundaki Katman Sorumlulukları dersinde kurulmuştu. İthal satırlarından katman kenarı
çıkarıp ilan edilmiş bir kurala göre ihlal sayma yordamı da Tasarım İlkeleri kursundaki
Bağımlılık Çizgesi Sağlığı dersinde yazılmıştı. Burada ikisi de yeniden anlatılmıyor;
kullanılıyor.

Yeni olan biçemin kendisidir. Katmanlı mimari üç şeyi birlikte söyler. Birincisi, her birim
tam bir katmana aittir; ikisine ait olan birim yoktur. İkincisi, katmanlar sıralıdır. Üçüncüsü
ve belirleyici olanı, hangi katmanın hangisini çağırabileceği **önceden ilan edilmiş bir kenar
kümesidir**. Üçüncü madde olmadan ilk ikisi bir dizin düzeninden fazlası değildir: kural
yazılmamışsa ihlal de tanımlı değildir.

Kenar kümesinin biçimi bir ayrım getirir. Bir katman **kapalı** (closed) ise üstündeki katman
onu atlayamaz, isteğin ondan geçmesi zorunludur. **Açık** (open) ise atlanmasına izin
verilmiştir. Açık katman kuralın ihlali değil, kuralın bir parçasıdır: izinli kenar kümesine
yazılır. Aynı çağrının izinli mi ihlal mi olduğu koda bakılarak anlaşılmaz, kenar kümesine
bakılarak anlaşılır.

#### Kitaplığın Katmanlı Düzeni

Ücretlendirme bağlamı dört katmana ayrılıyor. Bağımlılıklar ithalle değil parametreyle
kuruluyor; birimleri birbirine bağlayan tek yer bileşim köküdür.

```sh
mkdir -p katmanli/altyapi katmanli/alan katmanli/uygulama katmanli/sunum
```

```js
// katmanli/altyapi/tarife-deposu.mjs — altyapi katmani: tarife satirlari ve posta kodu esdegeri
const KADEMELER = [
  { ustAgirlik: 1, ucret: 4990 },
  { ustAgirlik: 5, ucret: 8490 },
  { ustAgirlik: 15, ucret: 14990 },
  { ustAgirlik: 30, ucret: 24990 },
];
const KATSAYI = { yakin: 1, orta: 1.35, uzak: 1.8 };
const POSTA_BOLGE = { 34: "yakin", "06": "orta", 35: "orta", 65: "uzak" };

export const tarifeDeposu = {
  kademeler: () => KADEMELER.map((k) => ({ ...k })),
  katsayi: (postaKodu) => KATSAYI[POSTA_BOLGE[postaKodu.slice(0, 2)] ?? "uzak"],
};
```

```js
// katmanli/alan/ucret-kurali.mjs — alan katmani: ucret kurali, hicbir katmani ithal etmiyor
const ASGARI_UCRET = 3990;
const YUVARLAMA_ADIMI = 50;

export const ucretKurali = {
  hesapla: (gonderi, kademeler, katsayi) => {
    const kademe = kademeler.find((k) => gonderi.agirlik <= k.ustAgirlik);
    if (kademe === undefined) throw new RangeError("agirlik kademesi yok");
    const ham = kademe.ucret * katsayi;
    return Math.max(Math.round(ham / YUVARLAMA_ADIMI) * YUVARLAMA_ADIMI, ASGARI_UCRET);
  },
};
```

```js
// katmanli/uygulama/teklif-ver.mjs — uygulama katmani: senaryonun sirasi
export const uygulamaKatmani = (alan, altyapi) => ({
  teklifVer: (gonderi) => {
    const kademeler = altyapi.kademeler();
    const katsayi = altyapi.katsayi(gonderi.postaKodu);
    return { tutar: alan.hesapla(gonderi, kademeler, katsayi) };
  },
  kademeListele: () => altyapi.kademeler(),
});
```

```js
// katmanli/sunum/masa.mjs — sunum katmani: govde cozumleme ve tutar bicimleme
export const sunumKatmani = (uygulama) => ({
  teklifIstegi: (govde) => {
    const gonderi = { agirlik: Number(govde.agirlik), postaKodu: String(govde.postaKodu) };
    const { tutar } = uygulama.teklifVer(gonderi);
    return { durum: 200, tutar: `${(tutar / 100).toFixed(2)} TL` };
  },
  kademeIstegi: () => uygulama.kademeListele(),
});

// acik katman: kural bu kenari ilan ettiginde okuma istegi uygulama katmanini atlar
export const acikKademeMasasi = (altyapi) => ({
  kademeIstegi: () => altyapi.kademeler(),
});
```

Sunum modülünde iki masa var. Birincisi kapalı düzenin masası, uygulama katmanını çağırır.
İkincisi açık katman ilanının karşılığıdır ve yalnız okuma isteği için altyapıyı doğrudan
çağırır. Aynı dosyada durmaları rastlantı değil: ikisi de aynı kuralın kapsamındadır ve fark
kuralın hangisini izinli saydığındadır.

#### Bir İsteğin Yolu

Bağımlılıklar parametreyle geldiği için sınırlara sayaç konabilir. Ölçüm betiği bileşim
kökünün işini yapar ve her katman nesnesini bir sarmalayıcıdan geçirir; sarmalayıcı çağrı
ağacını kurar. Ağaçtan üç sayı çıkar: isteğin dokunduğu katman sayısı, katmanlar arasındaki
çağrı sayısı ve **geçirgen katman** sayısı. Bir düğüm tek çocuklu ise, aldığı argümanlar ile
çocuğa verdiği argümanlar aynıysa ve çocuktan aldığı değeri değiştirmeden döndürüyorsa o
katman geçirgendir: isteğe hiçbir şey eklememiştir.

```js
// sinir-say.mjs — bir istegin dokundugu katman, gectigi sinir ve gecirgen katman sayisi
import { tarifeDeposu } from "./katmanli/altyapi/tarife-deposu.mjs";
import { ucretKurali } from "./katmanli/alan/ucret-kurali.mjs";
import { uygulamaKatmani } from "./katmanli/uygulama/teklif-ver.mjs";
import { sunumKatmani, acikKademeMasasi } from "./katmanli/sunum/masa.mjs";

const kokler = [];
const yigit = [];

function sar(katman, nesne) {
  const sarili = {};
  for (const [ad, fn] of Object.entries(nesne)) {
    sarili[ad] = (...args) => {
      const dugum = { katman, ad, args, cocuk: [], deger: null };
      (yigit.at(-1)?.cocuk ?? kokler).push(dugum);
      yigit.push(dugum);
      try { return (dugum.deger = fn(...args)); } finally { yigit.pop(); }
    };
  }
  return sarili;
}

function dolas(dugum, gorulen = new Set(), gecis = [], gecirgen = []) {
  gorulen.add(dugum.katman);
  for (const c of dugum.cocuk) {
    gecis.push(`${dugum.katman}->${c.katman}`);
    dolas(c, gorulen, gecis, gecirgen);
  }
  const tek = dugum.cocuk.length === 1 ? dugum.cocuk[0] : null;
  if (tek !== null && JSON.stringify(dugum.args) === JSON.stringify(tek.args)
      && JSON.stringify(dugum.deger) === JSON.stringify(tek.deger)) {
    gecirgen.push(dugum.katman);
  }
  return { gorulen, gecis, gecirgen };
}

const alan = sar("alan", ucretKurali);
const altyapi = sar("altyapi", tarifeDeposu);
const uygulama = sar("uygulama", uygulamaKatmani(alan, altyapi));
const sunum = sar("sunum", sunumKatmani(uygulama));
const acikSunum = sar("sunum", acikKademeMasasi(altyapi));

const ISTEKLER = [
  ["teklif / kapali    ", () => sunum.teklifIstegi({ agirlik: "3", postaKodu: "06800" })],
  ["kademe / kapali    ", () => sunum.kademeIstegi()],
  ["kademe / acik katman", () => acikSunum.kademeIstegi()],
];

for (const [ad, cagir] of ISTEKLER) {
  kokler.length = 0;
  const sonuc = cagir();
  const { gorulen, gecis, gecirgen } = dolas(kokler[0]);
  const ozet = Array.isArray(sonuc) ? `${sonuc.length} kademe` : JSON.stringify(sonuc);
  console.log(`${ad}  sonuc = ${ozet}`);
  console.log(`  dokunulan katman = ${gorulen.size}, sinir gecisi = ${gecis.length}, gecirgen katman = ${gecirgen.length}`);
  console.log(`  yol = ${gecis.join(", ")}`);
  if (gecirgen.length > 0) console.log(`  gecirgen = ${gecirgen.join(", ")}`);
}
```

```sh
node sinir-say.mjs
```

```
teklif / kapali      sonuc = {"durum":200,"tutar":"114.50 TL"}
  dokunulan katman = 4, sinir gecisi = 4, gecirgen katman = 0
  yol = sunum->uygulama, uygulama->altyapi, uygulama->altyapi, uygulama->alan
kademe / kapali      sonuc = 4 kademe
  dokunulan katman = 3, sinir gecisi = 2, gecirgen katman = 2
  yol = sunum->uygulama, uygulama->altyapi
  gecirgen = uygulama, sunum
kademe / acik katman  sonuc = 4 kademe
  dokunulan katman = 2, sinir gecisi = 1, gecirgen katman = 1
  yol = sunum->altyapi
  gecirgen = sunum
```

#### Geçirgen Katman

İlk istek biçemin işini gösteriyor. Teklif isteği dört katmana dokundu, dört sınır geçti ve
geçirgen katman sayısı 0 çıktı: sunum gövdeyi çözümledi ve tutarı biçimledi, uygulama çağrı
sırasını kurdu, altyapı iki tabloyu verdi, alan kuralı hesapladı. Her katman isteğe bir şey
ekledi.

İkinci istek aynı biçemin bedelini gösteriyor. Kademe listesi isteği üç katmana dokundu ama
iki katman geçirgen çıktı: uygulama katmanı aldığı boş argüman listesini olduğu gibi aşağı
geçirdi ve dönen listeyi değiştirmeden yukarı verdi; sunum katmanı da aynısını yaptı. Üç
katmanın ikisi yalnız bir aktarma noktasıydı. Katmanlı biçemin bu belirtisine **katman
atlama** deme eğilimi yanlıştır: burada atlanan bir şey yok, tersine hiçbir şey yapmayan iki
durak var.

Üçüncü koşum ölçünün ne kadar düştüğünü söylüyor. Aynı sonuç, açık katman ilanıyla iki
katmana dokunularak ve bir sınır geçilerek üretildi: dokunulan katman 3'ten 2'ye, sınır geçişi
2'den 1'e, geçirgen katman 2'den 1'e indi. Kazanç bedava değil; iki masa arasındaki fark, aynı
okuma isteğinin iki ayrı yerden karşılanabilmesidir ve okumaya bir iş kuralı eklendiği gün o
kural iki yere yazılır ya da açık kenar geri alınır.

#### Sızıntının İthal Grafiğinde Görünmesi

Açık katman ile sızıntı çalışma zamanında birbirine benzer: ikisinde de bir katman atlanır.
Fark ilan edilip edilmediğidir ve bu fark ithal grafiğinde ölçülür. Aşağıdaki blok kapalı
düzenin bir kopyasını alıp iki dosyayı değiştiriyor.

```sh
cp -r katmanli sizintili
cat > sizintili/sunum/masa.mjs <<'EOF'
// sizintili/sunum/masa.mjs — okuma istegi uygulama katmanini atlayip altyapiyi cagiriyor
import { tarifeDeposu } from "../altyapi/tarife-deposu.mjs";

export const sunumKatmani = (uygulama) => ({
  teklifIstegi: (govde) => {
    const gonderi = { agirlik: Number(govde.agirlik), postaKodu: String(govde.postaKodu) };
    const { tutar } = uygulama.teklifVer(gonderi);
    return { durum: 200, tutar: `${(tutar / 100).toFixed(2)} TL` };
  },
  kademeIstegi: () => tarifeDeposu.kademeler(),
});
EOF
cat > sizintili/alan/bolge-cozumleyici.mjs <<'EOF'
// sizintili/alan/bolge-cozumleyici.mjs — alan katmanindaki yardimci altyapi tablosuna uzaniyor
import { tarifeDeposu } from "../altyapi/tarife-deposu.mjs";

export const enPahaliKademe = () =>
  tarifeDeposu.kademeler().reduce((a, b) => (a.ucret >= b.ucret ? a : b));
EOF
find sizintili -name '*.mjs' | sort
```

```
sizintili/alan/bolge-cozumleyici.mjs
sizintili/alan/ucret-kurali.mjs
sizintili/altyapi/tarife-deposu.mjs
sizintili/sunum/masa.mjs
sizintili/uygulama/teklif-ver.mjs
```

İki değişiklik de tek başına bakıldığında masum görünür. Sunum masası bir liste isteği için
araya bir katman koymaya gerek görmüyor; alan katmanındaki yardımcı en pahalı kademeyi
öğrenmek istiyor. Denetim aracı her dosyanın bulunduğu dizinden katmanını okuyor, ithal
satırlarını katman kenarına indirgiyor ve iki ayrı kenar kümesine karşı sayıyor.

```js
// katman-ihlali.mjs — ithal grafigini katman kenarina indirger, ilan edilmis kurala gore ihlali sayar
import { readdirSync, readFileSync } from "node:fs";
import { basename, dirname, join, normalize } from "node:path";

const KAPALI = new Set(["sunum->uygulama", "uygulama->alan", "uygulama->altyapi"]);
const ACIK = new Set([...KAPALI, "sunum->altyapi"]);

function katmanKenarlari(kok) {
  const kenar = new Set();
  for (const katman of readdirSync(kok).sort()) {
    for (const dosya of readdirSync(join(kok, katman)).sort()) {
      const metin = readFileSync(join(kok, katman, dosya), "utf8");
      for (const m of metin.matchAll(/^import\s.*?from\s+"(\.[^"]+)"/gm)) {
        const hedef = basename(dirname(normalize(join(kok, katman, m[1]))));
        if (hedef !== katman) kenar.add(`${katman}->${hedef}`);
      }
    }
  }
  return [...kenar];
}

for (const kok of ["katmanli", "sizintili"]) {
  const kenarlar = katmanKenarlari(kok);
  console.log(`${kok}/  katman kenari = ${kenarlar.length}  (${kenarlar.join(", ") || "yok"})`);
  for (const [ad, kural] of [["kapali kural", KAPALI], ["acik katman kurali", ACIK]]) {
    const ihlal = kenarlar.filter((k) => kural.has(k) === false);
    console.log(`  ${ad.padEnd(19)} izinli kenar = ${kural.size}, ihlal = ${ihlal.length}${ihlal.length ? ` (${ihlal.join(", ")})` : ""}`);
  }
}
```

```sh
node katman-ihlali.mjs
```

```
katmanli/  katman kenari = 0  (yok)
  kapali kural        izinli kenar = 3, ihlal = 0
  acik katman kurali  izinli kenar = 4, ihlal = 0
sizintili/  katman kenari = 2  (alan->altyapi, sunum->altyapi)
  kapali kural        izinli kenar = 3, ihlal = 2 (alan->altyapi, sunum->altyapi)
  acik katman kurali  izinli kenar = 4, ihlal = 1 (alan->altyapi)
```

Kapalı düzenin katman kenarı 0 çıktı. Bu bir eksiklik değil, bileşim kökü düzeninin
sonucudur: hiçbir katman modülü başka bir katmanı ithal etmiyor, bağlar tek bir dosyada
kuruluyor. Böyle bir düzende katman kuralının denetlendiği yer ithal grafiği değil, o tek
dosyadır — ve sızıntı tam olarak o dosyanın atlandığı anda ithal grafiğinde görünür hâle
gelir.

Sızıntılı düzenin iki kenarı iki ayrı cinsten. `sunum->altyapi` kenarı açık katman kuralında
izinlidir, kapalı kuralda ihlaldir: aynı satır, iki kurala göre iki farklı yargı. Buna karşılık
`alan->altyapi` kenarı iki kuralda da ihlal, çünkü hiçbir kenar kümesi alan katmanından dışa
çıkan bir kenar ilan etmiyor. Sızıntının iki cinsi arasındaki fark budur: birincisi kuralın
gözden geçirilmesi gereken bir yerdir, ikincisi biçemin taşıyıcı kuralını bozar. Alan
katmanının bir tablo modülüne uzanması, o katmanın tek başına ithal edilebilmesini de bitirir;
kuralı sınamak için tabloyu da yüklemek gerekir.

#### Ödünleşimin Sayıları

Biçem bir kalite niteliğini iyileştirir, başka birini kötüleştirir. Sürdürülebilirlik tarafında
kazanç ölçüldü: ücret kuralı tek dosyada, dışa çıkan kenarı 0, kapalı düzende katman ihlali 0.
Kural değiştiğinde dokunulan dosya bir tanedir ve tabloların biçimi kurala hiç girmez.

Bedel de aynı ölçülerle yazılıyor. Aynı biçem, iş kuralı gerektirmeyen bir okuma isteğinde 3
katmana dokunuyor ve bu üçün 2'si isteğe hiçbir şey eklemiyor. Katman sayısı arttıkça bu oran
büyür: her yeni katman, kural içermeyen isteklerde bir geçirgen durak daha demektir. Biçemin
kendisi bu iki sayı arasındaki ödünleşimdir — ilan edilmiş kenar kümesi ne kadar sıkıysa
kuralın yeri o kadar kesin, geçirgen durak sayısı o kadar yüksektir.

#### Özet

- Katmanlı biçem üç şeyi birlikte söyler: her birim tam bir katmana aittir, katmanlar
  sıralıdır ve izinli çağrı kenarları önceden ilan edilmiştir; üçüncüsü olmadan ihlal tanımlı
  değildir.
- Teklif isteği 4 katmana dokundu, 4 sınır geçti ve geçirgen katman sayısı 0 çıktı; kademe
  listesi isteği 3 katmana dokundu ve 3 katmanın 2'si isteğe hiçbir şey eklemedi.
- Açık katman ilanı aynı okuma isteğini 2 katmana ve 1 sınıra indirdi; bedeli, aynı isteğin
  iki ayrı yerden karşılanabilir hâle gelmesidir.
- Kapalı düzende katman kenarı 0 çıktı, çünkü bağlar bileşim kökünde kuruluyor; sızıntı o
  kökün atlandığı anda ithal grafiğinde görünür.
- Sızıntılı düzendeki iki kenardan `sunum->altyapi` açık kuralda izinli, kapalı kuralda ihlal;
  `alan->altyapi` her iki kuralda da ihlal ve alan katmanının tek başına yüklenmesini bitiriyor.

#### Sonraki Adım

Kapalı düzenin izinli kenar kümesinde bir kenar dikkat çekiyor: `uygulama->altyapi`. Kural, iş
akışını kuran katmanın tabloya doğrudan uzanmasına izin veriyor; yani sıralamada altyapı,
alanın yanında bir başka aşağı katmandır. Sıra bu biçimde kurulduğunda kitaplığın merkezinde
alan modeli değil, isteğin geçtiği yol durur. Sonraki ders sıralamayı tersine çeviren iki
düzeni ele alır: bütün bağların içe, alana doğru baktığı halkalı yerleşimleri. Altıgen
mimarinin dışında sırasız bir bağdaştırıcı kuşağı vardır, soğan mimaride ise halkalar
sıralıdır; iki düzen, yasakladıkları halka kenarı kümesi ve aynı kod tabanındaki iki kusurdan
her kuralın yakaladığı ihlal sayısıyla karşılaştırılır.

### Altıgen ve Soğan Mimari

Source: https://academia.sh/tr/kurslar/mimari-bicemler/altigen-ve-sogan-mimari

Bağımlılığın içe doğru çevrildiği iki düzenin kural olarak karşılaştırılması: dört halkaya yerleşmiş ücretlendirme bağlamında on altı olası halka kenarının sayılması, altıgen ve soğan kurallarının yasakladığı kenar kümelerinin ayrı ayrı çıkarılması, aynı kod tabanındaki iki kusurdan her kuralın yalnız birini görmesi ve onarımdan sonra iki sayının da sıfıra inmesi.

Katmanlı düzenin izinli kenar kümesinde bir kenar vardı: `uygulama->altyapi`. Kural, iş akışını
kuran katmanın tabloya doğrudan uzanmasına izin veriyordu; sıralamada altyapı, alanın yanında
bir başka aşağı katmandı. Bu biçimde kurulan bir sırada kitaplığın merkezinde alan modeli değil,
isteğin geçtiği yol durur.

Bu ders sıralamayı tersine çeviren iki düzeni ele alır. İkisinin de ortak kuralı tek cümledir:
bağımlılık okları daima içe, alana doğru bakar. Ayrıldıkları yer içerinin nasıl bölündüğüdür ve
bu ayrım ölçülebilir bir sonuç verir — iki kuralın gördüğü kusurlar aynı değildir.

#### İki Düzenin Ayrıldığı Yer

Altıgen mimari, bağlantı noktası ve bağdaştırıcı Alan Odaklı Tasarım kursundaki Bağlantı
Noktaları ve Bağdaştırıcılar dersinde kurulmuş ve ölçülmüştür: alanın dışa açıldığı yerler
adlandırıldığında dış dünyaya giden doğrudan bağ sayısı 3'ten 0'a inmişti. Burada o ölçüm
tekrarlanmıyor ve kavramlar yeniden tanımlanmıyor.

Yeni olan şudur. Altıgen düzen içeriyi **bölmez**: alan, bağlantı noktaları ve senaryolar tek
bir içeri kümesidir, dışarıda ise bağdaştırıcılardan oluşan **sırasız** bir kuşak vardır.
Bağdaştırıcılar birbirinin eşidir ve birbirini tanımaz. **Soğan mimari** (onion architecture)
aynı içe bakma kuralını korur ama içeriyi sıralı halkalara böler: en içte alan modeli, onun
üstünde alan servisleri, onun üstünde uygulama servisleri, en dışta bağdaştırıcılar. Kural
halka sırasına göre yazılır: bir halka yalnız kendinden içteki halkaları ve kendi halkasını
ithal edebilir.

İki kural aynı dört bölmeye uygulandığında farklı şeyler yasaklar ve bu fark sayılabilir.

#### Halkalara Yerleşmiş Ücretlendirme Bağlamı

Ücretlendirme bağlamı dört dizine dağıtılıyor; dizin adı halka numarasını verir.

```sh
mkdir -p halkalar/alan-modeli halkalar/alan-servisleri halkalar/uygulama-servisleri halkalar/dis
```

```js
// halkalar/alan-modeli/kademe.mjs — halka 0: agirlik kademesi degeri
export const kademe = (ustAgirlik, ucret) => ({ ustAgirlik, ucret: Math.round(ucret) });
```

```js
// halkalar/alan-servisleri/indirim-servisi.mjs — halka 1: sozlesmeli indirim orani
const SOZLESME = { yok: 0, standart: 0.05, hacimli: 0.12 };

export const indirimServisi = {
  oran: (sozlesme, tutar) => (SOZLESME[sozlesme] ?? 0) + (tutar >= 20000 ? 0.03 : 0),
};
```

```js
// halkalar/alan-modeli/ucret-kurali.mjs — halka 0: kural, indirim servisine uzaniyor
import { indirimServisi } from "../alan-servisleri/indirim-servisi.mjs";

const ASGARI_UCRET = 3990;
const YUVARLAMA_ADIMI = 50;
const yuvarla = (t) => Math.round(t / YUVARLAMA_ADIMI) * YUVARLAMA_ADIMI;

export const ucretKurali = {
  hesapla: (gonderi, kademeler, katsayi) => {
    const kademe = kademeler.find((k) => gonderi.agirlik <= k.ustAgirlik);
    if (kademe === undefined) throw new RangeError("agirlik kademesi yok");
    const ham = kademe.ucret * katsayi;
    const oran = indirimServisi.oran(gonderi.sozlesme, ham);
    return Math.max(yuvarla(ham * (1 - oran)), ASGARI_UCRET);
  },
};
```

```js
// halkalar/alan-servisleri/tarife-noktasi.mjs — halka 1: disa acilan iki nokta ve bekledigi adlar
export const NOKTALAR = { tarife: ["kademeler", "katsayi"], duyurular: ["gonder"] };

export const eksikAd = (cevre) => Object.entries(NOKTALAR)
  .flatMap(([n, adlar]) => adlar
    .filter((a) => typeof cevre?.[n]?.[a] !== "function")
    .map((a) => `${n}.${a}`));
```

```js
// halkalar/uygulama-servisleri/teklif-akisi.mjs — halka 2: senaryonun sirasi
import { ucretKurali } from "../alan-modeli/ucret-kurali.mjs";
import { eksikAd } from "../alan-servisleri/tarife-noktasi.mjs";

export const teklifAkisi = (cevre) => {
  const eksik = eksikAd(cevre);
  if (eksik.length > 0) throw new TypeError(`karsilanmayan nokta: ${eksik.join(", ")}`);
  return {
    teklifVer: (gonderi) => {
      const tutar = ucretKurali.hesapla(gonderi, cevre.tarife.kademeler(), cevre.tarife.katsayi(gonderi.postaKodu));
      cevre.duyurular.gonder({ postaKodu: gonderi.postaKodu, tutar });
      return { tutar };
    },
  };
};
```

```js
// halkalar/dis/tarife-bagdastiricisi.mjs — halka 3: dis satirlari halka 0 degerine cevirir
import { kademe } from "../alan-modeli/kademe.mjs";

const SATIRLAR = [[1, 49.9], [5, 84.9], [15, 149.9], [30, 249.9]];
const KATSAYI = { yakin: 1, orta: 1.35, uzak: 1.8 };
const POSTA_BOLGE = { 34: "yakin", "06": "orta", 35: "orta", 65: "uzak" };

export const tarifeBagdastiricisi = {
  kademeler: () => SATIRLAR.map(([agirlik, tl]) => kademe(agirlik, tl * 100)),
  katsayi: (postaKodu) => KATSAYI[POSTA_BOLGE[postaKodu.slice(0, 2)] ?? "uzak"],
};
```

```js
// halkalar/dis/bildirim-bagdastiricisi.mjs — halka 3: duyuru noktasini karsilayan bagdastirici
const GIDEN = [];

export const bildirimBagdastiricisi = { gonder: (m) => GIDEN.push(m), sayi: () => GIDEN.length };
```

```js
// halkalar/dis/masa.mjs — halka 3: liste istegini komsu bagdastiriciya soruyor
import { tarifeBagdastiricisi } from "./tarife-bagdastiricisi.mjs";

export const masa = (akis) => ({
  teklifIstegi: (govde) => akis.teklifVer({
    agirlik: Number(govde.agirlik), postaKodu: String(govde.postaKodu), sozlesme: govde.sozlesme,
  }),
  kademeIstegi: () => tarifeBagdastiricisi.kademeler(),
});
```

Yerleşimde iki satır bilinçli olarak yanlış konmuş durumda: ücret kuralı indirim servisini
ithal ediyor ve masa, kademe listesini komşusu olan bağdaştırıcıdan istiyor. İkisi de tek
başına bakıldığında masum görünür.

#### İki Kuralın Yasakladığı Kenarlar

Dört halka arasında $4 \times 4 = 16$ olası kenar vardır. Araç önce iki kuralın bu on altı
kenardan hangilerini yasakladığını çıkarır, sonra ağacın gerçek kenarlarını ithal
satırlarından okuyup her iki kurala karşı sayar. İthal grafiğinden kenar çıkarma yordamı
Tasarım İlkeleri kursundaki Bağımlılık Çizgesi Sağlığı dersinde kurulmuştu.

```js
// halka-kurali.mjs — iki halka kuralinin yasakladigi kenarlari ve agactaki ihlalleri sayar
import { readdirSync, readFileSync } from "node:fs";
import { basename, dirname, join, normalize } from "node:path";

const HALKA = { "alan-modeli": 0, "alan-servisleri": 1, "uygulama-servisleri": 2, "dis": 3 };
const SOGAN = (i, j) => j <= i;
const ALTIGEN = (i, j) => j !== 3;
const KURALLAR = [["altigen", ALTIGEN], ["sogan", SOGAN]];

const tumKenarlar = [0, 1, 2, 3].flatMap((i) => [0, 1, 2, 3].map((j) => [i, j]));
const yasak = new Map(KURALLAR.map(([ad, izin]) =>
  [ad, new Set(tumKenarlar.filter(([i, j]) => izin(i, j) === false).map(([i, j]) => `${i}->${j}`))]));

console.log(`olasi halka kenari = ${tumKenarlar.length}`);
for (const [ad, kume] of yasak) console.log(`  ${ad.padEnd(8)} yasak kenar = ${kume.size}  ${[...kume].join(" ")}`);
const ortak = [...yasak.get("altigen")].filter((k) => yasak.get("sogan").has(k));
const birlesim = new Set([...yasak.get("altigen"), ...yasak.get("sogan")]);
console.log(`  ortak yasak = ${ortak.length}  ${ortak.join(" ")}`);
console.log(`  birlesik yasak = ${birlesim.size}`);

function halkaKenarlari(kok) {
  const kenar = new Map();
  for (const halka of readdirSync(kok).sort()) {
    for (const dosya of readdirSync(join(kok, halka)).sort()) {
      const metin = readFileSync(join(kok, halka, dosya), "utf8");
      for (const m of metin.matchAll(/^import\s.*?from\s+"(\.[^"]+)"/gm)) {
        const hedef = basename(dirname(normalize(join(kok, halka, m[1]))));
        const anahtar = `${HALKA[halka]}->${HALKA[hedef]}`;
        kenar.set(anahtar, `${halka}/${dosya} -> ${hedef}/${basename(m[1])}`);
      }
    }
  }
  return kenar;
}

for (const kok of process.argv.slice(2)) {
  const kenar = halkaKenarlari(kok);
  console.log(`\n${kok}/  halka kenari = ${kenar.size}  (${[...kenar.keys()].sort().join(" ")})`);
  for (const [ad, kume] of yasak) {
    const ihlal = [...kenar].filter(([k]) => kume.has(k));
    console.log(`  ${ad.padEnd(8)} ihlal = ${ihlal.length}`);
    for (const [k, kaynak] of ihlal) console.log(`    ${k}  ${kaynak}`);
  }
}
```

```sh
node halka-kurali.mjs halkalar
```

```
olasi halka kenari = 16
  altigen  yasak kenar = 4  0->3 1->3 2->3 3->3
  sogan    yasak kenar = 6  0->1 0->2 0->3 1->2 1->3 2->3
  ortak yasak = 3  0->3 1->3 2->3
  birlesik yasak = 7

halkalar/  halka kenari = 5  (0->1 2->0 2->1 3->0 3->3)
  altigen  ihlal = 1
    3->3  dis/masa.mjs -> dis/tarife-bagdastiricisi.mjs
  sogan    ihlal = 1
    0->1  alan-modeli/ucret-kurali.mjs -> alan-servisleri/indirim-servisi.mjs
```

#### Her Kuralın Kör Noktası

İlk üç satır iki kuralın büyüklüğünü veriyor. Altıgen kural on altı kenardan 4'ünü yasaklıyor:
dış kuşağa giden her kenarı ve dış kuşağın kendi içindeki kenarı. Soğan kural 6'sını
yasaklıyor: içeride de bir sıra ilan edildiği için dışa bakan her kenar yasaktır. Ortak yasak
3 kenar, yani iki kuralın anlaştığı şey yalnız "içeriden dışarıya bağ olmaz" cümlesidir.
Birleşik yasak 7'ye çıkıyor; ikisinin yasakları iç içe değil, kesişiyor.

Bunun bedeli ağaçta görünüyor. Beş halka kenarından ikisi kusurluydu ve her kural yalnız
birini gördü. Altıgen kural `masa -> tarife-bagdastiricisi` kenarını yakaladı: iki bağdaştırıcı
birbirini tanıyor, oysa dış kuşak sırasızdır ve eşler birbirine bağlanmaz. Soğan kural bunu
göremez, çünkü ikisi de aynı halkadadır ve halka içi kenar izinlidir. Buna karşılık soğan kural
`ucret-kurali -> indirim-servisi` kenarını yakaladı: alan modeli, kendisinden dışta duran alan
servisine uzanıyor. Altıgen kural bunu göremez, çünkü ikisi de "içeri" kümesindedir ve içerinin
sırası yoktur.

Sonuç, iki kuralın hangisinin doğru olduğu değildir. İkisi de aynı yönü söyler ve ikisi de
ötekinin gördüğü bir kusuru göremez.

#### İki Kenarın Kaldırılması

Onarım iki hareketten oluşuyor. Ücret kuralı indirim oranını ithal etmek yerine parametre
olarak alıyor; oranı çağıran taraf, yani uygulama servisi hesaplıyor. Masa ise kademe listesini
komşusundan değil akıştan istiyor.

```sh
cp -r halkalar onarilmis
cat > onarilmis/alan-modeli/ucret-kurali.mjs <<'EOF'
// onarilmis/alan-modeli/ucret-kurali.mjs — halka 0: indirim orani parametre olarak geliyor
const ASGARI_UCRET = 3990;
const YUVARLAMA_ADIMI = 50;
const yuvarla = (t) => Math.round(t / YUVARLAMA_ADIMI) * YUVARLAMA_ADIMI;

export const ucretKurali = {
  hamUcret: (gonderi, kademeler, katsayi) => {
    const kademe = kademeler.find((k) => gonderi.agirlik <= k.ustAgirlik);
    if (kademe === undefined) throw new RangeError("agirlik kademesi yok");
    return kademe.ucret * katsayi;
  },
  hesapla: (ham, oran) => Math.max(yuvarla(ham * (1 - oran)), ASGARI_UCRET),
};
EOF
cat > onarilmis/uygulama-servisleri/teklif-akisi.mjs <<'EOF'
// onarilmis/uygulama-servisleri/teklif-akisi.mjs — halka 2: indirimi ve liste istegini kendisi yurutuyor
import { ucretKurali } from "../alan-modeli/ucret-kurali.mjs";
import { indirimServisi } from "../alan-servisleri/indirim-servisi.mjs";
import { eksikAd } from "../alan-servisleri/tarife-noktasi.mjs";

export const teklifAkisi = (cevre) => {
  const eksik = eksikAd(cevre);
  if (eksik.length > 0) throw new TypeError(`karsilanmayan nokta: ${eksik.join(", ")}`);
  return {
    teklifVer: (gonderi) => {
      const ham = ucretKurali.hamUcret(gonderi, cevre.tarife.kademeler(), cevre.tarife.katsayi(gonderi.postaKodu));
      const tutar = ucretKurali.hesapla(ham, indirimServisi.oran(gonderi.sozlesme, ham));
      cevre.duyurular.gonder({ postaKodu: gonderi.postaKodu, tutar });
      return { tutar };
    },
    kademeListele: () => cevre.tarife.kademeler(),
  };
};
EOF
cat > onarilmis/dis/masa.mjs <<'EOF'
// onarilmis/dis/masa.mjs — halka 3: liste istegini de akistan soruyor, komsusunu tanimiyor
export const masa = (akis) => ({
  teklifIstegi: (govde) => akis.teklifVer({
    agirlik: Number(govde.agirlik), postaKodu: String(govde.postaKodu), sozlesme: govde.sozlesme,
  }),
  kademeIstegi: () => akis.kademeListele(),
});
EOF
node halka-kurali.mjs onarilmis
```

```
olasi halka kenari = 16
  altigen  yasak kenar = 4  0->3 1->3 2->3 3->3
  sogan    yasak kenar = 6  0->1 0->2 0->3 1->2 1->3 2->3
  ortak yasak = 3  0->3 1->3 2->3
  birlesik yasak = 7

onarilmis/  halka kenari = 3  (2->0 2->1 3->0)
  altigen  ihlal = 0
  sogan    ihlal = 0
```

Halka kenarı 5'ten 3'e indi ve kalan üç kenar iki kuralda da izinli: uygulama servisi iki iç
halkaya bakıyor, bağdaştırıcı en iç halkanın değerini üretiyor. Aynı ağaç artık iki kuralın
birleşik yasağını da geçiyor.

#### Davranış ve Kapanış

Bir yeniden düzenlemenin davranışı koruduğu iddia edilmez, sınanır. Bileşim kökü halkaların
dışındadır; aşağıdaki betik iki ağacı da kendisi kurar ve aynı gönderilerle çağırır. Ayrıca en
iç halkadaki kural dosyasının ithal kapanışını iki ağaçta ölçer.

```js
// esdeger-mi.mjs — bilesim koku halkalarin disindadir: iki agaci kurar, sonuc ve kapanis karsilastirir
import { readFileSync } from "node:fs";
import { dirname, join, normalize } from "node:path";
import { masa as bozukMasa } from "./halkalar/dis/masa.mjs";
import { teklifAkisi as bozukAkis } from "./halkalar/uygulama-servisleri/teklif-akisi.mjs";
import { tarifeBagdastiricisi as bozukTarife } from "./halkalar/dis/tarife-bagdastiricisi.mjs";
import { bildirimBagdastiricisi as bozukDuyuru } from "./halkalar/dis/bildirim-bagdastiricisi.mjs";
import { masa as onarilmisMasa } from "./onarilmis/dis/masa.mjs";
import { teklifAkisi as onarilmisAkis } from "./onarilmis/uygulama-servisleri/teklif-akisi.mjs";
import { tarifeBagdastiricisi as onarilmisTarife } from "./onarilmis/dis/tarife-bagdastiricisi.mjs";
import { bildirimBagdastiricisi as onarilmisDuyuru } from "./onarilmis/dis/bildirim-bagdastiricisi.mjs";

const GONDERILER = [
  { agirlik: "0.4", postaKodu: "34710", sozlesme: "yok" },
  { agirlik: "3", postaKodu: "06800", sozlesme: "standart" },
  { agirlik: "12", postaKodu: "65100", sozlesme: "hacimli" },
  { agirlik: "28", postaKodu: "35400", sozlesme: "yok" },
  { agirlik: "5", postaKodu: "34100", sozlesme: "hacimli" },
];

function kapanis(kok) {
  const gorulen = new Set(), yigit = [kok];
  while (yigit.length > 0) {
    const dosya = yigit.pop();
    if (gorulen.has(dosya)) continue;
    gorulen.add(dosya);
    for (const [, yol] of readFileSync(dosya, "utf8").matchAll(/^import[^"']*["'](\.[^"']+)["']/gm)) {
      yigit.push(normalize(join(dirname(dosya), yol)));
    }
  }
  return gorulen.size;
}

const bozuk = bozukMasa(bozukAkis({ tarife: bozukTarife, duyurular: bozukDuyuru }));
const onarilmis = onarilmisMasa(onarilmisAkis({ tarife: onarilmisTarife, duyurular: onarilmisDuyuru }));

let ayrilan = 0;
for (const g of GONDERILER) {
  const a = bozuk.teklifIstegi(g).tutar, b = onarilmis.teklifIstegi(g).tutar;
  if (a !== b) ayrilan += 1;
  console.log(`${g.agirlik.padStart(4)} kg  ${g.postaKodu}  ${g.sozlesme.padEnd(8)} ${String(a).padStart(6)}  ${String(b).padStart(6)}`);
}
console.log(`ayrilan sonuc = ${ayrilan} / ${GONDERILER.length}`);
console.log(`kademe istegi: ${bozuk.kademeIstegi().length} / ${onarilmis.kademeIstegi().length} kademe, duyuru = ${bozukDuyuru.sayi()} / ${onarilmisDuyuru.sayi()}`);
for (const kok of ["halkalar", "onarilmis"]) {
  console.log(`${kok}/alan-modeli/ucret-kurali.mjs ithal kapanisi = ${kapanis(`${kok}/alan-modeli/ucret-kurali.mjs`)}`);
}
```

```sh
node esdeger-mi.mjs
```

```
 0.4 kg  34710  yok        5000    5000
   3 kg  06800  standart  10900   10900
  12 kg  65100  hacimli   22950   22950
  28 kg  35400  yok       32700   32700
   5 kg  34100  hacimli    7450    7450
ayrilan sonuc = 0 / 5
kademe istegi: 4 / 4 kademe, duyuru = 5 / 5
halkalar/alan-modeli/ucret-kurali.mjs ithal kapanisi = 2
onarilmis/alan-modeli/ucret-kurali.mjs ithal kapanisi = 1
```

Beş gönderinin beşinde tutar aynı, iki ağaçta da beş duyuru gitti ve kademe listesi her iki
yoldan dört satır döndü. Onarım davranışı değiştirmedi. Değişen şey en iç halkanın ithal
kapanışıdır: 2 dosyadan 1 dosyaya indi, yani kuralı sınamak için artık indirim tablosunu
yüklemek gerekmiyor. Kalite niteliği tarafındaki karşılığı budur — sürdürülebilirlik ölçüsü
olarak en iç halkanın bağımsız yüklenebilmesi, iki kuralın ortak amacıdır; halka sırasının
sıkılığı ise o amacın ne kadarının otomatik denetlenebildiğini belirler.

#### Özet

- İki düzenin ortak kuralı bağımlılığın içe bakmasıdır; ayrıldıkları yer içerinin bölünüp
  bölünmediğidir: altıgende içeri tek küme ve dış kuşak sırasız, soğanda içeri sıralı
  halkalardır.
- Dört halka arasındaki 16 olası kenardan altıgen kural 4'ünü, soğan kural 6'sını yasaklıyor;
  ortak yasakları 3 kenar, birleşik yasak 7.
- Aynı ağaçtaki iki kusurdan altıgen kural yalnız `3->3` kenarını, soğan kural yalnız `0->1`
  kenarını gördü; her kural ötekinin yakaladığı kusura kördür.
- Onarımdan sonra halka kenarı 5'ten 3'e, iki kuralın ihlali de 0'a indi; beş gönderinin
  beşinde tutar aynı kaldı.
- En iç halkadaki kural dosyasının ithal kapanışı 2'den 1'e indi: kuralı yüklemek için artık
  başka bir halkayı yüklemek gerekmiyor.

#### Sonraki Adım

Bu dersin son sayısı bir soru bırakıyor: birleşik yasak 7 kenardı, ama hiçbir düzen o yedi
kenarı birlikte yasaklamıyordu. Bir kural içerinin sırasını ilan ediyor ama dış kuşağı tek
parça sayıyor; öteki dış kuşağı sıralamıyor ama içeriyi bölmüyor. İki kuralın birleşimi
adlandırılmış bir düzen olarak yazıldığında ortaya çıkan yerleşimin adı **temiz mimaridir**.
Sonraki ders bu birleşimi kurar ve iki yeni şeyi ölçer: sınırdan geçen verinin alan ve karakter
sayısı ile denetim akışının yönü ile bağımlılık okunun yönünün ayrıldığı kenar sayısı.

### Temiz Mimari

Source: https://academia.sh/tr/kurslar/mimari-bicemler/temiz-mimari

İki halka kuralının tek bir yükleme birleştirilmesi: on altı halka kenarından yedisini yasaklayan kuralın yazılması, bileşim kökünden geçen sınırlarda denetim akışının yönü ile bağımlılık okunun ayrıldığı kenarların sayılması ve sınırdan geçen iki veri biçiminin erişilebilir ad ile karakter sayısıyla karşılaştırılması.

Bir önceki ders iki halka kuralını aynı dört bölmeye uyguladı ve her birinin ötekinin yakaladığı
kusura kör olduğunu ölçtü. Birleşik yasak 7 kenardı, ama hiçbir düzen o yediyi birlikte
yasaklamıyordu: biri içerinin sırasını ilan ediyor ama dış kuşağı tek parça sayıyordu, öteki dış
kuşağı sıralamıyordu.

Bu ders o birleşimi adlandırılmış bir düzen olarak kurar. **Temiz mimari** (clean architecture)
halkaların sıralı olduğunu ve bağımlılık okunun daima içe baktığını birlikte söyler; yeni olan,
kuralın bütün halka çiftleri üzerinde tek bir yüklem olarak yazılmasıdır. İki ölçü bu yazımdan
çıkar: sınırdan geçen verinin alan ve karakter sayısı ile denetim akışı ve bağımlılık okunun
ayrıldığı kenar sayısı.

#### Birleşmiş Kural

Politika ile ayrıntının ayrılması ve sınırın nereye çizileceği Tasarım İlkeleri kursundaki
Politika ve Ayrıntı Ayrımı ile Sınırların Çizilmesi derslerinde, katman sorumlulukları da Veri
Erişim Katmanı ve İş Mantığı kursundaki Katman Sorumlulukları dersinde kurulmuştu. Burada
anlatılan ilke değil **düzen**.

Halka 0'da **varlık nesneleri** ile kural durur ve dışta hiçbir şeye bakmaz. Halka 1'de
**kullanım senaryoları**: bir isteğin adımları, sırası ve senaryonun dışarıdan beklediği adların
bildirimi. Halka 2 bağdaştırıcılardır; dış biçimi girdi modeline, çıktı modelini dış biçime
çevirir. Halka 3 en dıştadır: ham satırlar ve giriş masası. Kural iki koşulun çarpımıdır —
soğandan geleni, bir halkanın yalnız kendinden içteki ya da kendi halkasını ithal edebilmesi;
altıgenden geleni, en dış halkaya hiçbir kenarın girememesi.

#### Dört Halkaya Yerleşmiş Kitaplık

Ücretlendirme bağlamı dört dizine dağıtılıyor; dizin adı halka numarasını verir. Bağlar
parametreyle kuruluyor ve bileşim kökü halkaların dışındadır.

```sh
mkdir -p temiz/varliklar temiz/senaryolar temiz/bagdastiricilar temiz/altyapi
```

```js
// temiz/varliklar/gonderi.mjs — halka 0: fiyatlanabilir gonderi ve ucretlenen agirligi
const HACIM_BOLENI = 5000;

export const gonderi = (girdi) => Object.freeze({
  ...girdi,
  ucretlenenAgirlik: () => Math.max(girdi.agirlik, girdi.hacim / HACIM_BOLENI),
});
```

```js
// temiz/varliklar/ucret-kurali.mjs — halka 0: kural; kademe, katsayi ve oran disaridan gelir
const ASGARI_UCRET = 3990;
const YUVARLAMA_ADIMI = 50;
const SIGORTA_ORANI = 0.004;

export const ucretKurali = {
  hesapla: (g, kademeler, katsayi, indirimOrani) => {
    const agirlik = g.ucretlenenAgirlik();
    const kademe = kademeler.find((k) => agirlik <= k.ustAgirlik);
    if (kademe === undefined) throw new RangeError("agirlik kademesi yok");
    const ham = kademe.ucret * katsayi + Math.round(g.beyanDegeri * SIGORTA_ORANI);
    const net = Math.round((ham * (1 - indirimOrani)) / YUVARLAMA_ADIMI) * YUVARLAMA_ADIMI;
    return { tutar: Math.max(net, ASGARI_UCRET), kademe, ucretlenenAgirlik: agirlik };
  },
};
```

Halka 1'in ilk dosyası kod değil bildirimdir: girdi ile çıktı modelinin alanları ve senaryonun
geçitten beklediği adlar.

```js
// temiz/senaryolar/sinir.mjs — halka 1: girdi modeli, cikti modeli ve gecidin bekledigi adlar
export const GIRDI_ALANLARI = ["agirlik", "hacim", "bolge", "beyanDegeri", "sozlesme"];
export const CIKTI_ALANLARI = ["tutar", "paraBirimi", "kademeUstAgirlik", "ucretlenenAgirlik"];
export const GECIT_ADLARI = ["kademeler", "katsayi", "indirimOrani"];

export const eksikAd = (nesne, adlar) => adlar.filter((a) => typeof nesne?.[a] !== "function");

export const bildirilmemisAd = (nesne, izinli, onek = "") =>
  Object.entries(nesne ?? {}).flatMap(([ad, deger]) => {
    const yol = onek === "" ? ad : `${onek}.${ad}`;
    if (deger !== null && typeof deger === "object") return bildirilmemisAd(deger, izinli, yol);
    return izinli.includes(yol) ? [] : [yol];
  });
```

```js
// temiz/senaryolar/teklif-senaryosu.mjs — halka 1: girdi modelinden cikti modeline
import { gonderi } from "../varliklar/gonderi.mjs";
import { ucretKurali } from "../varliklar/ucret-kurali.mjs";
import { GECIT_ADLARI, eksikAd } from "./sinir.mjs";

export const teklifSenaryosu = (gecit) => {
  const eksik = eksikAd(gecit, GECIT_ADLARI);
  if (eksik.length > 0) throw new TypeError(`karsilanmayan gecit adi: ${eksik.join(", ")}`);
  return {
    calistir: (girdi) => {
      const g = gonderi(girdi);
      const sonuc = ucretKurali.hesapla(g, gecit.kademeler(), gecit.katsayi(g.bolge),
        gecit.indirimOrani(g.sozlesme));
      return {
        tutar: sonuc.tutar,
        paraBirimi: "kurus",
        kademeUstAgirlik: sonuc.kademe.ustAgirlik,
        ucretlenenAgirlik: sonuc.ucretlenenAgirlik,
      };
    },
  };
};
```

```js
// temiz/bagdastiricilar/teklif-denetleyicisi.mjs — halka 2: dis govdeyi girdi modeline, cikti modelini metne cevirir
import { CIKTI_ALANLARI, GIRDI_ALANLARI, bildirilmemisAd } from "../senaryolar/sinir.mjs";

const DONUSTUR = { agirlik: Number, hacim: Number, bolge: String, beyanDegeri: Number, sozlesme: String };

export const teklifDenetleyicisi = (senaryo) => ({
  teklifIstegi: (govde) => {
    const cikti = senaryo.calistir(
      Object.fromEntries(GIRDI_ALANLARI.map((a) => [a, DONUSTUR[a](govde[a])])));
    const bildirilmemis = bildirilmemisAd(cikti, CIKTI_ALANLARI);
    if (bildirilmemis.length > 0) return { durum: 500, govde: `sozlesme disi ad: ${bildirilmemis.join(", ")}` };
    return { durum: 200, govde: `${(cikti.tutar / 100).toFixed(2)} TL / ${cikti.ucretlenenAgirlik.toFixed(2)} kg` };
  },
});

export const hataBicimleyicisi = (hata) =>
  hata instanceof RangeError ? { durum: 422, govde: hata.message } : { durum: 500, govde: "beklenmeyen" };
```

```js
// temiz/bagdastiricilar/tarife-gecidi.mjs — halka 2: ham satirlari kademe degerine cevirir
import { GECIT_ADLARI, eksikAd } from "../senaryolar/sinir.mjs";

export const KAYNAK_ADLARI = ["satirlar", "katsayilar", "sozlesmeler"];

export const tarifeGecidi = (kaynak) => {
  const gecit = {
    kademeler: () => kaynak.satirlar()
      .map(([ustAgirlik, tl]) => ({ ustAgirlik, ucret: Math.round(tl * 100) })),
    katsayi: (bolge) => kaynak.katsayilar()[bolge] ?? 1.8,
    indirimOrani: (sozlesme) => kaynak.sozlesmeler()[sozlesme] ?? 0,
  };
  const eksik = [...eksikAd(kaynak, KAYNAK_ADLARI), ...eksikAd(gecit, GECIT_ADLARI)];
  if (eksik.length > 0) throw new TypeError(`karsilanmayan ad: ${eksik.join(", ")}`);
  return gecit;
};
```

```js
// temiz/altyapi/tarife-satirlari.mjs — halka 3: ham satirlar, bolge katsayilari, sozlesme oranlari
const SATIRLAR = [[1, 49.9], [5, 84.9], [15, 149.9], [30, 249.9]];
const KATSAYILAR = { yakin: 1, orta: 1.35, uzak: 1.8 };
const SOZLESMELER = { yok: 0, standart: 0.05, hacimli: 0.12 };

export const tarifeSatirlari = {
  satirlar: () => SATIRLAR,
  katsayilar: () => KATSAYILAR,
  sozlesmeler: () => SOZLESMELER,
};
```

```js
// temiz/altyapi/masa.mjs — halka 3: disaridan geleni denetleyiciye verir, hatayi bicimleyiciden gecirir
import { hataBicimleyicisi } from "../bagdastiricilar/teklif-denetleyicisi.mjs";

export const masa = (denetleyici) => ({
  istek: (govde) => {
    try { return denetleyici.teklifIstegi(govde); } catch (hata) { return hataBicimleyicisi(hata); }
  },
});
```

#### Kuralın Yazımı ve İhlal

Araç kuralı iki kuraldan üretir, birleşimle aynı olduğunu sınar, sonra ağacın halka kenarlarını
ithal satırlarından okuyup yasağa karşı sayar. Kenar çıkarma yordamı Tasarım İlkeleri kursundaki
Bağımlılık Çizgesi Sağlığı dersinde kurulmuştu.

```js
// temiz-kural.mjs — temiz kurali iki kuralin birlesimi olarak kurar ve agactaki ihlali sayar
import { readdirSync, readFileSync } from "node:fs";
import { basename, dirname, join, normalize } from "node:path";

const HALKA = { varliklar: 0, senaryolar: 1, bagdastiricilar: 2, altyapi: 3 };
const ALTIGEN = (i, j) => j !== 3;
const SOGAN = (i, j) => j <= i;
const TEMIZ = (i, j) => SOGAN(i, j) && ALTIGEN(i, j);

const tum = [0, 1, 2, 3].flatMap((i) => [0, 1, 2, 3].map((j) => [i, j]));
const yasakla = (izin) => new Set(tum
  .filter(([i, j]) => izin(i, j) === false).map(([i, j]) => `${i}->${j}`));
export const YASAK = yasakla(TEMIZ);

export function halkaKenarlari(kok) {
  const kenar = new Map();
  for (const halka of readdirSync(kok).sort()) {
    for (const dosya of readdirSync(join(kok, halka)).sort()) {
      for (const m of readFileSync(join(kok, halka, dosya), "utf8")
        .matchAll(/^import\s.*?from\s+"(\.[^"]+)"/gm)) {
        const hedef = basename(dirname(normalize(join(kok, halka, m[1]))));
        kenar.set(`${HALKA[halka]}->${HALKA[hedef]}`, `${halka}/${dosya}`);
      }
    }
  }
  return kenar;
}

if (process.argv.length > 2) {
  const birlesim = new Set([...yasakla(ALTIGEN), ...yasakla(SOGAN)]);
  console.log(`olasi halka kenari = ${tum.length}`);
  console.log(`temiz kural: yasak = ${YASAK.size}, izinli = ${tum.length - YASAK.size}`);
  console.log(`  yasak kenarlar = ${[...YASAK].join(" ")}`);
  console.log(`  birlesimle ayni mi = ${YASAK.size === birlesim.size
    && [...YASAK].every((k) => birlesim.has(k))}`);
  for (const kok of process.argv.slice(2)) {
    const kenar = halkaKenarlari(kok);
    const ihlal = [...kenar].filter(([k]) => YASAK.has(k));
    console.log(`\n${kok}/  halka kenari = ${kenar.size}  (${[...kenar.keys()].sort().join(" ")})`);
    console.log(`  ihlal = ${ihlal.length}`);
    for (const [k, kaynak] of ihlal) console.log(`    ${k}  ${kaynak}`);
  }
}
```

Ağacın bir kopyasına iki küçük dosya ekleniyor: biri halka 2'de ham satırları kendisi ithal
ediyor, öteki halka 0'da sınır bildirimini okuyor.

```sh
cp -r temiz kirik
cat > kirik/bagdastiricilar/satir-onbellegi.mjs <<'EOF'
// kirik/bagdastiricilar/satir-onbellegi.mjs — halka 2: ham satirlari kendisi ithal ediyor
import { tarifeSatirlari } from "../altyapi/tarife-satirlari.mjs";

export const satirOnbellegi = () => tarifeSatirlari.satirlar();
EOF
cat > kirik/varliklar/alan-ozeti.mjs <<'EOF'
// kirik/varliklar/alan-ozeti.mjs — halka 0: sinir bildirimini kendisi okuyan yardimci
import { CIKTI_ALANLARI } from "../senaryolar/sinir.mjs";

export const ciktiAlanSayisi = () => CIKTI_ALANLARI.length;
EOF
node temiz-kural.mjs temiz kirik
```

```
olasi halka kenari = 16
temiz kural: yasak = 7, izinli = 9
  yasak kenarlar = 0->1 0->2 0->3 1->2 1->3 2->3 3->3
  birlesimle ayni mi = true

temiz/  halka kenari = 4  (1->0 1->1 2->1 3->2)
  ihlal = 0

kirik/  halka kenari = 6  (0->1 1->0 1->1 2->1 2->3 3->2)
  ihlal = 2
    2->3  bagdastiricilar/satir-onbellegi.mjs
    0->1  varliklar/alan-ozeti.mjs
```

Yasak küme birleşimle birebir aynı çıktı; katkı yeni bir yasak eklemek değil, iki yasağı tek
yüklemde toplamaktır. Temiz ağacın dört kenarı izinli dokuzun içinde. Eklenen iki dosya kenarı
altıya çıkardı ve ikisi ihlal: biri en dış halkaya giren, öteki en iç halkadan çıkan kenar.
Önceki dersin iki kuralı bunlardan **birer** tanesini görüyordu.

#### Denetim Akışı ile Bağımlılık Okunun Ayrılması

Halka kenarı bir isteğin hangi yoldan geçtiğini söylemez: kaynakta bağımlılık içe bakarken
çalışma zamanında senaryo dışarıdaki geçidi çağırır. Betik bileşim kökünü kurar, her nesneyi bir
sarmalayıcıdan geçirir, çağrı anında yığıtın tepesindeki halkayı çağıran olarak kaydeder ve
kaydettiği her sınırı ithal kümesiyle karşılaştırır. Doğrudan ithalle yapılan çağrılar
kaydedilmiyor: onlarda iki yön tanım gereği aynıdır.

```js
// yon-ayrimi.mjs — bilesim kokunden gecen her sinirda denetim akisi ile bagimlilik okunu karsilastirir
import { halkaKenarlari } from "./temiz-kural.mjs";
import { tarifeSatirlari } from "./temiz/altyapi/tarife-satirlari.mjs";
import { masa } from "./temiz/altyapi/masa.mjs";
import { teklifDenetleyicisi } from "./temiz/bagdastiricilar/teklif-denetleyicisi.mjs";
import { tarifeGecidi, KAYNAK_ADLARI } from "./temiz/bagdastiricilar/tarife-gecidi.mjs";
import { teklifSenaryosu } from "./temiz/senaryolar/teklif-senaryosu.mjs";
import { GECIT_ADLARI } from "./temiz/senaryolar/sinir.mjs";

const ithal = new Set(halkaKenarlari("temiz").keys());
const yigit = [];
const cagri = new Map();

function sar(halka, nesne) {
  const sarili = {};
  for (const [ad, fn] of Object.entries(nesne)) {
    sarili[ad] = (...args) => {
      const cagiran = yigit.at(-1);
      if (cagiran !== undefined && cagiran !== halka) {
        const k = `${cagiran}->${halka}`;
        cagri.set(k, (cagri.get(k) ?? 0) + 1);
      }
      yigit.push(halka);
      try { return fn(...args); } finally { yigit.pop(); }
    };
  }
  return sarili;
}

const gecit = sar(2, tarifeGecidi(sar(3, tarifeSatirlari)));
const denetleyici = sar(2, teklifDenetleyicisi(sar(1, teklifSenaryosu(gecit))));
const disMasa = sar(3, masa(denetleyici));

const GOVDELER = [
  { agirlik: 3, hacim: 24000, bolge: "orta", beyanDegeri: 150000, sozlesme: "standart" },
  { agirlik: 60, hacim: 90000, bolge: "uzak", beyanDegeri: 400000, sozlesme: "hacimli" },
];
const yanit = GOVDELER.map((g) => disMasa.istek(g));
console.log(`istek = ${GOVDELER.length}  yanit = ${yanit.map((y) => `${y.durum} ${y.govde}`).join(" | ")}`);

const sayi = { ayni: 0, cevrilmis: 0, bagsiz: 0 };
console.log(`\nbilesim kokunden gecen sinir = ${cagri.size}`);
for (const [k, n] of [...cagri].sort()) {
  const [i, j] = k.split("->");
  const tur = ithal.has(k) ? "ayni" : ithal.has(`${j}->${i}`) ? "cevrilmis" : "bagsiz";
  sayi[tur] += 1;
  const ok = tur === "ayni" ? k : tur === "cevrilmis" ? `${j}->${i}` : "yok";
  console.log(`  denetim ${k}  bagimlilik ${ok}  ${tur.padEnd(9)} cagri = ${n}`);
}
console.log(`ayni yon = ${sayi.ayni}, cevrilmis = ${sayi.cevrilmis}, bagsiz = ${sayi.bagsiz}`);
console.log(`cevrilmis sinirlarda ice bildirilen ad: gecit ${GECIT_ADLARI.length}, kaynak ${KAYNAK_ADLARI.length}`);
```

```sh
node yon-ayrimi.mjs
```

```
istek = 2  yanit = 200 114.50 TL / 4.80 kg | 422 agirlik kademesi yok

bilesim kokunden gecen sinir = 4
  denetim 1->2  bagimlilik 2->1  cevrilmis cagri = 6
  denetim 2->1  bagimlilik 2->1  ayni      cagri = 2
  denetim 2->3  bagimlilik 3->2  cevrilmis cagri = 6
  denetim 3->2  bagimlilik 3->2  ayni      cagri = 2
ayni yon = 2, cevrilmis = 2, bagsiz = 0
cevrilmis sinirlarda ice bildirilen ad: gecit 3, kaynak 3
```

Dört sınırın ikisinde iki yön aynı: masa denetleyiciyi hem çağırıyor hem ithal ediyor,
denetleyici de senaryoyu. İkisinde ayrılıyor: senaryo geçidi çağırıyor ama geçit senaryoyu ithal
ediyor; geçit ham kaynağı çağırıyor ama kaynağın halkası geçidin halkasını ithal ediyor. Ayrımı
olanaklı kılan, çağrılan adların **içeride** bildirilmiş olmasıdır — üç ad halka 1'de, üç ad
halka 2'de yazılı. Bedel de aynı sayıdadır: bu iki sınırda çalışma zamanı yolu ithal
satırlarından okunamaz, yalnız bileşim köküne bakılarak bulunur.

#### Sınırdan Geçen Veri

İkinci ölçü sınırdan geçen verinin biçimidir. Senaryo, kuralın döndürdüğü nesneyi olduğu gibi
dışa verebilir ya da bildirilmiş alanlara daraltabilir. Betik ikisini erişilebilir ad,
bildirilmemiş ad ve JSON karakteri sayısıyla ölçüyor.

```js
// sinir-verisi.mjs — sinirdan gecen iki veri bicimini erisilebilir ad ve karakter sayisiyla olcer
import { gonderi } from "./temiz/varliklar/gonderi.mjs";
import { ucretKurali } from "./temiz/varliklar/ucret-kurali.mjs";
import { CIKTI_ALANLARI, bildirilmemisAd } from "./temiz/senaryolar/sinir.mjs";
import { teklifSenaryosu } from "./temiz/senaryolar/teklif-senaryosu.mjs";
import { teklifDenetleyicisi } from "./temiz/bagdastiricilar/teklif-denetleyicisi.mjs";

const sahteGecit = {
  kademeler: () => [{ ustAgirlik: 1, ucret: 4990 }, { ustAgirlik: 5, ucret: 8490 }],
  katsayi: (bolge) => (bolge === "orta" ? 1.35 : 1),
  indirimOrani: (sozlesme) => (sozlesme === "standart" ? 0.05 : 0),
};
const GIRDI = { agirlik: 3, hacim: 24000, bolge: "orta", beyanDegeri: 150000, sozlesme: "standart" };

const g = gonderi(GIRDI);
const ham = { ...ucretKurali.hesapla(g, sahteGecit.kademeler(), sahteGecit.katsayi(g.bolge),
  sahteGecit.indirimOrani(g.sozlesme)), gonderi: g };
const cikti = teklifSenaryosu(sahteGecit).calistir(GIRDI);

for (const [ad, veri] of [["kuralin nesnesi", ham], ["cikti modeli", cikti]]) {
  const adlar = bildirilmemisAd(veri, []);
  const bildirilmemis = bildirilmemisAd(veri, CIKTI_ALANLARI);
  console.log(`${ad.padEnd(16)} erisilebilir ad = ${String(adlar.length).padStart(2)}`
    + `  bildirilmemis = ${String(bildirilmemis.length).padStart(2)}`
    + `  json karakteri = ${String(JSON.stringify(veri).length).padStart(3)}`);
  for (let i = 0; i < bildirilmemis.length; i += 4) {
    console.log(`  bildirilmemis: ${bildirilmemis.slice(i, i + 4).join(", ")}`);
  }
}

const yanlis = teklifDenetleyicisi({ calistir: () => ham });
const dogru = teklifDenetleyicisi(teklifSenaryosu(sahteGecit));
console.log(`\nkuralin nesnesi disa verilirse -> durum ${yanlis.teklifIstegi(GIRDI).durum}`);
const y = dogru.teklifIstegi(GIRDI);
console.log(`cikti modeli disa verilirse    -> durum ${y.durum}  ${y.govde}`);
```

```sh
node sinir-verisi.mjs
```

```
kuralin nesnesi  erisilebilir ad = 10  bildirilmemis =  8  json karakteri = 174
  bildirilmemis: kademe.ustAgirlik, kademe.ucret, gonderi.agirlik, gonderi.hacim
  bildirilmemis: gonderi.bolge, gonderi.beyanDegeri, gonderi.sozlesme, gonderi.ucretlenenAgirlik
cikti modeli     erisilebilir ad =  4  bildirilmemis =  0  json karakteri =  81

kuralin nesnesi disa verilirse -> durum 500
cikti modeli disa verilirse    -> durum 200  114.50 TL / 4.80 kg
```

Kuralın nesnesinde dışarıdan erişilebilen 10 ad var ve sekizi çıktı sözleşmesinde bildirilmemiş:
ikisi kademe nesnesinin, altısı varlık nesnesinin iç alanları. Erişilebilir ad 10'dan 4'e, JSON
karakteri 174'ten 81'e iniyor.

Fark boyut değil, bilme yükümlülüğüdür. Varlık nesnesi sınırdan geçtiğinde halka 2, halka 0'ın
alan adlarını okur; kural bir alanı yeniden adlandırdığında bağdaştırıcı da değişir. Bu bağ ithal
grafiğinde görünmez, çünkü ortada ithal yoktur — bağ nesnenin biçimindedir. Denetleyicinin çıktıyı
bildirilmiş alanlara karşı sınaması bunu koşum anında yakalıyor: yanıt 500'e karşı 200.

#### Ödünleşimin Sayıları

Kazanç iki kalite niteliğinde ölçüldü. Sürdürülebilirlikte: sekiz dosyalı ağacın dört halka
kenarı izinli dokuzun içinde ve ihlal 0, eklenen iki küçük dosya ihlali 2'ye çıkardı.
Sınanabilirlikte: halka 1'den çıkan kenarlar `1->0` ve `1->1` olduğu için senaryo elle yazılmış
üç işlevlik bir geçitle koşuyor.

Bedel aynı sayılardadır: dört sınırın ikisi çevrilmiş ve her çevrilmiş sınır içeride bir bildirim
ile dışarıda onu karşılayan bir tanım istiyor. Kenar kümesi ne kadar sıkıysa ihlal o kadar erken
yakalanır, çevirme noktası sayısı da o kadar yüksek olur.

#### Özet

- Kural iki koşulun çarpımıdır: bir halka yalnız kendinden içteki ya da kendi halkasını ithal
  edebilir ve en dış halkaya hiçbir kenar giremez; on altı kenardan yedisi yasaklanır ve yasak
  küme iki halka kuralının birleşimiyle birebir aynıdır.
- Temiz ağacın dört kenarında ihlal 0; eklenen iki dosya en dış halkaya giren ve en iç halkadan
  çıkan iki kenar üretti, ihlal 2'ye çıktı.
- Bileşim kökünden geçen dört sınırın ikisinde denetim akışı ile bağımlılık oku ayrıldı; ayrımı
  çağrılan altı adın içeride bildirilmesi olanaklı kıldı.
- Kuralın nesnesi sınırdan geçtiğinde erişilebilir ad 10 ve bildirilmemiş ad 8, çıktı modeli
  geçtiğinde 4 ve 0; JSON karakteri 174'ten 81'e indi.

#### Sonraki Adım

Bu dersin bütün ölçüleri **halka** üzerindeydi. Halka bir kural birimidir, yayın birimi değildir:
sekiz dosyanın hangi kümesinin birlikte paketlendiği, hangisinin ayrı sürümlenebildiği ve bir
tüketicinin tek yetenek için kaç dosyaya bağlandığı hiçbir ölçüde görünmüyor. Sonraki ders birimi
halkadan **bileşene** taşır: aynı kitaplığı dağıtılabilir birimlere böler, her bileşenin dışa
açtığı ad sayısı ile iki bileşenin birlikte değişme oranını ölçer.

### Bileşen Tabanlı Mimari

Source: https://academia.sh/tr/kurslar/mimari-bicemler/bilesen-tabanli-mimari

Birimin halkadan yayınlanabilir bileşene taşınması: her bileşenin dışa açtığı ve kapsadığı ad sayısının sayılması, aynı dosyaların iki bölünmesinde birlikte yayın oranının karşılaştırılması, yüzeyi atlayan bir tüketicinin dışa açılan ad sayısını nasıl yükselttiğinin ölçülmesi ve yeni bir yeteneğin düzenlettiği, eklettiği, yeniden yayınlattığı parçaların sayılması.

Bir önceki dersin bütün ölçüleri halka üzerindeydi ve halka bir kural birimidir. Hangi dosya
kümesinin birlikte paketlendiği, hangisinin ayrı sürümlenebildiği ve bir tüketicinin tek bir
yetenek için kaç ada bağlandığı o ölçülerde görünmüyordu.

Bu ders birimi değiştirir. **Bileşen** (component) burada ayrı paketlenen, ayrı sürümlenen ve
dışa açtığı adları bildirilmiş bir dosya kümesidir; Bileşen Tabanlı Arayüz Geliştirme
kursundaki bileşen ise ekranda bir parçayı çizen bir görünüm işleviydi — aynı sözcüğün iki ayrı
birimi. Ölçüler de değişir: bir bileşenin dışa açtığı ad sayısı, kapsadığı ad sayısı, iki
bileşenin birlikte yayınlanma oranı ve yeni bir yeteneğin düzenlettiği dosya sayısı.

#### Yayın Birimi Olarak Bileşen

Bileşen sınırının nereden geçeceği Tasarım İlkeleri kursundaki Bileşen Uyum İlkeleri ve Bileşen
Bağlaşım İlkeleri derslerinde kurulmuştu: yeniden kullanım–yayın eşdeğerliği, ortak kapanış,
ortak yeniden kullanım ve kararlı bağımlılıklar. Burada o ilkeler yeniden anlatılmıyor, bir
düzen olarak uygulanıyor.

Düzenin taşıyıcı kararı şudur: her bileşen dışa açtığı adları tek bir dosyada bildirir, geri
kalanı kendi içinde tutar. Bu karar iki sayıyı ayırır. **Dışa açılan ad**, bileşenin dışındaki
bir dosyanın o bileşenden ithal ettiği addır. **Kapsanan ad**, bileşenin ihraç ettiği ama
dışından kimsenin ithal etmediği addır. İkincisi serbestlik payıdır: kapsanan bir adı
değiştirmek hiçbir tüketiciyi kırmaz.

#### Kitaplığın Bileşenleri

Kitaplık beş bileşene bölünüyor. Üçü ücretlendirme bağlamındadır (`ortak`, `bolge`, `ucret`),
biri teslimat operasyonundadır (`rota`), biri de ikisini tek bir istekte birleştirir (`masa`).
Her bileşenin `dis.mjs` dosyası o bileşenin yüzeyini bildirir.

```sh
mkdir -p bilesenler/ortak bilesenler/bolge bilesenler/ucret bilesenler/rota bilesenler/masa
```

```js
// bilesenler/ortak/dis.mjs — ortak bileseni: para birimi cevrimi
export const kurus = (lira) => Math.round(lira * 100);
export const yazi = (k) => `${(k / 100).toFixed(2)} TL`;
```

```js
// bilesenler/bolge/esleme.mjs — bolge bileseni ici: posta kodu esdegeri ve katsayilar
export const POSTA_BOLGE = { 34: "yakin", "06": "orta", 35: "orta", 65: "uzak" };
export const KATSAYI = { yakin: 1, orta: 1.35, uzak: 1.8 };
export const bolgeBul = (postaKodu) => POSTA_BOLGE[postaKodu.slice(0, 2)] ?? "uzak";
```

```js
// bilesenler/bolge/dis.mjs — bolge bileseninin disa actigi yuzey
import { KATSAYI, bolgeBul } from "./esleme.mjs";

export const bolge = (postaKodu) => bolgeBul(postaKodu);
export const katsayi = (postaKodu) => KATSAYI[bolgeBul(postaKodu)];
```

```js
// bilesenler/ucret/tarife.mjs — ucret bileseni ici: kademeler, asgari tutar, yuvarlama
import { kurus } from "../ortak/dis.mjs";

export const KADEMELER = [[1, 49.9], [5, 84.9], [15, 149.9], [30, 249.9]]
  .map(([ustAgirlik, lira]) => ({ ustAgirlik, ucret: kurus(lira) }));
export const ASGARI = kurus(39.9);
export const yuvarla = (k) => Math.round(k / 50) * 50;
```

```js
// bilesenler/ucret/kural.mjs — ucret bileseni ici: ucret kurali
import { ASGARI, KADEMELER, yuvarla } from "./tarife.mjs";

export const ucret = (gonderi, katsayi) => {
  const kademe = KADEMELER.find((k) => gonderi.agirlik <= k.ustAgirlik);
  if (kademe === undefined) throw new RangeError("agirlik kademesi yok");
  return Math.max(yuvarla(kademe.ucret * katsayi), ASGARI);
};
```

```js
// bilesenler/ucret/dis.mjs — ucret bileseninin disa actigi yuzey
import { katsayi } from "../bolge/dis.mjs";
import { yazi } from "../ortak/dis.mjs";
import { ucret } from "./kural.mjs";

export const teklifVer = (gonderi) => {
  const tutar = ucret(gonderi, katsayi(gonderi.postaKodu));
  return { tutar, gosterim: yazi(tutar) };
};
```

```js
// bilesenler/rota/tasiyici.mjs — rota bileseni ici: tasiyici listesi ve kapsamlari
export const TASIYICILAR = [
  { ad: "kuzey", kapsam: ["yakin", "orta"], aktarma: 1 },
  { ad: "genel", kapsam: ["yakin", "orta", "uzak"], aktarma: 2 },
];
export const uygunlar = (b) => TASIYICILAR.filter((t) => t.kapsam.includes(b));
```

```js
// bilesenler/rota/dis.mjs — rota bileseninin disa actigi yuzey
import { bolge } from "../bolge/dis.mjs";
import { uygunlar } from "./tasiyici.mjs";

export const rotaKur = (gonderi) => {
  const secilen = uygunlar(bolge(gonderi.postaKodu))
    .sort((x, y) => x.aktarma - y.aktarma)[0];
  if (secilen === undefined) throw new RangeError("uygun tasiyici yok");
  return { tasiyici: secilen.ad, aktarma: secilen.aktarma };
};
```

```js
// bilesenler/masa/dis.mjs — masa bileseni: dis istegi iki bilesene dagitir
import { rotaKur } from "../rota/dis.mjs";
import { teklifVer } from "../ucret/dis.mjs";

export const istek = (gonderi) => ({ ...teklifVer(gonderi), ...rotaKur(gonderi) });
```

#### Dışa Açılan ve Kapsanan Adlar

Araç her dosyanın ihraç ettiği adları ve ithal satırlarındaki adları okur, sonra aynı dosyaları
iki bölünmeye göre gruplar: **yeteneğe** göre (dizin adı) ve **role** göre (`dis.mjs` yüzey,
`kural.mjs` kural, geri kalanı veri). Dosyalar ve ithaller iki bölünmede aynıdır; değişen yalnız
sınırın nereden geçtiğidir.

```js
// yuzey-olc.mjs — her birimin ihrac ettigi, disa actigi ve kapsadigi ad sayisini olcer
import { readdirSync, readFileSync } from "node:fs";
import { join, normalize } from "node:path";

const IHRAC = /^export const (\w+)/gm;
const ITHAL = /^import\s*\{([^}]*)\}\s*from\s+"([^"]+)"/gm;
export const BOLUNME = {
  yetenek: (f) => f.dizin,
  rol: (f) => (f.ad === "dis.mjs" ? "arayuz" : f.ad === "kural.mjs" ? "kural" : "veri"),
};

export function oku(kok) {
  const dosyalar = [];
  for (const dizin of readdirSync(kok).sort()) {
    for (const ad of readdirSync(join(kok, dizin)).sort()) {
      const metin = readFileSync(join(kok, dizin, ad), "utf8");
      dosyalar.push({
        yol: `${dizin}/${ad}`, dizin, ad,
        ihrac: [...metin.matchAll(IHRAC)].map((m) => m[1]),
        ithal: [...metin.matchAll(ITHAL)].map((m) => ({
          adlar: m[1].split(",").map((s) => s.trim()).filter((s) => s !== ""),
          hedef: normalize(join(dizin, m[2])),
        })),
      });
    }
  }
  return dosyalar;
}

if (import.meta.filename === process.argv[1]) {
  const kok = process.argv[2];
  const dosyalar = oku(kok);
  const toplamIhrac = dosyalar.reduce((n, f) => n + f.ihrac.length, 0);
  console.log(`${kok}/  dosya = ${dosyalar.length}, toplam ihrac = ${toplamIhrac}`);
  for (const [bad, birimi] of Object.entries(BOLUNME)) {
    const ihrac = new Map();
    const disa = new Map();
    for (const f of dosyalar) {
      ihrac.set(birimi(f), (ihrac.get(birimi(f)) ?? 0) + f.ihrac.length);
      disa.set(birimi(f), disa.get(birimi(f)) ?? new Set());
    }
    for (const f of dosyalar) {
      for (const i of f.ithal) {
        const hedef = dosyalar.find((d) => d.yol === i.hedef);
        if (hedef === undefined || birimi(hedef) === birimi(f)) continue;
        for (const n of i.adlar) disa.get(birimi(hedef)).add(n);
      }
    }
    console.log(`  ${bad} bolunmesi (${ihrac.size} birim)`);
    let td = 0;
    for (const b of [...ihrac.keys()].sort()) {
      const d = disa.get(b).size;
      td += d;
      console.log(`    ${b.padEnd(8)} ihrac ${String(ihrac.get(b)).padStart(2)}`
        + `  disa acilan ${String(d).padStart(2)}  kapsanan ${String(ihrac.get(b) - d).padStart(2)}`);
    }
    console.log(`    ${"toplam".padEnd(8)} ihrac ${String(toplamIhrac).padStart(2)}`
      + `  disa acilan ${String(td).padStart(2)}  kapsanan ${String(toplamIhrac - td).padStart(2)}`);
  }
}
```

```sh
node yuzey-olc.mjs bilesenler
```

```
bilesenler/  dosya = 9, toplam ihrac = 16
  yetenek bolunmesi (5 birim)
    bolge    ihrac  5  disa acilan  2  kapsanan  3
    masa     ihrac  1  disa acilan  0  kapsanan  1
    ortak    ihrac  2  disa acilan  2  kapsanan  0
    rota     ihrac  3  disa acilan  1  kapsanan  2
    ucret    ihrac  5  disa acilan  1  kapsanan  4
    toplam   ihrac 16  disa acilan  6  kapsanan 10
  rol bolunmesi (3 birim)
    arayuz   ihrac  7  disa acilan  1  kapsanan  6
    kural    ihrac  1  disa acilan  1  kapsanan  0
    veri     ihrac  8  disa acilan  6  kapsanan  2
    toplam   ihrac 16  disa acilan  8  kapsanan  8
```

On altı ihraç edilen addan yetenek bölünmesinde 6'sı dışa açılıyor ve 10'u kapsanıyor; rol
bölünmesinde 8'i dışa açılıyor, 8'i kapsanıyor. Rol bölünmesinde `veri` birimi sekiz adından
altısını dışa açmak zorundadır, çünkü kuralı ve yüzeyi ondan ayıran sınır tam o adların geçtiği
yerdedir. `ucret` bileşeni ise beş adından dördünü içinde tutuyor; dışarıya yalnız `teklifVer`
çıkıyor. `masa` bileşeninin dışa açılan adı 0: ona bağlı başka bir bileşen yok, yani Bileşen
Bağlaşım İlkeleri dersindeki kararsızlık ölçüsünün üst ucundadır.

#### Yüzeyi Atlayan Tüketici

Dışa açılan ad sayısı bileşenin tek başına özelliği değil, bileşen ile tüketicilerinin ortak
özelliğidir. Aşağıdaki blok ağacın bir kopyasına yüzeyleri atlayıp komşularının içine bağlanan
bir bileşen ekliyor.

```sh
cp -r bilesenler derin-agac
mkdir -p derin-agac/derin
cat > derin-agac/derin/dis.mjs <<'EOF'
// derin-agac/derin/dis.mjs — yuzeyi atlayip komsularin icine baglanan tuketici
import { KATSAYI, bolgeBul } from "../bolge/esleme.mjs";
import { yazi } from "../ortak/dis.mjs";
import { uygunlar } from "../rota/tasiyici.mjs";
import { ucret } from "../ucret/kural.mjs";

export const istek = (gonderi) => {
  const b = bolgeBul(gonderi.postaKodu);
  const tutar = ucret(gonderi, KATSAYI[b]);
  return { tutar, gosterim: yazi(tutar), tasiyici: uygunlar(b)[0].ad };
};
EOF
node yuzey-olc.mjs derin-agac | head -9
node -e "import('./derin-agac/derin/dis.mjs').then(m => console.log('derin tuketici ' + JSON.stringify(m.istek({ agirlik: 3, postaKodu: '06800' }))))"
```

```
derin-agac/  dosya = 10, toplam ihrac = 17
  yetenek bolunmesi (6 birim)
    bolge    ihrac  5  disa acilan  4  kapsanan  1
    derin    ihrac  1  disa acilan  0  kapsanan  1
    masa     ihrac  1  disa acilan  0  kapsanan  1
    ortak    ihrac  2  disa acilan  2  kapsanan  0
    rota     ihrac  3  disa acilan  2  kapsanan  1
    ucret    ihrac  5  disa acilan  2  kapsanan  3
    toplam   ihrac 17  disa acilan 10  kapsanan  7
derin tuketici {"tutar":11450,"gosterim":"114.50 TL","tasiyici":"kuzey"}
```

Tüketici doğru çalışıyor ve yüzeyden geçen yolla aynı tutarı üretiyor: 11450 kuruş. Değişen şey
sınırdır. Dışa açılan ad 6'dan 10'a çıktı, kapsanan ad 10'dan 7'ye indi; `bolge` bileşeninin
kapsanan adı 3'ten 1'e düştü. Tek bir dosya, dokunmadığı üç bileşenin serbestlik payını harcadı.
Ortak yeniden kullanım ilkesinin ölçülebilir karşılığı budur: derin ithal, tüketiciyi bileşenin
sürüm sözleşmesinin dışında kalan adlara bağlar.

#### Birlikte Yayın Oranı

İkinci ölçü yayınla ilgilidir. Bir dosya değiştiğinde onu ithal eden her dosya yeniden sınanır ve
yayınlanır, bu yüzden bir değişikliğin yeniden yayınlattığı birim kümesi ithal grafiğinden
çıkarılabilir. İki birimin **birlikte yayın oranı**, ikisini birlikte yayınlatan dosya sayısının
en az birini yayınlatan dosya sayısına oranıdır.

```js
// yayin-olc.mjs — bir dosya degisince hangi birimlerin yeniden yayinlanmasi gerektigini sayar
import { BOLUNME, oku } from "./yuzey-olc.mjs";

const [kok, hedef] = process.argv.slice(2);
const dosyalar = oku(kok);
const ithalEden = new Map(dosyalar.map((f) => [f.yol, []]));
for (const f of dosyalar) {
  for (const i of f.ithal) if (ithalEden.has(i.hedef)) ithalEden.get(i.hedef).push(f.yol);
}

function kapanis(yol) {
  const gorulen = new Set([yol]);
  for (const y of gorulen) for (const k of ithalEden.get(y)) gorulen.add(k);
  return gorulen;
}

for (const [bad, birimi] of Object.entries(BOLUNME)) {
  const birimOf = new Map(dosyalar.map((f) => [f.yol, birimi(f)]));
  const etkilenen = dosyalar.map((f) => new Set([...kapanis(f.yol)].map((y) => birimOf.get(y))));
  const birimler = [...new Set(birimOf.values())].sort();
  console.log(`${kok}/ ${bad} bolunmesi (${birimler.length} birim)`);
  for (const b of birimler) {
    console.log(`  ${b.padEnd(8)} dosya ${dosyalar.filter((f) => birimi(f) === b).length}`
      + `  yeniden yayinlatan dosya ${etkilenen.filter((e) => e.has(b)).length} / ${dosyalar.length}`);
  }
  let toplam = 0, cift = 0, enYuksek = ["yok", 0];
  for (let i = 0; i < birimler.length; i += 1) {
    for (let j = i + 1; j < birimler.length; j += 1) {
      const [a, b] = [birimler[i], birimler[j]];
      const oran = etkilenen.filter((e) => e.has(a) && e.has(b)).length
        / etkilenen.filter((e) => e.has(a) || e.has(b)).length;
      toplam += oran;
      cift += 1;
      if (oran > enYuksek[1]) enYuksek = [`${a}+${b}`, oran];
    }
  }
  console.log(`  cift ${cift}, ortalama birlikte yayin orani ${(toplam / cift).toFixed(2)}`
    + `, en yuksek ${enYuksek[0]} ${enYuksek[1].toFixed(2)}`);
  if (hedef !== undefined) {
    const kume = new Set([...kapanis(hedef)].map((y) => birimOf.get(y)));
    const dosyaSayisi = dosyalar.filter((f) => kume.has(birimi(f))).length;
    console.log(`  ${hedef} degisince: ${kume.size} / ${birimler.length} birim,`
      + ` ${dosyaSayisi} / ${dosyalar.length} dosya  (${[...kume].sort().join(", ")})`);
  }
}
```

```sh
node yayin-olc.mjs bilesenler
```

```
bilesenler/ yetenek bolunmesi (5 birim)
  bolge    dosya 2  yeniden yayinlatan dosya 2 / 9
  masa     dosya 1  yeniden yayinlatan dosya 9 / 9
  ortak    dosya 1  yeniden yayinlatan dosya 1 / 9
  rota     dosya 2  yeniden yayinlatan dosya 4 / 9
  ucret    dosya 3  yeniden yayinlatan dosya 6 / 9
  cift 10, ortalama birlikte yayin orani 0.27, en yuksek masa+ucret 0.67
bilesenler/ rol bolunmesi (3 birim)
  arayuz   dosya 5  yeniden yayinlatan dosya 9 / 9
  kural    dosya 1  yeniden yayinlatan dosya 3 / 9
  veri     dosya 3  yeniden yayinlatan dosya 4 / 9
  cift 3, ortalama birlikte yayin orani 0.39, en yuksek arayuz+veri 0.44
```

Ortalama birlikte yayın oranı yetenek bölünmesinde 0.27, rol bölünmesinde 0.39. Daha keskin olan
sayı tablodadır: iki bölünmede de bir birim dokuz dosyanın dokuzunda yeniden yayınlanıyor, ama
yetenek bölünmesinde o birim 1 dosyalık `masa`, rol bölünmesinde 5 dosyalık `arayuz`. Her
değişiklikte yeniden yayınlanan parça beş katına çıkıyor. `bolge` bileşeni ise dokuz dosyanın
yalnız ikisinde yeniden yayınlanıyor; ortak kapanış ilkesinin karşılığı bu satırdır.

#### Yeni Bir Yetenek

Üçüncü ölçü uzantı maliyetidir. Ücretlendirmeye yakıt ek ücreti ekleniyor: oran kendi dosyasına
giriyor, kural onu uyguluyor.

```sh
cp -r bilesenler yakitli
cat > yakitli/ucret/yakit.mjs <<'EOF'
// yakitli/ucret/yakit.mjs — ucret bileseni ici: yakit ek ucreti orani
export const YAKIT_ORANI = 0.06;
export const yakitEkle = (k) => k * (1 + YAKIT_ORANI);
EOF
cat > yakitli/ucret/kural.mjs <<'EOF'
// yakitli/ucret/kural.mjs — ucret bileseni ici: ucret kurali, yakit ek ucretiyle
import { ASGARI, KADEMELER, yuvarla } from "./tarife.mjs";
import { yakitEkle } from "./yakit.mjs";

export const ucret = (gonderi, katsayi) => {
  const kademe = KADEMELER.find((k) => gonderi.agirlik <= k.ustAgirlik);
  if (kademe === undefined) throw new RangeError("agirlik kademesi yok");
  return Math.max(yuvarla(yakitEkle(kademe.ucret * katsayi)), ASGARI);
};
EOF
diff -rq bilesenler yakitli
node yuzey-olc.mjs yakitli | head -8
node yayin-olc.mjs yakitli ucret/kural.mjs | grep degisince
node -e "import('./bilesenler/masa/dis.mjs').then(m => console.log('yakitsiz ' + JSON.stringify(m.istek({ agirlik: 3, postaKodu: '06800' }))))"
node -e "import('./yakitli/masa/dis.mjs').then(m => console.log('yakitli  ' + JSON.stringify(m.istek({ agirlik: 3, postaKodu: '06800' }))))"
```

```
Files bilesenler/ucret/kural.mjs and yakitli/ucret/kural.mjs differ
Only in yakitli/ucret: yakit.mjs
yakitli/  dosya = 10, toplam ihrac = 18
  yetenek bolunmesi (5 birim)
    bolge    ihrac  5  disa acilan  2  kapsanan  3
    masa     ihrac  1  disa acilan  0  kapsanan  1
    ortak    ihrac  2  disa acilan  2  kapsanan  0
    rota     ihrac  3  disa acilan  1  kapsanan  2
    ucret    ihrac  7  disa acilan  1  kapsanan  6
    toplam   ihrac 18  disa acilan  6  kapsanan 12
  ucret/kural.mjs degisince: 2 / 5 birim, 5 / 10 dosya  (masa, ucret)
  ucret/kural.mjs degisince: 2 / 3 birim, 6 / 10 dosya  (arayuz, kural)
yakitsiz {"tutar":11450,"gosterim":"114.50 TL","tasiyici":"kuzey","aktarma":1}
yakitli  {"tutar":12150,"gosterim":"121.50 TL","tasiyici":"kuzey","aktarma":1}
```

Değişiklik bir dosyayı düzenledi ve bir dosya ekledi. `ucret` bileşeninin ihraç ettiği ad 5'ten
7'ye çıktı ama dışa açılan adı 1'de kaldı: yeni iki ad kapsanan tarafa yazıldı, yüzey değişmedi
ve hiçbir tüketici düzenlenmedi. Yeniden yayınlanan parça yetenek bölünmesinde 5 birimin 2'si ve
10 dosyanın 5'i, rol bölünmesinde 3 birimin 2'si ve 10 dosyanın 6'sı. Teslimat operasyonu tarafına
dokunulmadı: iki koşumda `tasiyici` ve `aktarma` alanları aynı kaldı, yalnız tutar 11450'den
12150'ye çıktı.

Bedelin sayısı da burada. Beş bileşen beş yüzey dosyası ve beş sürüm demektir; tek ad ihraç eden
`masa` için bile ayrı bir yayın birimi tutuluyor. Yüzeyi daraltmak kapsanan ad sayısını yükseltir,
bileşen sayısını artırmak yayınlanacak birim sayısını yükseltir — sürdürülebilirlik ile dağıtımın
basitliği arasındaki ödünleşim bu iki sayıdır.

#### Özet

- Bileşen burada ayrı paketlenen, ayrı sürümlenen ve dışa açtığı adları bir yüzey dosyasında
  bildirilmiş bir dosya kümesidir; kapsanan ad sayısı o bileşenin serbestlik payıdır.
- Aynı dokuz dosya yeteneğe göre bölündüğünde 16 addan 6'sı dışa açılıyor, role göre bölündüğünde
  8'i; `ucret` beş adından dördünü, `veri` sekiz adından yalnız ikisini içinde tutuyor.
- Yüzeyleri atlayan tek bir tüketici bileşeni dışa açılan adı 6'dan 10'a çıkardı ve `bolge`
  bileşeninin kapsanan adını 3'ten 1'e indirdi; ürettiği tutar yüzeyden geçen yolla aynıydı.
- Ortalama birlikte yayın oranı yeteneğe göre 0.27, role göre 0.39; her değişiklikte yeniden
  yayınlanan birim yetenek bölünmesinde 1 dosyalık, rol bölünmesinde 5 dosyalık.
- Yakıt ek ücreti 1 dosya düzenletti, 1 dosya eklettirdi, dışa açılan adı 6'da bıraktı ve yetenek
  bölünmesinde 5 birimin 2'sini, rol bölünmesinde 3 birimin 2'sini yeniden yayınlattı.

#### Sonraki Adım

Bileşen sınırı yeniden kullanım birimini verdi: `ucret` bileşenine yeni bir yetenek eklendiğinde
yüzey değişmedi ve dört bileşene dokunulmadı. Ama bu ölçülerin hepsi **yayın** zamanındadır. Bu
birimlerden hangisi program **koşarken** takılıp çıkarılabilir? `masa` bileşeni iki bileşeni adıyla
ithal ediyor, dolayısıyla üçüncü bir yeteneği eklemek için yine düzenlenmesi gerekir; bileşen
sınırı bu soruyu yanıtlamıyor. Sonraki ders birimleri iki sınıfa böler: değişmeyen ve tek başına
koşabilen bir **çekirdek** ile ona kayıtla takılan **eklentiler**. Ölçüler de değişir: çekirdeğin
eklentiler hakkında bildiği ad sayısı, yeni bir eklentinin çekirdekte düzenlettiği satır sayısı ve
eklentisiz çekirdeğin koşup koşmadığı.

### Mikroçekirdek ve Eklenti Mimarisi

Source: https://academia.sh/tr/kurslar/mimari-bicemler/mikrocekirdek-ve-eklenti-mimarisi

Değişmeyen küçük bir çekirdek ile sayısı önceden bilinmeyen eklentilerin ayrılması: çekirdek dosyasının eklentiler hakkında bildiği ad sayısının dörtten sıfıra inmesi, beşinci eklenti eklendiğinde çekirdekte düzenlenen satır sayısının ölçülmesi, eklentisiz çekirdeğin tek dosyalık kapanışla koşması ve sözleşmeyi bozan bir eklentinin iki düzende kaç isteği durdurduğunun sayılması.

Bileşen sınırı yeniden kullanım birimini verdi, ama o ölçülerin hepsi yayın zamanındaydı. Açıkta
kalan soru şudur: bu birimlerden hangisi program **koşarken** takılıp çıkarılabilir. Sınır bir
yayın kararıdır, takılıp çıkarılabilirlik bir çalışma zamanı kararı; ikisi aynı kümeyi vermez.

Bu ders birimleri iki sınıfa bölen düzeni ele alır: değişmeyen, tek başına koşabilen küçük bir
parça ile onun üzerine takılan, sayısı önceden bilinmeyen parçalar. Dört şey ölçülür: küçük
parçanın ötekiler hakkında bildiği ad sayısı, yeni bir parçanın düzenlettiği satır sayısı,
parçasız çekirdeğin koşup koşmadığı ve bozuk bir parçanın bütünü durdurup durdurmadığı.

#### Çekirdek, Eklenti ve Sözleşme

**Mikroçekirdek** (microkernel) düzeni üç şey ilan eder. Birincisi **çekirdektir**: hiçbir eklenti
takılmasa bile bir yanıt üretebilen en küçük çalışan bütün. Linux'a Giriş kursunda geçen çekirdek
işletim sisteminin donanımı yöneten parçasıydı; buradaki çekirdek bir kitaplığın en küçük çalışan
parçasıdır ve iki kullanım aynı şey değildir. İkincisi **eklentidir** (plugin): çekirdeğin
yeteneğini genişleten, çekirdeğin adını bilmediği birim. Üçüncüsü **eklenti sözleşmesidir**:
eklentinin karşılaması gereken adların bildirimi.

Bu sözleşme yeni bir kavram değil, bir **bağlantı noktasıdır**; Alan Odaklı Tasarım kursundaki
Bağlantı Noktaları ve Bağdaştırıcılar dersinde kurulmuştur. Bileşen sınırı ile bileşen bölme
ilkeleri de Tasarım İlkeleri kursundaki Bileşen Uyum İlkeleri ve Bileşen Bağlaşım İlkeleri
derslerinde kurulmuştu. Burada anlatılan ilke değil **düzen**: sözleşme bir çalışma zamanı
kaydına bağlandığında hangi sayıların değiştiği. Belirleyici kısıt tek cümledir — çekirdek
hiçbir eklentinin adını taşımaz, yalnız sözleşmenin adlarını taşır.

#### Kitaplığın Eklentili Düzeni

Ücret hesabı iki parçaya bölünüyor. Çekirdek kademe tablosundan tabanı bulur, yuvarlar ve asgari
ücreti uygular; tabana eklenen ya da tabandan düşen her kalem bir eklentidir. Dört eklenti aynı
sözleşmeyi karşılıyor: `ad`, okuduğu gönderi alanlarını bildiren `alanlar` ve kuruş döndüren
`kalem`. Son dosya kitaplığın değil ölçümün parçasıdır.

```sh
mkdir -p mikrocekirdek/eklentiler
cat > mikrocekirdek/tarife.mjs <<'EOF'
// mikrocekirdek/tarife.mjs — kademe tablosu: eklenti degil, cekirdegin kurulus girdisi
export const KADEMELER = [
  { ustAgirlik: 1, ucret: 4990 }, { ustAgirlik: 5, ucret: 8490 },
  { ustAgirlik: 15, ucret: 14990 }, { ustAgirlik: 30, ucret: 24990 },
];
EOF
cat > mikrocekirdek/eklentiler/sozlesme-indirimi.mjs <<'EOF'
// mikrocekirdek/eklentiler/sozlesme-indirimi.mjs — sozlesme oranina gore eksi kalem
const ORAN = { yok: 0, standart: 0.05, hacimli: 0.12 };

export const sozlesmeIndirimi = { ad: "sozlesme-indirimi", alanlar: ["sozlesme"],
  kalem: (g, taban) => -Math.round(taban * (ORAN[g.sozlesme] ?? 0)) };
EOF
cat > mikrocekirdek/eklentiler/hacim-farki.mjs <<'EOF'
// mikrocekirdek/eklentiler/hacim-farki.mjs — hacimsel agirligin gercek agirligi astigi kadar
const HACIM_BOLENI = 5000;
const KG_UCRETI = 900;

export const hacimFarki = { ad: "hacim-farki", alanlar: ["agirlik", "hacim"],
  kalem: (g) => Math.round(Math.max(0, g.hacim / HACIM_BOLENI - g.agirlik) * KG_UCRETI) };
EOF
cat > mikrocekirdek/eklentiler/sigorta-ucreti.mjs <<'EOF'
// mikrocekirdek/eklentiler/sigorta-ucreti.mjs — beyan degerinin binde dordu
const SIGORTA_ORANI = 0.004;

export const sigortaUcreti = { ad: "sigorta-ucreti", alanlar: ["beyanDegeri"],
  kalem: (g) => Math.round(g.beyanDegeri * SIGORTA_ORANI) };
EOF
cat > mikrocekirdek/eklentiler/bolge-farki.mjs <<'EOF'
// mikrocekirdek/eklentiler/bolge-farki.mjs — bolgeye gore sabit ek kalem
const FARK = { yakin: 0, orta: 1500, uzak: 4200 };

export const bolgeFarki = { ad: "bolge-farki", alanlar: ["bolge"],
  kalem: (g) => FARK[g.bolge] ?? FARK.uzak };
EOF
cat > ornek-gonderiler.mjs <<'EOF'
// ornek-gonderiler.mjs — butun olcumlerde kullanilan dort gonderi
export const GONDERILER = [
  { agirlik: 3, hacim: 24000, bolge: "orta", beyanDegeri: 150000, sozlesme: "standart" },
  { agirlik: 0.4, hacim: 1200, bolge: "yakin", beyanDegeri: 0, sozlesme: "yok" },
  { agirlik: 12, hacim: 90000, bolge: "uzak", beyanDegeri: 400000, sozlesme: "hacimli" },
  { agirlik: 28, hacim: 60000, bolge: "orta", beyanDegeri: 25000, sozlesme: "yok" },
];
EOF
```

Karşılaştırma iki çekirdek arasında yapılıyor. Birincisi dört bileşeni adıyla ithal eder ve
sırasını kendi içinde sabitler.

```js
// mikrocekirdek/gomulu.mjs — dort bileseni adiyla ithal eden ve sirasini kendi icinde sabitleyen cekirdek
import { sozlesmeIndirimi } from "./eklentiler/sozlesme-indirimi.mjs";
import { hacimFarki } from "./eklentiler/hacim-farki.mjs";
import { sigortaUcreti } from "./eklentiler/sigorta-ucreti.mjs";
import { bolgeFarki } from "./eklentiler/bolge-farki.mjs";

const ASGARI_UCRET = 3990;
const YUVARLAMA_ADIMI = 50;

export const gomuluCekirdek = (kademeler) => ({
  hesapla: (gonderi) => {
    const kademe = kademeler.find((k) => gonderi.agirlik <= k.ustAgirlik);
    if (kademe === undefined) throw new RangeError("agirlik kademesi yok");
    const taban = kademe.ucret;
    const toplam = taban
      + sozlesmeIndirimi.kalem(gonderi, taban)
      + hacimFarki.kalem(gonderi, taban)
      + sigortaUcreti.kalem(gonderi, taban)
      + bolgeFarki.kalem(gonderi, taban);
    const net = Math.round(toplam / YUVARLAMA_ADIMI) * YUVARLAMA_ADIMI;
    return { tutar: Math.max(net, ASGARI_UCRET), uygulanan: 4, dusen: [] };
  },
});
```

İkincisi eklenti kaydı tutar. Sözleşmeyi karşılamayan eklenti takma anında reddedilir; koşum
anında bozan eklenti o istekte düşürülür ve adı raporlanır.

```js
// mikrocekirdek/eklentili.mjs — cekirdek yalniz sozlesme adlarini bilir; hicbir eklenti adi gecmez
const ASGARI_UCRET = 3990;
const YUVARLAMA_ADIMI = 50;

export const SOZLESME = ["ad", "alanlar", "kalem"];

const karsilar = (e) => typeof e?.ad === "string"
  && Array.isArray(e?.alanlar) && typeof e?.kalem === "function";

export const cekirdek = (kademeler) => {
  const kayit = [];
  return {
    tak: (e) => (karsilar(e) ? (kayit.push(e), true) : false),
    takili: () => kayit.map((e) => e.ad),
    hesapla: (gonderi) => {
      const kademe = kademeler.find((k) => gonderi.agirlik <= k.ustAgirlik);
      if (kademe === undefined) throw new RangeError("agirlik kademesi yok");
      const taban = kademe.ucret;
      const dusen = [];
      let toplam = taban;
      for (const e of kayit) {
        let d = null;
        try { if (e.alanlar.every((a) => gonderi[a] !== undefined)) d = e.kalem(gonderi, taban); }
        catch { d = null; }
        if (Number.isFinite(d)) toplam += d; else dusen.push(e.ad);
      }
      const net = Math.round(toplam / YUVARLAMA_ADIMI) * YUVARLAMA_ADIMI;
      return { tutar: Math.max(net, ASGARI_UCRET), uygulanan: kayit.length - dusen.length, dusen };
    },
  };
};
```

Eklenti adlarının yazılı olduğu tek yer bileşim köküdür.

```js
// mikrocekirdek/kur.mjs — bilesim koku: cekirdegi kurar, eklentileri bildirilmis sirayla takar
import { cekirdek } from "./eklentili.mjs";
import { sozlesmeIndirimi } from "./eklentiler/sozlesme-indirimi.mjs";
import { hacimFarki } from "./eklentiler/hacim-farki.mjs";
import { sigortaUcreti } from "./eklentiler/sigorta-ucreti.mjs";
import { bolgeFarki } from "./eklentiler/bolge-farki.mjs";

export const EKLENTILER = [sozlesmeIndirimi, hacimFarki, sigortaUcreti, bolgeFarki];

export const kur = (kademeler, eklentiler = EKLENTILER) => {
  const c = cekirdek(kademeler);
  const red = eklentiler.filter((e) => c.tak(e) === false).length;
  return { c, red };
};
```

#### Çekirdeğin Bildiği Ad Sayısı

Betik üç dosyanın metninde kaç eklenti adının geçtiğini sayar, kapanışlarını çıkarır, eklentisiz
çekirdeği koşturur ve iki düzenin aynı tutarı verdiğini sınar.

```js
// cekirdek-olcum.mjs — cekirdek dosyalarinin bildigi eklenti adi, ithal kapanisi ve eklentisiz kosum
import { readFileSync } from "node:fs";
import { dirname, join, normalize } from "node:path";
import { KADEMELER } from "./mikrocekirdek/tarife.mjs";
import { gomuluCekirdek } from "./mikrocekirdek/gomulu.mjs";
import { cekirdek, SOZLESME } from "./mikrocekirdek/eklentili.mjs";
import { EKLENTILER, kur } from "./mikrocekirdek/kur.mjs";
import { GONDERILER } from "./ornek-gonderiler.mjs";

const ADLAR = EKLENTILER.map((e) => e.ad);

const kapanis = (kok, g = new Set()) => {
  if (g.has(kok)) return g;
  g.add(kok);
  const bul = readFileSync(kok, "utf8").matchAll(/^import\s.*?from\s+"(\.[^"]+)"/gm);
  for (const [, y] of bul) kapanis(normalize(join(dirname(kok), y)), g);
  return g;
};

console.log(`eklenti = ${ADLAR.length}, sozlesme adi = ${SOZLESME.length} (${SOZLESME.join(", ")})`);
for (const d of ["gomulu.mjs", "eklentili.mjs", "kur.mjs"]) {
  const metin = readFileSync(`mikrocekirdek/${d}`, "utf8");
  console.log(`  ${d.padEnd(14)} bildigi eklenti adi = ${ADLAR.filter((a) => metin.includes(a)).length}`
    + `  ithal kapanisi = ${kapanis(`mikrocekirdek/${d}`).size} dosya`);
}

const bos = cekirdek(KADEMELER).hesapla(GONDERILER[0]);
console.log(`\neklentisiz cekirdek: takili = 0, tutar = ${bos.tutar}, uygulanan = ${bos.uygulanan}`);
const cift = GONDERILER.map((g) => [gomuluCekirdek(KADEMELER).hesapla(g), kur(KADEMELER).c.hesapla(g)]);
console.log(`dort eklentiyle tutarlar = ${cift.map(([a]) => a.tutar).join(", ")}`
  + `  uygulanan = ${cift[0][1].uygulanan}`);
console.log(`ayrilan sonuc = ${cift.filter(([a, b]) => a.tutar !== b.tutar).length} / ${cift.length}`
  + `, reddedilen eklenti = ${kur(KADEMELER).red}`);
```

```sh
node cekirdek-olcum.mjs
```

```
eklenti = 4, sozlesme adi = 3 (ad, alanlar, kalem)
  gomulu.mjs     bildigi eklenti adi = 4  ithal kapanisi = 5 dosya
  eklentili.mjs  bildigi eklenti adi = 0  ithal kapanisi = 1 dosya
  kur.mjs        bildigi eklenti adi = 4  ithal kapanisi = 6 dosya

eklentisiz cekirdek: takili = 0, tutar = 8500, uygulanan = 0
dort eklentiyle tutarlar = 11800, 5000, 24400, 26600  uygulanan = 4
ayrilan sonuc = 0 / 4, reddedilen eklenti = 0
```

İlk üç satır düzeni ölçüyor. Gömülü çekirdek dört eklenti adı taşıyor ve kapanışı beş dosya:
kuralı yüklemek için dört eklentiyi de yüklemek gerekiyor. Eklentili çekirdek sıfır ad taşıyor
ve kapanışı bir dosya. Adlar yok olmadı, yer değiştirdi: bileşim kökü dördünü de biliyor ve
kapanışı altı dosya. Düzen bilme yükümlülüğünü çekirdekten alıp tek bir kuruluş dosyasına
yığıyor.

Son üç satır eklentisiz çekirdeğin koştuğunu gösteriyor: 3 kg'lık gönderi için taban 8.490 kuruş
yuvarlanarak 8.500 kuruş çıktı, uygulanan kalem 0. Dört eklentiyle aynı gönderi 11.800 kuruşa
çıkıyor ve dört gönderinin dördünde iki düzenin tutarı birebir aynı.

#### Beşinci Eklentinin Maliyeti

Uzantı maliyeti, yeni bir yetenek eklendiğinde var olan dosyalarda düzenlenen satır sayısıdır.
Beşinci eklenti tabanın yüzdesi olarak bir yakıt farkı ekliyor. Blok, ağacın bir kopyasında iki
düzene de aynı eklentiyi ekler; yama yol yorumlarını da düzelttiği için sayım ilk satırı atlıyor.

```sh
cp -r mikrocekirdek besinci
cat > besinci/eklentiler/yakit-farki.mjs <<'EOF'
// besinci/eklentiler/yakit-farki.mjs — besinci eklenti: tabanin yuzdesi olarak yakit farki
const YAKIT_ORANI = 0.085;

export const yakitFarki = { ad: "yakit-farki", alanlar: ["bolge"],
  kalem: (g, taban) => Math.round(taban * YAKIT_ORANI) };
EOF
python3 - <<'EOF'
from pathlib import Path

ITHAL = 'import { bolgeFarki } from "./eklentiler/bolge-farki.mjs";'
YAKIT = 'import { yakitFarki } from "./eklentiler/yakit-farki.mjs";'
YAMA = {
    "gomulu.mjs": [(ITHAL, f"{ITHAL}\n{YAKIT}"),
                   ("      + bolgeFarki.kalem(gonderi, taban);",
                    "      + bolgeFarki.kalem(gonderi, taban)\n      + yakitFarki.kalem(gonderi, taban);"),
                   ("uygulanan: 4", "uygulanan: 5"), ("dort bileseni", "bes bileseni")],
    "kur.mjs": [(ITHAL, f"{ITHAL}\n{YAKIT}"), ("bolgeFarki];", "bolgeFarki, yakitFarki];")],
}
for yol in sorted(Path("besinci").rglob("*.mjs")):
    metin = yol.read_text().replace("// mikrocekirdek/", "// besinci/")
    for eski, yeni in YAMA.get(yol.name, []):
        metin = metin.replace(eski, yeni)
    yol.write_text(metin)
EOF
for d in gomulu.mjs eklentili.mjs kur.mjs; do
  f=$(diff <(tail -n +2 mikrocekirdek/$d) <(tail -n +2 besinci/$d))
  echo "$d  cikan satir = $(echo "$f" | grep -c '^<')  eklenen satir = $(echo "$f" | grep -c '^>')"
done
echo "eklenen dosya = $(( $(find besinci -name '*.mjs' | wc -l) - $(find mikrocekirdek -name '*.mjs' | wc -l) ))"
cat > besinci-kontrol.mjs <<'EOF'
// besinci-kontrol.mjs — yamali agacta iki duzen ayni tutari uretiyor mu
import { KADEMELER } from "./besinci/tarife.mjs";
import { gomuluCekirdek } from "./besinci/gomulu.mjs";
import { kur } from "./besinci/kur.mjs";
import { GONDERILER } from "./ornek-gonderiler.mjs";
const iki = [gomuluCekirdek(KADEMELER), kur(KADEMELER).c].map((c) => c.hesapla(GONDERILER[0]).tutar);
console.log(`besinci eklentiyle gomulu = ${iki[0]}, eklentili = ${iki[1]}`);
EOF
node besinci-kontrol.mjs
```

```
gomulu.mjs  cikan satir = 2  eklenen satir = 4
eklentili.mjs  cikan satir = 0  eklenen satir = 0
kur.mjs  cikan satir = 1  eklenen satir = 2
eklenen dosya = 1
besinci eklentiyle gomulu = 12500, eklentili = 12500
```

Aynı yetenek iki düzende de bir dosya ekletti ve iki düzen aynı tutarı verdi, ama düzenlenen
satırların yeri farklı. Gömülü çekirdekte üç ayrı yer değişti — ithal satırı, toplama terimi ve
uygulanan sayısı — 2 satır çıktı, 4 satır girdi. Eklentili çekirdek dosyasında 0 satır değişti;
değişen tek dosya bileşim kökü oldu. Ölçü çekirdeğin **kapalı** olduğunu söylüyor: yeni bir kalem
türü çekirdeği yeniden sınamayı ve yayınlamayı gerektirmiyor.

#### Sözleşmeyi Bozan Eklenti

Takılıp çıkarılabilirliğin ikinci yüzü, takılan şeyin bozuk olabilmesidir. Blok, sigorta
eklentisini imzası doğru, davranışı bozuk bir eklentiyle değiştiriyor.

```sh
cp -r mikrocekirdek bozuk
cat > bozuk/eklentiler/sigorta-ucreti.mjs <<'EOF'
// bozuk/eklentiler/sigorta-ucreti.mjs — sozlesmeyi kosum aninda bozan eklenti: sayi yerine hata atiyor
export const sigortaUcreti = { ad: "sigorta-ucreti", alanlar: ["beyanDegeri"],
  kalem: () => { throw new TypeError("beyan degeri okunamadi"); } };
EOF
```

```js
// dayaniklilik.mjs — sozlesmeyi bozan eklentinin iki duzende kac istegi durdurdugunu sayar
import { KADEMELER } from "./mikrocekirdek/tarife.mjs";
import { gomuluCekirdek as saglamGomulu } from "./mikrocekirdek/gomulu.mjs";
import { EKLENTILER, kur as saglamKur } from "./mikrocekirdek/kur.mjs";
import { gomuluCekirdek as bozukGomulu } from "./bozuk/gomulu.mjs";
import { kur as bozukKur } from "./bozuk/kur.mjs";
import { GONDERILER } from "./ornek-gonderiler.mjs";

const DUZENLER = [
  ["gomulu    / saglam", () => saglamGomulu(KADEMELER)],
  ["gomulu    / bozuk ", () => bozukGomulu(KADEMELER)],
  ["eklentili / saglam", () => saglamKur(KADEMELER).c],
  ["eklentili / bozuk ", () => bozukKur(KADEMELER).c],
];

for (const [ad, yap] of DUZENLER) {
  const c = yap();
  let yanit = 0, ilk = "-", dusen = "-", hata = "";
  for (const g of GONDERILER) {
    try {
      const s = c.hesapla(g);
      if (++yanit === 1) { ilk = String(s.tutar); dusen = s.dusen.join(",") || "yok"; }
    } catch (h) { hata = hata || `${h.name}: ${h.message}`; }
  }
  console.log(`${ad}  yanitlanan = ${yanit}/${GONDERILER.length}  ilk tutar = ${ilk.padStart(6)}`
    + `  ${hata === "" ? `dusen = ${dusen}` : hata}`);
}

const kalemsiz = { ad: "yakit-farki", alanlar: ["bolge"] };
const { c, red } = saglamKur(KADEMELER, [...EKLENTILER, kalemsiz]);
console.log(`\nsozlesmeyi takma aninda bozan eklenti: reddedilen = ${red}, takili = ${c.takili().length}`);
console.log(`  takili adlar = ${c.takili().join(", ")}`);
console.log(`  ayni gonderinin tutari = ${c.hesapla(GONDERILER[0]).tutar}`);
```

```sh
node dayaniklilik.mjs
```

```
gomulu    / saglam  yanitlanan = 4/4  ilk tutar =  11800  dusen = yok
gomulu    / bozuk   yanitlanan = 0/4  ilk tutar =      -  TypeError: beyan degeri okunamadi
eklentili / saglam  yanitlanan = 4/4  ilk tutar =  11800  dusen = yok
eklentili / bozuk   yanitlanan = 4/4  ilk tutar =  11200  dusen = sigorta-ucreti

sozlesmeyi takma aninda bozan eklenti: reddedilen = 1, takili = 4
  takili adlar = sozlesme-indirimi, hacim-farki, sigorta-ucreti, bolge-farki
  ayni gonderinin tutari = 11800
```

Aynı bozuk dosya iki düzende iki ayrı sonuç veriyor. Gömülü çekirdekte dört isteğin dördü yanıtsız
kaldı: bileşen çağrısı hesabın gövdesinde olduğu için hata bütün hesabı durdurdu. Eklentili
çekirdekte dördü de yanıtlandı, bozuk eklenti düşürüldü ve adı raporlandı; tutar 11.200'e indi,
yani sigorta kalemi eksik. Bu doğru bir yanıt değil, **eksik** bir yanıttır ve raporlanması
eksikliği görülebilir kılar.

Son üç satır sözleşmenin öteki yüzü. `kalem` alanı olmayan eklenti takma anında reddedildi:
reddedilen 1, takılı 4 ve tutar 11.800'de kaldı. İki savunma noktası var — biri imzayı, öteki
dönen değeri sınıyor — ve ikisi de eklenti adını bilmiyor.

#### Ödünleşimin Sayıları

Kazanç iki **kalite niteliğinde** ölçüldü. Değiştirilebilirlikte: çekirdeğin bildiği eklenti adı
4'ten 0'a, kapanışı 5 dosyadan 1 dosyaya indi ve beşinci eklenti çekirdekte 0 satır düzenletti.
Kullanılabilirlikte: yanıtlanan istek 0/4'ten 4/4'e çıktı.

Bedel üç kalemde yazılıyor. Birincisi yer değiştirmedir: dört ad bileşim kökünde toplandı ve o
dosyanın kapanışı 6 dosya oldu, sıra artık yalnız o dosyadan okunur. İkincisi eksik yanıttır: çekirdeğin ayakta kalması sonucun
tam olduğu anlamına gelmez. Üçüncüsü sözleşmedir — üç ad her eklentide tekrarlanır ve sözleşme
değiştiği gün beş dosya birlikte değişir.

#### Özet

- Mikroçekirdek düzeni üç şey ilan eder: eklentisiz koşabilen bir çekirdek, sayısı önceden
  bilinmeyen eklentiler ve eklentinin karşılaması gereken adları bildiren sözleşme; sözleşme bir
  bağlantı noktasıdır.
- Gömülü çekirdek 4 eklenti adı taşıyor ve kapanışı 5 dosya; eklentili çekirdek 0 ad taşıyor ve
  kapanışı 1 dosya. Adlar kapanışı 6 dosya olan bileşim köküne taşındı.
- Eklentisiz çekirdek 3 kg'lık gönderi için 8.500 kuruş üretti, dört eklentiyle 11.800 kuruş
  çıktı ve dört gönderinin dördünde iki düzenin tutarı aynı.
- Beşinci eklenti iki düzende de 1 dosya ekletti; gömülü çekirdekte 2 satır çıkıp 4 satır girdi,
  eklentili çekirdek dosyasında 0 satır değişti.
- Koşum anında sözleşmeyi bozan eklentiyle yanıtlanan istek gömülü düzende 0/4, eklentili düzende
  4/4 oldu; takma anında sözleşmeyi karşılamayan eklenti reddedildi.

#### Sonraki Adım

Bu dersin bütün ölçüleri **çekirdeğin** ne bildiği üzerineydi. Eklentiler birbirini hiç
tanımıyordu, ama hepsi çekirdek tarafından biliniyordu: sıranın ne olduğu, kimin çağrılacağı ve
sonucun nasıl toplanacağı tek bir yerde duruyordu. Sonraki ders o tek yeri de kaldırır. Birimler
ne birbirini ne kendilerini çağıran bir merkezi tanır; ortak bir bilgi alanına yazar, oradan okur
ve buluşmayı alanın kendisi sağlar. Bunun adı **kara tahtadır** ve üç şey ölçülür: birimlerin
birbiri hakkında bildiği ad sayısı, aynı sonuca ulaşmak için yapılan gereksiz iş — kaç birim aynı
veriyi okudu, kaç tur boşa döndü — ve aynı girdinin her koşumda aynı çıktıyı verip vermediği.

### Kara Tahta Mimarisi

Source: https://academia.sh/tr/kurslar/mimari-bicemler/kara-tahta-mimarisi

Birbirini tanımayan çözümleyicilerin ortak bir bilgi alanı üzerinden buluşması: bir adresten ücret bölgesinin belirlenmesinde birimlerin birbiri hakkında bildiği ad sayısının sıfır çıkması, aynı sonuca ulaşmak için harcanan fazladan okuma ve yoklama sayısı, yeni bir çözümleyicinin düzenlettiği dosya sayısı ve yirmi dört yoklama sırasında sonucun belirlenimci olup olmadığı.

Bir önceki dersin çekirdeği eklentilerin adını bilmiyordu, ama sıranın ne olduğunu ve sonucun nasıl
toplanacağını biliyordu.

Bu ders o merkezi de kaldırır. Birimler ne birbirini ne kendilerini çağıran bir düzeni tanır; ortak
bir bilgi alanına yazar, oradan okur ve buluşmayı alanın kendisi sağlar. Üç şey ölçülür: bildikleri
ad sayısı, aynı sonuç için harcanan fazladan iş — kaç birim aynı veriyi okudu, kaç tur boşa döndü —
ve aynı girdinin her koşumda aynı çıktıyı verip vermediği.

#### Kara Tahta, Çözümleyici ve Denetim

**Kara tahta** (blackboard) düzeni üç parçadan oluşur. Birincisi tahtadır: adlandırılmış kısmi
sonuçların ortak alanı. İkincisi **çözümleyicilerdir**: tahtadan okuyup tahtaya yazan,
birbirinin adını bilmeyen birimler. Üçüncüsü **denetim döngüsüdür**: hangi çözümleyicinin ne zaman
yoklanacağına karar veren ama ne yaptıklarını bilmeyen döngü.

Bu, gözlemci kalıbındaki bildirimden farklıdır: Tasarım Kalıpları kursundaki Gözlemci dersinde
yayıncı abone listesini tutar ve bildirimi doğrudan onlara gönderir, oysa kara tahtada yazan taraf
kimin okuyacağını bilmez. Ayırt edici kısıt şudur: bir çözümleyici yalnız tahtayı okuyup yazabilir,
başka bir çözümleyiciyi çağıramaz.

#### Belirsiz Bölge ve Dört Çözümleyici

Ücretin bağlı olduğu bölge, gelen adresten tek başına belirlenemiyor: posta kodunda bir hane eksik.
Dört çözümleyici aynı sözleşmeyi karşılıyor — `ad` ve `coz(oku)` — ve her biri kısmi bir sonuçla
bir **güven** derecesi döndürür. İl adı çözümleyicisi bölge iddiası yazmaz, yalnız normalize
edilmiş il adını yazar; posta kodu çözümleyicisi kod eksikse o ada gerek duyar.

```sh
mkdir -p kara-tahta/cozumleyiciler
cat > kara-tahta/ucret.mjs <<'EOF'
// kara-tahta/ucret.mjs — bolgeden ucrete: ayni gonderi, bolgeye gore katsayi
const TABAN = 8490;
const ADIM = 50;
const KATSAYI = { yakin: 1, orta: 1.35, uzak: 1.8 };

export const tutar = (bolge) => Math.round((TABAN * (KATSAYI[bolge] ?? KATSAYI.uzak)) / ADIM) * ADIM;
EOF
cat > kara-tahta/cozumleyiciler/il-adi.mjs <<'EOF'
// kara-tahta/cozumleyiciler/il-adi.mjs — il adini normalize eder; bolge iddiasi yazmaz
const ILLER = ["istanbul", "ankara", "izmir", "van"];

export const ilAdi = { ad: "il-adi", coz: (oku) => {
  const ad = oku("adres").il.toLowerCase();
  return ILLER.includes(ad) ? { guven: 0.7, yaz: { ilNormal: ad } } : null;
} };
EOF
cat > kara-tahta/cozumleyiciler/posta-kodu.mjs <<'EOF'
// kara-tahta/cozumleyiciler/posta-kodu.mjs — tam kod dogrudan; eksik kod il icindeki en uzak bolgeye duser
const ONEK = { 34: "yakin", "06": "orta", 35: "orta", 65: "uzak" };
const IL_EN_UZAK = { istanbul: "orta", ankara: "uzak", izmir: "uzak", van: "uzak" };

export const postaKodu = { ad: "posta-kodu", coz: (oku) => {
  const kod = oku("adres").postaKodu;
  if (kod.length === 5) return { guven: 0.85, yaz: { bolge: ONEK[kod.slice(0, 2)] ?? "uzak" } };
  const il = oku("ilNormal");
  return il === undefined ? null : { guven: 0.3, yaz: { bolge: IL_EN_UZAK[il] ?? "uzak" } };
} };
EOF
cat > kara-tahta/cozumleyiciler/koordinat.mjs <<'EOF'
// kara-tahta/cozumleyiciler/koordinat.mjs — merkeze kus ucusu yaklasik uzakligi esiklere vurur
const MERKEZ = { enlem: 41.02, boylam: 28.98 };
const ESIK = [[300, "yakin"], [900, "orta"]];

export const koordinat = { ad: "koordinat", coz: (oku) => {
  const a = oku("adres");
  const dEnlem = (a.enlem - MERKEZ.enlem) * 111;
  const dBoylam = (a.boylam - MERKEZ.boylam) * 111 * Math.cos((MERKEZ.enlem * Math.PI) / 180);
  const km = Math.round(Math.hypot(dEnlem, dBoylam));
  return { guven: 0.9, yaz: { bolge: ESIK.find(([s]) => km <= s)?.[1] ?? "uzak" } };
} };
EOF
cat > kara-tahta/cozumleyiciler/gecmis-gonderi.mjs <<'EOF'
// kara-tahta/cozumleyiciler/gecmis-gonderi.mjs — ayni musteriye kesilmis son gonderilerin bolgesi
const GECMIS = { "M-118": "yakin", "M-204": "orta" };

export const gecmisGonderi = { ad: "gecmis-gonderi", coz: (oku) => {
  const b = GECMIS[oku("adres").musteri];
  return b === undefined ? null : { guven: 0.55, yaz: { bolge: b } };
} };
EOF
```

#### Tahta, Denetim Döngüsü ve Doğrudan Karşılık

Tahta iki yazma kuralından birini alır: **ilk yazan kazanır** kuralında dolu bir anahtara ikinci
yazma reddedilir, **en güvenli kazanır** kuralında yalnız daha yüksek güvenli yazma geçer.

```js
// kara-tahta/tahta.mjs — ortak bilgi alani: yazma kurali, okuma ve yazma sayaclari
export const karaTahta = (kural, baslangic) => {
  const alan = new Map(Object.entries(baslangic));
  const guven = new Map();
  const okuyan = new Map();
  const sayac = { okuma: 0, yazma: 0, reddedilen: 0 };
  return {
    oku: (anahtar, kim) => {
      sayac.okuma += 1;
      okuyan.set(anahtar, (okuyan.get(anahtar) ?? new Set()).add(kim));
      return alan.get(anahtar);
    },
    yaz: (anahtar, deger, g) => {
      const dolu = alan.has(anahtar);
      const gecer = kural === "ilk-yazan" ? dolu === false : (guven.get(anahtar) ?? -1) < g;
      if (gecer === false) { sayac.reddedilen += 1; return; }
      alan.set(anahtar, deger);
      guven.set(anahtar, g);
      sayac.yazma += 1;
    },
    deger: (anahtar) => alan.get(anahtar),
    olcum: () => ({ ...sayac, okuyanBirim: Object.fromEntries([...okuyan].map(([a, s]) => [a, s.size])) }),
  };
};
```

Denetim döngüsü verilen sırayla yoklar: katkı veren çözümleyici bekleyenlerden çıkar, veremeyen
sonraki turda yeniden yoklanır ve döngü hiçbir katkının gelmediği bir tur görene kadar sürer.

```js
// kara-tahta/denetim.mjs — denetim dongusu: verilen sirayla yoklar, katki kalmayana kadar tur cevirir
export const denetimDongusu = (t, cozumleyiciler) => {
  const bekleyen = new Set(cozumleyiciler);
  let tur = 0, yoklama = 0, katki = 0, bosTur = 0;
  while (true) {
    tur += 1;
    let turKatkisi = 0;
    for (const c of cozumleyiciler) {
      if (bekleyen.has(c) === false) continue;
      yoklama += 1;
      const sonuc = c.coz((a) => t.oku(a, c.ad));
      if (sonuc === null) continue;
      bekleyen.delete(c);
      for (const [a, d] of Object.entries(sonuc.yaz)) t.yaz(a, d, sonuc.guven);
      katki += 1;
      turKatkisi += 1;
    }
    if (turKatkisi === 0) { bosTur += 1; break; }
  }
  return { tur, yoklama, katki, bosTur };
};
```

Karşılaştırılacak düzen aynı dört çözümleyiciyi doğrudan çağırır: sıra ve seçim kuralı bu dosyada
yazılıdır; ölçüm için sıra parametreleştirildi.

```js
// kara-tahta/dogrudan.mjs — dogrudan cagri: dort adi, sirayi ve secim kuralini bu dosya biliyor
import { ilAdi } from "./cozumleyiciler/il-adi.mjs";
import { postaKodu } from "./cozumleyiciler/posta-kodu.mjs";
import { koordinat } from "./cozumleyiciler/koordinat.mjs";
import { gecmisGonderi } from "./cozumleyiciler/gecmis-gonderi.mjs";

export const SIRA = [ilAdi, postaKodu, koordinat, gecmisGonderi];

export const dogrudanCoz = (adres, sira = SIRA) => {
  const veri = { adres };
  let okuma = 0, katki = 0, enIyi = { guven: -1, bolge: undefined };
  for (const c of sira) {
    const sonuc = c.coz((a) => (okuma += 1, veri[a]));
    if (sonuc === null) continue;
    katki += 1;
    Object.assign(veri, sonuc.yaz);
    const b = sonuc.yaz.bolge;
    if (b !== undefined && sonuc.guven > enIyi.guven) enIyi = { guven: sonuc.guven, bolge: b };
  }
  return { ...enIyi, tur: 1, yoklama: sira.length, katki, okuma };
};
```

```js
// kara-tahta/kur.mjs — bilesim koku: cozumleyici listesi ve olculen adres
import { postaKodu } from "./cozumleyiciler/posta-kodu.mjs";
import { ilAdi } from "./cozumleyiciler/il-adi.mjs";
import { koordinat } from "./cozumleyiciler/koordinat.mjs";
import { gecmisGonderi } from "./cozumleyiciler/gecmis-gonderi.mjs";

export const COZUMLEYICILER = [postaKodu, ilAdi, koordinat, gecmisGonderi];

export const ADRES = { postaKodu: "0680", il: "ANKARA", enlem: 39.93, boylam: 32.86, musteri: "M-118" };
```

#### Bilinen Ad ve Yapılan İş

Betik her dosyada kaç **başka** birim adının geçtiğini sayar ve iki düzeni koşturur.

```js
// ad-ve-is.mjs — birimlerin birbiri hakkinda bildigi ad sayisi ve iki duzenin yaptigi is
import { readdirSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { karaTahta } from "./kara-tahta/tahta.mjs";
import { denetimDongusu } from "./kara-tahta/denetim.mjs";
import { dogrudanCoz } from "./kara-tahta/dogrudan.mjs";
import { COZUMLEYICILER, ADRES } from "./kara-tahta/kur.mjs";
import { tutar } from "./kara-tahta/ucret.mjs";

const ADLAR = COZUMLEYICILER.map((c) => c.ad);
const dosyalar = readdirSync("kara-tahta", { recursive: true })
  .map((a) => join("kara-tahta", a)).filter((y) => y.endsWith(".mjs")).sort();

const tablo = dosyalar.map((yol) => {
  const metin = readFileSync(yol, "utf8");
  const kendi = ADLAR.find((a) => yol.includes(a));
  return [yol, ADLAR.filter((a) => a !== kendi && metin.includes(a))];
});
console.log(`dosya = ${tablo.length}, baska birim adi 0 olan = ${tablo.filter(([, b]) => b.length === 0).length}`);
for (const [yol, b] of tablo.filter(([, b]) => b.length > 0)) {
  console.log(`  ${yol.padEnd(23)} baska birim adi = ${b.length}  (${b.join(", ")})`);
}

const t = karaTahta("en-guvenli", { adres: ADRES });
const o = denetimDongusu(t, COZUMLEYICILER);
const m = t.olcum();
const b = t.deger("bolge");
console.log(`kara tahta     bolge = ${b} tutar = ${tutar(b)}`
  + `  tur = ${o.tur}  yoklama = ${o.yoklama}  katki = ${o.katki}  bos tur = ${o.bosTur}`);
console.log(`  okuma = ${m.okuma}  yazma = ${m.yazma}  reddedilen yazma = ${m.reddedilen}`
  + `  okuyan birim = ${Object.entries(m.okuyanBirim).map(([a, n]) => `${a}:${n}`).join(" ")}`);

const d = dogrudanCoz(ADRES);
console.log(`dogrudan cagri bolge = ${d.bolge} tutar = ${tutar(d.bolge)}`
  + `  tur = ${d.tur}  yoklama = ${d.yoklama}  katki = ${d.katki}  bos tur = 0`);
console.log(`  okuma = ${d.okuma}  yazma = yok  reddedilen yazma = yok`);
```

```sh
node ad-ve-is.mjs
```

```
dosya = 9, baska birim adi 0 olan = 7
  kara-tahta/dogrudan.mjs baska birim adi = 4  (posta-kodu, il-adi, koordinat, gecmis-gonderi)
  kara-tahta/kur.mjs      baska birim adi = 4  (posta-kodu, il-adi, koordinat, gecmis-gonderi)
kara tahta     bolge = orta tutar = 11450  tur = 3  yoklama = 5  katki = 4  bos tur = 1
  okuma = 7  yazma = 2  reddedilen yazma = 2  okuyan birim = adres:4 ilNormal:1
dogrudan cagri bolge = orta tutar = 11450  tur = 1  yoklama = 4  katki = 4  bos tur = 0
  okuma = 5  yazma = yok  reddedilen yazma = yok
```

Dokuz dosyanın yedisi başka hiçbir birimin adını taşımıyor: tahta, denetim döngüsü, ücret dönüşümü
ve dört çözümleyicinin hepsi 0. Adları bilen iki dosya var, ikisi de 4 taşıyor ve fark o dosyaların
cinsindedir: kara tahtada bilgi bileşim kökündeki bir listede, doğrudan çağrı düzeninde çağrı
sırasının ve seçim kuralının yanında duruyor.

İkinci yarı bedeli gösteriyor. İki düzen de aynı bölgeyi ve aynı tutarı buldu, ama kara tahta 3
tur döndü, 5 yoklama yaptı ve 7 okuma harcadı; doğrudan çağrı 1 turda 4 yoklama ve 5 okumayla
bitti. Fazladan yoklama posta kodu çözümleyicisine ait: ilk turda normalize il adı tahtada yoktu ve
ikinci turda yeniden yoklandı; bir tur da yalnız sonlanmayı anlamak için boşa döndü. `adres`
anahtarını dört birim okudu, çünkü hiçbiri ötekinin ne okuduğunu bilmiyor.

#### Aynı Girdi, Yirmi Dört Sıra

Denetim döngüsü sırayı dışarıdan alıyor; sıra değiştiğinde sonucun değişmesi ölçülebilir bir
sorudur. Dört çözümleyicinin 24 dizilişi koşturuluyor.

```js
// belirlenim.mjs — ayni girdi, 24 farkli yoklama sirasi: sonuc ve katki siraya bagli mi
import { karaTahta } from "./kara-tahta/tahta.mjs";
import { denetimDongusu } from "./kara-tahta/denetim.mjs";
import { dogrudanCoz } from "./kara-tahta/dogrudan.mjs";
import { COZUMLEYICILER, ADRES } from "./kara-tahta/kur.mjs";
import { tutar } from "./kara-tahta/ucret.mjs";

const permutasyon = (d) => (d.length <= 1 ? [d]
  : d.flatMap((e, i) => permutasyon([...d.slice(0, i), ...d.slice(i + 1)]).map((p) => [e, ...p])));
const SIRALAR = permutasyon(COZUMLEYICILER);

const dagilim = (kosumlar, alan) => {
  const m = new Map();
  for (const k of kosumlar) m.set(k[alan], (m.get(k[alan]) ?? 0) + 1);
  return [...m].sort();
};

const bildir = (ad, kosumlar) => {
  const bolgeler = dagilim(kosumlar, "bolge");
  console.log(`${ad.padEnd(23)} farkli bolge = ${bolgeler.length}:`
    + ` ${bolgeler.map(([b, n]) => `${b} ${n} sira ${tutar(b)}`).join(", ")}`);
  console.log(`  katki = ${dagilim(kosumlar, "katki").map(([k, n]) => `${k} (${n} sira)`).join(", ")}`);
};

for (const kural of ["ilk-yazan", "en-guvenli"]) {
  bildir(`kara tahta / ${kural}`, SIRALAR.map((sira) => {
    const t = karaTahta(kural, { adres: ADRES });
    const o = denetimDongusu(t, sira);
    return { bolge: t.deger("bolge"), katki: o.katki };
  }));
}
bildir("dogrudan cagri", SIRALAR.map((sira) => dogrudanCoz(ADRES, sira)));
```

```sh
node belirlenim.mjs
```

```
kara tahta / ilk-yazan  farkli bolge = 3: orta 11 sira 11450, uzak 2 sira 15300, yakin 11 sira 8500
  katki = 4 (24 sira)
kara tahta / en-guvenli farkli bolge = 1: orta 24 sira 11450
  katki = 4 (24 sira)
dogrudan cagri          farkli bolge = 1: orta 24 sira 11450
  katki = 3 (12 sira), 4 (12 sira)
```

İlk satır çifti düzenin en pahalı kusurunu gösteriyor. İlk yazan kazanır kuralıyla aynı adres 24
sıralamada üç ayrı bölgeye çözümlendi ve aynı gönderi 8.500, 11.450 ya da 15.300 kuruş ücretlendi.
Sonucu belirleyen şey adres değil, denetim döngüsünün hangi çözümleyiciyi önce yokladığıdır; bu
kural altında düzen **belirlenimci değildir**.

İkinci çift kusurun tahtada değil yazma kuralında olduğunu gösteriyor: en güvenli kazanır kuralıyla
24 sıralamanın hepsi aynı bölgeyi ve aynı tutarı verdi. Belirlenimciliği sağlayan şey,
karşılaştırılabilir bir güven derecesinin sözleşmede olmasıdır.

Üçüncü çift kara tahtanın karşılığını veriyor. Doğrudan çağrı her sırada aynı sonucu üretti, ama 24
sıralamanın 12'sinde katkı 4 yerine 3 çıktı: posta kodu çözümleyicisi normalize il adından önce
çağrıldığı için elenip bir daha sorulmadı. Kara tahtada katkı hepsinde 4, çünkü bekleyen
çözümleyici sonraki turda yeniden yoklanıyor.

#### Beşinci Çözümleyici

Beşinci çözümleyici müşteri sözleşmesinde yazılı varsayılan bölgeyi öneriyor. Blok, ağacın bir
kopyasına onu ekler, düzenlenen dosyaları listeler ve yamalı ağacı yükler.

```sh
cp -r kara-tahta besinci
cat > besinci/cozumleyiciler/sozlesme-varsayilani.mjs <<'EOF'
// besinci/cozumleyiciler/sozlesme-varsayilani.mjs — besinci cozumleyici: sozlesmede yazili varsayilan bolge
const SOZLESME = { "M-118": "orta", "M-204": "yakin" };

export const sozlesmeVarsayilani = { ad: "sozlesme-varsayilani", coz: (oku) => {
  const b = SOZLESME[oku("adres").musteri];
  return b === undefined ? null : { guven: 0.4, yaz: { bolge: b } };
} };
EOF
python3 - <<'EOF'
from pathlib import Path

GECMIS = 'import { gecmisGonderi } from "./cozumleyiciler/gecmis-gonderi.mjs";'
YENI = 'import { sozlesmeVarsayilani } from "./cozumleyiciler/sozlesme-varsayilani.mjs";'
CIFT = [(GECMIS, f"{GECMIS}\n{YENI}"), ("gecmisGonderi];", "gecmisGonderi, sozlesmeVarsayilani];")]
for ad in ["kur.mjs", "dogrudan.mjs"]:
    yol = Path("besinci") / ad
    metin = yol.read_text().replace("// kara-tahta/", "// besinci/")
    for eski, yeni in CIFT:
        metin = metin.replace(eski, yeni)
    yol.write_text(metin)
EOF
diff -rq kara-tahta besinci
node -e 'Promise.all([import("./besinci/kur.mjs"), import("./besinci/dogrudan.mjs")])
  .then(([k, d]) => console.log(`besinci agac: kur listesi = ${k.COZUMLEYICILER.length}, dogrudan sira = ${d.SIRA.length}`))'
```

```
Only in besinci/cozumleyiciler: sozlesme-varsayilani.mjs
Files kara-tahta/dogrudan.mjs and besinci/dogrudan.mjs differ
Files kara-tahta/kur.mjs and besinci/kur.mjs differ
besinci agac: kur listesi = 5, dogrudan sira = 5
```

Bir dosya eklendi ve iki dosya düzenlendi: bileşim kökü ile doğrudan çağrı düzeni. Tahta, denetim
döngüsü, ücret dönüşümü ve dört çözümleyici hiç değişmedi. Dosya sayısı iki düzende aynı, cinsi
değil — biri yalnız bir liste, öteki çağrı sırası ile seçim kuralı.

#### Ödünleşimin Sayıları

Kazanç iki **kalite niteliğinde** ölçüldü: değiştirilebilirlikte dokuz dosyanın yedisi başka birim
adı taşımıyor ve beşinci çözümleyici tahtayı, denetim döngüsünü ve dört çözümleyiciyi hiç
düzenletmedi; bütünlükte 24 sıralamanın hepsinde katkı 4.

Bedel üç kalemdedir. Fazladan iş: 3 tur, 5 yoklama ve 7 okumaya karşılık doğrudan çağrının 1 turu,
4 yoklaması ve 5 okuması. Belirlenimcilik: yazma kuralı yanlış seçildiğinde aynı adres üç ayrı
ücret üretti. Görünürlük: sonucu hangi çözümleyicinin belirlediği yalnız tahtanın kaydından okunur.

#### Özet

- Kara tahta düzeni üç parçadan oluşur: kısmi sonuçların yazıldığı ortak alan, birbirinin adını
  bilmeyen çözümleyiciler ve ne yaptıklarını bilmeyen bir denetim döngüsü; yazan taraf kimin
  okuyacağını bilmediği için bu, gözlemci kalıbındaki bildirimden farklıdır.
- Dokuz dosyanın yedisinde başka birim adı 0; adları bilen iki dosya bileşim kökü ile doğrudan
  çağrı düzeni ve ikisi de 4 ad taşıyor.
- Aynı sonuç için kara tahta 3 tur, 5 yoklama ve 7 okuma harcadı, doğrudan çağrı 1 tur, 4 yoklama
  ve 5 okuma; `adres` anahtarını 4 birim okudu ve 1 tur boşa döndü.
- İlk yazan kazanır kuralıyla 24 sıralama 3 ayrı bölge ve 3 ayrı tutar (8.500, 11.450, 15.300)
  üretti; en güvenli kazanır kuralıyla hepsi tek bölge ve tek tutar verdi.
- Katkı kara tahtada 24 sıralamanın hepsinde 4, doğrudan çağrıda 12 sıralamada 3; beşinci
  çözümleyici 1 dosya ekletti ve 2 dosya düzenletti.

#### Sonraki Adım

Bu konu birimlerin **nasıl düzenlendiğini** ve bağımlılık okunun hangi yöne baktığını ele aldı:
katmanlı sıralar, içe bakan halkalar, dağıtılabilir bileşenler, takılabilir eklentiler ve birbirini
hiç tanımayan çözümleyiciler. Kara tahta düzeninde birimler ortak bir bilgi alanına yazıyor, oradan
okuyor ve buluşmayı alanın kendisi sağlıyordu. Sıradaki konu düzeni değil **konuşma biçimini**
sorar: iki birim arasındaki bir etkileşimde kim başlatır, kim bekler ve karşı taraf hakkında
bilinmesi zorunlu olan ne kadardır.

## Etkileşim Biçemleri

Birimlerin birbiriyle konuşma düzeninin seçilmesi: rollerin istemci ve sunucu olarak ayrılması, merkezsiz eş etkileşimi, model ile görünüm arasındaki sorumluluğun denetleyici, sunucu ve görünüm modeli varyantlarına dağıtılması, akış temelli işleme için boru ve süzgeç zinciri, olay tabanlı gevşek bağlanma ve eşzamansız tümleştirme omurgası olarak kuyruklar ile akışlar.

### İstemci–Sunucu

Source: https://academia.sh/tr/kurslar/mimari-bicemler/istemci-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ı.

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.

```sh
mkdir -p ucret dogrudan istemci sunucu
```

```js
// ucret/bolge.mjs — adresten ucret bolgesi turetir
const BOLGE = { "34": "yakin", "06": "orta", "35": "orta", "65": "uzak" };
export const bolgeBelirle = (il) => BOLGE[il] ?? "uzak";
```

```js
// 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]);
```

```js
// 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.

```js
// 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.

```js
// 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;
```

```js
// 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.

```js
// 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]}`);
  }
}
```

```sh
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.

```js
// 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}`);
```

```sh
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ı.

### Eşler Arası

Source: https://academia.sh/tr/kurslar/mimari-bicemler/esler-arasi

Başlatan ve bekleyen rolünün tek birimde birleştiği merkezsiz düzenin ölçülmesi: altı aktarma merkezinin üç komşuluk düzeninde tanımak zorunda olduğu komşu sayısı, bir tarife güncellemesinin herkese ulaşması için gereken tur ve ileti sayısı, bir birim çekildiğinde öğrenen birim sayısı ve yeni birim eklendiğinde düzenlenen komşu listesi sayısı.

Önceki düzende asimetri kasıtlıydı: bir taraf hep başlatıyor, öbürü hep bekliyor ve bekleyen
taraf tekti. Teslimat operasyonu bağlamında 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.

**Eşler arası** (peer-to-peer) biçemin yazılım mimarisi düzeyindeki imzası budur: rol ayrımı
kalkar, iki rol aynı modülde birleşir. Bilgisayar Ağları müfredatındaki İstemci–Sunucu Modeli
dersi bu düzenin talep arttıkça kapasite kazandırdığını, ama kararlı adres ve merkezî denetim
gerektiren web için istemci–sunucu modelinin seçildiğini söylemişti. Adres, bağlantı ve ağ
katmanı oraya aittir. Buradaki soru üç sayıdır: 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ı, bir birim
çekildiğinde iş görmeye devam eden birim sayısı.

Bağlandığı **kalite niteliği** güvenilirliktir ve nitelik sorusu şudur: bir aktarma merkezi
devre dışı kaldığında kaç merkez yeni tarifeyi öğrenemez.

#### İki Rolü Taşıyan Modül

Altı aktarma merkezi var ve hepsi için aynı dosya koşuyor. Modülün içinde iki şey birden durur:
gelen isteği karşılayan `istekIsle` ve komşulara istek gönderen `yay`. Önceki dersin iki
dosyası — istemci ve sunucu — burada tek dosyada birleşmiştir.

```sh
mkdir -p es
```

```js
// es/es.mjs — her aktarma merkezi icin kosan tek modul: hem karsilayan hem baslatan
export function es(ad) {
  const komsular = [];
  let surum = "T1", alinan = 0, gonderilen = 0;
  return {
    ad,
    get surum() { return surum; },
    tanit: (komsu) => komsular.push(komsu),
    sayac: () => ({ alinan, gonderilen, komsu: komsular.length }),
    istekIsle(istek) {
      alinan += 1;
      if (istek.islem !== "tarife-duyur") return { durum: "bilinmeyen-islem" };
      if (istek.surum === surum) return { durum: "bilinen" };
      surum = istek.surum;
      return { durum: "yeni" };
    },
    yay() {
      for (const komsu of komsular) {
        gonderilen += 1;
        komsu.istekIsle({ islem: "tarife-duyur", surum });
      }
    },
  };
}
```

Bir eşin bildiği tek şey komşularıdır. Kaç komşu tanıdığı ise dışarıdan verilir; biçemin
ölçülebilir yüzü buradadır.

#### Üç Komşuluk Düzeni

Aynı altı birim üç ayrı biçimde bağlanır. Yıldız düzeninde bir merkez herkesi, herkes yalnız
merkezi tanır; bu, önceki dersin düzeninin komşuluk olarak yazılmış hâlidir. Halka düzeninde her
birim iki komşu tanır. Tam ağda her birim diğer beşini tanır.

Güncelleme her üç düzende de aynı yerden girer: `ANK` merkezine yeni tarife sürümü bildirilir.
Sonra tur tur yayılır — her turda sürümü bilen bütün birimler komşularına duyurur.

```js
// es/ag.mjs — ayni birimlerin uc komsuluk duzeni ve bir guncellemenin yayilim kosumu
import { es } from "./es.mjs";

export const AD = ["IST", "ANK", "IZM", "BUR", "ADA", "TRA"];
export const GIRIS = "ANK";

export const yildiz = (ad) => new Map(ad.map((a, i) => [a, i === 0 ? ad.slice(1) : [ad[0]]]));
export const halka = (ad) =>
  new Map(ad.map((a, i) => [a, [ad[(i + 1) % ad.length], ad[(i - 1 + ad.length) % ad.length]]]));
export const tamAg = (ad) => new Map(ad.map((a) => [a, ad.filter((b) => b !== a)]));
export const DUZENLER = [["yildiz", yildiz], ["halka", halka], ["tam ag", tamAg]];

export function yayilim(harita) {
  const esler = new Map([...harita.keys()].map((a) => [a, es(a)]));
  for (const [a, komsular] of harita) for (const k of komsular) esler.get(a).tanit(esler.get(k));
  esler.get(GIRIS).istekIsle({ islem: "tarife-duyur", surum: "T2" });
  const hepsi = [...esler.values()];
  let tur = 0;
  while (hepsi.some((e) => e.surum !== "T2") && tur < 20) {
    tur += 1;
    for (const e of hepsi.filter((e) => e.surum === "T2")) e.yay();
  }
  return {
    tur,
    ileti: hepsi.reduce((t, e) => t + e.sayac().gonderilen, 0),
    komsu: hepsi.reduce((t, e) => t + e.sayac().komsu, 0),
    ogrenen: hepsi.filter((e) => e.surum === "T2").length,
    birim: hepsi.length,
  };
}
```

```js
// yayilim.mjs — bilinen komsu sayisi, yayilim turu ve ileti sayisi uc duzende
import { AD, DUZENLER, yayilim } from "./es/ag.mjs";

console.log("duzen   birim  bilinen komsu  tur  ileti  ogrenen");
for (const [etiket, kurucu] of DUZENLER) {
  const s = yayilim(kurucu(AD));
  console.log(
    `${etiket.padEnd(7)}${String(s.birim).padStart(4)}${String(s.komsu).padStart(13)}` +
      `${String(s.tur).padStart(6)}${String(s.ileti).padStart(7)}${`${s.ogrenen}/${s.birim}`.padStart(9)}`,
  );
}
```

```sh
node yayilim.mjs
```

```
duzen   birim  bilinen komsu  tur  ileti  ogrenen
yildiz    6           10     2      7      6/6
halka     6           12     3     18      6/6
tam ag    6           30     1      5      6/6
```

Yayılım burada bir modeldir, ağ ölçümü değil: sayılan şey ileti ve tur sayısıdır, hiçbir süre
ölçülmemiştir.

#### Sayıların Okunması

Üç düzen de altı birimin altısına ulaştı, yani aynı işi yapıyorlar. Farklar üç sütunda toplanır.

Bilinen komşu sayısı bilme yükümlülüğünün toplamıdır. Yıldızda 10: merkez beşini, beş birim
birer merkezi tanıyor. Halkada 12, tam ağda 30. Tam ağ en pahalı bilme düzenidir ve bu, birim
sayısının karesiyle büyür.

İleti ve tur sayıları ters yönde hareket eder. Tam ağ en az iletiyle (5) ve tek turda bitirdi,
çünkü giriş birimi herkesi tanıyordu. Halka en çok iletiyi harcadı (18) ve üç tur sürdü, çünkü
haber komşudan komşuya atlamak zorunda; her turda haberi bilen bütün birimler yeniden duyurur ve
duyuruların çoğu bilinen bir sürümü taşır. Yıldız ikisinin arasında kaldı: 7 ileti, 2 tur —
haberin ilk turda merkeze çıkması, ikinci turda merkezden dağılması gerekti.

Bu üç sütun bir **ödünleşim** tablosudur: bilme yükümlülüğünü azaltmak ileti sayısını artırır.

#### Bir Birim Çekildiğinde

Güvenilirlik sorusunun ölçümü şu deneydir: giriş birimi dışındaki her birim sırayla ağdan
çıkarılır, yayılım baştan koşturulur ve kalan birimlerden kaçının yeni sürümü öğrendiği sayılır.
Aynı betik ağa yedinci bir merkez eklendiğinde kaç komşu listesinin düzenlenmek zorunda kaldığını
da hesaplar.

```js
// dayaniklilik.mjs — bir birim cekildiginde ogrenen birim, yeni birim eklemenin maliyeti, tek dosyadaki iki rol
import { readFileSync } from "node:fs";
import { AD, GIRIS, DUZENLER, yayilim } from "./es/ag.mjs";

const cikar = (harita, ad) =>
  new Map([...harita].filter(([a]) => a !== ad).map(([a, k]) => [a, k.filter((x) => x !== ad)]));

console.log("duzen    en kotu ogrenen  kritik birim  yeni birim icin duzenlenen liste");
for (const [etiket, kurucu] of DUZENLER) {
  const tam = kurucu(AD);
  let kritik = 0, enKotu = [Infinity, 0];
  for (const ad of AD.filter((a) => a !== GIRIS)) {
    const s = yayilim(cikar(tam, ad));
    if (s.ogrenen < s.birim) kritik += 1;
    if (s.ogrenen < enKotu[0]) enKotu = [s.ogrenen, s.birim];
  }
  const buyuk = kurucu([...AD, "ESK"]);
  const degisen = AD.filter((a) => tam.get(a).join(",") !== buyuk.get(a).join(",")).length;
  console.log(
    `${etiket.padEnd(9)}${`${enKotu[0]}/${enKotu[1]}`.padStart(16)}${String(kritik).padStart(14)}` +
      `${String(degisen).padStart(34)}`,
  );
}

const kaynak = readFileSync("es/es.mjs", "utf8");
const karsilayan = (kaynak.match(/istekIsle\(istek\)/g) ?? []).length;
const baslatan = (kaynak.match(/\.istekIsle\(\{/g) ?? []).length;
console.log(`es/es.mjs: karsilayan tanim = ${karsilayan}, baslatan cagri = ${baslatan}`);
```

```sh
node dayaniklilik.mjs
```

```
duzen    en kotu ogrenen  kritik birim  yeni birim icin duzenlenen liste
yildiz                1/5             1                                 1
halka                 5/5             0                                 2
tam ag                5/5             0                                 6
es/es.mjs: karsilayan tanim = 1, baslatan cagri = 1
```

Yıldızda kritik birim sayısı 1'dir ve o birim merkezdir: çekildiğinde kalan beş birimden yalnız
biri — haberin girdiği birim — sürümü biliyor, dördü öğrenemiyor. Halkada ve tam ağda kritik
birim sayısı 0; hangi birim çekilirse çekilsin kalan beşinin beşi öğreniyor. Merkezsizliğin
sayısı budur. Sözcük bir yapı betimlemesi değil, bir ölçüm sonucudur: çekildiğinde başkalarının
işini durduran birim sayısı sıfır.

Son sütun bedelin öbür yarısıdır. Yedinci merkez eklendiğinde yıldızda tek bir liste — merkezin
listesi — düzenlenir. Halkada iki, tam ağda altı liste düzenlenir. Tam ağın tek turda bitirme
kazancı, her yeni birimde var olan bütün birimlerin bilgisini güncellemekle ödenir.

Son satır biçemin kod imzasını verir. Tek dosyada bir karşılayan tanım ve bir başlatan çağrı
bulunuyor. Önceki dersin iki ayrı dosyası burada bir dosyaya indi ve bu, ölçümün nedenidir:
başlatma yönü tek yöne yığılmadığı için ağdan bir birim çıkarmak kalanların konuşmasını
engellemiyor.

#### Özet

- Eşler arası biçemin kod imzası rol ayrımının kalkmasıdır: karşılayan tanım ile başlatan çağrı
  aynı modülde durur, altı birim için tek dosya koşar.
- Bilinen komşu sayısı yıldızda 10, halkada 12, tam ağda 30 çıktı; bilme yükümlülüğü tam ağda
  birim sayısının karesiyle büyür.
- İleti ve tur sayıları ters yönde hareket etti: tam ağ 5 ileti ve 1 tur, yıldız 7 ileti ve 2
  tur, halka 18 ileti ve 3 tur harcadı; üçü de altı birimin altısına ulaştı.
- Kritik birim sayısı yıldızda 1, halkada ve tam ağda 0; merkez çekildiğinde kalan beş birimden
  dördü yeni tarifeyi öğrenemedi.
- Merkezsizliğin bedeli yeni birim eklemenin maliyetidir: yedinci merkez için yıldızda 1,
  halkada 2, tam ağda 6 komşu listesi düzenlendi.

#### Sonraki Adım

Buraya kadarki iki biçem de aynı türden birimler arasındaki etkileşimi ele aldı: konuşan
tarafların ikisi de ücret hesaplayan ya da tarife duyuran birimlerdi, aradaki fark yalnız kimin
başlattığıydı. Kitaplıkta bir de türü farklı bir sınır var. Operasyon masasındaki görevli
ücretin nasıl hesaplandığını değil, ekranda ne yazdığını görür: bölge adı, kademe, indirim
gerekçesi ve ödenecek tutar belirli bir biçimde dizilmiş olmalıdır. Bu sınırın iki tarafı eşit
değildir — biri kuralı ve veriyi taşır, öbürü onu gösterir. Sonraki ders bu sorumluluğun üç
ayrı dağıtımını karşılaştırır: gösteren dosyanın alan adlarından kaç tanesini tanıdığını,
biçimlendirmenin kaç ayrı yerde yapıldığını ve bir görünüm değişikliğinin kaç dosyaya
dokunduğunu sayar.

### Model–Görünüm Aileleri

Source: https://academia.sh/tr/kurslar/mimari-bicemler/model-gorunum-aileleri

Aynı teklif ekranının denetleyici, sunucu ve görünüm modeli varyantlarıyla üretilmesi ve sorumluluk dağılımının sayıya dökülmesi: gösteren dosyanın kaç alan adını tanıdığı, ithal kapanışının kaç dosya olduğu, sınırdan kaç çağrı ve kaç bayt geçtiği, sıra ve biçim kararlarının hangi dosyalarda durduğu.

Buraya kadarki iki biçem aynı türden birimler arasındaki etkileşimi ele aldı: konuşan tarafların
ikisi de ücret hesaplayan ya da tarife duyuran birimlerdi, aradaki fark yalnız kimin
başlattığıydı. Kitaplıkta bir de türü farklı bir sınır var. Operasyon masasındaki görevli ücretin
nasıl hesaplandığını değil, ekranda ne yazdığını görür: bölge adı, kademe, indirim ve ödenecek
tutar belirli bir biçimde dizilmiş olmalıdır. Bu sınırın iki tarafı eşit değildir — biri kuralı
ve veriyi taşır, öbürü onu gösterir.

Bu ders sorumluluğun üç ayrı dağıtımını karşılaştırır ve gösteren dosyanın kaç alan adını
tanıdığını, sınırdan kaç çağrı ile kaç bayt geçtiğini, sıra ve biçim kararlarının hangi
dosyalarda durduğunu sayar. Üç varyantın hepsi aynı ekranı üretmek zorundadır; ölçüm ancak o
zaman aynı işin iki düzenlenişini karşılaştırır.

#### Adlandırma ve Sınır

Aile adları üç harfle kısaltılır: **model–görünüm–denetleyici** (model–view–controller),
**model–görünüm–sunucu** (model–view–presenter) ve **görünüm modeli** (view model) varyantı.
Buradaki **sunucu** sözcüğü gösterilecek değerleri sunan taraftır; İstemci–Sunucu dersindeki
bekleyen taraf anlamıyla akraba değildir, aynı sözcüğün iki ayrı kataloğa ait kullanımıdır.

Görünüm modeli Sunucu Tarafı Temelleri kursundaki Sunucuda Şablon Oluşturma dersinde kurulmuştu:
şablon ile veri kaynağı arasındaki sözleşmedir, hesap ve veri erişimi şablonda değil onu kuran
kodda yapılır. Şablon değerlendirmesi, kaçışlama ve düzen şablonları oraya aittir ve burada
yeniden ele alınmaz. Uygulama Mimarisi: Yönlendirme, Durum ve Veri kursundaki durum yönetimi
dersleri de aynı sınırın arayüz tarafındaydı. Burada yeni olan tek şey **sorumluluk dağılımının
sayılmasıdır**.

Bağlandığı **kalite niteliği** sürdürülebilirliktir ve nitelik sorusu şudur: ekranın düzeni ya
da tutar biçimi değiştiğinde kaç dosya düzenlenir.

#### Ortak Model

Üç varyant aynı hesabı kullanır. Model beş alan döndürür ve hiçbirinde biçimlendirme yoktur;
sayılar sayı, kodlar kod olarak kalır.

```sh
mkdir -p model mvd mvs gm
```

```js
// model/fiyat.mjs — ucretlendirme baglami: hesap ve alan adlari
export const IL_ADI = { "34": "Istanbul", "06": "Ankara", "65": "Van" };
const KATSAYI = { "34": 1, "06": 1.4, "65": 2.1 };

export function fiyatla(gonderi) {
  const kademe = gonderi.agirlik <= 1 ? 1 : gonderi.agirlik <= 5 ? 2 : gonderi.agirlik <= 20 ? 3 : 4;
  const ham = 25 * kademe * KATSAYI[gonderi.il];
  const indirim = ham * gonderi.sozlesmeOrani;
  return { il: gonderi.il, kademe, ham, indirim, net: ham - indirim };
}
```

#### Denetleyici Varyantı

Birinci varyantta denetleyici girdiyi alır, modeli çağırır ve görünümü seçer. Görünüm model
sonucunu doğrudan alır: alan adlarını kendisi okur, biçimlendirmeyi kendisi yapar.

```js
// mvd/gorunum.mjs — gorunum modelin alan adlarini kendisi okur ve bicimlendirmeyi kendisi yapar
import { IL_ADI } from "../model/fiyat.mjs";

export const goster = (sonuc) =>
  [
    ["Bolge", IL_ADI[sonuc.il]],
    ["Kademe", String(sonuc.kademe)],
    ["Taban ucret", `${sonuc.ham.toFixed(2)} TL`],
    ["Indirim", `${sonuc.indirim.toFixed(2)} TL`],
    ["Odenecek", `${sonuc.net.toFixed(2)} TL`],
  ]
    .map(([etiket, deger]) => `${etiket.padEnd(11)}: ${deger}`)
    .join("\n");
```

#### Sunucu Varyantı

İkinci varyantta görünüm edilgendir: kendisine yazılanı tutar, hiçbir alan adı bilmez, hiçbir
hesap yapmaz. Sunucu modelden okur, biçimlendirir ve satır satır görünüme yazar.

```js
// mvs/gorunum.mjs — edilgen gorunum: yalnizca yazilani tutar, hicbir alan adi bilmez
export function gorunum() {
  const satir = new Map();
  return {
    yaz: (etiket, deger) => satir.set(etiket, deger),
    metin: () => [...satir].map(([e, d]) => `${e.padEnd(11)}: ${d}`).join("\n"),
  };
}
```

```js
// mvs/sunucu.mjs — modelden okur, bicimlendirir ve edilgen gorunume satir satir yazar
import { fiyatla, IL_ADI } from "../model/fiyat.mjs";
import { gorunum } from "./gorunum.mjs";

export function teklifEkrani(gonderi, kur = gorunum) {
  const s = fiyatla(gonderi);
  const g = kur();
  g.yaz("Bolge", IL_ADI[s.il]);
  g.yaz("Kademe", String(s.kademe));
  g.yaz("Taban ucret", `${s.ham.toFixed(2)} TL`);
  g.yaz("Indirim", `${s.indirim.toFixed(2)} TL`);
  g.yaz("Odenecek", `${s.net.toFixed(2)} TL`);
  return g.metin();
}
```

#### Görünüm Modeli Varyantı

Üçüncü varyantta araya bir veri yapısı girer. Görünüm modeli modelden okur ve gösterilmeye hazır
alanlar üretir; görünüm yalnız o alanları okur ve nasıl dizileceğine karar verir.

```js
// gm/gorunum-modeli.mjs — modelden gorunume hazir alanlar ureten ayri veri yapisi
import { fiyatla, IL_ADI } from "../model/fiyat.mjs";

export function gorunumModeli(gonderi) {
  const s = fiyatla(gonderi);
  return {
    bolgeAdi: IL_ADI[s.il],
    kademeMetni: String(s.kademe),
    tabanUcret: `${s.ham.toFixed(2)} TL`,
    indirimTutari: `${s.indirim.toFixed(2)} TL`,
    odenecek: `${s.net.toFixed(2)} TL`,
  };
}
```

```js
// gm/gorunum.mjs — yalnizca gorunum modelinin alanlarini okur, hicbir hesap yapmaz
const DUZEN = [
  ["Bolge", "bolgeAdi"],
  ["Kademe", "kademeMetni"],
  ["Taban ucret", "tabanUcret"],
  ["Indirim", "indirimTutari"],
  ["Odenecek", "odenecek"],
];

export const goster = (gm) => DUZEN.map(([e, a]) => `${e.padEnd(11)}: ${gm[a]}`).join("\n");
```

Kalan iki dosya birer satırdır: denetleyici ile görünüm modelini kuran ekran. İkisi de görünümü
bir parametre olarak alır, böylece ölçüm sırasında sınırdan geçenler sayılabilir.

```sh
cat > mvd/denetleyici.mjs <<'SON'
// mvd/denetleyici.mjs — girdiyi alir, modeli cagirir, gorunumu secer
import { fiyatla } from "../model/fiyat.mjs";
import { goster } from "./gorunum.mjs";

export const teklifEkrani = (gonderi, ciz = goster) => ciz(fiyatla(gonderi));
SON
cat > gm/ekran.mjs <<'SON'
// gm/ekran.mjs — gorunum modelini kurar ve gorunume verir
import { gorunumModeli } from "./gorunum-modeli.mjs";
import { goster } from "./gorunum.mjs";

export const teklifEkrani = (gonderi, ciz = goster) => ciz(gorunumModeli(gonderi));
SON
```

#### Ölçüm

Betik önce üç varyantı aynı gönderiyle koşturup çıktılarının aynı olduğunu doğrular. Sonra her
ailenin görünüm dosyasında kaç model alan adı ve kaç sunum adı geçtiğini sayar, görünümün ithal
kapanışını çıkarır ve sınırdan geçen çağrıları kaydederek bayt miktarını toplar. Son tablo iki
kararı arar: satırların sırasını belirleyen dosya ile tutar biçimini belirleyen dosya.

```js
// sorumluluk.mjs — uc ailenin ayni ekrani uretmesi, sinirdan gecen veri ve gorunumun bildigi adlar
import { readFileSync } from "node:fs";
import { dirname, join, normalize } from "node:path";
import { teklifEkrani as mvd } from "./mvd/denetleyici.mjs";
import { teklifEkrani as mvs } from "./mvs/sunucu.mjs";
import { teklifEkrani as gmEkran } from "./gm/ekran.mjs";
import { goster as gosterMvd } from "./mvd/gorunum.mjs";
import { goster as gosterGm } from "./gm/gorunum.mjs";

const GONDERI = { il: "06", agirlik: 12, sozlesmeOrani: 0.15 };
const ALAN = ["il", "kademe", "ham", "indirim", "net"];
const SUNUM = ["bolgeAdi", "kademeMetni", "tabanUcret", "indirimTutari", "odenecek"];

const kapanis = (kok) => {
  const gorulen = new Set([kok]), yigit = [kok];
  while (yigit.length > 0) {
    const d = yigit.pop();
    for (const [, y] of readFileSync(d, "utf8").matchAll(/from "(\.[^"]+)"/g)) {
      const h = normalize(join(dirname(d), y));
      if (gorulen.has(h) === false) { gorulen.add(h); yigit.push(h); }
    }
  }
  return gorulen.size;
};

const adSay = (yol, kume) => {
  const metin = readFileSync(yol, "utf8");
  return kume.filter((a) => new RegExp(`[.\\["']${a}\\b`).test(metin)).length;
};

const sayarak = (asil) => {
  const kayit = [];
  return [(...a) => { kayit.push(a); return asil(...a); }, kayit];
};

function mvsKosum() {
  const kayit = [];
  const kur = () => {
    const satir = new Map();
    return {
      yaz: (e, d) => { kayit.push([e, d]); satir.set(e, d); },
      metin: () => { kayit.push([]); return [...satir].map(([e, d]) => `${e.padEnd(11)}: ${d}`).join("\n"); },
    };
  };
  return [mvs(GONDERI, kur), kayit];
}

const [cizMvd, kayitMvd] = sayarak(gosterMvd);
const [cizGm, kayitGm] = sayarak(gosterGm);
const [mvsCikti, kayitMvs] = mvsKosum();
const cikti = [mvd(GONDERI, cizMvd), mvsCikti, gmEkran(GONDERI, cizGm)];

console.log(cikti[0]);
console.log(`uc ailenin ciktisi ayni mi = ${new Set(cikti).size === 1}`);

const AILE = [
  ["MVD", "mvd/gorunum.mjs", ["mvd/gorunum.mjs", "mvd/denetleyici.mjs"], kayitMvd],
  ["MVS", "mvs/gorunum.mjs", ["mvs/gorunum.mjs", "mvs/sunucu.mjs"], kayitMvs],
  ["GM ", "gm/gorunum.mjs", ["gm/gorunum.mjs", "gm/gorunum-modeli.mjs", "gm/ekran.mjs"], kayitGm],
];

console.log("\naile  alan adi  sunum adi  kapanis  sinir cagrisi  gecen bayt");
for (const [ad, gorunumYolu, , kayit] of AILE) {
  const bayt = kayit.reduce((t, a) => t + JSON.stringify(a).length, 0);
  console.log(
    `${ad}   ${String(adSay(gorunumYolu, ALAN)).padStart(6)}${String(adSay(gorunumYolu, SUNUM)).padStart(11)}` +
      `${String(kapanis(gorunumYolu)).padStart(9)}${String(kayit.length).padStart(15)}${String(bayt).padStart(12)}`,
  );
}

console.log("\naile  sirayi belirleyen        bicimi belirleyen         dokunulan dosya");
for (const [ad, , dosyalar] of AILE) {
  const bul = (desen) => dosyalar.find((d) => readFileSync(d, "utf8").includes(desen)) ?? "-";
  const sira = bul('"Odenecek"'), bicim = bul("toFixed");
  console.log(`${ad}   ${sira.padEnd(24)}${bicim.padEnd(26)}${new Set([sira, bicim]).size}`);
}
```

```sh
node sorumluluk.mjs
```

```
Bolge      : Ankara
Kademe     : 3
Taban ucret: 105.00 TL
Indirim    : 15.75 TL
Odenecek   : 89.25 TL
uc ailenin ciktisi ayni mi = true

aile  alan adi  sunum adi  kapanis  sinir cagrisi  gecen bayt
MVD        5          0        2              1          62
MVS        0          0        1              6         106
GM         0          5        1              1         115

aile  sirayi belirleyen        bicimi belirleyen         dokunulan dosya
MVD   mvd/gorunum.mjs         mvd/gorunum.mjs           1
MVS   mvs/sunucu.mjs          mvs/sunucu.mjs            1
GM    gm/gorunum.mjs          gm/gorunum-modeli.mjs     2
```

#### Sayıların Okunması

Üç varyant aynı beş satırı üretti, yani ölçüm aynı işi karşılaştırıyor.

Birinci sütun bilme yükümlülüğünü verir. Denetleyici varyantında görünüm beş model alan adını
tanıyor; öbür ikisinde sıfır. İkinci sütun bunun nereye gittiğini gösterir: görünüm modeli
varyantında görünüm beş **sunum** adı tanıyor, yani bilgi yok olmadı, model adlarından sunum
adlarına çevrildi. Sunucu varyantında görünüm hiç ad tanımıyor; yazılan etiketi ve değeri olduğu
gibi tutuyor.

Üçüncü sütun bunun somut sonucudur. Denetleyici varyantının görünüm dosyasının ithal kapanışı 2:
görünümü yüklemek modeli de yüklemek anlamına gelir. Öbür iki görünümün kapanışı 1; ikisi de
model olmadan yüklenip sınanabilir.

Dördüncü ve beşinci sütunlar sınırdan geçeni ölçer ve ödünleşimi açığa çıkarır. Denetleyici
varyantında sınırı tek çağrı geçti ve 62 bayt taşıdı — ham sayılar kısa. Sunucu varyantında altı
çağrı geçti ve 106 bayt taşıdı; her satır ayrı bir çağrıdır, biçimlendirilmiş dizeler ham
sayıdan uzundur. Görünüm modeli varyantında tek çağrı 115 bayt taşıdı: beş biçimlendirilmiş alan
bir arada. Sınırı geçen veri en az olan varyant, görünümün model hakkında en çok şey bildiği
varyanttır. Ucuz sınır ile bilgisiz görünüm aynı düzende bulunmuyor.

#### Kararların Yeri

Son tablo sürdürülebilirlik sorusunu yanıtlar. Ekranın satır sırası ile tutar biçimi iki ayrı
karardır ve hangi dosyada durdukları aileye göre değişir.

Denetleyici ve sunucu varyantlarında iki karar aynı dosyada duruyor, dolayısıyla ikisini de
değiştiren bir istek tek dosyaya dokunur. Görünüm modeli varyantında sıra görünümde, biçim
görünüm modelinde; aynı istek iki dosyaya dokunur. Bu, ayrımın bedelidir ve karşılığı da
aynı tabloda okunur: yalnız sıranın değiştiği bir istek görünüm modeli varyantında görünüm
dosyasına dokunur, o dosya ise modeli hiç tanımaz ve tek başına sınanabilir. Denetleyici
varyantında aynı istek, beş model alan adını tanıyan dosyayı düzenlemeyi gerektirir.

Seçim ölçütü isteklerin türüne bağlıdır. Sık değişen şey düzense ayrım kazanç sağlar; sık değişen
şey hangi alanların gösterildiğiyse iki dosyayı birlikte düzenlemek maliyettir.

#### Özet

- Üç varyant aynı beş satırlık ekranı üretti; fark işlevde değil, model ile görünüm arasındaki
  sorumluluk dağılımındadır.
- Görünümün tanıdığı model alan adı denetleyici varyantında 5, öbür ikisinde 0; görünüm modeli
  varyantında bu bilgi 5 sunum adına çevrildi, sunucu varyantında görünümde hiç ad kalmadı.
- Görünüm dosyasının ithal kapanışı denetleyici varyantında 2, öbür ikisinde 1; ikinci ve üçüncü
  görünüm model olmadan yüklenebilir.
- Sınırdan geçen veri ters yönde arttı: denetleyici varyantında 1 çağrı ve 62 bayt, sunucu
  varyantında 6 çağrı ve 106 bayt, görünüm modeli varyantında 1 çağrı ve 115 bayt.
- Sıra ve biçim kararları ilk iki varyantta tek dosyada, görünüm modeli varyantında iki ayrı
  dosyada duruyor; ikisini birden değiştiren istek 1 yerine 2 dosyaya dokunur.

#### Sonraki Adım

Bu üç varyantta da veri tek bir sıçrayışla sınırı geçiyordu: model sonucu bir çağrıda verildi,
biçimlendirme bir yerde yapıldı. Ücretlendirme bağlamının asıl iş akışı ise böyle değil. Günlük
gönderi listesi işlenirken her kayıt sırayla birkaç adımdan geçer: alan adları normalleştirilir,
bölge türetilir, tarife uygulanır, indirim düşülür, tutar yuvarlanır. Bugün bu beş adım tek bir
gövdenin içinde. Yeni bir adım — yakıt ek ücreti — istendiğinde o gövde düzenlenmek zorunda ve
adımların hiçbiri tek başına sınanamıyor. Sonraki ders adımları ayrı birimlere böler ve zinciri
ölçer: yeni adım eklendiğinde düzenlenen dosya sayısı, her adımın tanıdığı ad sayısı, adımlar
arasındaki sınırdan geçen alan sayısı ve kayıtlar tek tek akıtıldığında bellekte aynı anda
tutulan kayıt sayısı.

### Borular ve Süzgeçler

Source: https://academia.sh/tr/kurslar/mimari-bicemler/borular-ve-suzgecler

Aynı günlük gönderi listesinin tek gövdede ve süzgeç zincirinde işlenmesi: her adımın okuduğu alan sayısının, süzgeç girişlerinden geçen alan sayısının, yeni bir adım eklendiğinde düzenlenen dosya ile o dosyanın okuduğu alan sayısının ve kayıtlar tek tek akıtıldığında aynı anda canlı kalan kayıt sayısının ölçülmesi.

Önceki üç varyantta veri tek bir sıçrayışla sınırı geçiyordu: model sonucu bir çağrıda verildi,
biçimlendirme bir yerde yapıldı. Ücretlendirme bağlamının asıl iş akışı ise böyle değil. Günlük
gönderi listesi işlenirken her kayıt sırayla beş adımdan geçer: alan adları normalleştirilir,
bölge türetilir, tarife uygulanır, indirim düşülür, tutar yuvarlanır. Bugün bu beş adım tek bir
gövdenin içinde duruyor; yeni bir adım — yakıt ek ücreti — istendiğinde o gövde düzenlenmek
zorunda ve adımların hiçbiri tek başına koşturulamıyor.

**Boru hattı** (pipeline) biçemi bu işi adımlara böler. Her adım bir **süzgeçtir** (filter):
girdisini bir kayıt olarak alır, çıktısını bir kayıt olarak verir, komşularının kim olduğunu
bilmez. Adımlar arasındaki bağlantı **borudur** (pipe); zincirin sırası süzgeçlerin içinde değil,
zinciri kuran yerde durur. Kabuk Programlama kursundaki Boru Hattı dersinde kurulan süreç zinciri
bu biçemin bir örneğidir: orada süzgeç bir süreç, boru bir bellek tamponuydu; burada süzgeç bir
modül, boru bir işlev çağrısıdır. Kabuk sözdizimi oraya aittir.

Bağlandığı **kalite niteliği** sürdürülebilirliktir ve nitelik sorusu şudur: zincire yeni bir
işlem adımı eklendiğinde kaç dosya düzenlenir ve düzenlenen dosya kaç alan adı tanır. Yanında iki
ölçü daha alınır: süzgeç girişlerinden geçen alan sayısı ve başarım verimliliği ailesine giren
canlı kayıt sayısı.

#### Beş Adım Tek Gövdede

Birinci düzen bugünkü durumdur: tek bir işlev listeyi alır ve her satır için beş adımı sırayla
uygular. Adımlar arasındaki geçiş bir sınır değil, aynı gövdedeki bir sonraki deyimdir.

```sh
mkdir -p tek boru
```

```js
// tek/govde.mjs — bes adim tek govdenin icinde: alan adlari, bolge, tarife, indirim, yuvarlama
const BOLGE = { "34": "yakin", "06": "orta", "35": "orta", "65": "uzak" };
const KATSAYI = { yakin: 1, orta: 1.4, uzak: 2.1 };
const ASGARI = { yakin: 30, orta: 45, uzak: 70 };
const SOZLESME = { "MUS-1": 0.1, "MUS-2": 0.2 };

export function ucretle(gunlukListe) {
  return gunlukListe.map((satir) => {
    const kayit = {
      il: satir.IL.trim(),
      agirlik: Number(satir.AGIRLIK),
      musteri: satir.MUSTERI.trim(),
      adet: Number(satir.ADET),
    };
    kayit.bolge = BOLGE[kayit.il] ?? "uzak";
    kayit.kademe = kayit.agirlik <= 1 ? 1 : kayit.agirlik <= 5 ? 2 : kayit.agirlik <= 20 ? 3 : 4;
    kayit.ham = Math.max(ASGARI[kayit.bolge], 25 * kayit.kademe * KATSAYI[kayit.bolge]);
    kayit.net = kayit.ham * (1 - (SOZLESME[kayit.musteri] ?? 0)) * (kayit.adet >= 10 ? 0.95 : 1);
    kayit.net = Math.round(kayit.net * 100) / 100;
    return kayit;
  });
}
```

#### Süzgeçler

İkinci düzende her adım kendi dosyasındadır ve hepsi aynı imzayı taşır: bir kayıt alır, bir kayıt
verir. Dışa açtıkları adın aynı olması (`suz`) biçemin koşuludur: zinciri kuran yer adımları
birbirinden ayırt etmez.

```js
// boru/normalle.mjs — ham satirin alan adlarini kayit alanlarina cevirir
export const suz = (satir) => ({
  il: satir.IL.trim(),
  agirlik: Number(satir.AGIRLIK),
  musteri: satir.MUSTERI.trim(),
  adet: Number(satir.ADET),
});
```

```js
// boru/bolge.mjs — ilden ucret bolgesi turetir
const BOLGE = { "34": "yakin", "06": "orta", "35": "orta", "65": "uzak" };
export const suz = (k) => ({ ...k, bolge: BOLGE[k.il] ?? "uzak" });
```

```js
// boru/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 };
const kademe = (kg) => (kg <= 1 ? 1 : kg <= 5 ? 2 : kg <= 20 ? 3 : 4);
export const suz = (k) => {
  const d = kademe(k.agirlik);
  return { ...k, kademe: d, ham: Math.max(ASGARI[k.bolge], 25 * d * KATSAYI[k.bolge]) };
};
```

```js
// boru/indirim.mjs — sozlesmeli musteri indirimi ve hacim indirimi
const SOZLESME = { "MUS-1": 0.1, "MUS-2": 0.2 };
export const suz = (k) => ({
  ...k,
  net: k.ham * (1 - (SOZLESME[k.musteri] ?? 0)) * (k.adet >= 10 ? 0.95 : 1),
});
```

```js
// boru/yuvarla.mjs — tutari kurusa yuvarlar
export const suz = (k) => ({ ...k, net: Math.round(k.net * 100) / 100 });
```

Her süzgeç kendisine geleni `...k` ile taşır ve üzerine kendi alanını ekler; ilgilendirmediği
alanların adını yazmaz.

#### Zincirin Kendisi

Sıra tek bir dosyada, adların listesi olarak durur; aynı dosya kayıtları tek tek akıtan üreteci
de taşır. `gozle` parametresi ölçüm içindir, her süzgeç girişinde çağrılır.

```js
// boru/hat.mjs — zincirin sirasi yalnizca burada durur, hicbir alan adi gecmez
export const HAT = ["normalle", "bolge", "tarife", "indirim", "yuvarla"];

export async function* akit(kaynak, gozle = () => {}) {
  const suzgecler = [];
  for (const ad of HAT) suzgecler.push({ ad, suz: (await import(`./${ad}.mjs`)).suz });
  for await (const satir of kaynak) {
    let k = satir;
    for (const { ad, suz } of suzgecler) {
      gozle(ad, k);
      k = suz(k);
    }
    yield k;
  }
}
```

#### Ölçüm

Betik altı satırlık günlük listeyi iki düzende koşturur, çıktıların aynı olduğunu doğrular ve her
adım için üç sayı çıkarır. Girdi ile çıktı alan sayısı koşumda sayılır; okunan alan sayısı dosya
kaynağında `.alanAdi` biçiminde geçen adların sayısıdır.

```js
// alan-say.mjs — iki duzenin ayni listeyi uretmesi, adimlarin okunan alanlari ve sinirdan gecen alan
import { readFileSync } from "node:fs";
import { ucretle } from "./tek/govde.mjs";
import { HAT, akit } from "./boru/hat.mjs";

const GUNLUK = [
  { 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" },
  { IL: "35", AGIRLIK: "4", MUSTERI: "MUS-1", ADET: "20" },
  { IL: "34", AGIRLIK: "21", MUSTERI: "MUS-2", ADET: "2" },
  { IL: "65", AGIRLIK: "0.4", MUSTERI: "MUS-9", ADET: "1" },
];
const ALAN = ["IL", "AGIRLIK", "MUSTERI", "ADET", "il", "agirlik", "musteri", "adet", "bolge", "kademe", "ham", "net"];
const okunanAlan = (yol) => {
  const metin = readFileSync(yol, "utf8");
  return ALAN.filter((a) => new RegExp(`\\.${a}\\b`).test(metin)).length;
};

const girdiAlan = new Map();
const gozle = (ad, k) => { if (girdiAlan.has(ad) === false) girdiAlan.set(ad, Object.keys(k).length); };

const govde = ucretle(GUNLUK);
const hat = [];
for await (const k of akit(GUNLUK, gozle)) hat.push(k);

console.log(`ilk kayit: ${JSON.stringify(hat[0])}`);
console.log(`iki duzen esit mi = ${JSON.stringify(govde) === JSON.stringify(hat)}, kayit = ${hat.length}`);

console.log("\nadim      girdi alan     okunan alan  cikti alan");
let gecen = 0, okunanToplam = 0;
for (const [i, ad] of HAT.entries()) {
  const girdi = girdiAlan.get(ad);
  const cikti = i + 1 < HAT.length ? girdiAlan.get(HAT[i + 1]) : Object.keys(hat[0]).length;
  const okunan = okunanAlan(`boru/${ad}.mjs`);
  gecen += girdi;
  okunanToplam += okunan;
  console.log(
    `${ad.padEnd(10)}${String(girdi).padStart(10)}${String(okunan).padStart(16)}${String(cikti).padStart(12)}`,
  );
}
console.log(
  `${"tek govde".padEnd(10)}${String(Object.keys(GUNLUK[0]).length).padStart(10)}` +
    `${String(okunanAlan("tek/govde.mjs")).padStart(16)}${String(Object.keys(govde[0]).length).padStart(12)}`,
);
console.log(`\nsuzgec girisinden gecen alan = ${gecen}, bunlardan okunan = ${okunanToplam}`);
```

```sh
node alan-say.mjs
```

```
ilk kayit: {"il":"34","agirlik":0.8,"musteri":"MUS-1","adet":3,"bolge":"yakin","kademe":1,"ham":30,"net":27}
iki duzen esit mi = true, kayit = 6

adim      girdi alan     okunan alan  cikti alan
normalle           4               4           4
bolge              4               1           5
tarife             5               2           7
indirim            7               3           8
yuvarla            8               1           8
tek govde          4              12           8

suzgec girisinden gecen alan = 28, bunlardan okunan = 11
```

#### Sayıların Okunması

Altı kayıt iki düzende birebir aynı çıktı, yani ölçüm aynı işin iki düzenlenişini karşılaştırıyor.

Okunan alan sütunu bilme yükümlülüğünün dağılımını verir. Tek gövde on iki alan adının hepsini
tanıyor: dört ham başlık, dört normalleşmiş alan ve dört türetilmiş alan. Zincirde aynı bilgi
beş dosyaya dağıldı ve hiçbir dosya dörtten fazla ad tanımıyor; bir alanın adı değiştiğinde
düzenlenecek dosyayı bulmak bu sütunu okumaktır.

Girdi ve çıktı sütunları biçemin bedelini verir. Süzgeç girişlerinden 28 alan geçti, bunlardan
yalnız 11'i adıyla okundu. Kalan 17 alan hiç okunmadan taşındı — her süzgeç kendisini
ilgilendirmeyeni de bir sonrakine iletmek zorunda, çünkü sonraki adımın neye ihtiyaç duyduğunu
bilmiyor. Tek gövdede bu taşıma yok: ara değerler aynı kapsamda duruyor, sınır geçilmiyor.
Ödünleşim budur — adların dağılması, taşınan alanla ödenir.

#### Yeni Bir Adım

Nitelik sorusu şimdi sınanır. Yakıt ek ücreti adımı indirimden sonra, yuvarlamadan önce girer.
Betik iki düzenin kopyasını çıkarır, değişikliği ikisine de uygular ve düzenlenen dosyaları sayar.
Son bölümde her süzgeç kendi sınırında yakalanan kayıtla tek başına çağrılır, çıktısı bir sonraki
adımın aldığı kayıtla karşılaştırılır.

```js
// uzanti.mjs — yakit ek ucreti adimi eklendiginde duzenlenen dosya, degisen satir ve o dosyanin okudugu alan
import { cpSync, readdirSync, readFileSync, writeFileSync } from "node:fs";

const ALAN = ["IL", "AGIRLIK", "MUSTERI", "ADET", "il", "agirlik", "musteri", "adet", "bolge", "kademe", "ham", "net"];
const okunanAlan = (metin) => ALAN.filter((a) => new RegExp(`\\.${a}\\b`).test(metin)).length;
const TANIM = "const YAKIT = { yakin: 0.02, orta: 0.04, uzak: 0.07 };";
const duzenle = (yol, don) => writeFileSync(yol, don(readFileSync(yol, "utf8")));

for (const d of ["tek", "boru"]) cpSync(d, `yeni/${d}`, { recursive: true });
duzenle("yeni/tek/govde.mjs", (m) =>
  m.replace("const SOZLESME", `${TANIM}\nconst SOZLESME`)
    .replace("    kayit.net = Math.round", "    kayit.net = kayit.net * (1 + YAKIT[kayit.bolge]);\n    kayit.net = Math.round"));
writeFileSync("yeni/boru/yakit.mjs",
  `// boru/yakit.mjs — bolgeye gore yakit ek ucreti\n${TANIM}\nexport const suz = (k) => ({ ...k, net: k.net * (1 + YAKIT[k.bolge]) });\n`);
duzenle("yeni/boru/hat.mjs", (m) => m.replace('"indirim", "yuvarla"', '"indirim", "yakit", "yuvarla"'));

const degisenSatir = (eski, yeni) => {
  const say = (m) => m.split("\n").reduce((h, s) => h.set(s, (h.get(s) ?? 0) + 1), new Map());
  const a = say(eski), b = say(yeni);
  const eksik = (x, y) => [...x].reduce((t, [s, n]) => t + Math.max(0, n - (y.get(s) ?? 0)), 0);
  return eksik(b, a) + eksik(a, b);
};

console.log("duzen  duzenlenen  eklenen  degisen satir  duzenlenende okunan alan");
for (const d of ["tek", "boru"]) {
  const eskiler = new Set(readdirSync(d));
  const duzenlenen = [];
  let eklenen = 0, satir = 0, okunan = 0;
  for (const ad of readdirSync(`yeni/${d}`)) {
    const yeniMetin = readFileSync(`yeni/${d}/${ad}`, "utf8");
    if (eskiler.has(ad) === false) { eklenen += 1; continue; }
    const eskiMetin = readFileSync(`${d}/${ad}`, "utf8");
    if (eskiMetin === yeniMetin) continue;
    duzenlenen.push(ad);
    satir += degisenSatir(eskiMetin, yeniMetin);
    okunan += okunanAlan(yeniMetin);
  }
  console.log(
    `${d.padEnd(7)}${String(duzenlenen.length).padStart(10)}${String(eklenen).padStart(9)}` +
      `${String(satir).padStart(15)}${String(okunan).padStart(25)}  [${duzenlenen.join(" ")}]`,
  );
}

const GUNLUK = [{ IL: "34", AGIRLIK: "0.8", MUSTERI: "MUS-1", ADET: "3" }, { IL: "65", AGIRLIK: "12", MUSTERI: "MUS-2", ADET: "12" }];
const { ucretle: eski } = await import("./tek/govde.mjs");
const { ucretle } = await import("./yeni/tek/govde.mjs");
const { HAT, akit } = await import("./yeni/boru/hat.mjs");
const sinir = new Map();
const hat = [];
for await (const k of akit(GUNLUK, (ad, k) => { if (sinir.has(ad) === false) sinir.set(ad, k); })) hat.push(k);
console.log(`\nyeni adimdan sonra adim = ${HAT.length}, iki duzen esit mi = ${JSON.stringify(ucretle(GUNLUK)) === JSON.stringify(hat)}`);
console.log(`ikinci kayit net: yakit adimindan once ${eski(GUNLUK)[1].net}, sonra ${hat[1].net}`);

let sinanan = 0;
for (const [i, ad] of HAT.entries()) {
  const { suz } = await import(`./yeni/boru/${ad}.mjs`);
  const beklenen = i + 1 < HAT.length ? sinir.get(HAT[i + 1]) : hat[0];
  if (JSON.stringify(suz(sinir.get(ad))) === JSON.stringify(beklenen)) sinanan += 1;
}
console.log(`kendi sinir kaydiyla tek basina kosturulan adim: boru hatti ${sinanan}/${HAT.length}, tek govde 1/1`);
```

```sh
node uzanti.mjs
```

```
duzen  duzenlenen  eklenen  degisen satir  duzenlenende okunan alan
tek             1        0              2                       12  [govde.mjs]
boru            1        1              2                        0  [hat.mjs]

yeni adimdan sonra adim = 6, iki duzen esit mi = true
ikinci kayit net: yakit adimindan once 119.7, sonra 128.08
kendi sinir kaydiyla tek basina kosturulan adim: boru hatti 6/6, tek govde 1/1
```

Düzenlenen dosya sayısı iki düzende de 1'dir; biçem bu sütunda bir kazanç sağlamadı. Kazanç son
sütunda: tek gövdede düzenlenen dosya on iki alan adı tanır, yani yakıt ek ücretini eklemek tarife
ve indirim hesabının durduğu gövdeyi açmayı gerektirir. Zincirde düzenlenen dosya `hat.mjs` hiçbir
alan adı tanımıyor, değişiklik bir dizinin içine bir dize eklemektir; yeni adım ayrı bir dosyada,
iki alan adı tanıyarak durur.

Son satır ikinci kazancı verir. Altı adımın altısı, kendi sınırında yakalanmış kayıtla tek başına
çağrıldığında bir sonraki adımın aldığı kaydı üretti. Tek gövdede böyle bir düzenek kurulamaz:
çağrılabilir tek birim gövdenin tamamıdır, ara değerler dışarıya çıkmaz. Yuvarlama kuralını
sınamak için tarifeyi ve indirimi de koşturmak gerekir.

#### Kayıtların Akıtılması

Süzgeç sınırının tek kayıt olması bir yan sonuç doğurur: zincirin tamamı liste beklemeden
sürülebilir. Aşağıdaki betik iki düzeni üç boyda koşturur ve kaynaktan alınmış ama çıktısı henüz
yazılmamış kayıtların en yüksek sayısını sayar. Sayılan şey kayıt adedidir; bellek baytı
ölçülmemiştir.

```js
// akis.mjs — ayni anda canli olan kayit sayisi: liste toptan alindiginda ve tek tek akitildiginda
import { ucretle } from "./tek/govde.mjs";
import { akit } from "./boru/hat.mjs";

const IL = ["34", "06", "35", "65"], MUSTERI = ["MUS-1", "MUS-2", "MUS-9"];
const satir = (i) => ({
  IL: IL[i % IL.length], AGIRLIK: String(1 + (i % 25)),
  MUSTERI: MUSTERI[i % MUSTERI.length], ADET: String(1 + (i % 12)),
});

function sayac() {
  let canli = 0, tepe = 0;
  return {
    tepe: () => tepe,
    al: (k) => { canli += 1; tepe = Math.max(tepe, canli); return k; },
    yaz: () => { canli -= 1; },
  };
}

function toptan(n) {
  const s = sayac();
  const gunluk = Array.from({ length: n }, (_, i) => s.al(satir(i)));
  let toplam = 0;
  for (const k of ucretle(gunluk)) { toplam += k.net; s.yaz(); }
  return { tepe: s.tepe(), toplam: Math.round(toplam * 100) / 100 };
}

async function akitarak(n) {
  const s = sayac();
  function* kaynak() { for (let i = 0; i < n; i += 1) yield s.al(satir(i)); }
  let toplam = 0;
  for await (const k of akit(kaynak())) { toplam += k.net; s.yaz(); }
  return { tepe: s.tepe(), toplam: Math.round(toplam * 100) / 100 };
}

console.log("kayit  toptan tepe  akitarak tepe  toplam esit mi");
for (const n of [6, 60, 600]) {
  const a = toptan(n), b = await akitarak(n);
  console.log(`${String(n).padStart(5)}${String(a.tepe).padStart(13)}${String(b.tepe).padStart(15)}${String(a.toplam === b.toplam).padStart(16)}`);
}
```

```sh
node akis.mjs
```

```
kayit  toptan tepe  akitarak tepe  toplam esit mi
    6            6              1            true
   60           60              1            true
  600          600              1            true
```

Toptan düzende canlı kayıt sayısı liste boyuyla büyüdü: 6, 60, 600. Akıtılan zincirde üç boyda da
1 kaldı ve iki düzenin ürettiği toplam tutar eşit. Sayı girdi boyundan bağımsız olduğu için
listenin sonu olmasa da zincir çalışabilir; **akış** (stream) kavramının biçemdeki karşılığı
budur.

Bu fark biçemin kendisinden değil sınırın tanımından geliyor: tek gövde de kayıt kayıt çağrılacak
biçimde yazılabilir, ama o gövde beş adımı yine tek dosyada tutar ve adımları tek tek
koşturulamaz. Boru hattında akıtılabilirlik ek bir çaba değil, süzgeç imzasının sonucudur.

#### Özet

- Boru hattı biçeminde her adım bir süzgeçtir: bir kayıt alır, bir kayıt verir, komşusunu tanımaz;
  sıra süzgeçlerin içinde değil, zinciri kuran dosyada durur.
- Altı kayıtlık liste iki düzende aynı sonucu verdi; tek gövde on iki alan adı tanıyor, beş
  süzgeçten hiçbiri dörtten fazla tanımıyor.
- Süzgeç girişlerinden 28 alan geçti, bunlardan 11'i okundu; kalan 17 alan hiç okunmadan taşındı
  ve bu, adları dağıtmanın bedelidir.
- Yeni bir adım eklemek iki düzende de 1 dosya düzenletti, ama düzenlenen dosya tek gövdede 12
  alan adı tanırken zincirde 0 tanıyor.
- Altı adımın altısı kendi sınır kaydıyla tek başına koşturuldu, tek gövdede çağrılabilir birim
  sayısı 1'dir; kayıtlar akıtıldığında canlı kayıt sayısı 600'lük listede de 1 kaldı.

#### Sonraki Adım

Boru hattı sabit ve doğrusal bir zincirdir: her kayıt her adımdan geçer ve her adımın çıktısı bir
sonrakinin girdisidir. Kitaplıkta bu kalıba uymayan bir iş kümesi var. Bir gönderinin ücreti
kesinleştiğinde birkaç şey olmalı: teslimat operasyonu
bağlamında kayıt açılıp rota atanmalı, sözleşmeli müşterinin hacim sayacı artmalı, hangi tarife
sürümüyle fiyatlandığı arşive geçmeli. Bu işler birbirini beslemez, kaydı dönüştürmez ve
aralarında bir sıra yoktur; sayıları da zamanla değişir. Ücreti kesinleştiren modül bunları
kendisi çağırırsa hepsini ithal eder ve her yeni iş o dosyayı düzenletir. Sonraki
ders araya adlandırılmış bir olay ve bir veriyolu koyar, sonra ölçer: yayımcının karşı taraf
hakkında tanıdığı modül ve ad sayısı, yayımcının ithal kapanışı, dördüncü bir tüketici
eklendiğinde yayımcıda düzenlenen satır sayısı ve "bu olayı kim işliyor" sorusu için okunması
gereken dosya sayısı.

### Yayımla–Abone Ol

Source: https://academia.sh/tr/kurslar/mimari-bicemler/yayimla-abone-ol

Ücret kesinleştiğinde koşan üç işin doğrudan çağrıyla ve olay veriyolu üzerinden bağlanmasının karşılaştırılması: yayımcının tanıdığı modül ve ad sayısının, ithal kapanışının, dördüncü bir tüketici eklendiğinde yayımcıda düzenlenen satır sayısının ve işleyicileri bulmak için okunması gereken dosya sayısının ölçülmesi.

Boru hattı sabit ve doğrusal bir zincirdi: her kayıt her adımdan geçiyor, her adımın çıktısı bir
sonrakinin girdisi oluyordu. Kitaplıkta bu kalıba uymayan bir iş kümesi var. Bir gönderinin ücreti
kesinleştiğinde teslimat operasyonu bağlamında kayıt açılıp rota atanmalı, sözleşmeli müşterinin
hacim sayacı artmalı, hangi tarife sürümüyle fiyatlandığı arşive geçmeli. Bu üç iş birbirini
beslemiyor, kaydı dönüştürmüyor ve aralarında bir sıra yok; sayıları da zamanla değişiyor. Ücreti
kesinleştiren modül bunları kendisi çağırırsa üçünü de ithal etmek zorunda kalır.

**Yayımla–abone ol** (publish–subscribe) düzeni Önbellekleme, Kuyruklar ve Eşzamansız İşleme
kursunun Mesajlaşma konusunda kurulmuş ve ölçülmüştü; konu, abonelik, üretici ve tüketici orada
tanımlandı, teslim anlamları ile yeniden deneme de oraya aittir ve burada yeniden ele alınmaz.
Tasarım Kalıpları kursundaki Gözlemci kalıbı aynı fikri nesne düzeyinde kurar: haber veren nesne
abone listesini kendisi taşır. Buradaki biçemin ayırt edici yüzü bir dosyada görünür — abone
listesi ne yayımcıda ne tüketicidedir. Araya adlandırılmış bir **konu** (topic) ile bir **olay
veriyolu** (event bus) girer; iki taraf birbirini değil veriyolunu tanır.

Bağlandığı **kalite niteliği** sürdürülebilirliktir ve nitelik sorusu şudur: kesinleşen ücrete
tepki veren dördüncü bir iş eklendiğinde ücreti kesinleştiren dosyada kaç satır düzenlenir. Aynı
sorunun ters yüzü de ölçülür: yayımcı işleyicileri tanımadığında, "bu olayı kim işliyor" sorusunu
yanıtlamak için kaç dosya okunur.

#### Üç İş

Üç iş her iki düzende **birebir aynı** dosyalardır; değişen tek şey çağrılma yollarıdır. Her biri
kendi durumunu tutar ve dışarıya bir sayaç açar, böylece iki düzenin aynı etkiyi ürettiği
sayılabilir.

```sh
mkdir -p isler dogrudan veriyolu
```

```js
// isler/rota.mjs — teslimat operasyonu: teslimat kaydi acar ve rota atar
const AGAC = { yakin: ["34"], orta: ["34", "06"], uzak: ["34", "41", "65"] };
const acilan = [];
export const rotaAta = (olay) => { acilan.push({ kod: olay.kod, rota: AGAC[olay.bolge] }); };
export const acilanKayit = () => acilan.length;
```

```js
// isler/hacim.mjs — ucretlendirme: sozlesmeli musterinin hacim sayaci
const sayac = new Map();
export const hacimArtir = (olay) => { sayac.set(olay.musteri, (sayac.get(olay.musteri) ?? 0) + 1); };
export const hacim = (musteri) => sayac.get(musteri) ?? 0;
```

```js
// isler/arsiv.mjs — kesinlesen tutari tarife surumuyle birlikte arsive yazar
const satir = [];
export const arsivle = (olay) => { satir.push(`${olay.kod} ${olay.tarifeSurumu} ${olay.net}`); };
export const arsivSatiri = () => satir.length;
```

#### Doğrudan Çağrı

Birinci düzende ücreti kesinleştiren modül üç işi sırayla çağırır. Üç ithal satırı ve üç çağrı,
yayımcının bilme yükümlülüğünün tamamıdır.

```js
// dogrudan/kesinlestir.mjs — ucreti kesinlestiren modul uc isi kendisi cagirir
import { rotaAta } from "../isler/rota.mjs";
import { hacimArtir } from "../isler/hacim.mjs";
import { arsivle } from "../isler/arsiv.mjs";

export function kesinlestir(olay) {
  rotaAta(olay);
  hacimArtir(olay);
  arsivle(olay);
  return olay.kod;
}
```

#### Olay Veriyolu

İkinci düzende araya bir modül girer. Veriyolu iki işlem açar: `abone` bir konuya işleyici bağlar,
`yayimla` o konuya gelen olayı bağlı bütün işleyicilere verir. Her işleyici çağrısı yalıtılır ve
sayaçla kaydedilir. Bir tüketici hata verdiğinde ne olduğu ve olay sistem sınırını geçtiğinde
neyin değiştiği bu kursun Olay Güdümlü Mimari dersinde ölçülür; buradaki veriyolu tek süreçtedir
ve yayım eşzamanlıdır.

```js
// veriyolu/veriyolu.mjs — konu adiyla yayim: iki taraf birbirini degil veriyolunu tanir
const abonelik = new Map();
let yayim = 0, teslim = 0, hata = 0;

export function abone(konu, isleyici) {
  if (abonelik.has(konu) === false) abonelik.set(konu, []);
  abonelik.get(konu).push(isleyici);
}

export function yayimla(konu, olay) {
  yayim += 1;
  for (const isleyici of abonelik.get(konu) ?? []) {
    try { isleyici(olay); teslim += 1; } catch { hata += 1; }
  }
  return (abonelik.get(konu) ?? []).length;
}

export const sayac = () => ({ yayim, teslim, hata });
```

Yayımcı bu iki işlemden yalnız birini tanır. Tanıdığı öteki şey bir dizedir: konunun adı.

```js
// veriyolu/kesinlestir.mjs — yalnizca konu adini ve olay bicimini taniyor
import { yayimla } from "./veriyolu.mjs";

export function kesinlestir(olay) {
  yayimla("ucret-kesinlesti", olay);
  return olay.kod;
}
```

Abonelikler üçüncü bir dosyada kurulur. Bu dosya bileşim kökünün işini yapar: hem veriyolunu hem
üç işi tanır, ama kendisini kimse tanımaz — yayımcı onu ithal etmez.

```js
// veriyolu/kayit.mjs — abonelikler burada kurulur; yayimci bu dosyayi tanimaz
import { abone } from "./veriyolu.mjs";
import { rotaAta } from "../isler/rota.mjs";
import { hacimArtir } from "../isler/hacim.mjs";
import { arsivle } from "../isler/arsiv.mjs";

abone("ucret-kesinlesti", rotaAta);
abone("ucret-kesinlesti", hacimArtir);
abone("ucret-kesinlesti", arsivle);
```

#### Ölçüm

Betik üç olayı iki düzende koşturur ve işlerin sayaçlarındaki artışı karşılaştırır. Sonra iki
yayımcı dosyasının ithal satırlarını okuyup bilinen modül ile ad sayısını çıkarır, ithal
kapanışını hesaplar ve son bölümde "bu olayı kim işliyor" sorusunu iki düzende yanıtlamaya
çalışır: doğrudan düzende yayımcı dosyası, veriyolu düzeninde kaynak ağacındaki bütün modüller
taranır.

```js
// bag-say.mjs — iki duzenin ayni etkiyi uretmesi, yayimcinin tanidigi ad ve isleyicileri bulmak icin okunan dosya
import { readdirSync, readFileSync } from "node:fs";
import { dirname, join, normalize } from "node:path";
import { kesinlestir as dogrudan } from "./dogrudan/kesinlestir.mjs";
import { kesinlestir as veriyolu } from "./veriyolu/kesinlestir.mjs";
import { sayac } from "./veriyolu/veriyolu.mjs";
import { acilanKayit } from "./isler/rota.mjs";
import { hacim } from "./isler/hacim.mjs";
import { arsivSatiri } from "./isler/arsiv.mjs";
import "./veriyolu/kayit.mjs";

const OLAY = [
  { kod: "G-1", bolge: "orta", musteri: "MUS-1", net: 89.25, tarifeSurumu: "T2" },
  { kod: "G-2", bolge: "uzak", musteri: "MUS-2", net: 119.7, tarifeSurumu: "T2" },
  { kod: "G-3", bolge: "yakin", musteri: "MUS-1", net: 27, tarifeSurumu: "T2" },
];
const durum = () => ({ rota: acilanKayit(), arsiv: arsivSatiri(), hacim: hacim("MUS-1") });
const fark = (a, b) => `rota kaydi +${b.rota - a.rota}, arsiv satiri +${b.arsiv - a.arsiv}, MUS-1 hacim +${b.hacim - a.hacim}`;

const d0 = durum();
for (const o of OLAY) dogrudan(o);
const d1 = durum();
for (const o of OLAY) veriyolu(o);
const d2 = durum();
console.log(`dogrudan duzen: ${fark(d0, d1)}`);
console.log(`veriyolu duzen: ${fark(d1, d2)}`);
console.log(`veriyolu sayaci: ${JSON.stringify(sayac())}`);

const ITHAL = /import\s*\{([^}]*)\}\s*from\s*"(\.[^"]+)"/g;
const kapanis = (kok) => {
  const gorulen = new Set([kok]), yigit = [kok];
  while (yigit.length > 0) {
    const d = yigit.pop();
    for (const [, , y] of readFileSync(d, "utf8").matchAll(ITHAL)) {
      const h = normalize(join(dirname(d), y));
      if (gorulen.has(h) === false) { gorulen.add(h); yigit.push(h); }
    }
  }
  return gorulen.size;
};

console.log("\nyayimci                    bilinen modul  bilinen ad  ithal kapanisi");
for (const yol of ["dogrudan/kesinlestir.mjs", "veriyolu/kesinlestir.mjs"]) {
  const bag = [...readFileSync(yol, "utf8").matchAll(ITHAL)];
  const ad = bag.flatMap((m) => m[1].split(",").map((s) => s.trim()));
  console.log(
    `${yol.padEnd(27)}${String(bag.length).padStart(13)}${String(ad.length).padStart(12)}` +
      `${String(kapanis(yol)).padStart(16)}  [${ad.join(" ")}]`,
  );
}

const mjs = readdirSync(".", { recursive: true }).filter((y) => y.endsWith(".mjs") && y.includes("/"));
const dogrudanIsleyici = (readFileSync("dogrudan/kesinlestir.mjs", "utf8").match(/^ {2}\w+\(olay\);$/gm) ?? []).length;
const aboneDosyasi = mjs.filter((y) => readFileSync(y, "utf8").includes('abone("ucret-kesinlesti"'));
const veriyoluIsleyici = aboneDosyasi.reduce(
  (t, y) => t + (readFileSync(y, "utf8").match(/abone\("ucret-kesinlesti", \w+\)/g) ?? []).length, 0);
console.log(`\n"ucret-kesinlesti olayini kim isliyor" sorusu`);
console.log(`  dogrudan: 1 dosya okunur, ${dogrudanIsleyici} isleyici gorunur`);
console.log(`  veriyolu: yayimcida 0 gorunur, ${mjs.length} dosya taranir, ${aboneDosyasi.length} dosyada ${veriyoluIsleyici} isleyici bulunur`);
```

```sh
node bag-say.mjs
```

```
dogrudan duzen: rota kaydi +3, arsiv satiri +3, MUS-1 hacim +2
veriyolu duzen: rota kaydi +3, arsiv satiri +3, MUS-1 hacim +2
veriyolu sayaci: {"yayim":3,"teslim":9,"hata":0}

yayimci                    bilinen modul  bilinen ad  ithal kapanisi
dogrudan/kesinlestir.mjs               3           3               4  [rotaAta hacimArtir arsivle]
veriyolu/kesinlestir.mjs               1           1               2  [yayimla]

"ucret-kesinlesti olayini kim isliyor" sorusu
  dogrudan: 1 dosya okunur, 3 isleyici gorunur
  veriyolu: yayimcida 0 gorunur, 7 dosya taranir, 1 dosyada 3 isleyici bulunur
```

#### Sayıların Okunması

İlk iki satır ölçümün geçerlilik koşuludur: üç olay iki düzende de üç rota kaydı, üç arşiv satırı
ve `MUS-1` için iki hacim artışı üretti. İki düzen aynı işi yapıyor.

Üçüncü satır yayımın karşılığını verir. Üç yayım çağrısı dokuz işleyici çağrısına dönüştü;
yayımcının yaptığı çağrı sayısı 3, gerçekleşen iş sayısı 9. Aradaki çarpan yayımcının bilmediği
sayıdır ve `hata` alanı sıfır olduğu için dokuzunun dokuzu tamamlandı.

Tablo bilme yükümlülüğünü sayar. Doğrudan düzende yayımcı üç modül ve üç ad tanıyor, ithal
kapanışı 4 dosya: yayımcıyı yüklemek üç işi de yüklemektir. Veriyolu düzeninde bir modül ve bir ad
tanınıyor, kapanış 2 dosya. Üç iş, yayımcının ithal kapanışının dışına çıktı; kapanışın içinde
kalan tek yeni şey veriyolunun kendisidir. Konu adı `"ucret-kesinlesti"` bir ithal değil bir
dizedir, yani derleme anında denetlenmez — bu, bedelin ilk yarısıdır.

Son üç satır bedelin ikinci yarısıdır. Doğrudan düzende soruyu bir dosya yanıtlıyor: üç çağrı
orada, sırasıyla okunuyor. Veriyolu düzeninde yayımcı dosyası hiçbir işleyici göstermiyor;
yanıtı bulmak için ağaçtaki yedi modülün taranması ve abonelik satırlarının aranması gerekti.
Bulundu — bir dosyada üç işleyici — ama arama maliyeti dosya sayısıyla büyür. Gevşek bağlanmanın
ölçüsü budur: yayımcının bildiği ad sayısı 3'ten 1'e inerken, işleyicileri bulmak için okunan
dosya sayısı 1'den 7'ye çıktı.

#### Dördüncü Tüketici

Nitelik sorusu dördüncü bir işle sınanır: kesinleşen ücret taşıyıcıya bildirilecek.

```js
// isler/tasiyici.mjs — dorduncu is: kesinlesen ucreti tasiyiciya bildirir
const bildirim = [];
export const tasiyiciBildir = (olay) => { bildirim.push(`${olay.kod} ${olay.net}`); };
export const bildirimSayisi = () => bildirim.length;
```

Betik iki düzenin kopyasını çıkarır, dördüncü işi ikisine de bağlar, hangi dosyada kaç satırın
değiştiğini sayar ve ayrıca yayımcı dosyasında değişen satırı ayrı bir sütunda gösterir. Sonra
iki düzeni yeni ağaçta koşturarak dördüncü işin gerçekten çalıştığını doğrular.

```js
// uzanti.mjs — dorduncu tuketici eklendiginde degisen satir, yayimcida degisen satir ve teslim sayisi
import { cpSync, readFileSync, writeFileSync } from "node:fs";

const duzenle = (yol, don) => writeFileSync(yol, don(readFileSync(yol, "utf8")));
const degisenSatir = (eski, yeni) => {
  const say = (m) => m.split("\n").reduce((h, s) => h.set(s, (h.get(s) ?? 0) + 1), new Map());
  const a = say(eski), b = say(yeni);
  const eksik = (x, y) => [...x].reduce((t, [s, n]) => t + Math.max(0, n - (y.get(s) ?? 0)), 0);
  return eksik(b, a) + eksik(a, b);
};
const ARSIV = 'import { arsivle } from "../isler/arsiv.mjs";';
const TASIYICI = 'import { tasiyiciBildir } from "../isler/tasiyici.mjs";';

for (const d of ["isler", "dogrudan", "veriyolu"]) cpSync(d, `yeni/${d}`, { recursive: true });
duzenle("yeni/dogrudan/kesinlestir.mjs", (m) =>
  m.replace(ARSIV, `${ARSIV}\n${TASIYICI}`).replace("  arsivle(olay);", "  arsivle(olay);\n  tasiyiciBildir(olay);"));
duzenle("yeni/veriyolu/kayit.mjs", (m) =>
  m.replace(ARSIV, `${ARSIV}\n${TASIYICI}`)
    .replace('abone("ucret-kesinlesti", arsivle);', 'abone("ucret-kesinlesti", arsivle);\nabone("ucret-kesinlesti", tasiyiciBildir);'));

const YAYIMCI = { dogrudan: "dogrudan/kesinlestir.mjs", veriyolu: "veriyolu/kesinlestir.mjs" };
const DOSYA = {
  dogrudan: ["dogrudan/kesinlestir.mjs"],
  veriyolu: ["veriyolu/kesinlestir.mjs", "veriyolu/veriyolu.mjs", "veriyolu/kayit.mjs"],
};
console.log("duzen     duzenlenen dosya          degisen satir  yayimcida degisen satir");
for (const [duzen, dosyalar] of Object.entries(DOSYA)) {
  const satirFarki = (y) => degisenSatir(readFileSync(y, "utf8"), readFileSync(`yeni/${y}`, "utf8"));
  const degisen = dosyalar.filter((y) => satirFarki(y) > 0);
  const toplam = degisen.reduce((t, y) => t + satirFarki(y), 0);
  const yayimcida = degisen.includes(YAYIMCI[duzen]) ? satirFarki(YAYIMCI[duzen]) : 0;
  console.log(`${duzen.padEnd(10)}${degisen.join(" ").padEnd(26)}${String(toplam).padStart(13)}${String(yayimcida).padStart(25)}`);
}

const OLAY = [
  { kod: "G-4", bolge: "orta", musteri: "MUS-1", net: 89.25, tarifeSurumu: "T2" },
  { kod: "G-5", bolge: "uzak", musteri: "MUS-2", net: 119.7, tarifeSurumu: "T2" },
];
const { bildirimSayisi } = await import("./yeni/isler/tasiyici.mjs");
const { kesinlestir: dogrudan } = await import("./yeni/dogrudan/kesinlestir.mjs");
const { kesinlestir: veriyolu } = await import("./yeni/veriyolu/kesinlestir.mjs");
const { sayac } = await import("./yeni/veriyolu/veriyolu.mjs");
await import("./yeni/veriyolu/kayit.mjs");

for (const o of OLAY) dogrudan(o);
const dogrudanBildirim = bildirimSayisi();
for (const o of OLAY) veriyolu(o);
console.log(`\ndorduncu is kostu mu: dogrudan ${dogrudanBildirim}/${OLAY.length}, veriyolu ${bildirimSayisi() - dogrudanBildirim}/${OLAY.length}`);
console.log(`veriyolu sayaci: ${JSON.stringify(sayac())}, olay basina teslim = ${sayac().teslim / sayac().yayim}`);
```

```sh
node uzanti.mjs
```

```
duzen     duzenlenen dosya          degisen satir  yayimcida degisen satir
dogrudan  dogrudan/kesinlestir.mjs              2                        2
veriyolu  veriyolu/kayit.mjs                    2                        0

dorduncu is kostu mu: dogrudan 2/2, veriyolu 2/2
veriyolu sayaci: {"yayim":2,"teslim":8,"hata":0}, olay basina teslim = 4
```

Değişen satır sayısı iki düzende de 2'dir: bir ithal satırı ve bir bağlama satırı. Fark son
sütunda: doğrudan düzende bu iki satır ücreti kesinleştiren dosyanın içindedir, veriyolu düzeninde
o dosyaya dokunulmadı. Yayımcı dosyası değişmeden olay başına teslim sayısı 3'ten 4'e çıktı.

Bu, kazancın nereye yerleştiğini gösterir: değişiklik miktarı aynı kaldı, değişikliğin **yeri**
değişti. Yayımcı ücretlendirme bağlamının içindedir; `veriyolu/kayit.mjs` ise hiçbir alan kuralı
taşımayan bir bağlama dosyasıdır. On tüketicili bir düzende her yeni iş yine iki satırdır, ama
ücret hesabının durduğu dosya hiç açılmaz.

#### Özet

- Yayımla–abone ol biçeminde abone listesi ne yayımcıda ne tüketicidedir; araya bir konu adı ile
  bir olay veriyolu girer ve iki taraf birbirini değil veriyolunu tanır.
- Üç olay iki düzende de aynı etkiyi üretti; üç yayım çağrısı dokuz işleyici çağrısına dönüştü ve
  yayımcı bu çarpanı bilmiyor.
- Yayımcının bildiği modül sayısı 3'ten 1'e, ad sayısı 3'ten 1'e, ithal kapanışı 4 dosyadan 2
  dosyaya indi; konu adı bir ithal değil bir dize olduğu için derleme anında denetlenmez.
- Bedel izlenebilirliktir: "bu olayı kim işliyor" sorusu doğrudan düzende 1 dosya okunarak,
  veriyolu düzeninde 7 modül taranarak yanıtlandı.
- Dördüncü tüketici iki düzende de 2 satır değiştirdi, ama doğrudan düzende bu satırlar yayımcı
  dosyasına, veriyolu düzeninde bağlama dosyasına düştü; yayımcı dosyası değişmeden olay başına
  teslim 3'ten 4'e çıktı.

#### Sonraki Adım

Bu veriyolunun ölçülmemiş bir özelliği var: `yayimla` çağrısı, bağlı bütün işleyiciler bitmeden
geri dönmüyor. Dört işleyici aynı çağrı yığıtında sırayla koşuyor, dolayısıyla ücreti
kesinleştiren modülün işi en yavaş işleyicinin işi kadar sürüyor. Arşiv yazımı yavaşlarsa ücret
kesinleştirme de yavaşlar; oysa arşivin ne zaman yazıldığı ücretlendirme için önemli değildir.
İşleyiciyi araya bir depo koyarak beklemekten çıkarmak, yani olayı bir yere bırakıp dönmek
mümkün. Sonraki ders bu depoyu kurar ve ölçer: yayımcı olayı bıraktığında kaç tüketici adımı
koşmuş oluyor, tüketici üreticiden yavaş olduğunda beklemede kaç iş birikiyor, iki tüketici aynı
deponun ucunda çalıştığında sıra korunuyor mu ve aynı ileti iki kez işlendiğinde teslimat kaydı
kaç kez açılıyor.

### Mesaj Kuyrukları ve Akışlar

Source: https://academia.sh/tr/kurslar/mimari-bicemler/mesaj-kuyruklari-ve-akislar

Yayımcı ile tüketici arasına bir depo konmasının ölçülmesi: iletiyi bıraktığı anda koşmuş tüketici iş birimi, tüketici üreticiden yavaş kaldığında bekleyen ileti sayısı, tek ve iki tüketicide sıranın korunup korunmadığı, bir ileti ikinci kez teslim edildiğinde açılan teslimat kaydı ve sonradan eklenen bir birimin kuyruk ile akışta okuyabildiği geçmiş kayıt sayısı.

Önceki dersin veriyolunun ölçülmemiş bir özelliği vardı: `yayimla` çağrısı, bağlı bütün
işleyiciler bitmeden geri dönmüyordu. Dört işleyici aynı çağrı yığıtında sırayla koşuyor,
dolayısıyla ücreti kesinleştiren modülün işi en yavaş işleyicinin işi kadar sürüyordu. Arşiv
yazımı yavaşlarsa ücret kesinleştirme de yavaşlar; oysa arşivin ne zaman yazıldığı ücretlendirme
için önemli değildir. Bu ders araya bir **depo** koyar: yayımcı iletiyi bırakır ve döner, tüketici
onu sonra alır.

**Mesaj kuyruğu** (message queue) ile **akış** (stream) Önbellekleme, Kuyruklar ve Eşzamansız
İşleme kursunun Mesajlaşma konusunda kurulmuş ve ölçülmüştü: teslim anlamları, sıralama ile
bölümleme arasındaki ödünleşim, ölü mektup kuyruğu ve yeniden deneme oraya aittir ve burada
yeniden ele alınmaz. Buraya düşen kısım biçemin kendisidir — deponun neyi garanti ettiği, neyi
etmediği ve bunun mimariye getirdiği kısıt. Ölçülecek dört şey vardır: bırakma anında koşmuş
tüketici işi, bekleyen ileti sayısı, sıranın korunup korunmadığı ve aynı iletinin iki kez
işlenmesinin sonucu.

Bağlandığı **kalite niteliği** güvenilirliktir ve nitelik sorusu şudur: tüketici üreticiden yavaş
kaldığında ya da bir ileti ikinci kez teslim edildiğinde kaç iş birikir ve kaç teslimat kaydı iki
kez açılır. İkinci bir ölçü başarım verimliliği ailesine girer: yayımcı iletiyi bıraktığında kaç
tüketici iş birimi koşmuş olur.

#### İki Uç ve Bir Depo

Tüketici teslimat operasyonu bağlamındadır: gelen her ileti için teslimat kaydı açar. Her iletinin
üç iş birimi tuttuğu varsayılır. **Bu bir modeldir, ölçüm değildir**; sayılan şey iş birimi
adedidir, hiçbir süre ölçülmemiştir. `anahtarli` seçeneği aynı gönderi kodunu ikinci kez
gördüğünde kayıt açmaz; dersin sonunda bu seçeneğin ölçüsü alınır.

```sh
mkdir -p esz
```

```js
// esz/teslimat.mjs — tuketici: her ileti icin teslimat kaydi acar ve 3 is birimi harcar
export const IS_BIRIMI = 3;

export function teslimatci(anahtarli = false) {
  const kayit = [], gorulen = new Set();
  let birim = 0;
  return {
    isle(ileti) {
      birim += IS_BIRIMI;
      if (anahtarli && gorulen.has(ileti.kod)) return;
      gorulen.add(ileti.kod);
      kayit.push(ileti.kod);
    },
    birim: () => birim,
    kayit: () => kayit,
  };
}
```

Depo iki işlem açar. `koy` iletiyi sona ekler ve hiçbir tüketici çağırmaz; `al` en eski iletiyi
verir. Bu iki imzanın arasındaki boşluk biçemin tamamıdır: koyan ile alan aynı anda çalışmak
zorunda değildir.

```js
// esz/kuyruk.mjs — mesaj kuyrugu: koyan beklemez, alan sirayla alir
export function kuyruk() {
  const bekleyen = [];
  let konan = 0, alinan = 0, tepe = 0;
  return {
    koy(ileti) { bekleyen.push(ileti); konan += 1; tepe = Math.max(tepe, bekleyen.length); },
    al() { if (bekleyen.length === 0) return null; alinan += 1; return bekleyen.shift(); },
    uzunluk: () => bekleyen.length,
    sayac: () => ({ konan, alinan, tepe }),
  };
}
```

#### Yayımcı Beklemiyor

İlk betik yirmi iletiyi iki düzende geçirir. Eşzamanlı düzende yayımcı tüketiciyi doğrudan çağırır;
kuyruk düzeninde iletiyi bırakır. Bırakma bittiği anda koşmuş tüketici işi sayılır, sonra kuyruk
boşaltılır ve iki düzenin aynı kaydı ürettiği doğrulanır. İkinci bölümde üretici tur başına beş,
tüketici tur başına üç ileti işler ve üretim dördüncü turdan sonra durur.

```js
// bloke.mjs — yayimci olayi biraktiginda kosmus tuketici is birimi ve kuyrukta bekleyen ileti
import { teslimatci, IS_BIRIMI } from "./esz/teslimat.mjs";
import { kuyruk } from "./esz/kuyruk.mjs";

const ILETI = Array.from({ length: 20 }, (_, i) => ({ kod: `G-${String(i + 1).padStart(2, "0")}` }));

const hemen = teslimatci();
for (const ileti of ILETI) hemen.isle(ileti);

const k = kuyruk(), sonra = teslimatci();
for (const ileti of ILETI) k.koy(ileti);
const birakmaAninda = { birim: sonra.birim(), bekleyen: k.uzunluk() };
let alinan;
while ((alinan = k.al()) !== null) sonra.isle(alinan);

const GENISLIK = [15, 25, 16, 18];
const SATIR = [
  ["eszamanli", ILETI.length, hemen.birim(), 0, hemen.birim()],
  ["kuyruk", ILETI.length, birakmaAninda.birim, birakmaAninda.bekleyen, sonra.birim()],
];
console.log("duzen      yayimci cagrisi  birakma aninda kosan is  bekleyen ileti  sonunda kosan is");
for (const [ad, ...s] of SATIR) console.log(ad.padEnd(11) + s.map((v, i) => String(v).padStart(GENISLIK[i])).join(""));
console.log(`kuyruk sayaci: ${JSON.stringify(k.sayac())}, kayit esit mi = ${JSON.stringify(hemen.kayit()) === JSON.stringify(sonra.kayit())}`);

const URETIM = [5, 5, 5, 5, 0, 0, 0, 0];
const TUKETIM = 3;
const k2 = kuyruk(), t2 = teslimatci();
let sira = 0;
console.log("\ntur  uretilen  tuketilen  bekleyen");
for (const [i, uret] of URETIM.entries()) {
  for (let n = 0; n < uret; n += 1) { sira += 1; k2.koy({ kod: `H-${sira}` }); }
  let tuketilen = 0;
  for (let n = 0; n < TUKETIM; n += 1) {
    const ileti = k2.al();
    if (ileti === null) break;
    t2.isle(ileti);
    tuketilen += 1;
  }
  console.log(`${String(i + 1).padStart(3)}${String(uret).padStart(10)}${String(tuketilen).padStart(11)}${String(k2.uzunluk()).padStart(10)}`);
}
console.log(`tepe bekleyen = ${k2.sayac().tepe}, is birimi = ${t2.birim()}, ileti basina is birimi = ${IS_BIRIMI}`);
```

```sh
node bloke.mjs
```

```
duzen      yayimci cagrisi  birakma aninda kosan is  bekleyen ileti  sonunda kosan is
eszamanli               20                       60               0                60
kuyruk                  20                        0              20                60
kuyruk sayaci: {"konan":20,"alinan":20,"tepe":20}, kayit esit mi = true

tur  uretilen  tuketilen  bekleyen
  1         5          3         2
  2         5          3         4
  3         5          3         6
  4         5          3         8
  5         0          3         5
  6         0          3         2
  7         0          2         0
  8         0          0         0
tepe bekleyen = 11, is birimi = 60, ileti basina is birimi = 3
```

#### Sayıların Okunması

İlk tablonun son sütunu iki düzende de 60'tır: iş yok olmadı, aynı iş yapıldı ve iki düzen aynı
kaydı üretti. Değişen şey işin **ne zaman** yapıldığıdır. Eşzamanlı düzende yayımcı yirminci
çağrıyı bitirdiğinde altmış iş birimi onun zamanından gitmişti; kuyruk düzeninde aynı anda sıfır
iş birimi koşmuştu ve depoda yirmi ileti bekliyordu. Bekleyen ileti sayısı, bedelin adıdır:
yayımcı beklemekten kurtuldu, ama sistemde artık işlenmemiş iş duruyor ve bu iş bir yerde
tutulmak zorunda.

İkinci tablo bu birikimin nasıl davrandığını gösterir. Üretim tüketimden hızlıyken bekleyen ileti
her turda ikişer artıyor; dördüncü turun üretiminden sonra kuyruk en yüksek değerine, on bire
ulaşıyor. Üretim durduğunda kuyruk erimeye başlıyor ve üç tur sonra boşalıyor. Bu, biçemin
temel kısıtıdır: kuyruk hız farkını **saklar**, ortadan kaldırmaz. Üretim hızı tüketim hızının
üstünde kalıcı olarak durursa bekleyen ileti sayısı sınırsız büyür; kuyruğun sağladığı şey
farkın geçici olduğu durumda yayımcının beklememesidir.

#### Sıra ve Tekrar

İkinci betik iki soru sorar. Birincisi sıradır: kuyruğa konma sırası ile işlenme sırası aynı mı?
Tek tüketicide ve aynı kuyruğun ucunda çalışan iki tüketicide ayrı ayrı ölçülür. İki tüketicili
düzende işçilerin iş birimi maliyeti farklıdır ve boşalan işçi sıradaki iletiyi alır; bitiş sırası
buna göre kurulur. **Bu bir modeldir, ölçüm değildir** — sayılan şey iş birimi ve bitiş sırasıdır,
hiçbir süre ölçülmemiştir. İkinci soru tekrardır: bir ileti onaylanmadığı için kuyruğa geri
konursa ne olur?

```js
// sira-tekrar.mjs — tek ve iki tuketicide sira, bir ileti yeniden teslim edildiginde acilan kayit
import { teslimatci } from "./esz/teslimat.mjs";
import { kuyruk } from "./esz/kuyruk.mjs";

const ILETI = Array.from({ length: 8 }, (_, i) => ({ kod: `G-${i + 1}` }));
const SIRA = ILETI.map((i) => i.kod);

const k = kuyruk(), tek = teslimatci();
for (const ileti of ILETI) k.koy(ileti);
let alinan;
while ((alinan = k.al()) !== null) tek.isle(alinan);

const tersine = (dizi) => {
  const yer = new Map(SIRA.map((kod, i) => [kod, i]));
  let t = 0;
  for (let a = 0; a < dizi.length; a += 1)
    for (let b = a + 1; b < dizi.length; b += 1) if (yer.get(dizi[a]) > yer.get(dizi[b])) t += 1;
  return t;
};

const MALIYET = [1, 4];
const bos = MALIYET.map(() => 0);
const bitis = [];
const k2 = kuyruk();
for (const ileti of ILETI) k2.koy(ileti);
while ((alinan = k2.al()) !== null) {
  const j = bos[0] <= bos[1] ? 0 : 1;
  bos[j] += MALIYET[j];
  bitis.push([alinan.kod, bos[j]]);
}
const bitisSirasi = [...bitis].sort((a, b) => a[1] - b[1]).map(([kod]) => kod);

console.log(`koyulan sira : ${SIRA.join(" ")}`);
console.log(`tek tuketici : ${tek.kayit().join(" ")}  tersine cift = ${tersine(tek.kayit())}`);
console.log(`iki tuketici : ${bitisSirasi.join(" ")}  tersine cift = ${tersine(bitisSirasi)}`);

for (const [ad, anahtarli] of [["anahtarsiz", false], ["anahtarli", true]]) {
  const k3 = kuyruk(), t3 = teslimatci(anahtarli);
  for (const ileti of ILETI) k3.koy(ileti);
  let sayi = 0;
  while ((alinan = k3.al()) !== null) {
    t3.isle(alinan);
    sayi += 1;
    if (sayi === 3) k3.koy(alinan);
  }
  const s = k3.sayac();
  console.log(`${ad.padEnd(11)} teslim = ${s.alinan}, farkli ileti = ${new Set(ILETI.map((i) => i.kod)).size}, acilan teslimat kaydi = ${t3.kayit().length}, is birimi = ${t3.birim()}`);
}
```

```sh
node sira-tekrar.mjs
```

```
koyulan sira : G-1 G-2 G-3 G-4 G-5 G-6 G-7 G-8
tek tuketici : G-1 G-2 G-3 G-4 G-5 G-6 G-7 G-8  tersine cift = 0
iki tuketici : G-1 G-3 G-4 G-2 G-5 G-6 G-8 G-7  tersine cift = 3
anahtarsiz  teslim = 9, farkli ileti = 8, acilan teslimat kaydi = 9, is birimi = 27
anahtarli   teslim = 9, farkli ileti = 8, acilan teslimat kaydi = 8, is birimi = 27
```

Tek tüketicide tersine çift sayısı sıfırdır: sıra korunmuştur ve bu, deponun `al` imzasının
sonucudur. İkinci tüketici eklendiğinde aynı kuyruk, aynı sırayla dağıtım yapmasına karşın bitiş
sırası üç yerde bozuldu. Sıranın korunması tek tüketiciye bağlı bir özelliktir; ikinci tüketici
eklemek bir başarım kararı değil, **sıra garantisinden vazgeçme** kararıdır. Mimari sonucu şudur:
sırası önemli olan işler ile paralel işlenebilen işler aynı kuyruktan geçemez.

Son iki satır tekrarın faturasıdır. Sekiz farklı ileti dokuz kez teslim edildi. Anahtarsız
tüketicide açılan teslimat kaydı dokuz oldu: aynı gönderi için iki kayıt açıldı. Anahtarlı
tüketicide sekiz kaldı, yani ikinci teslim etkisiz geçti — ama iş birimi iki düzende de 27'dir,
çünkü ileti yine alındı ve yine işlendi. Buradaki ölçüm biçemin kısıtını verir: depo iletiyi
sıraya koyar, **tekliği** garanti etmez; teklik tüketicinin kendi anahtarıyla sağlanmak
zorundadır. Aynı iletinin iki kez ulaşmasına karşı iki tüketici tasarımının nasıl saptığı bu
kursun Olay Güdümlü Mimari dersinde ayrıca ölçülür.

#### Tüketmek ile Silmek

Kuyruğun `al` işlemi iletiyi depodan çıkarır: tüketmek silmektir. Akış bu iki işi ayırır — kayıt
yerinde kalır, her tüketici kendi **konumunu** tutar. Konum, saklama süresi ve yeniden okuma
Mesajlaşma konusundaki Akışlar ve Kuyruk Farkı dersinde ölçülmüştü. Buradaki soru mimaridir:
sonradan eklenen bir birim geçmişi kendi başına kurabilir mi?

```js
// esz/gunluk.mjs — akis: tuketmek silmez, her tuketici kendi konumunu tutar
export function gunluk() {
  const kayit = [];
  return { ekle: (ileti) => kayit.push(ileti), oku: (konum) => kayit.slice(konum), uzunluk: () => kayit.length };
}
```

```js
// gecmis.mjs — kuyruk tuketilince bosalir, akis konumla okunur: sonradan eklenen birimin gecmisi
import { kuyruk } from "./esz/kuyruk.mjs";
import { gunluk } from "./esz/gunluk.mjs";
import { teslimatci } from "./esz/teslimat.mjs";

const ILETI = Array.from({ length: 20 }, (_, i) => ({ kod: `G-${i + 1}` }));

const k = kuyruk(), kuyrukTuketicisi = teslimatci();
for (const ileti of ILETI) k.koy(ileti);
let alinan;
while ((alinan = k.al()) !== null) kuyrukTuketicisi.isle(alinan);

const g = gunluk(), akisTuketicisi = teslimatci();
for (const ileti of ILETI) g.ekle(ileti);
let konum = 0;
for (const ileti of g.oku(konum)) { akisTuketicisi.isle(ileti); konum += 1; }
const konumOnce = konum;

const sonradan = teslimatci();
for (const ileti of g.oku(0)) sonradan.isle(ileti);

console.log(`kuyruk: konan ${k.sayac().konan}, tuketildikten sonra bekleyen ${k.uzunluk()}`);
console.log(`  sonradan eklenen birimin kuyruktan kendi basina okudugu gecmis = ${k.uzunluk()} kayit`);
console.log(`  gecmis ancak tuketicinin kaydi disa acilarak alinir: ${kuyrukTuketicisi.kayit().length} kayit, baska tuketiciye kurulan bag = 1`);
console.log(`akis: gunluk uzunlugu ${g.uzunluk()}, ilk tuketicinin konumu ${konumOnce}`);
console.log(`  sonradan eklenen birimin konum 0'dan okudugu gecmis = ${sonradan.kayit().length} kayit, baska tuketiciye kurulan bag = 0`);
console.log(`  ilk tuketicinin konumu ${konumOnce} -> ${konum}, gunluk uzunlugu ${g.uzunluk()}`);
```

```sh
node gecmis.mjs
```

```
kuyruk: konan 20, tuketildikten sonra bekleyen 0
  sonradan eklenen birimin kuyruktan kendi basina okudugu gecmis = 0 kayit
  gecmis ancak tuketicinin kaydi disa acilarak alinir: 20 kayit, baska tuketiciye kurulan bag = 1
akis: gunluk uzunlugu 20, ilk tuketicinin konumu 20
  sonradan eklenen birimin konum 0'dan okudugu gecmis = 20 kayit, baska tuketiciye kurulan bag = 0
  ilk tuketicinin konumu 20 -> 20, gunluk uzunlugu 20
```

Kuyruk boşaldıktan sonra sonradan eklenen birimin kendi başına okuyabildiği geçmiş sıfır kayıttır.
Aynı bilgiyi almanın tek yolu var olan tüketicinin kaydını dışa açması, yani iki tüketici arasında
yeni bir bağ kurulmasıdır: yeni birim artık yayımcıya değil, bir başka tüketiciye bağlıdır.
Akışta aynı sayı yirmidir ve kurulan bağ sıfır; yeni birim yalnız günlüğü tanır. İkinci okuma ilk
tüketicinin konumunu değiştirmedi ve günlüğün uzunluğu artmadı — tüketici sayısı bilme
yükümlülüğünü büyütmüyor.

Seçim ölçütü budur: iş bir kez yapılıp bitecekse kuyruk yeterlidir; sonradan eklenen birimlerin
geçmişten kendi durumunu kurması gerekiyorsa akış gerekir ve bedeli kaydın saklanmasıdır.

#### Özet

- Depo yayımcı ile tüketiciyi ayırır: yirmi iletiyi bırakan yayımcı eşzamanlı düzende 60 iş
  birimini kendi zamanından harcadı, kuyruk düzeninde 0 harcadı ve depoda 20 ileti bekledi.
- Toplam iş iki düzende de 60'tır ve iki düzen aynı kaydı üretti; kuyruk işi azaltmaz, zamanını
  değiştirir.
- Kuyruk hız farkını saklar: tur başına 5 üretim ile 3 tüketimde bekleyen ileti tepe değeri 11
  oldu ve üretim durduktan üç tur sonra kuyruk boşaldı.
- Sıra tek tüketicide korundu (tersine çift 0), ikinci tüketici eklendiğinde bitiş sırası üç
  yerde bozuldu; ikinci tüketici eklemek sıra garantisinden vazgeçmektir.
- Sekiz ileti dokuz kez teslim edildiğinde anahtarsız tüketici 9, anahtarlı tüketici 8 teslimat
  kaydı açtı; iş birimi ikisinde de 27'dir — depo sırayı garanti eder, tekliği etmez.
- Kuyrukta tüketmek silmektir: sonradan eklenen birimin okuyabildiği geçmiş 0 kayıt, akışta 20
  kayıttır ve akışta başka bir tüketiciye bağ kurulmaz.

#### Sonraki Adım

Bu konu birimlerin birbiriyle nasıl konuştuğunu ele aldı. Altı biçemde aynı üç şey ölçüldü:
birimin karşı tarafı bilme yükümlülüğü, bağlanma yönü ve sınırdan geçen veri. İstemci ile sunucu,
eşler, model ile görünüm, boru ile süzgeç, yayımcı ile abone, kuyruğun iki ucu — hepsi bu üç
sayıyla karşılaştırıldı. Ancak bütün bu ölçümler bir soruyu sormadan geçti: bu birimler kaç ayrı
parça olarak yayınlanıyor? Bu dersteki kuyruk da, önceki dersteki veriyolu da tek bir çalışan
programın içindeydi; yayımla–abone ol düzeni için ikinci bir süreç gerekmedi, bir nesne yetti.
Etkileşim biçemi ile parça sayısı birbirinden bağımsız iki karardır. Sonraki konu ikincisini
sorar: yazılım kaç ayrı dağıtım biriminde yayınlanır ve bu sayı neyi değiştirir.

## Dağıtım Biçemleri

Aynı yazılımın kaç ayrı parça olarak yayınlandığının getirdiği fark: tek parçanın basitliği ve ölçek sınırı, kurumsal servis paylaşımı, bağımsız yayın karşılığında ödenen dağıtık karmaşıklık, olay akışı üzerine kurulu sistemler, işlev ve yönetilen servis bileşimi ve bütün bu düzenlerin kalite nitelikleri üzerinden karşılaştırılarak seçilmesi.

### Tek Parça Mimari

Source: https://academia.sh/tr/kurslar/mimari-bicemler/tek-parca-mimari

Aynı kitaplığın iki bağlamının tek dağıtım birimi olarak yayınlanması: bir teklif isteğinin geçtiği sınır sayısının, serileştirme noktasının ve sınırdan geçen baytın sayılması, tek dosyalık bir değişikliğin yeniden yayınlattığı dosya sayısının ölçülmesi ve süreci sonlandıran bir kusurun her iki bağlamı birden düşürdüğünün gösterilmesi.

Önceki konu birimlerin birbiriyle nasıl konuştuğunu ele aldı. Her etkileşim biçeminde aynı üç
şey ölçüldü: birimin karşı tarafı bilme yükümlülüğü, bağlanma yönü ve sınırdan geçen veri.
Ancak bütün bu ölçümler bir soruyu sormadan geçti. İstemci ile sunucu, yayımcı ile abone, boru
ile süzgeç — bunların hepsi tek bir çalışan programın içinde de kurulabilir. Yayımla–abone ol
düzeni için ikinci bir süreç gerekmez; bir olay veriyolu nesnesi yeter.

Bu konu o sorunun kendisini sorar: **yazılım kaç ayrı parça olarak yayınlanır?** Cevap etkileşim
biçeminden bağımsızdır ve kendi ölçüleri vardır. Bir **dağıtım birimi** (deployment unit)
birlikte yayınlanmak zorunda olan dosya kümesidir; bir sürüm çıkarıldığında bu kümenin tamamı
aynı anda yerine geçer. **Tek parça mimari** (monolithic architecture) bütün bağlamları tek bir
dağıtım biriminde tutan biçemdir: kaç modül olduğu değişir, dağıtım birimi sayısı 1'dir.

#### İki Bağlam, Tek Süreç

Kitaplığın iki sınırlı bağlamı var. Ücretlendirme bağlamında gönderi fiyatlanabilir bir
birimdir; teslimat operasyonu bağlamında rotası ve taşıyıcısı olan fiziksel bir pakettir. Her
ikisi de derli biçimde şöyledir.

```js
// ortak/ucret.mjs — ucretlendirme baglami: gonderi fiyatlanabilir birimdir
export const TARIFE = { kademe: [[1, 3000], [5, 4800], [20, 9600]], bolge: { "34": 100, "06": 115, "35": 125 }, asgari: 2500 };

export function taban(agirlik, bolge) {
  const kademe = TARIFE.kademe.find(([k]) => agirlik <= k) ?? [0, 9600];
  return Math.max(TARIFE.asgari, Math.round((kademe[1] * TARIFE.bolge[bolge]) / 100));
}

export function net(agirlik, bolge, sozlesmeOrani) {
  const t = taban(agirlik, bolge);
  return t - Math.round(t * Math.min(sozlesmeOrani, 0.4));
}
```

```js
// ortak/operasyon.mjs — teslimat operasyonu baglami: gonderi rotali fiziksel pakettir
export const AGAC = { "34": ["34"], "06": ["34", "06"], "35": ["34", "41", "35"] };

export function rota(bolge) { return AGAC[bolge] ?? ["34"]; }

export function tasiyici(bolge) { return rota(bolge).length > 2 ? "MT" : "AN"; }

export function teslimGunu(bolge) { return rota(bolge).length; }
```

Sınır geçişlerini saymak için birimlerin dış yüzü sarılır. Bu sarmalayıcı kurs boyunca aynı
kalır: hangi biçem ölçülüyorsa geçişleri o toplar.

```js
// ortak/sar.mjs — bir modulun dis yuzunu sarar; her cagri birim adiyla kayda gecer
export function sar(ad, modul, kayit) {
  const yuz = {};
  for (const [k, v] of Object.entries(modul)) {
    yuz[k] = typeof v === "function" ? (...a) => { kayit.push(`${ad}.${k}`); return v(...a); } : v;
  }
  return yuz;
}
```

Teklif isteği iki bağlama birden dokunur: net ücret ücretlendirmeden, teslim günü ve taşıyıcı
operasyondan gelir. Tek parça düzende bu birleştirme bir fonksiyon çağrısıdır.

```js
// tek-parca/uygulama.mjs — iki baglam ayni surecte, ayni bellekte, tek dagitim birimi
import * as ucretModul from "../ortak/ucret.mjs";
import * as operasyonModul from "../ortak/operasyon.mjs";
import { sar } from "../ortak/sar.mjs";

export function uygulama(kayit) {
  const ucret = sar("ucret", ucretModul, kayit);
  const operasyon = sar("operasyon", operasyonModul, kayit);
  return {
    teklif(g) {
      kayit.push("uygulama.teklif");
      return {
        id: g.id,
        net: ucret.net(g.agirlik, g.bolge, g.oran),
        gun: operasyon.teslimGunu(g.bolge),
        tasiyici: operasyon.tasiyici(g.bolge),
      };
    },
  };
}
```

#### İsteğin ve Yayının Ölçülmesi

Ölçüm iki cinsten sayı çıkarır. Birincisi koşumdan gelir: bir isteğin dokunduğu birim, geçtiği
sınır, serileştirme noktası ve sınırdan geçen bayt. İkincisi dosyalardan gelir: dağıtım
biriminin ithal kapanışı, yani birlikte yayınlanmak zorunda olan dosya kümesi.

```js
// tek-parca/olc.mjs — bir istegin dokundugu birim, gectigi sinir, yayin kumesi ve komsu gereksinimi
import { readFileSync } from "node:fs";
import { dirname, join, normalize } from "node:path";
import { uygulama } from "./uygulama.mjs";

const kayit = [];
const cikti = uygulama(kayit).teklif({ id: "G1", agirlik: 4, bolge: "35", oran: 0.15 });
const birim = new Set(kayit.map((c) => c.split(".")[0]));
console.log(`teklif ciktisi = ${JSON.stringify(cikti)}`);
console.log(`sinir gecisi = ${kayit.length} (${kayit.join(" -> ")})`);
console.log(`dokunulan birim = ${birim.size}, serilestirme noktasi = 0, sinirdan gecen bayt = 0`);

const ITHAL = /from\s+"(\.[^"]+)"/g;
function kapanis(giris) {
  const yigit = [giris], gorulen = new Set();
  while (yigit.length > 0) {
    const d = normalize(yigit.pop());
    if (gorulen.has(d)) continue;
    gorulen.add(d);
    for (const [, y] of readFileSync(d, "utf8").matchAll(ITHAL)) yigit.push(join(dirname(d), y));
  }
  return [...gorulen].sort();
}

const kume = kapanis("tek-parca/uygulama.mjs");
console.log(`dagitim birimi = 1, birimin kapanisi = ${kume.length} dosya (${kume.join(", ")})`);
console.log(`ucret.mjs degisirse: degisen dosya = 1, yeniden yayinlanan dosya = ${kume.length}`);
for (const g of ["ortak/ucret.mjs", "ortak/operasyon.mjs"]) {
  console.log(`${g} tek basina kosmak icin gereken komsu = ${kapanis(g).length - 1}`);
}
```

```sh
node tek-parca/olc.mjs
```

```
teklif ciktisi = {"id":"G1","net":5100,"gun":3,"tasiyici":"MT"}
sinir gecisi = 4 (uygulama.teklif -> ucret.net -> operasyon.teslimGunu -> operasyon.tasiyici)
dokunulan birim = 3, serilestirme noktasi = 0, sinirdan gecen bayt = 0
dagitim birimi = 1, birimin kapanisi = 4 dosya (ortak/operasyon.mjs, ortak/sar.mjs, ortak/ucret.mjs, tek-parca/uygulama.mjs)
ucret.mjs degisirse: degisen dosya = 1, yeniden yayinlanan dosya = 4
ortak/ucret.mjs tek basina kosmak icin gereken komsu = 0
ortak/operasyon.mjs tek basina kosmak icin gereken komsu = 0
```

#### Sayıların Okunması

Dört sınır geçişi var ve dördü de fonksiyon çağrısıdır. Serileştirme noktası 0, sınırdan geçen
bayt 0: `g` nesnesi ücretlendirmeye kopyalanmadan, aynı bellekteki başvurusuyla geçti. Bu
biçemin en somut kazancıdır. Sınırdan geçen veri için bir biçim kararlaştırılmaz, sürüm
uyumluluğu tartışılmaz, dönüştürme noktası tanımlanmaz — çünkü dönüştürme yoktur.

İkinci sayı çifti biçemin sınırını gösterir. Ücretlendirme kuralında bir satır değişirse
değişen dosya 1'dir, ama yeniden yayınlanan dosya 4'tür. Ücretlendirmenin asgari ücretini
düzelten bir sürüm, operasyon bağlamının kodunu da yeniden yayına sokar. Bu, kod düzeyinde bir
bağlaşım değildir; iki bağlam birbirini ithal etmiyor. Yayın düzeyinde bir bağlaşımdır ve
kaynağı biçemin kendisidir.

Son iki satır önemli bir ayrımı ortaya koyar. Her iki bağlam **tek başına sınanabilir**: hiçbiri
ötekinin dosyasına ihtiyaç duymuyor, gereken komşu sayısı 0. Yani tek parça mimari bağımsız
*sınanabilirliği* engellemez; engellediği bağımsız *yayındır*. Bu iki şeyin karıştırılması,
biçem tartışmalarında en sık yapılan hatadır.

#### Paylaşılan Kader

Tek parça biçemin ikinci sınırı arıza davranışındadır. İki bağlam aynı süreçte koştuğu için
süreci sonlandıran bir kusur ikisini birden durdurur. İstek başına yakalama koymak yetmez:
yakalama yalnız kendi çağrı yığıtındaki hatayı tutar, sonraya bırakılmış bir görevden yükselen
hatayı tutmaz.

```js
// tek-parca/ariza.mjs — istek basina yakalama varken bile sureci sonlandiran kusur
import * as ucret from "../ortak/ucret.mjs";
import * as operasyon from "../ortak/operasyon.mjs";

const ISTEK = [
  { birim: "ucret", id: "G1" }, { birim: "operasyon", id: "G2" },
  { birim: "ucret", id: "G3", kusurlu: true }, { birim: "operasyon", id: "G4" },
  { birim: "ucret", id: "G5" }, { birim: "operasyon", id: "G6" },
];

let yanitlanan = 0;
process.on("exit", () => {
  console.log(`yanitlanan istek = ${yanitlanan} / ${ISTEK.length}`);
  console.log(`ayakta kalan birim = ${yanitlanan === ISTEK.length ? 2 : 0} / 2`);
});

for (const istek of ISTEK) {
  try {
    if (istek.birim === "ucret") {
      if (istek.kusurlu) setTimeout(() => { throw new RangeError(`tarife kademesi yok: ${istek.id}`); });
      console.log(`${istek.id} ucret  = ${ucret.net(4, "35", 0.15)}`);
    } else {
      console.log(`${istek.id} rota   = ${operasyon.rota("35").join("-")}`);
    }
    yanitlanan += 1;
  } catch (e) {
    console.log(`${istek.id} yakalandi: ${e.message}`);
  }
  await new Promise((c) => setTimeout(c, 5));
}
```

Yığıt izi standart hata akışına yazılır; aşağıdaki komut onu ayırıp yalnız sayılan çıktıyı ve
çıkış kodunu bırakır.

```sh
node tek-parca/ariza.mjs 2>/dev/null; echo "cikis kodu = $?"
```

```
G1 ucret  = 5100
G2 rota   = 34-41-35
G3 ucret  = 5100
yanitlanan istek = 3 / 6
ayakta kalan birim = 0 / 2
cikis kodu = 1
```

Altı isteğin üçü yanıtlandı. Kusur ücretlendirme bağlamındaydı, ama G4 ve G6 operasyon
isteklerinin ikisi de yanıtsız kaldı; ayakta kalan birim 0. Arıza yalıtımı bu biçemde ölçülemez
bir nitelik değildir, ölçülür ve sonucu sıfırdır.

#### Biçemin Bıraktığı Fatura

Sayılar bir kalite niteliği ödünleşimidir ve iki yönü de aynı derste görünüyor. Kazanç
tarafında: sınırdan geçen bayt 0, serileştirme noktası 0, bir isteğin uçtan uca izlenmesi için
bakılacak süreç sayısı 1. Kayıp tarafında: bir dosyalık değişiklik için yeniden yayınlanan dosya
4, altı istekte ayakta kalan birim 0.

Bu ödünleşimin yönü ekip ve sürüm sıklığıyla değişir. İki bağlamı aynı ekip aynı takvimde
yayınlıyorsa "yeniden yayınlanan dosya 4" bir maliyet değildir; yayın zaten birlikte
yapılacaktı. Bağlamlar ayrı takvimlere geçtiğinde aynı sayı doğrudan bir bekleme süresine
dönüşür: ücretlendirme sürümü, operasyon tarafının kararlı olmasını beklemek zorundadır.

Ölçek sınırı da aynı yerden çıkar. Süreç tek olduğu için kopyalama ancak bütün olarak yapılır:
ücretlendirme isteklerinin sayısı arttığında operasyon kodu da onunla birlikte çoğaltılır.
Kopyalanan birim sayısı 1 olduğu sürece hangi bağlamın yüklendiği bilgisi kopyalama kararına
giremez.

#### Özet

- Dağıtım birimi birlikte yayınlanmak zorunda olan dosya kümesidir; tek parça mimari bütün
  bağlamları tek dağıtım biriminde tutan biçemdir.
- Teklif isteği 3 birime dokundu ve 4 sınırdan geçti; dördü de fonksiyon çağrısı olduğu için
  serileştirme noktası 0, sınırdan geçen bayt 0 ölçüldü.
- Bir dosyada yapılan değişiklik 4 dosyayı yeniden yayınlattı; bu, kod bağlaşımı değil yayın
  bağlaşımıdır ve kaynağı biçemin kendisidir.
- Her iki bağlam tek başına koşabildi (gereken komşu 0); biçem bağımsız sınanabilirliği değil
  bağımsız yayını engelliyor.
- Süreci sonlandıran bir kusurda altı isteğin 3'ü yanıtlandı ve ayakta kalan birim 0 oldu; arıza
  yalıtımı ölçüldü ve sıfır çıktı.
- Ödünleşimin yönü sabit değildir: bağlamlar aynı takvimde yayınlanıyorsa yayın bağlaşımı bedel
  değildir, ayrı takvimlere geçildiğinde bekleme süresine dönüşür.

#### Sonraki Adım

Yayın bağlaşımının bedeli iki bağlam ayrı takvimlere geçtiğinde ortaya çıkıyor. İlk çözüm
denemesi bağlamları ayırmak değil, aralarına paylaşılan bir tümleştirme yüzeyi koymaktır: her
bağlam kendi dağıtım birimine çıkar, ama ikisi de ortak bir kayıt biçimi ve ortak bir çağrı
yolu üzerinden konuşur. Bu düzen kurum çapında yeniden kullanımı hedefler ve ölçüsü de oradan
gelir: ortak biçimin alan sayısı, her sınır geçişindeki dönüştürme noktası sayısı ve ortak
biçimde bir alan değiştiğinde birlikte yayınlanmak zorunda kalan servis sayısı. Sonraki ders bu
üç sayıyı ölçer.

### Servis Yönelimli Mimari

Source: https://academia.sh/tr/kurslar/mimari-bicemler/servis-yonelimli-mimari

İki bağlamın kurum çapında paylaşılan tek bir kayıt biçimi ve tek bir çağrı yolu üzerinden konuşması: kanonik kaydın alan sayısının, her servisin taşıdığı ama okumadığı alan sayısının, dönüştürme noktasının ve sınırdan geçen baytın ölçülmesi, dar sözleşmeyle karşılaştırılması ve ortak biçimde bir alan değiştiğinde düzenlenen dosya sayısının sayılması.

Tek parça düzende iki bağlam aynı dağıtım biriminde durduğu için bir dosyalık değişiklik dört
dosyayı yeniden yayınlatıyordu. İlk çözüm denemesi bağlamları birbirinden koparmak değil,
aralarına paylaşılan bir tümleştirme yüzeyi koymaktır. Her bağlam kendi dağıtım birimine çıkar,
ama ikisi de ortak bir kayıt biçimi ve ortak bir çağrı yolu üzerinden konuşur.

**Servis yönelimli mimari** (service-oriented architecture) bu düzeni kurumun tamamı için
kurar: her yetenek kaba taneli bir **servis** olarak yayınlanır, servisler kendi
gerçekleştirimlerini gizler ve aralarındaki alışveriş kurum çapında geçerli tek bir kayıt
biçimi üzerinden yapılır. Buradaki servis, Tasarım Kalıpları kursundaki Servis Katmanı
dersinde ölçülen uygulama içi katmanla aynı sözcüğü paylaşır ama farklı bir şeydir: orada
servis bir uygulamanın iç sınırıydı, burada kendi başına yayınlanan bir dağıtım birimidir.

#### Kanonik Kayıt ve Ortak Çağrı Yolu

Biçemin merkezinde **kanonik veri modeli** (canonical data model) durur: iki bağlamın da
kabul ettiği tek bir gönderi kaydı. Kayıt her iki bağlamın ihtiyacını karşılamak zorunda
olduğu için alanları ikisinin birleşimidir.

```js
// ortak/kanonik.mjs — kurum capinda tek gonderi kaydi: her servis ayni bicimi konusur
export const KANONIK_ALAN = [
  "id", "agirlik", "hacim", "bolge", "deger",
  "sozlesmeNo", "sozlesmeOrani", "tasiyiciKodu", "teslimDurumu", "sonZamanDamgasi",
];

export function kanonikYap(g) {
  const k = {};
  for (const a of KANONIK_ALAN) k[a] = g[a] ?? null;
  return k;
}
```

Çağrılar doğrudan servisten servise gitmez, paylaşılan bir yüzeyden geçer. Bu yüzey servisi
adıyla bulur, gövdeyi kanonik biçimde taşır ve geçişleri sayar. Serileştirme burada gerçek:
gövde metne çevrilir, karşı tarafta yeniden okunur.

```js
// veriyolu/veriyolu.mjs — paylasilan tumlestirme yuzeyi: cagriyi kanonik bicimde tasir ve sayar
import { kanonikYap } from "../ortak/kanonik.mjs";

export function veriyolu(kayit) {
  const servisler = new Map();
  return {
    tanit(ad, servis) { servisler.set(ad, servis); },
    cagir(ad, islem, gonderi) {
      const govde = JSON.stringify(kanonikYap(gonderi));
      kayit.gecis.push(`veriyolu -> ${ad}.${islem}`);
      kayit.donusum += 1;
      kayit.bayt += govde.length;
      const sonuc = servisler.get(ad)[islem](JSON.parse(govde));
      const yanit = JSON.stringify(sonuc);
      kayit.gecis.push(`${ad}.${islem} -> veriyolu`);
      kayit.donusum += 1;
      kayit.bayt += yanit.length;
      return JSON.parse(yanit);
    },
  };
}
```

Her servis kanonik kaydı alır, kendi iç biçimine çevirir, işini yapar ve sonucu döndürür.
`OKUNAN_ALAN` listesi servisin kanonik kayıttan gerçekten kullandığı alanları bildirir; ölçüm
bu listeyi kullanır.

```js
// servis/ucret-servisi.mjs — kaba taneli servis: kanonik kaydi alir, kendi bicimine cevirir
const TARIFE = { kademe: [[1, 3000], [5, 4800], [20, 9600]], bolge: { "34": 100, "06": 115, "35": 125 }, asgari: 2500 };
export const OKUNAN_ALAN = ["id", "agirlik", "bolge", "sozlesmeOrani"];

export const ucretServisi = {
  fiyatla(kanonik) {
    const g = { agirlik: kanonik.agirlik, bolge: kanonik.bolge, oran: kanonik.sozlesmeOrani ?? 0 };
    const kademe = TARIFE.kademe.find(([k]) => g.agirlik <= k) ?? [0, 9600];
    const taban = Math.max(TARIFE.asgari, Math.round((kademe[1] * TARIFE.bolge[g.bolge]) / 100));
    return { id: kanonik.id, net: taban - Math.round(taban * Math.min(g.oran, 0.4)) };
  },
};
```

```js
// servis/operasyon-servisi.mjs — kaba taneli servis: ayni kanonik kayittan rota uretir
const AGAC = { "34": ["34"], "06": ["34", "06"], "35": ["34", "41", "35"] };
export const OKUNAN_ALAN = ["id", "bolge", "teslimDurumu"];

export const operasyonServisi = {
  planla(kanonik) {
    const rota = AGAC[kanonik.bolge] ?? ["34"];
    return {
      id: kanonik.id, rota, gun: rota.length,
      tasiyici: rota.length > 2 ? "MT" : "AN",
      durum: kanonik.teslimDurumu ?? "planlandi",
    };
  },
};
```

#### Paylaşılan Biçimin Ölçülmesi

Ölçüm dört soru sorar: bir istek kaç sınırdan geçiyor ve kaç dönüştürme noktası taşıyor, her
servis kanonik kaydın kaç alanını gerçekten okuyor, aynı istek servise özel dar bir sözleşmeyle
kaç bayta iner, ortak biçimde bir alan değiştiğinde kaç dosya düzenlenir.

```js
// soa/olc.mjs — kanonik bicimin alan kullanimi, donusum noktasi, bayt ve ortak alan degisiminin etkisi
import { readFileSync } from "node:fs";
import { KANONIK_ALAN, kanonikYap } from "../ortak/kanonik.mjs";
import { veriyolu } from "../veriyolu/veriyolu.mjs";
import { ucretServisi, OKUNAN_ALAN as UCRET_ALAN } from "../servis/ucret-servisi.mjs";
import { operasyonServisi, OKUNAN_ALAN as OPERASYON_ALAN } from "../servis/operasyon-servisi.mjs";

const GONDERI = { id: "G1", agirlik: 4, hacim: 18, bolge: "35", deger: 12000, sozlesmeNo: "S7", sozlesmeOrani: 0.15 };
const kayit = { gecis: [], donusum: 0, bayt: 0 };
const yol = veriyolu(kayit);
yol.tanit("ucret", ucretServisi);
yol.tanit("operasyon", operasyonServisi);

const fiyat = yol.cagir("ucret", "fiyatla", GONDERI);
const plan = yol.cagir("operasyon", "planla", GONDERI);
console.log(`teklif = ${JSON.stringify({ id: fiyat.id, net: fiyat.net, gun: plan.gun, tasiyici: plan.tasiyici })}`);
console.log(`sinir gecisi = ${kayit.gecis.length}, donusum noktasi = ${kayit.donusum}, sinirdan gecen bayt = ${kayit.bayt}`);

console.log(`kanonik alan = ${KANONIK_ALAN.length}`);
for (const [ad, okunan] of [["ucret", UCRET_ALAN], ["operasyon", OPERASYON_ALAN]]) {
  console.log(`  ${ad.padEnd(9)} okudugu alan = ${okunan.length}, tasidigi ama okumadigi alan = ${KANONIK_ALAN.length - okunan.length}`);
}

const dar = (alanlar) => JSON.stringify(Object.fromEntries(alanlar.map((a) => [a, GONDERI[a] ?? null])));
const kanonikBayt = 2 * JSON.stringify(kanonikYap(GONDERI)).length;
const darBayt = dar(UCRET_ALAN).length + dar(OPERASYON_ALAN).length;
console.log(`iki istek govdesi: kanonik = ${kanonikBayt} bayt, servise ozel dar sozlesme = ${darBayt} bayt`);
for (const n of [2, 4, 8]) {
  console.log(`  servis = ${n}: kanonik donusturucu = ${n}, noktadan noktaya donusturucu = ${n * (n - 1)}`);
}

const DOSYA = ["ortak/kanonik.mjs", "veriyolu/veriyolu.mjs", "servis/ucret-servisi.mjs", "servis/operasyon-servisi.mjs"];
for (const alan of ["bolge", "sozlesmeOrani"]) {
  const gecen = DOSYA.filter((d) => readFileSync(d, "utf8").includes(alan));
  console.log(`"${alan}" alani degisirse duzenlenen dosya = ${gecen.length} (${gecen.join(", ")})`);
}
```

```sh
node soa/olc.mjs
```

```
teklif = {"id":"G1","net":5100,"gun":3,"tasiyici":"MT"}
sinir gecisi = 4, donusum noktasi = 4, sinirdan gecen bayt = 427
kanonik alan = 10
  ucret     okudugu alan = 4, tasidigi ama okumadigi alan = 6
  operasyon okudugu alan = 3, tasidigi ama okumadigi alan = 7
iki istek govdesi: kanonik = 326 bayt, servise ozel dar sozlesme = 101 bayt
  servis = 2: kanonik donusturucu = 2, noktadan noktaya donusturucu = 2
  servis = 4: kanonik donusturucu = 4, noktadan noktaya donusturucu = 12
  servis = 8: kanonik donusturucu = 8, noktadan noktaya donusturucu = 56
"bolge" alani degisirse duzenlenen dosya = 3 (ortak/kanonik.mjs, servis/ucret-servisi.mjs, servis/operasyon-servisi.mjs)
"sozlesmeOrani" alani degisirse duzenlenen dosya = 2 (ortak/kanonik.mjs, servis/ucret-servisi.mjs)
```

#### Sayıların Okunması

Tek parça derste teklif isteği 4 sınırdan geçiyordu, dönüştürme noktası 0, sınırdan geçen bayt
0'dı. Aynı çıktıyı üreten servis yönelimli düzende sınır geçişi yine 4 ama dönüştürme noktası
4'e, sınırdan geçen bayt 427'ye çıktı. Bu, biçemin ilk faturasıdır: her sınır artık bir
serileştirme noktasıdır ve gövdenin biçimi bir sözleşmedir.

İkinci sayı kümesi kanonik modelin karakteristik maliyetini gösterir. Kayıt 10 alan taşıyor;
ücret servisi bunların 4'ünü okuyor, 6'sını taşıyıp bırakıyor. Operasyon servisi 3'ünü okuyor,
7'sini taşıyıp bırakıyor. Bu boş alanlar yalnız bayt değildir: her biri servisin bilmek zorunda
olmadığı bir kavramı sözleşmesine sokar. `deger` alanının anlamı değiştiğinde operasyon servisi
o alanı hiç okumasa da sözleşmesi değişmiş olur.

Dar sözleşme karşılaştırması bedeli sayıya çeviriyor: aynı iki istek servise özel biçimlerle
101 bayt, kanonik biçimle 326 bayt. Kanonik modelin karşılığını aldığı yer ise dönüştürücü
sayısıdır. İki servis varken iki düzen eşit; sekiz servise çıkıldığında kanonik düzende 8
dönüştürücü, noktadan noktaya düzende 56 dönüştürücü gerekir. Kanonik modelin gerekçesi budur
ve servis sayısı arttıkça güçlenir.

Son iki satır biçemin gerçek sınırını gösterir. Ortak biçimde `bolge` alanı değişirse 3 dosya
düzenlenir ve bunlardan ikisi ayrı dağıtım birimidir: iki servis birlikte yayınlanmak zorunda
kalır. `sozlesmeOrani` değiştiğinde 2 dosya düzenlenir ve yalnız bir servis etkilenir. Yayın
bağlaşımı tek parça düzende olduğu gibi %100 değil, ama sıfır da değil: paylaşılan sözleşmenin
kapsadığı her alan bir bağlaşım noktasıdır.

#### Biçemin Yerleştiği Yer

Sayılar bir kalite niteliği ödünleşimini betimliyor. Kazanılan nitelik uyumluluk ve yeniden
kullanımdır: on alanlık tek bir kayıt biçimi öğrenildiğinde kuruma yeni katılan bir servis
mevcut iki servisle konuşmak için 1 dönüştürücü yazar, iki ayrı biçim öğrenmek zorunda kalmaz.
Bırakılan nitelik bağımsız değişebilirliktir: paylaşılan biçimin her alanı, o alanı okumayan
servisleri de yayına bağlar.

Bu yüzden biçemin uygulandığı yer kurum ölçeğidir. İki servis için kanonik model kazanç
getirmez — dönüştürücü sayısı iki düzende de 2, karşılığında istek gövdesi üç katına çıkar.
Servis sayısı arttıkça ve servisler ayrı ekiplere dağıldıkça denge tersine döner: öğrenilecek
biçim sayısı 1'de kalır, dönüştürücü sayısı doğrusal büyür.

#### Özet

- Servis yönelimli mimari her yeteneği kaba taneli bir dağıtım birimi olarak yayınlar ve
  alışverişi kurum çapında tek bir kanonik kayıt biçimi üzerinden yürütür.
- Aynı teklif isteği tek parça düzende 0 dönüştürme noktası ve 0 bayt taşırken bu düzende 4
  dönüştürme noktası ve 427 bayt taşıdı.
- On alanlık kanonik kaydın ücret servisi 4, operasyon servisi 3 alanını okudu; taşınıp
  okunmayan alan sayısı 6 ve 7 oldu.
- İki istek dar sözleşmelerle 101 bayt, kanonik biçimle 326 bayt tuttu; kanonik modelin
  karşılığı dönüştürücü sayısıdır: sekiz servis için 8'e karşı 56.
- Ortak biçimde `bolge` alanı değiştiğinde 3 dosya düzenlendi ve iki servis birlikte yayınlanmak
  zorunda kaldı; yayın bağlaşımı %100'den düştü ama sıfırlanmadı.
- Biçem kurum ölçeğinde kazanır: öğrenilecek biçim sayısı 1'de kalırken dönüştürücü sayısı
  servis sayısıyla doğrusal büyür.

#### Sonraki Adım

Paylaşılan sözleşme bağlaşımın kaynağı olduğu ortaya çıktı: on alanın hepsi, o alanı okumayan
servisi bile yayına bağlıyor. Bir sonraki adım ortak biçimi kaldırmaktır. Her servis kendi
sözleşmesini, kendi veri biçimini ve kendi yayın takvimini alır; aralarındaki çağrı ortak bir
yüzeyden değil doğrudan geçer. Bu düzen bağımsız yayını gerçekten sağlar ama karşılığında yeni
bir sayı üretir: bir servis durduğunda kaç isteğin yanıtlanmaya devam ettiği. Sonraki ders iki
servisi ayrı yerel süreç olarak çalıştırır, sınır geçişini ve gövde boyutunu ölçer, sonra bir
süreci durdurup ayakta kalan uç sayısını sayar.

### Mikroservis Mimarisi

Source: https://academia.sh/tr/kurslar/mimari-bicemler/mikroservis-mimarisi

Paylaşılan sözleşme kaldırıldığında ne kazanıldığı ve ne ödendiği: iki bağlamın ayrı yerel süreçler olarak koşturulması, dağıtım birimi başına yeniden yayınlanan dosya sayısının 1'e indirilmesi, bağımsızlık için ödenen yapıştırma satırının sayılması, bir süreç durdurulduğunda yanıt veren uç sayısının ve kısmi yanıtın ölçülmesi.

Servis yönelimli düzende yayın bağlaşımının kaynağı paylaşılan sözleşmeydi: on alanlık kanonik
kayıt, o alanları okumayan servisi bile yayına bağlıyordu. Bir sonraki adım ortak biçimi
kaldırmaktır. Her bağlam kendi sözleşmesini, kendi veri biçimini ve kendi yayın takvimini alır;
çağrı ortak bir yüzeyden değil doğrudan karşı servise gider.

**Mikroservis mimarisi** (microservice architecture) bu düzendir: her servis kendi süreci, kendi
verisi ve kendi sözleşmesiyle tek başına yayınlanabilen bir dağıtım birimidir. Servis yönelimli
mimariden ayrıldığı yer boyut değil, paylaşılan varlıkların yokluğudur — kurum çapında kanonik
model yoktur, ortak çağrı yüzeyi yoktur. Bu dersteki ölçüm iki bağlamı gerçekten ayrı yerel
süreçler olarak koşturur.

#### Üç Süreç

Ücretlendirme bağlamı kendi sunucusudur. Sözleşmesi kendisine aittir: yalnız ihtiyacı olan
alanları sorgu dizisinden okur, kendi yanıt biçimini döndürür.

```js
// mikro/ucret.mjs — ucretlendirme baglami: kendi sureci, kendi sozlesmesi, kendi yayin takvimi
import { createServer } from "node:http";

const TARIFE = { kademe: [[1, 3000], [5, 4800], [20, 9600]], bolge: { "34": 100, "06": 115, "35": 125 }, asgari: 2500 };

function fiyatla(s) {
  const agirlik = Number(s.get("agirlik")), oran = Number(s.get("oran") ?? 0);
  const kademe = TARIFE.kademe.find(([k]) => agirlik <= k) ?? [0, 9600];
  const taban = Math.max(TARIFE.asgari, Math.round((kademe[1] * TARIFE.bolge[s.get("bolge")]) / 100));
  return { id: s.get("id"), net: taban - Math.round(taban * Math.min(oran, 0.4)) };
}

const port = Number(process.argv[2]);
if (Number.isInteger(port) === false) console.log("kullanim: node mikro/ucret.mjs <baglanti-noktasi>");
else createServer((istek, yanit) => {
  const govde = JSON.stringify(fiyatla(new URL(istek.url, "http://yerel").searchParams));
  yanit.writeHead(200, { "content-type": "application/json" });
  yanit.end(govde);
}).listen(port);
```

```js
// mikro/operasyon.mjs — teslimat operasyonu baglami: ayri surec, ayri sozlesme
import { createServer } from "node:http";

const AGAC = { "34": ["34"], "06": ["34", "06"], "35": ["34", "41", "35"] };

function planla(s) {
  const rota = AGAC[s.get("bolge")] ?? ["34"];
  return { id: s.get("id"), rota, gun: rota.length, tasiyici: rota.length > 2 ? "MT" : "AN" };
}

const port = Number(process.argv[2]);
if (Number.isInteger(port) === false) console.log("kullanim: node mikro/operasyon.mjs <baglanti-noktasi>");
else createServer((istek, yanit) => {
  const govde = JSON.stringify(planla(new URL(istek.url, "http://yerel").searchParams));
  yanit.writeHead(200, { "content-type": "application/json" });
  yanit.end(govde);
}).listen(port);
```

Teklif isteği artık bir fonksiyon çağrısı değil, iki ağ çağrısının birleştirilmesidir. Çağrılar
başarısız olabildiği için birleştirici bir karar vermek zorundadır: isteği tümden reddetmek mi,
elde edileni döndürüp eksiği bildirmek mi. Aşağıdaki gerçekleştirim ikincisini seçer ve eksik
parça sayısını yanıt sözleşmesine yazar.

```js
// mikro/teklif.mjs — birlestirici: iki servisi ag uzerinden cagirir, eksik yaniti isaretler
import { createServer } from "node:http";

async function al(adres, sorgu) {
  const y = await fetch(`${adres}?${sorgu}`);
  if (y.ok === false) throw new Error(`${adres} 200 dondurmedi`);
  return y.json();
}

async function teklif(sorgu, id) {
  const [f, p] = await Promise.allSettled([
    al(process.env.UCRET_ADRES, sorgu), al(process.env.OPERASYON_ADRES, sorgu),
  ]);
  return {
    id, net: f.status === "fulfilled" ? f.value.net : null,
    gun: p.status === "fulfilled" ? p.value.gun : null,
    tasiyici: p.status === "fulfilled" ? p.value.tasiyici : null,
    eksik: [f, p].filter((r) => r.status === "rejected").length,
  };
}

const port = Number(process.argv[2]);
if (Number.isInteger(port) === false) console.log("kullanim: node mikro/teklif.mjs <baglanti-noktasi>");
else createServer(async (istek, yanit) => {
  const s = new URL(istek.url, "http://yerel").searchParams;
  const govde = JSON.stringify(await teklif(s.toString(), s.get("id")));
  yanit.writeHead(200, { "content-type": "application/json" });
  yanit.end(govde);
}).listen(port);
```

#### İki Durumun Ölçülmesi

Sürücü betik üç süreci başlatır, üç ucu da ölçer, sonra ücretlendirme sürecini durdurup aynı üç
ucu yeniden ölçer. Bağlantı noktası numaraları ortama bağlıdır; makinede kullanımdaysa
değiştirilmelidir.

```bash
# olc.sh — uc ayri surec baslatilir; hepsi ayaktayken ve ucret durdugunda olculur
node mikro/ucret.mjs 8791 & UCRET=$!
node mikro/operasyon.mjs 8792 & OPERASYON=$!
UCRET_ADRES=http://127.0.0.1:8791/fiyat OPERASYON_ADRES=http://127.0.0.1:8792/plan \
  node mikro/teklif.mjs 8793 & TEKLIF=$!
sleep 1
S="id=G1&agirlik=4&bolge=35&oran=0.15"

olc() {
  for u in 8791/fiyat 8792/plan 8793/teklif; do
    printf "  %-12s %s\n" "$u" "$(curl -s -o /dev/null -w '%{http_code} kod, %{size_download} bayt' "http://127.0.0.1:$u?$S")"
  done
  printf "  teklif govdesi = %s\n" "$(curl -s "http://127.0.0.1:8793/teklif?$S")"
}

echo "durum 1: uc surec ayakta"
olc
kill $UCRET
sleep 0.5
echo "durum 2: ucret sureci durduruldu"
olc
kill $OPERASYON $TEKLIF 2>/dev/null

echo "dagitim birimi = 3"
for d in mikro/ucret.mjs mikro/operasyon.mjs mikro/teklif.mjs; do
  printf "  %-20s ithal = %s, sunucu yapistirmasi = %s satir\n" "$d" \
    "$(grep -c '^import' $d)" "$(grep -cE 'createServer|writeHead|yanit.end|process.argv|Number.isInteger' $d)"
done
```

```
durum 1: uc surec ayakta
  8791/fiyat   200 kod, 22 bayt
  8792/plan    200 kod, 59 bayt
  8793/teklif  200 kod, 56 bayt
  teklif govdesi = {"id":"G1","net":5100,"gun":3,"tasiyici":"MT","eksik":0}
durum 2: ucret sureci durduruldu
  8791/fiyat   000 kod, 0 bayt
  8792/plan    200 kod, 59 bayt
  8793/teklif  200 kod, 56 bayt
  teklif govdesi = {"id":"G1","net":null,"gun":3,"tasiyici":"MT","eksik":1}
dagitim birimi = 3
  mikro/ucret.mjs      ithal = 1, sunucu yapistirmasi = 6 satir
  mikro/operasyon.mjs  ithal = 1, sunucu yapistirmasi = 6 satir
  mikro/teklif.mjs     ithal = 1, sunucu yapistirmasi = 6 satir
```

#### Sayıların Okunması

Arıza yalıtımı ilk derste sıfır çıkmıştı: tek süreçte bir kusur altı isteğin üçünü yanıtsız
bıraktı, ayakta kalan birim 0 oldu. Burada aynı bağlam durdurulduğunda üç uçtan 2'si 200
döndürmeye devam etti. Operasyon ucu 59 baytlık yanıtını tek başına üretti; ücretlendirme
tarafına hiç bakmadı. Ölçülen fark budur ve biçemin asıl gerekçesidir.

Birleştirici ucun davranışı ikinci bir sayı üretir. Teklif ucu 200 döndürdü ama gövdesi
değişti: `net` alanı `null`, `eksik` alanı 1. Bu **kısmi yanıt** (partial response) bir kaza
değil, biçemin zorladığı bir tasarım kararıdır. Tek parça düzende gerekmiyordu, çünkü çağrı
başarısız olamıyordu; burada yanıt sözleşmesine bir alan eklendi ve o alanın anlamını
istemcinin bilmesi gerekiyor.

Yayın tarafında kazanç ölçüldü: her dağıtım biriminin kapanışı 1 dosyadır, dolayısıyla
ücretlendirme kuralında bir satır değişirse yeniden yayınlanan dosya 1'dir. Tek parça düzende
bu sayı 4, servis yönelimli düzende `bolge` alanı için 3'tü. Ödenen bedel de aynı çıktıda
görünüyor: üç dosyanın her birinde 6 satır sunucu yapıştırma kodu tekrarlanıyor, toplam 18
satır. Bu tekrarı ortak bir dosyaya almak yayın bağlaşımını geri getirir — o dosya değiştiğinde
üç birim birlikte yayınlanır.

#### Dağıtık Karmaşıklığın Sayısı

Biçemin faturası tek bir yerde toplanır: birleştirilmiş bir isteğin sonucu artık iki değil, alt
servis sayısına bağlı olarak dört durumdan biridir. Ayakta olma durumları $2^n$ ile büyür; iki
alt servis için 4 durum vardır ve ölçüm bunların ikisini gerçekten koşturdu. Tek parça düzende
$n = 1$ olduğu için durum sayısı 2'ydi: çalışıyor ya da çalışmıyor.

Durum sayısının büyümesi doğrudan bir sürdürülebilirlik maliyetidir. Her ek durum için bir karar
gerekir — reddetmek mi, kısmi yanıt vermek mi, eski bir değeri kullanmak mı — ve o karar bir
sözleşme alanına, bir sınama senaryosuna ve bir istemci davranışına dönüşür. Ölçümde 3 uç ve 2
durum sınandı; alt servis sayısı dörde çıksa aynı sınama kümesi 16 duruma bakmak zorunda kalır.

İkinci maliyet sözleşmelerin çoğalmasıdır. Servis yönelimli düzende öğrenilecek biçim sayısı
1'di; burada her servis kendi biçimini yayınlar. Ölçümde üç ucun üçü de farklı bir gövde
biçiminde yanıt verdi: 22, 59 ve 56 baytlık üç ayrı sözleşme. Kanonik modelin kaldırılması bayt
sayısını düşürdü ve yayın bağlaşımını kırdı, ama öğrenilecek ve sürüm uyumu izlenecek biçim
sayısını servis sayısına eşitledi.

#### Özet

- Mikroservis mimarisinde her servis kendi süreci, kendi verisi ve kendi sözleşmesiyle tek başına
  yayınlanabilen bir dağıtım birimidir; paylaşılan kanonik model ve ortak çağrı yüzeyi yoktur.
- Ücretlendirme süreci durdurulduğunda üç uçtan 2'si 200 döndürmeye devam etti; tek parça
  düzende aynı kusur ayakta kalan birimi 0'a indirmişti.
- Birleştirici uç kısmi yanıt verdi: `net` alanı `null`, `eksik` alanı 1: biçem, yanıt
  sözleşmesine yeni bir alan ve istemciye yeni bir karar yükledi.
- Her dağıtım biriminin kapanışı 1 dosya olduğu için yeniden yayınlanan dosya sayısı 1'e indi;
  karşılığında üç dosyada 6'şar satır, toplam 18 satır sunucu yapıştırması tekrarlandı.
- Birleştirilmiş isteğin sonuç durumu $2^n$ ile büyür: iki alt servis için 4 durum, tek parça
  düzende 2 durum.
- Öğrenilecek gövde biçimi sayısı servis sayısına eşitlendi; ölçümde üç uç 22, 59 ve 56 baytlık
  üç ayrı sözleşme döndürdü.

#### Sonraki Adım

Buraya kadarki üç biçemde çağrı yönü hiç değişmedi: birleştirici, ücretlendirmeyi ve operasyonu
adıyla biliyor, çağırıyor ve yanıtı bekliyor. Bilme yükümlülüğü ve bekleme, ölçülen bütün
maliyetlerin ortak kaynağıdır — kısmi yanıt kararı da, sözleşme sayısı da buradan çıktı. Bir
sonraki biçem çağrı yönünü tersine çevirir: ücretlendirme bir olay yayınlar, kimin dinlediğini
bilmez ve yanıt beklemez. Sonraki ders olayın sistem sınırını geçtiğinde nelerin değiştiğini
ölçer: yayıncının bildiği tüketici sayısı, yeni bir tüketici eklemek için yayıncıda düzenlenen
satır sayısı ve aynı olay iki kez geldiğinde iki tüketici tasarımının sonucu arasındaki sapma.

### Olay Güdümlü Mimari

Source: https://academia.sh/tr/kurslar/mimari-bicemler/olay-gudumlu-mimari

Çağrı yönünün tersine çevrilmesi: yayıncının bildiği ve adıyla çağırdığı tüketici sayısının sayılması, yeni bir tüketici eklendiğinde yayıncı dosyasında düzenlenen satırın ölçülmesi, sınırdan geçen bayt ile dönüştürme noktasının tüketici sayısıyla nasıl büyüdüğünün hesaplanması, bir tüketici düştüğünde yanıtlanan istek sayısının karşılaştırılması ve aynı olay ikinci kez ulaştığında iki tüketici tasarımının sapmasının ölçülmesi.

Buraya kadarki üç biçemde tek bir şey hiç değişmedi: çağrı yönü. Tek parçada birleştirme bir
fonksiyon çağrısıydı, servis yönelimli düzende paylaşılan bir yüzeyden geçen bir çağrıydı,
mikroservis düzeninde iki ağ çağrısının birleşimiydi. Üçünde de çağıran taraf karşısındaki
birimi adıyla biliyor, çağırıyor ve yanıtını bekliyordu. Ölçülen bütün maliyetler bu iki
yükümlülükten çıktı.

**Olay güdümlü mimari** (event-driven architecture) çağrı yönünü tersine çevirir: birim, olmuş
bir olguyu ilan eder; kimin dinlediğini bilmez, dönüş değeri almaz. Ücretlendirme bağlamı artık
operasyonu çağırmaz, `gonderi-fiyatlandi` olayını yayınlar. Alan olayı kavramı Alan Odaklı
Tasarım kursunda kuruldu; o kursun Olay Kaynaklı Kalıcılık dersi olay dizisini bir *kalıcılık
biçimi* olarak ölçtü. Buradaki soru başkadır: olay bir *sistem düzeninin* taşıyıcısı olduğunda,
yani uygulama içinden çıkıp sistem sınırını geçtiğinde ne değişir.

#### Sınırı Geçen Olayın Üç Farkı

Uygulama içinde alan olayı bellekteki bir nesnedir. Yayıncı ile ele alan aynı çağrı yığıtında
durur; ele alan bir hata attığında yayıncı bunu görür, hepsi bittiğinde yayıncı işin tamamlandığını
bilir. Olay sistem sınırını geçtiğinde bu üç şeyin üçü de aynı anda gider.

Birincisi, olay **serileşir**: bir metne çevrilir, karşı tarafta yeniden okunur. Bu andan sonra
olayın alan listesi yayınlanmış bir sözleşmedir; her alan sürüm uyumu izlenecek bir yükümlülük
olur. İkincisi, yayıncı **dönüş değeri almaz**: işin yapıldığını değil, ilanın verildiğini bilir.
Üçüncüsü, olayın **varış sayısı 1 olarak varsayılamaz**. Teslim anlamlarının ayrıntısı
Önbellekleme, Kuyruklar ve Eşzamansız İşleme kursunun konusudur; buraya düşen kısım mimari
kısıttır — tüketici, aynı olayın ikinci kez ulaşmasına göre tasarlanmak zorundadır.

Veriyolu bu üç farkı da açık eder. Yayımla–abone ol modeli aynı kursta kurulmuştu; buradaki
gerçekleştirim onun ölçülebilir en küçük hâlidir.

```js
// olay/veriyolu.mjs — olay veriyolu: yayimi serilestirir, her tuketiciyi yalitir, gecisleri sayar
export function olayVeriyolu(kayit) {
  const dinleyici = new Map();
  return {
    dinle(tur, tuketici) {
      if (dinleyici.has(tur) === false) dinleyici.set(tur, []);
      dinleyici.get(tur).push(tuketici);
    },
    yayimla(tur, govde) {
      const metin = JSON.stringify(govde);
      kayit.donusum += 1;
      kayit.olay.push([tur, metin]);
      for (const t of dinleyici.get(tur) ?? []) {
        kayit.donusum += 1;
        kayit.bayt += metin.length;
        kayit.gecis.push(`${tur} -> ${t.ad}`);
        try { t.ele(JSON.parse(metin)); } catch (e) { kayit.dusen.push(`${t.ad}: ${e.message}`); }
      }
    },
  };
}
```

`yayimla` bilinçli olarak hiçbir şey döndürmez ve tüketicinin hatasını yayıncıya geçirmez. Bu iki
karar biçemin kendisidir, bir kolaylık değil.

Yayıncı yalnız kendi işini yapar ve olayı verir. Dosyada hiçbir tüketici adı geçmez.

```js
// olay/ucret.mjs — ucretlendirme baglami: fiyatlar ve olayi yayinlar, dinleyeni bilmez
const TARIFE = { kademe: [[1, 3000], [5, 4800], [20, 9600]], bolge: { "34": 100, "06": 115, "35": 125 }, asgari: 2500 };

export function net(agirlik, bolge, oran) {
  const kademe = TARIFE.kademe.find(([k]) => agirlik <= k) ?? [0, 9600];
  const taban = Math.max(TARIFE.asgari, Math.round((kademe[1] * TARIFE.bolge[bolge]) / 100));
  return taban - Math.round(taban * Math.min(oran, 0.4));
}

export function ucretlendirme(veriyolu) {
  let sira = 0;
  return {
    fiyatla(g) {
      sira += 1;
      return veriyolu.yayimla("gonderi-fiyatlandi", {
        olayNo: `O${sira}`, id: g.id, agirlik: g.agirlik, bolge: g.bolge,
        sozlesmeNo: g.sozlesmeNo, net: net(g.agirlik, g.bolge, g.oran),
      });
    },
  };
}
```

Sözleşmeye eklenen `olayNo` alanı gönderinin değil olayın kendi kimliğidir; üçüncü farkın
karşılığı budur ve ölçümde kullanılacaktır.

İki tüketici var. Teslimat operasyonu rotayı gönderi kimliğine yazar; aynı olay ikinci kez
gelirse aynı anahtarın üzerine aynı değeri yazar.

```js
// olay/operasyon.mjs — teslimat operasyonu tuketicisi: rotayi gonderi kimligine yazar
const AGAC = { "34": ["34"], "06": ["34", "06"], "35": ["34", "41", "35"] };

export function operasyonTuketicisi() {
  const plan = new Map();
  return {
    ad: "operasyon",
    ele(olay) {
      const rota = AGAC[olay.bolge] ?? ["34"];
      plan.set(olay.id, { rota, gun: rota.length, tasiyici: rota.length > 2 ? "MT" : "AN" });
    },
    durum() {
      return { plan: plan.size, gun: [...plan.values()].reduce((t, p) => t + p.gun, 0) };
    },
  };
}
```

İkinci tüketici hacim indirimi için sözleşme başına ağırlık toplar. Toplama işi doğası gereği
biriktiricidir; aşağıda aynı işin iki tasarımı yan yana durur.

```js
// olay/sozlesme.mjs — hacim indirimi tuketicisi: ayni isin biriktiren ve anahtarli tasarimi
function hacim(toplam) {
  return { sozlesme: toplam.size, hacim: [...toplam.values()].reduce((t, a) => t + a, 0) };
}

export function biriktiren() {
  const toplam = new Map();
  return {
    ad: "sozlesme-biriktiren",
    ele(olay) {
      if (olay.sozlesmeNo === null) throw new TypeError(`hacim indirimi sozlesme ister: ${olay.id}`);
      toplam.set(olay.sozlesmeNo, (toplam.get(olay.sozlesmeNo) ?? 0) + olay.agirlik);
    },
    durum() { return hacim(toplam); },
  };
}

export function anahtarli() {
  const toplam = new Map();
  const islenen = new Set();
  return {
    ad: "sozlesme-anahtarli",
    ele(olay) {
      if (olay.sozlesmeNo === null) throw new TypeError(`hacim indirimi sozlesme ister: ${olay.id}`);
      if (islenen.has(olay.olayNo)) return;
      islenen.add(olay.olayNo);
      toplam.set(olay.sozlesmeNo, (toplam.get(olay.sozlesmeNo) ?? 0) + olay.agirlik);
    },
    durum() { return hacim(toplam); },
  };
}
```

Karşılaştırma tarafı için aynı iş doğrudan çağrıyla da yazılır. İki düzen aynı sonucu üretir;
ayrıldıkları yer ölçülecek olan yerdir.

```js
// dogrudan/ucret.mjs — ayni is dogrudan cagriyla: ucretlendirme tuketicileri adiyla bilir
import { net } from "../olay/ucret.mjs";
import { operasyonTuketicisi } from "../olay/operasyon.mjs";
import { biriktiren } from "../olay/sozlesme.mjs";

export function dogrudanUcretlendirme(kayit) {
  const operasyon = operasyonTuketicisi();
  const sozlesme = biriktiren();
  return {
    tuketici: [operasyon, sozlesme],
    fiyatla(g) {
      const olay = {
        id: g.id, agirlik: g.agirlik, bolge: g.bolge,
        sozlesmeNo: g.sozlesmeNo, net: net(g.agirlik, g.bolge, g.oran),
      };
      kayit.gecis.push("ucret -> operasyon");
      operasyon.ele(olay);
      kayit.gecis.push("ucret -> sozlesme");
      sozlesme.ele(olay);
      return { id: g.id, net: olay.net, plan: operasyon.durum().plan };
    },
  };
}
```

#### Beş Ölçüm

Sürücü betik beş soruyu sırayla yanıtlar: yayıncı kaç tüketici biliyor ve kaçını adıyla
çağırıyor; aynı üç gönderi iki düzende kaç sınırdan geçiyor, kaç dönüştürme noktası ve kaç bayt
taşıyor; üçüncü bir tüketici eklendiğinde yayıncı dosyasında kaç satır düzenleniyor; bir tüketici
düştüğünde kaç istek yanıtlanıyor; aynı olay ikinci kez ulaştığında iki tüketici tasarımı ne
kadar sapıyor.

```js
// olay/olc.mjs — bilme yukumlulugu, sinir olculeri, ariza yalitimi ve tekrar sapmasi
import { readFileSync } from "node:fs";
import { olayVeriyolu } from "./veriyolu.mjs";
import { ucretlendirme } from "./ucret.mjs";
import { operasyonTuketicisi } from "./operasyon.mjs";
import { biriktiren, anahtarli } from "./sozlesme.mjs";
import { dogrudanUcretlendirme } from "../dogrudan/ucret.mjs";

const GONDERI = [
  { id: "G1", agirlik: 4, bolge: "35", sozlesmeNo: "S7", oran: 0.15 },
  { id: "G2", agirlik: 1, bolge: "34", sozlesmeNo: "S7", oran: 0 },
  { id: "G3", agirlik: 12, bolge: "06", sozlesmeNo: "S9", oran: 0.25 },
];
const bosKayit = () => ({ gecis: [], donusum: 0, bayt: 0, olay: [], dusen: [] });
const kur = (kayit, tuketiciler) => {
  const yol = olayVeriyolu(kayit);
  for (const t of tuketiciler) yol.dinle("gonderi-fiyatlandi", t);
  return [yol, ucretlendirme(yol)];
};

const YOL = /from\s+"[^"]*(operasyon|sozlesme)\.mjs"/g;
for (const d of ["olay/ucret.mjs", "dogrudan/ucret.mjs"]) {
  const m = readFileSync(d, "utf8");
  console.log(`${d.padEnd(19)} bildigi tuketici = ${[...m.matchAll(YOL)].length}, cagirdigi tuketici = ${(m.match(/\.ele\(/g) ?? []).length}`);
}

const dk = bosKayit();
const dogrudan = dogrudanUcretlendirme(dk);
const dDonus = GONDERI.map((g) => dogrudan.fiyatla(g));
console.log(`dogrudan    : sinir gecisi = ${dk.gecis.length}, donusum noktasi = ${dk.donusum}, bayt = ${dk.bayt}, donus = ${dDonus.every((d) => d === undefined) ? "yok" : "nesne"}`);

const ok = bosKayit();
const [, oUcret] = kur(ok, [operasyonTuketicisi(), biriktiren()]);
const oDonus = GONDERI.map((g) => oUcret.fiyatla(g));
console.log(`olay gudumlu: sinir gecisi = ${ok.gecis.length}, donusum noktasi = ${ok.donusum}, bayt = ${ok.bayt}, donus = ${oDonus.every((d) => d === undefined) ? "yok" : "nesne"}`);
console.log(`  olay govdesi = ${ok.olay[0][1]}`);

const oncesi = readFileSync("olay/ucret.mjs", "utf8");
const uk = bosKayit();
const bolgeSayaci = {
  ad: "bolge-sayaci", sayi: new Map(),
  ele(o) { this.sayi.set(o.bolge, (this.sayi.get(o.bolge) ?? 0) + 1); },
};
const [, uUcret] = kur(uk, [operasyonTuketicisi(), biriktiren(), bolgeSayaci]);
for (const g of GONDERI) uUcret.fiyatla(g);
const duzenlenen = oncesi === readFileSync("olay/ucret.mjs", "utf8") ? 0 : 1;
console.log(`ucuncu tuketici: yayincida duzenlenen satir = ${duzenlenen}, bilesim kokune eklenen satir = 1, gordugu bolge = ${bolgeSayaci.sayi.size}`);
console.log(`  uc tuketiciyle: sinir gecisi = ${uk.gecis.length}, donusum noktasi = ${uk.donusum}, bayt = ${uk.bayt}`);

const SOZLESMESIZ = [GONDERI[0], { id: "G2", agirlik: 1, bolge: "34", sozlesmeNo: null, oran: 0 }, GONDERI[2]];
const ak = bosKayit();
const aDogrudan = dogrudanUcretlendirme(ak);
let aYanit = 0;
for (const g of SOZLESMESIZ) {
  try { aDogrudan.fiyatla(g); aYanit += 1; } catch (e) { ak.dusen.push(`yayinci: ${e.message}`); }
}
console.log(`dogrudan    : yanitlanan istek = ${aYanit}/3, dusen = ${ak.dusen.length} (${ak.dusen.join("; ")})`);
console.log(`  operasyon = ${JSON.stringify(aDogrudan.tuketici[0].durum())}, sozlesme = ${JSON.stringify(aDogrudan.tuketici[1].durum())}`);

const ek = bosKayit();
const eOp = operasyonTuketicisi();
const eSoz = biriktiren();
const [, eUcret] = kur(ek, [eOp, eSoz]);
let eYanit = 0;
for (const g of SOZLESMESIZ) {
  try { eUcret.fiyatla(g); eYanit += 1; } catch (e) { ek.dusen.push(`yayinci: ${e.message}`); }
}
console.log(`olay gudumlu: yanitlanan istek = ${eYanit}/3, dusen = ${ek.dusen.length} (${ek.dusen.join("; ")})`);
console.log(`  operasyon = ${JSON.stringify(eOp.durum())}, sozlesme = ${JSON.stringify(eSoz.durum())}`);

const tk = bosKayit();
const tOp = operasyonTuketicisi();
const tBirik = biriktiren();
const tAnahtar = anahtarli();
const [tYol, tUcret] = kur(tk, [tOp, tBirik, tAnahtar]);
for (const g of GONDERI) tUcret.fiyatla(g);
const IZLENEN = [[tOp, "operasyon", "gun"], [tBirik, "sozlesme-biriktiren", "hacim"], [tAnahtar, "sozlesme-anahtarli", "hacim"]];
const once = IZLENEN.map(([t, , k]) => t.durum()[k]);
const [tur, metin] = tk.olay[2];
tYol.yayimla(tur, JSON.parse(metin));
console.log(`ucuncu olay ikinci kez ulasti: ${metin}`);
IZLENEN.forEach(([t, ad, k], i) => {
  const sonra = t.durum()[k];
  console.log(`  ${ad.padEnd(20)} ${k} once = ${once[i]}, sonra = ${sonra}, sapma = ${sonra - once[i]}`);
});

const kaynak = readFileSync("olay/sozlesme.mjs", "utf8");
const satir = (ad) => kaynak.split(`export function ${ad}()`)[1].split("\n}")[0].split("\n").filter((s) => s.trim() !== "").length;
console.log(`tekrara dayanikli tasarimin bedeli = ${satir("anahtarli") - satir("biriktiren")} satir`);
```

```sh
node olay/olc.mjs
```

```
olay/ucret.mjs      bildigi tuketici = 0, cagirdigi tuketici = 0
dogrudan/ucret.mjs  bildigi tuketici = 2, cagirdigi tuketici = 2
dogrudan    : sinir gecisi = 6, donusum noktasi = 0, bayt = 0, donus = nesne
olay gudumlu: sinir gecisi = 6, donusum noktasi = 9, bayt = 476, donus = yok
  olay govdesi = {"olayNo":"O1","id":"G1","agirlik":4,"bolge":"35","sozlesmeNo":"S7","net":5100}
ucuncu tuketici: yayincida duzenlenen satir = 0, bilesim kokune eklenen satir = 1, gordugu bolge = 3
  uc tuketiciyle: sinir gecisi = 9, donusum noktasi = 12, bayt = 714
dogrudan    : yanitlanan istek = 2/3, dusen = 1 (yayinci: hacim indirimi sozlesme ister: G2)
  operasyon = {"plan":3,"gun":6}, sozlesme = {"sozlesme":2,"hacim":16}
olay gudumlu: yanitlanan istek = 3/3, dusen = 1 (sozlesme-biriktiren: hacim indirimi sozlesme ister: G2)
  operasyon = {"plan":3,"gun":6}, sozlesme = {"sozlesme":2,"hacim":16}
ucuncu olay ikinci kez ulasti: {"olayNo":"O3","id":"G3","agirlik":12,"bolge":"06","sozlesmeNo":"S9","net":8280}
  operasyon            gun once = 6, sonra = 6, sapma = 0
  sozlesme-biriktiren  hacim once = 17, sonra = 29, sapma = 12
  sozlesme-anahtarli   hacim once = 17, sonra = 17, sapma = 0
tekrara dayanikli tasarimin bedeli = 3 satir
```

#### Sayıların Okunması

İlk iki satır bilme yükümlülüğünü sayıya çeviriyor. Doğrudan çağrı düzeninde ücretlendirme
dosyası 2 tüketici modülünü ithal ediyor ve 2 tüketiciyi adıyla çağırıyor; olay güdümlü düzende
iki sayı da 0. Bu, uzantı maliyetinde doğrudan görünür: üçüncü tüketici eklendiğinde yayıncı
dosyasında düzenlenen satır 0 oldu, bileşim kökünde 1 satır arttı. Doğrudan düzende aynı ekleme
ücretlendirme dosyasında bir ithal, bir alan ve bir çağrı satırı ister.

Sınır geçişi sayısı iki düzende de 6. Bu, kolay atlanan bir sonuçtur: olay güdümlü biçem sınır
sayısını azaltmaz, sınırda ne olduğunu değiştirir. Doğrudan düzende dönüştürme noktası 0 ve
sınırdan geçen bayt 0'dı; olay güdümlü düzende dönüştürme noktası 9, bayt 476. Dokuz sayısının
yapısı da okunabilir: her yayımda 1 serileştirme ve tüketici başına 1 çözümleme, yani üç gönderi
için $3 \times (1 + 2)$. Tüketici üçe çıktığında $3 \times (1 + 3) = 12$ oldu ve bayt 476'dan
714'e çıktı — tam olarak $238 \times 3$. Yayma çarpanı doğrusaldır ve maliyeti tüketici sayısıyla
büyür; yayıncının kodu ise sabit kalır.

Arıza satırları biçemin en keskin kazancını gösteriyor. Sözleşmesiz gönderi hacim indirimi
tüketicisinde hata attı. Doğrudan düzende bu hata çağrı yığıtından yayıncıya yükseldi ve isteği
düşürdü: yanıtlanan istek 2/3. Üstelik operasyon tüketicisi o gönderinin rotasını *zaten*
yazmıştı — plan sayısı 3 kaldı. Yani istek başarısız görünürken sistemin bir bölümünde işi
yapılmış durumdadır. Olay güdümlü düzende yanıtlanan istek 3/3; düşen tüketici sayısı yine 1 ama
düşenin adı kayda geçti ve ücretlendirmenin yanıtı etkilenmedi. İki düzende tüketici durumları
birebir aynı çıktı (`plan 3`, `hacim 16`); farklı olan tek şey hatanın kimin sorunu olduğudur.

Bu ayrım bir bedelle gelir. `dusen` listesi ölçümde göründü çünkü veriyolu aynı süreçteydi.
Tüketici ayrı bir dağıtım birimine geçtiğinde o liste yayıncının erişemediği bir yerde tutulur.

#### Tekrarın Tüketiciye Yüklediği Şey

Son ölçüm üçüncü farkın faturasıdır. Aynı olay gövdesi ikinci kez veriyoluna verildiğinde
operasyon tüketicisinin gün toplamı 6'dan 6'ya, yani hiç değişmedi: rota gönderi kimliğine
yazıldığı için ikinci yazım birincinin üstüne aynı değeri koydu. Biriktiren tüketicinin hacmi
17'den 29'a çıktı, sapma 12. Bu sapma sözleşmeli müşterinin hacim indirimini olduğundan yüksek
gösterir; kusur veriyolunda değil, tüketicinin tasarımındadır.

Anahtarlı tasarım aynı tekrarda 17'de kaldı. Farkı yapan şey `olayNo` alanının sözleşmede
bulunması ve tüketicinin işlediği kimlikleri tutmasıdır. Bedeli ölçüldü: 3 satır. Bu üç satır
biçemin tüketiciye yüklediği asıl yükümlülüktür — yayıncı varış sayısını garanti edemediği için
her biriktirici tüketici kendi tekrar dayanıklılığını yazmak zorundadır. Tüketici sayısı arttıkça
bu üç satır her biriktiricide yeniden görünür.

#### Biçemin Yerleştiği Yer

Sayılar bir kalite niteliği ödünleşimi betimliyor. Kazanılan nitelikler sürdürülebilirlik ve
güvenilirliktir: yeni bir tüketici için yayıncıda düzenlenen satır 0, bir tüketici düştüğünde
yanıtlanan istek 3/3. Bırakılan nitelik uçtan uca kesinliktir: yayıncı 476 baytlık bir sözleşme
yayınlar, karşılığında hiçbir dönüş değeri almaz ve işin yapıldığını yanıt anında bilemez.

Bu yüzden biçem, işin yanıt anında bitmesi gerekmeyen alışverişler için yerindedir. Ücret hesabı
gönderi kaydedilirken bitmek zorundadır; rota planı ve hacim indirimi bitmek zorunda değildir.
Ücret hesabını da olaya çevirmek, ölçümdeki 2/3 satırının ücret yanıtına taşınması olur. Biçem
seçimi bu yüzden sistem geneli için değil, alışveriş başına verilir.

#### Özet

- Olay güdümlü mimaride birim olmuş bir olguyu ilan eder; yayıncı dosyasında bildiği tüketici 0,
  adıyla çağırdığı tüketici 0 ölçüldü, doğrudan çağrı düzeninde iki sayı da 2'ydi.
- Olay sistem sınırını geçtiğinde üç şey birden değişir: gövde serileşip yayınlanmış sözleşme
  olur, yayıncı dönüş değeri almaz, varış sayısı 1 varsayılamaz.
- Sınır geçişi iki düzende de 6 kaldı; değişen sınırda olan şeydir — dönüştürme noktası 0'dan 9'a,
  sınırdan geçen bayt 0'dan 476'ya çıktı ve tüketici üçe çıkınca 12 ile 714 oldu.
- Üçüncü tüketici yayıncı dosyasında 0 satır düzenlenerek eklendi; maliyet bileşim kökündeki 1
  satır ve doğrusal büyüyen yayma çarpanıdır.
- Bir tüketici hata attığında doğrudan düzende yanıtlanan istek 2/3'e indi ve yarım kalan iş
  bıraktı; olay güdümlü düzende 3/3 kaldı, düşen tüketici adıyla kayda geçti.
- Aynı olay ikinci kez ulaştığında anahtarlı yazan operasyon tüketicisinin sapması 0, biriktiren
  tüketicinin sapması 12 oldu; anahtarlı tasarımın bedeli 3 satırdır.

#### Sonraki Adım

Bu dersteki üç düzenin ortak bir örtük varsayımı var: ücretlendirme, operasyon ve veriyolu için
bir süreç vardır ve o süreç istek gelmeden önce ayaktadır. Mikroservis dersinde üç süreç elle
başlatıldı ve elle durduruldu; olay güdümlü düzende veriyolunun da bir yerde koşması gerekir.
Sonraki biçem bu varsayımı kaldırır: kod, bir olay ya da bir istek geldiğinde çağrılan bir işleve
indirgenir, süreci ayakta tutma işi yazılımın dışına çıkar. Sonraki ders o düzende neyin ölçüldüğünü
sayar: uygulamanın kendi taşıdığı sunucu satırı, bir isteğin dokunduğu işlev sayısı, işlevler
arasında paylaşılamayan durumun nereye taşındığı ve bileşimin yapılandırmaya kaydırılmasının
düzenlenen dosya sayısına etkisi.

### Sunucusuz Mimari

Source: https://academia.sh/tr/kurslar/mimari-bicemler/sunucusuz-mimari

Koşum sorumluluğunun uygulamadan çıkması: işlev dosyalarında kalan koşum satırının sayılması, bileşimin bildirime taşınmasının düzenlenen dosya sayısıyla ölçülmesi, soğuk koşumda modül durumunun ürettiği sonuç sapmasının ve durumu dışarı taşımanın bedelinin ölçülmesi, soğuk başlatmanın kurulum sayısıyla sayılıp süresinin bir modele bırakılması.

Buraya kadarki dört biçemin ortak bir örtük varsayımı vardı: istek gelmeden önce ayakta olan bir
süreç. Tek parça düzende bir süreç, mikroservis düzeninde üç süreç elle başlatıldı ve elle
durduruldu; olay güdümlü düzende veriyolunun da bir yerde koşması gerekiyordu. Süreci ayakta
tutmak, dinlemek ve isteği doğru birime yönlendirmek uygulamanın taşıdığı koddu.

**Sunucusuz mimari** (serverless architecture) bu kodu uygulamadan alır. Dağıtım birimi bir süreç
değil, bir olay ya da istek geldiğinde çağrılan bir işlevdir; süreci açmak, bekletmek, çoğaltmak ve
kapatmak çalıştırıcının işidir. Ad sunucunun yokluğunu anlatmaz — sunucu vardır, ama uygulamanın
sorumluluk alanında değildir. Bu ders o kaydırmanın iki yüzünü sayar: uygulamadan çıkan koşum
satırını ve karşılığında kısıta dönüşen varsayımları.

#### Kısıta Dönüşen İlke

Sunucu Tarafı Temelleri kursunun On İki Etmen Uygulama İlkeleri dersinde süreçlerin durumsuz ve
paylaşımsız tutulması bir ilke olarak sayılmıştı; aynı kursun Uygulama Çalışma Zamanı dersi
ölçekleme biriminin süreç olduğunu ölçtü. Sunucusuz biçemde **durumsuz süreç** bir tercih olmaktan
çıkar: çalıştırıcı işlevin örneğini istediği anda açıp kapatabildiği için iki çağrının aynı bellek
alanını göreceği garanti edilemez. İlkenin ihlali burada bir üslup kusuru değil, ölçülebilir bir
sonuç sapmasıdır.

İkinci kısıt bileşimle ilgilidir. İşlev kendisini bir yola bağlamaz, komşusunu ithal etmez, çağrı
sırasını bilmez. Bileşim işlevlerin dışına, çalıştırıcının okuduğu bir **bildirime** (deployment
manifest) taşınır.

#### Üç İşlev ve Bir Bildirim

Kurulum işinin kaç kez yapıldığını saymak için işlev modülleri paylaşılan bir ize bir satır bırakır.
Bu iz ölçüm içindir; işlevin işiyle ilgisi yoktur.

```js
// sunucusuz/sayac.mjs — kurulum izi: her islev modulunun ilk yuklenisi buraya bir iz birakir
export const kurulum = [];
```

Ücretlendirme bağlamının fiyat işlevi tek bir dışa açılım taşır. Sunucu, dinleme, yol çözümleme ve
bağlantı noktası kodu yoktur.

```js
// sunucusuz/ucret.mjs — fiyat islevi: sunucu, dinleme ve yol kodu yok; disa yalniz ele acilir
import { kurulum } from "./sayac.mjs";

const TARIFE = { kademe: [[1, 3000], [5, 4800], [20, 9600]], bolge: { "34": 100, "06": 115, "35": 125 }, asgari: 2500 };
kurulum.push("fiyat");                     // modul yuklenisinde bir kez calisir: kurulum isi

export function ele(olay) {
  const kademe = TARIFE.kademe.find(([k]) => olay.agirlik <= k) ?? [0, 9600];
  const taban = Math.max(TARIFE.asgari, Math.round((kademe[1] * TARIFE.bolge[olay.bolge]) / 100));
  return { id: olay.id, net: taban - Math.round(taban * Math.min(olay.oran, 0.4)) };
}
```

Teslimat operasyonunun plan işlevi aynı biçimdedir; ayrı bir dağıtım birimi olduğu tek kanıtı ayrı
bir dosya olmasıdır.

```js
// sunucusuz/operasyon.mjs — plan islevi: ayri dagitim birimi, ayni cagri bicimi
import { kurulum } from "./sayac.mjs";

const AGAC = { "34": ["34"], "06": ["34", "06"], "35": ["34", "41", "35"] };
kurulum.push("plan");

export function ele(olay) {
  const rota = AGAC[olay.bolge] ?? ["34"];
  return { rota, gun: rota.length, tasiyici: rota.length > 2 ? "MT" : "AN" };
}
```

Üçüncü işlev hacim indirimi için sözleşme başına ağırlık toplar. Toplama işi doğası gereği çağrılar
arasında bir şey hatırlar; aşağıda aynı işin iki tasarımı yan yana durur. Birinci tasarım toplamı
modül düzeyinde tutar, ikincisi çalıştırıcının verdiği depoya yazar.

```js
// sunucusuz/hacim.mjs — hacim indirimi islevi: ayni isin modul durumlu ve depo durumlu tasarimi
import { kurulum } from "./sayac.mjs";

const toplam = new Map();                  // MODUL DURUMU: modul yeniden yuklendiginde bosalir
kurulum.push("hacim");

export function ele(olay) {
  toplam.set(olay.sozlesmeNo, (toplam.get(olay.sozlesmeNo) ?? 0) + olay.agirlik);
  return { sozlesme: toplam.size, hacim: [...toplam.values()].reduce((t, a) => t + a, 0) };
}

export function eleDepolu(olay, depo) {
  const anahtar = `hacim:${olay.sozlesmeNo}`;
  depo.yaz(anahtar, (depo.oku(anahtar) ?? 0) + olay.agirlik);
  const liste = depo.anahtarlar();
  return { sozlesme: liste.length, hacim: liste.reduce((t, k) => t + depo.oku(k), 0) };
}
```

Bildirim iki eşlemeden oluşur: hangi adın hangi dosyanın hangi dışa açılımına karşılık geldiği ve
hangi yolun hangi işlev dizisini çalıştırdığı. Bu dosyada tek bir alan kuralı yoktur; yalnız
bileşim vardır.

```js
// sunucusuz/yol.mjs — bildirim: hangi ad hangi dosyanin hangi disa acilimi, hangi yol hangi dizi
export const bildirim = {
  islev: {
    fiyat: "./ucret.mjs#ele",
    plan: "./operasyon.mjs#ele",
    hacim: "./hacim.mjs#ele",
    "hacim-depolu": "./hacim.mjs#eleDepolu",
  },
  yol: {
    "/teklif": ["fiyat", "plan"],
    "/kayit": ["fiyat", "hacim"],
    "/kayit-depolu": ["fiyat", "hacim-depolu"],
  },
};
```

#### Çalıştırıcının Yerel Durağı

Çalıştırıcı bu derste yerel bir durakla modellenir. Yaptığı iş üç adımdır: bildirimden işlevin
yerini bulmak, modülü yüklemek ve gövdeyi serileştirip çözerek işleve vermek. **Kap** (container)
burada işlevin içinde koştuğu, çalıştırıcının açıp kapattığı örnektir. `soguk` seçeneği kap
kararını taklit eder: kapalıyken modül kap ömrü boyunca bir kez kurulur, açıkken her çağrıda
yeniden kurulur. Sorgu eki modülün yeniden kurulmasını sağlar; gerçek bir çalıştırıcıda bunun
karşılığı yeni bir kap açmaktır.

```js
// sunucusuz/calistirici.mjs — islev calistiricisinin yerel duragi: bildirimi okur, islevleri cagirir
let sira = 0;                              // surec boyunca artan yukleme numarasi

export function calistirici(bildirim, kayit, { soguk = false } = {}) {
  const kap = ++sira;                      // bu ornek bir kap: sicak kosumda modul bir kez kurulur
  const bellek = new Map();
  const ham = new Map();
  const depo = {
    oku(k) { kayit.depo += 1; return ham.get(k); },
    yaz(k, v) { kayit.depo += 1; ham.set(k, v); },
    anahtarlar() { kayit.depo += 1; return [...ham.keys()]; },
  };
  return {
    async cagir(yolAdi, olay) {
      const sonuc = {};
      for (const ad of bildirim.yol[yolAdi]) {
        const [dosya, disa] = bildirim.islev[ad].split("#");
        if (soguk || bellek.has(ad) === false) {
          bellek.set(ad, await import(`${dosya}?k=${soguk ? ++sira : kap}`));
        }
        const metin = JSON.stringify(olay);
        kayit.donusum += 2;
        kayit.bayt += metin.length;
        kayit.gecis.push(`${yolAdi} -> ${ad}`);
        sonuc[ad] = bellek.get(ad)[disa](JSON.parse(metin), depo);
      }
      return sonuc;
    },
  };
}
```

Birleştirici bir işlev yoktur. Yanıt nesnesini çalıştırıcı kurar: yolun dizisindeki her adın sonucu
kendi adıyla toplanır.

#### Beş Ölçüm

Sürücü betik beş soruyu yanıtlar: işlev dosyalarında kaç koşum satırı kalmıştır; bir isteğin
dokunduğu işlev ve sınırdan geçen bayt kaçtır; aynı üç gönderi sıcak ve soğuk koşumda hangi sonucu
verir; durumu depoya taşımanın satır ve erişim bedeli nedir; bileşime bir işlev eklemek kaç işlev
dosyası düzenletir.

```js
// sunucusuz/olc.mjs — kosum satiri, dokunulan islev, kurulum sayisi, durum sapmasi, bildirim maliyeti
import { readFileSync, writeFileSync } from "node:fs";
import { createHash } from "node:crypto";
import { kurulum } from "./sayac.mjs";
import { calistirici } from "./calistirici.mjs";
import { bildirim } from "./yol.mjs";

const GONDERI = [
  { id: "G1", agirlik: 4, bolge: "35", sozlesmeNo: "S7", oran: 0.15 },
  { id: "G2", agirlik: 1, bolge: "34", sozlesmeNo: "S7", oran: 0 },
  { id: "G3", agirlik: 12, bolge: "06", sozlesmeNo: "S9", oran: 0.25 },
];
const ISLEV = ["sunucusuz/ucret.mjs", "sunucusuz/operasyon.mjs", "sunucusuz/hacim.mjs"];
const KOSUM = /createServer|\.listen\(|writeHead|yanit\.end|process\.argv/;
const CAGIRMA = /await import|bildirim\.|JSON\.(stringify|parse)/;
const ozet = (d) => createHash("sha256").update(readFileSync(d)).digest("hex").slice(0, 12);

for (const d of [...ISLEV, "sunucusuz/calistirici.mjs"]) {
  const s = readFileSync(d, "utf8").split("\n").filter((x) => x.trim() !== "");
  const say = (r) => s.filter((x) => r.test(x)).length;
  console.log(`${d.padEnd(26)} satir = ${String(s.length).padStart(2)}, kosum = ${say(KOSUM)}, cagirma = ${say(CAGIRMA)}`);
}

async function kos(harita, yolAdi, gonderiler, soguk) {
  const k = { gecis: [], donusum: 0, bayt: 0, depo: 0 };
  const oncesi = kurulum.length;
  const c = calistirici(harita, k, { soguk });
  let son;
  for (const g of gonderiler) son = await c.cagir(yolAdi, g);
  return { k, kurulum: kurulum.length - oncesi, son };
}

const bicim = (ad, r) => `${ad.padEnd(20)} sinir gecisi = ${r.k.gecis.length}, donusum = ${r.k.donusum}, bayt = ${r.k.bayt}, kurulum = ${r.kurulum}, depo erisimi = ${r.k.depo}`;

const t = await kos(bildirim, "/teklif", [GONDERI[0]], false);
console.log(bicim("/teklif sicak", t));
console.log(`  sonuc = ${JSON.stringify(t.son)}`);

const sicak = await kos(bildirim, "/kayit", GONDERI, false);
const soguk = await kos(bildirim, "/kayit", GONDERI, true);
const depolu = await kos(bildirim, "/kayit-depolu", GONDERI, true);
for (const [ad, r, anahtar] of [["/kayit sicak", sicak, "hacim"], ["/kayit soguk", soguk, "hacim"], ["/kayit-depolu soguk", depolu, "hacim-depolu"]]) {
  console.log(`${bicim(ad, r)}\n  ${anahtar} = ${JSON.stringify(r.son[anahtar])}`);
}
console.log(`modul durumunun sapmasi = ${sicak.son.hacim.hacim - soguk.son.hacim.hacim} birim agirlik, ${sicak.son.hacim.sozlesme - soguk.son.hacim.sozlesme} sozlesme`);
const kaynak = readFileSync("sunucusuz/hacim.mjs", "utf8");
const satir = (ad) => kaynak.split(`export function ${ad}(`)[1].split("\n}")[0].split("\n").filter((s) => s.trim() !== "").length;
console.log(`durumu disariya tasimanin bedeli = ${satir("eleDepolu") - satir("ele")} satir`);

const oncekiOzet = ISLEV.map(ozet);
const eski = readFileSync("sunucusuz/yol.mjs", "utf8");
writeFileSync("sunucusuz/yol.mjs", eski.replace('"plan"]', '"plan", "hacim"]'));
const yeni = readFileSync("sunucusuz/yol.mjs", "utf8").split("\n");
const degisen = eski.split("\n").filter((s, i) => s !== yeni[i]).length;
const { bildirim: genis } = await import("./yol.mjs?s=2");
const g = await kos(genis, "/teklif", [GONDERI[0]], false);
writeFileSync("sunucusuz/yol.mjs", eski);
console.log(`bildirime hacim eklendi: duzenlenen islev dosyasi = ${ISLEV.filter((d, i) => ozet(d) !== oncekiOzet[i]).length}, bildirimde degisen satir = ${degisen}`);
console.log(bicim("/teklif yeniden", g));
console.log(`  gecis = ${g.k.gecis.join(", ")}`);
```

```sh
node sunucusuz/olc.mjs
```

```
sunucusuz/ucret.mjs        satir =  9, kosum = 0, cagirma = 0
sunucusuz/operasyon.mjs    satir =  8, kosum = 0, cagirma = 0
sunucusuz/hacim.mjs        satir = 14, kosum = 0, cagirma = 0
sunucusuz/calistirici.mjs  satir = 29, kosum = 0, cagirma = 5
/teklif sicak        sinir gecisi = 2, donusum = 4, bayt = 132, kurulum = 2, depo erisimi = 0
  sonuc = {"fiyat":{"id":"G1","net":5100},"plan":{"rota":["34","41","35"],"gun":3,"tasiyici":"MT"}}
/kayit sicak         sinir gecisi = 6, donusum = 12, bayt = 392, kurulum = 2, depo erisimi = 0
  hacim = {"sozlesme":2,"hacim":17}
/kayit soguk         sinir gecisi = 6, donusum = 12, bayt = 392, kurulum = 6, depo erisimi = 0
  hacim = {"sozlesme":1,"hacim":12}
/kayit-depolu soguk  sinir gecisi = 6, donusum = 12, bayt = 392, kurulum = 6, depo erisimi = 13
  hacim-depolu = {"sozlesme":2,"hacim":17}
modul durumunun sapmasi = 5 birim agirlik, 1 sozlesme
durumu disariya tasimanin bedeli = 2 satir
bildirime hacim eklendi: duzenlenen islev dosyasi = 0, bildirimde degisen satir = 1
/teklif yeniden      sinir gecisi = 3, donusum = 6, bayt = 198, kurulum = 3, depo erisimi = 0
  gecis = /teklif -> fiyat, /teklif -> plan, /teklif -> hacim
```

#### Sayıların Okunması

İlk dört satır uygulamadan çıkan kodu sayıyor. Üç işlev dosyasının hiçbirinde koşum satırı yok:
`createServer`, `listen`, `writeHead` ya da `process.argv` geçen satır sayısı 0. Mikroservis
dersinde aynı tarama her dağıtım biriminde 6, üç birimde toplam 18 satır bulmuştu. O satırlar
silinmedi, yer değiştirdi: çalıştırıcının yerel durağı 29 satırdır ve çağırma taraması onda 5 satır
buluyor — hangi işlevin çalışacağına karar veren, gövdeyi serileştiren ve çözen satırlar. Gerçek bir
çalıştırıcıda bu 29 satır uygulamanın deposunda hiç bulunmaz; burada yazılmasının nedeni ölçümün tek
komutla koşabilmesidir.

İkinci kazanç birleştiricinin yokluğudur. Mikroservis düzeninde teklif isteğini karşılayan üçüncü
bir dağıtım birimi vardı: kendi sunucusunu taşıyor, iki servisi adıyla biliyor ve yanıtı
birleştiriyordu. Burada `/teklif` yolunun karşılığı bildirimdeki tek satırdır ve sonuç nesnesini
çalıştırıcı kurar. Tek istek için sınır geçişi 2, dönüşüm noktası 4, sınırdan geçen 132 bayt —
gönderi başına 66 bayt, iki geçişte iki kez.

Son iki satır bileşimin bildirime taşınmasının bedelini veriyor. Teklif isteğinin hacim indirimini
de tetiklemesi istendiğinde bildirimde 1 satır değişti ve işlev dosyalarının özetleri değişmedi:
düzenlenen işlev dosyası 0. Olay güdümlü derste aynı ekleme bileşim kökünde 1 satır koda mal
olmuştu; burada o satır da koddan çıktı. Karşılığı aynı satırdadır: sınır geçişi 2'den 3'e, dönüşüm
4'ten 6'ya, bayt 132'den 198'e çıktı. Bir isteğin maliyeti artık kodda değil bildirimde okunur ve
bildirim yanlış yazıldığında çözümleyici uyarmaz.

#### Durumun Gidecek Yeri

Üç `/kayit` koşumu aynı üç gönderiyi aynı sırayla verdi; üçünde de sınır geçişi 6, dönüşüm 12 ve
bayt 392 çıktı. Çalıştırıcı tarafında hiçbir fark yok. Ayrılan tek şey sonuçtur. Sıcak koşumda hacim
indirimi işlevi `{"sozlesme":2,"hacim":17}` bildirdi; soğuk koşumda aynı işlev
`{"sozlesme":1,"hacim":12}` bildirdi. Sapma 5 birim ağırlık ve 1 sözleşmedir. Kaynağı `hacim.mjs`
dosyasının modül düzeyindeki `toplam` eşlemesidir: modül her çağrıda yeniden kurulduğu için eşleme
boşaldı ve yalnız son gönderi sayıldı.

Bu, biçemin en sessiz kusurudur. Kod doğru, sonuç yanlış ve hata iletisi yok. Sıcak koşumda aynı kod
doğru sonucu verdiği için kusur, çalıştırıcının kap kararına bağlı olarak görünür ya da görünmez —
sınama sıcak bir kapta koşuyorsa hiç görünmez.

Düzeltme durumu işlevin dışına almaktır. `eleDepolu` aynı toplamı çalıştırıcının verdiği depoya
yazar ve soğuk koşumda `{"sozlesme":2,"hacim":17}` döndürür; sapma 0. Bedeli iki sayıda ölçüldü:
gövde 2 satır uzadı ve üç istek 13 depo erişimi üretti. Modül durumlu tasarımda bu sayı 0'dı. Depo
burada yerel bir eşlemedir; ayrı bir dağıtım birimi olduğunda o 13 erişimin her biri bir sınır
geçişi olur ve mikroservis dersinde sayılan ağ çağrılarına eklenir.

#### Soğuk Başlatma Bir Model

Kurulum sayısı gerçek bir sayıdır: sıcak koşumda üç istek 2 kurulum yaptı, soğuk koşumda 6. İki
işlevin her biri her istekte yeniden kuruldu. Kurulumun *süresi* ise ölçülmedi. Modülün yüklenme
süresi makineye, dosya sistemine ve çalıştırıcının kendi hazırlığına bağlıdır; bu yüzden aşağıdaki
anlatım bir modeldir, ölçüm değildir.

Bir çağrının süresi iki parçaya ayrılır: kurulum işi $B$ ve asıl iş $I$. Çağrıların $c$ oranı yeni
bir örnek üzerinde başlıyorsa ortalama süre

$$\bar{T} = I + c \cdot B$$

olur. Model iki şey söyler. Birincisi, $c$ uygulamanın değil trafiğin biçiminin sonucudur: yoğun ve
sürekli trafikte kap yeniden kullanılır, seyrek ve düzensiz trafikte $c$ 1'e yaklaşır. Ölçümdeki
soğuk koşum $c = 1$ durumunun karşılığıdır. İkincisi, $B$'yi küçültmenin tek yolu kurulum işini
küçültmektir. `ucret.mjs` modül düzeyinde yalnız bir tarife nesnesi kurar; aynı yerde büyük bir
tablo ya da bir bağlantı havuzu kurulsaydı $B$ büyürdü. Biçemin işlev başına taşınabilecek kurulum
işine koyduğu sınır budur.

Sayılar bir kalite niteliği ödünleşimi betimliyor. Kazanılan nitelik sürdürülebilirliktir:
uygulamanın taşıdığı koşum satırı 0, yeni bir bileşim için düzenlenen işlev dosyası 0. Bırakılan
nitelikler ikidir. Başarım verimliliğinde öngörülebilirlik gider: aynı istek, çalıştırıcının kap
kararına göre 2 ya da 6 kurulum işi yapar ve o kararı uygulama vermez. Güvenilirlikte ise bellekte
biriken her değer, ölçülen 5 birimlik sapmanın adayı olur. Bu yüzden biçem çağrı başına bağımsız iş
için yerindedir; biriktiren iş ancak durumu dışarı taşıyarak, yani 13 erişimi ödeyerek girer.

#### Özet

- Sunucusuz mimaride dağıtım birimi bir süreç değil, çağrıldığında koşan bir işlevdir; süreci ayakta
  tutma ve yönlendirme işi uygulamanın dışına çıkar.
- Üç işlev dosyasında koşum satırı 0 ölçüldü; mikroservis dersinde aynı tarama birim başına 6,
  toplam 18 satır bulmuştu. O satırların yerel karşılığı 29 satırlık çalıştırıcı durağıdır.
- Birleştirici işlev yoktur: `/teklif` yolunun karşılığı bildirimdeki bir satırdır, sonuç nesnesini
  çalıştırıcı kurar; tek istek 2 sınır geçişi, 4 dönüşüm noktası ve 132 bayt üretti.
- Bileşime bir işlev eklemek 0 işlev dosyası düzenletti, bildirimde 1 satır değiştirdi; aynı istek
  3 sınır geçişine, 6 dönüşüm noktasına ve 198 bayta çıktı.
- Modül düzeyinde biriken durum soğuk koşumda 5 birim ağırlık ve 1 sözleşme saptı; durumu depoya
  taşımak sapmayı 0'a indirdi, bedeli 2 satır ve 13 depo erişimi oldu.
- Soğuk başlatmanın sayılan yüzü kurulum sayısıdır: üç istek sıcak koşumda 2, soğuk koşumda 6
  kurulum yaptı. Süre bir modelle anlatılır, ölçülmez.

#### Sonraki Adım

Beş biçem beş ayrı ölçü kümesi bıraktı: birlikte yayınlanan dosya sayısı, sınır geçişi, sınırdan
geçen bayt, bir birim durduğunda ayakta kalan birim sayısı, yeni bir yetenek için düzenlenen dosya
sayısı. Hiçbir biçem bu ölçülerin hepsinde önde değildi; her kazanç bir başka sayıyı büyüttü. Bu
durumda seçim nasıl yapılır ve bir seçimin savunması neye dayanır. Sonraki ders ölçüleri kalite
niteliklerine bağlar, aynı kuralları iki sınır yerleşiminde ortak temelde ölçer ve bir biçem
kararının hangi eşik altında haklı çıktığını, hangi eşik değiştiğinde döndüğünü gösterir; kursta
ölçülen sayılar orada tek bir karar tablosunda toplanır.

### Biçem Seçimi ve Ödünleşim Çözümlemesi

Source: https://academia.sh/tr/kurslar/mimari-bicemler/bicem-secimi-ve-odunlesim-cozumlemesi

Biçem kararının ölçülebilir bir savunmaya çevrilmesi: aynı üç kuralın iki sınır yerleşiminde ortak temelde ölçülmesi, üç nitelik senaryosunun eşiğine karşı geçme durumunun hesaplanması, hiçbir düzenin üç eşiği birden geçmemesinin gösterilmesi ve kararı çeviren eşiğin bulunması.

Beş dağıtım düzeni beş ayrı sayı kümesi bıraktı: birlikte yayınlanan dosya sayısı, sınır geçişi,
sınırdan geçen bayt, bir birim durduğunda ayakta kalan birim sayısı, yeni bir yetenek için
düzenlenen dosya sayısı. Hiçbir düzen bu ölçülerin hepsinde önde değildi; her kazanç bir başka
sayıyı büyüttü. Bu ders o durumda seçimin nasıl yapıldığını ve bir seçimin savunmasının neye
dayandığını ele alır.

Kalite niteliği ve nitelik senaryosu Kalite ve Test Temelleri kursunun Kalite Nitelikleri dersinde
kuruldu. Burada tanımlanmazlar, karar aracı olarak kullanılırlar.

#### Kararı Taşıyan Üç Parça

Bir biçem savunması üç parçadan oluşur. Birincisi **senaryo**: hangi nitelik, hangi uyaran altında,
hangi ortamda sorulmaktadır. İkincisi **eşik**: o niteliğin yanıt ölçütünde kabul edilen değer.
Üçüncüsü **ölçülen değer**: düzenin o ölçütte gerçekten aldığı sayı. Üçünden biri eksikse savunma
yoktur, yalnız tercih vardır. "Mikroservis daha esnektir" cümlesinde üçü de eksiktir: hangi
değişikliğin, hangi eşiğin ve hangi ölçünün konuşulduğu belirsizdir.

Ödünleşim de aynı biçimden çıkar. Bir düzen bir eşiği geçmek için başka bir ölçütün değerini
büyütür; ödünleşim, bu iki sayının aynı karar içinde yan yana durmasıdır. İki düzeni karşılaştıran
bir ölçüm bu yüzden tek bir sayı üretmez, bir vektör üretir.

#### Ortak Temel

Karşılaştırmanın önkoşulu ortak temeldir: aynı kurallar, aynı girdi, aynı sayma yordamı. Aksi
hâlde ölçülen fark biçemden değil, iki gerçekleştirimin ayrıntı farkından gelir. Bu yüzden üç kural
tek bir dosyada durur ve iki düzen de aynı dosyayı kullanır. Değişen tek şey sınırların yeridir.

```js
// karar/kural.mjs — uc kural: iki duzen de ayni kurallari kullanir, degisen yalniz sinirlarin yeri
const TARIFE = { kademe: [[1, 3000], [5, 4800], [20, 9600]], bolge: { "34": 100, "06": 115, "35": 125 }, asgari: 2500 };
const AGAC = { "34": ["34"], "06": ["34", "06"], "35": ["34", "41", "35"] };

export const KURAL = {
  fiyat(g) {
    const kademe = TARIFE.kademe.find(([k]) => g.agirlik <= k) ?? [0, 9600];
    const taban = Math.max(TARIFE.asgari, Math.round((kademe[1] * TARIFE.bolge[g.bolge]) / 100));
    return { net: taban - Math.round(taban * Math.min(g.oran, 0.4)) };
  },
  plan(g) {
    const rota = AGAC[g.bolge] ?? ["34"];
    return { gun: rota.length, tasiyici: rota.length > 2 ? "MT" : "AN" };
  },
  hacim(g, durum) {
    durum.set(g.sozlesmeNo, (durum.get(g.sozlesmeNo) ?? 0) + g.agirlik);
    return { hacim: [...durum.values()].reduce((t, a) => t + a, 0) };
  },
};
```

Düzen bir listedir: hangi kuralın hangi dağıtım biriminde durduğunu söyler. Tek birimli düzende üç
kural aynı birimdedir ve aralarında sınır yoktur; üç birimli düzende her kural kendi birimindedir ve
gövde her sınırda serileşir. Koşturucu ikisini aynı sayaçlarla ölçer ve bir birimi durdurma
seçeneği taşır.

```js
// karar/duzen.mjs — ayni uc kurali iki duzende kosturur: tek dagitim birimi ve uc ayri birim
import { KURAL } from "./kural.mjs";

export const DUZEN = {
  "tek birim": [["fiyat", "plan", "hacim"]],
  "uc birim": [["fiyat"], ["plan"], ["hacim"]],
};

export function kostur(birimler, gonderiler, duran = null) {
  const olcu = { gecis: 0, donusum: 0, bayt: 0, yanit: 0, ayakta: 0, son: {} };
  const durum = new Map();
  for (const g of gonderiler) {
    const sonuc = {};
    for (const birim of birimler) {
      if (birim.includes(duran)) continue;
      const metin = birimler.length === 1 ? null : JSON.stringify(g);   // sinir varsa govde serilesir
      if (metin !== null) olcu.donusum += 2, olcu.bayt += metin.length;
      olcu.gecis += 1;
      for (const ad of birim) Object.assign(sonuc, KURAL[ad](metin === null ? g : JSON.parse(metin), durum));
    }
    if ("net" in sonuc) olcu.yanit += 1;                               // ucret olmadan istek yanitsizdir
    olcu.son = sonuc;
  }
  olcu.ayakta = birimler.filter((b) => b.includes(duran) === false).length;
  return olcu;
}
```

#### Üç Senaryo ve Bir Eşik

Üç senaryo üç ayrı nitelik ailesinden gelir ve her biri tek bir yanıt ölçütüne indirilmiştir.
Sürdürülebilirlik senaryosu tarife kuralı değiştiğinde onunla birlikte yayınlanması gereken kural
sayısını sorar ve en çok 1 kabul eder. Başarım verimliliği senaryosu üç gönderi için sınırdan geçen
baytı sorar ve en çok 100 kabul eder. Güvenilirlik senaryosu hacim biriminin durduğu ortamda
yanıtlanan istek sayısını sorar ve en az 3 ister. Betik üç eşiği iki düzene uygular, sonra her eşiği
düzenin ölçülen değerine taşıyarak kararın hangi eşikte döndüğünü arar.

```js
// karar/olc.mjs — iki duzeni uc nitelik senaryosunun esigine karsi olcer ve karari ceviren esigi arar
import { DUZEN, kostur } from "./duzen.mjs";

const GONDERI = [
  { id: "G1", agirlik: 4, bolge: "35", sozlesmeNo: "S7", oran: 0.15 },
  { id: "G2", agirlik: 1, bolge: "34", sozlesmeNo: "S7", oran: 0 },
  { id: "G3", agirlik: 12, bolge: "06", sozlesmeNo: "S9", oran: 0.25 },
];
const SENARYO = [
  { ad: "S1 surdurulebilirlik", olcut: "birlikte yayinlanan kural", esik: 1, enCok: true },
  { ad: "S2 basarim verimliligi", olcut: "sinirdan gecen bayt", esik: 100, enCok: true },
  { ad: "S3 guvenilirlik", olcut: "yanitlanan istek", esik: 3, enCok: false },
];
const ALAN = ["dagitim birimi", "birlikte yayinlanan kural", "sinir gecisi", "donusum noktasi",
  "sinirdan gecen bayt", "yanitlanan istek", "ayakta kalan birim"];

const olcum = Object.entries(DUZEN).map(([ad, birimler]) => {
  const tam = kostur(birimler, GONDERI);
  const arizali = kostur(birimler, GONDERI, "hacim");
  return {
    ad, son: tam.son,
    "dagitim birimi": birimler.length,
    "birlikte yayinlanan kural": birimler.find((b) => b.includes("fiyat")).length,
    "sinir gecisi": tam.gecis,
    "donusum noktasi": tam.donusum,
    "sinirdan gecen bayt": tam.bayt,
    "yanitlanan istek": arizali.yanit,
    "ayakta kalan birim": `${arizali.ayakta}/${birimler.length}`,
  };
});

console.log(`${"olcu".padEnd(26)}${olcum.map((o) => o.ad.padStart(11)).join("")}`);
for (const a of ALAN) console.log(`${a.padEnd(26)}${olcum.map((o) => String(o[a]).padStart(11)).join("")}`);
for (const o of olcum) console.log(`${o.ad.padEnd(10)} son gonderinin sonucu = ${JSON.stringify(o.son)}`);

const gecti = (o, s) => (s.enCok ? o[s.olcut] <= s.esik : o[s.olcut] >= s.esik);
for (const s of SENARYO) {
  const okuma = olcum.map((o) => `${o.ad} = ${o[s.olcut]} ${gecti(o, s) ? "gecti" : "kaldi"}`);
  console.log(`${s.ad}: ${s.olcut} ${s.enCok ? "<=" : ">="} ${s.esik} -> ${okuma.join(", ")}`);
}
for (const o of olcum) console.log(`${o.ad.padEnd(10)} gectigi senaryo = ${SENARYO.filter((s) => gecti(o, s)).length}/3`);
console.log(`ucunu birden gecen duzen sayisi = ${olcum.filter((o) => SENARYO.every((s) => gecti(o, s))).length}`);

for (const s of SENARYO) {
  for (const o of olcum.filter((x) => gecti(x, s) === false)) {
    console.log(`${s.ad} esigi ${s.esik} yerine ${o[s.olcut]} olmadikca "${o.ad}" gecmez`);
  }
}
for (const s of SENARYO) {
  const kalan = SENARYO.filter((x) => x !== s);
  const gecen = olcum.filter((o) => kalan.every((x) => gecti(o, x))).map((o) => o.ad);
  console.log(`${s.ad} birakilirsa kalan ikisini gecen duzen = ${gecen.join(", ") || "yok"}`);
}
```

```sh
node karar/olc.mjs
```

```
olcu                        tek birim   uc birim
dagitim birimi                      1          3
birlikte yayinlanan kural           3          1
sinir gecisi                        3          9
donusum noktasi                     0         18
sinirdan gecen bayt                 0        588
yanitlanan istek                    0          3
ayakta kalan birim                0/1        2/3
tek birim  son gonderinin sonucu = {"net":8280,"gun":2,"tasiyici":"AN","hacim":17}
uc birim   son gonderinin sonucu = {"net":8280,"gun":2,"tasiyici":"AN","hacim":17}
S1 surdurulebilirlik: birlikte yayinlanan kural <= 1 -> tek birim = 3 kaldi, uc birim = 1 gecti
S2 basarim verimliligi: sinirdan gecen bayt <= 100 -> tek birim = 0 gecti, uc birim = 588 kaldi
S3 guvenilirlik: yanitlanan istek >= 3 -> tek birim = 0 kaldi, uc birim = 3 gecti
tek birim  gectigi senaryo = 1/3
uc birim   gectigi senaryo = 2/3
ucunu birden gecen duzen sayisi = 0
S1 surdurulebilirlik esigi 1 yerine 3 olmadikca "tek birim" gecmez
S2 basarim verimliligi esigi 100 yerine 588 olmadikca "uc birim" gecmez
S3 guvenilirlik esigi 3 yerine 0 olmadikca "tek birim" gecmez
S1 surdurulebilirlik birakilirsa kalan ikisini gecen duzen = yok
S2 basarim verimliligi birakilirsa kalan ikisini gecen duzen = uc birim
S3 guvenilirlik birakilirsa kalan ikisini gecen duzen = yok
```

#### Sayıların Okunması

İki düzenin son gönderi için ürettiği sonuç birebir aynı: `{"net":8280,"gun":2,"tasiyici":"AN","hacim":17}`.
Bu satır kararın ne hakkında *olmadığını* söylüyor. Doğruluk iki düzende de aynıdır; biçem seçimi
işlevsel beklenti üzerine değil, onun etrafındaki sayılar üzerine verilir.

Vektörün geri kalanı iki düzeni ters yönde ayırıyor. Tek birimli düzende sınır geçişi 3, dönüştürme
noktası 0, sınırdan geçen bayt 0 — üç kural aynı bellek alanında çağrıldığı için sınır maliyeti
yoktur. Karşılığında tarife kuralı değiştiğinde onunla birlikte yayınlanan kural sayısı 3'tür ve
hacim kuralı durduğunda yanıtlanan istek 0/3'e, ayakta kalan birim 0/1'e iner: aynı birimde
oldukları için biri durunca hepsi durur. Üç birimli düzende ilk üç sayı büyüyor — geçiş 9, dönüşüm
18, bayt 588, yani gönderi başına yaklaşık 65 baytın dokuz kez taşınması — ama birlikte yayınlanan
kural 1'e, yanıtlanan istek 3/3'e çıkıyor.

Bu iki satırın ilk ve üçüncü derste ölçülenlerle akrabalığı vardır; yeni olan altlarındaki üç
satırdır. Üç eşiğe karşı tek birimli düzen 1/3, üç birimli düzen 2/3 geçiyor ve üçünü birden geçen
düzen sayısı 0. Bu bir ölçüm eksiği değil, yanıtın biçimidir: eşik kümesi çelişkiliyse hiçbir düzen
onu karşılamaz ve karar eşikleri sıralamaya döner.

Sıralamanın hangi eşikte döndüğü de hesaplandı. Kararı çeviren eşik S2'dir: yalnız o bırakıldığında
kalan iki senaryoyu geçen bir düzen çıkıyor, üç birimli düzen. S1 ya da S3 bırakıldığında geçen düzen
yok, çünkü ikisi de tek birimli düzenin kaldığı ölçütlerdir. Aynı hesap eşiklerin ne kadar oynaması
gerektiğini de veriyor: S2 eşiği 100'den 588'e çıkarılmadıkça üç birimli düzen geçmez, S1 eşiği
1'den 3'e ve S3 eşiği 3'ten 0'a inmedikçe tek birimli düzen geçmez. Bir savunma bu biçimde yazılır:
"üç birimli düzen seçilmiştir, çünkü S1 ve S3 eşikleri korunmuş ve S2 eşiği 588 bayta gevşetilmiştir."

Son olarak ölçülmemiş eksen. Betik yalnız üç ölçüt biliyor; bir düzeni dördüncü bir nitelikte
değerlendirmesi istenseydi karşılığında bir sayı bulamazdı. Ölçülmemiş bir eksen karar taşımaz —
tartışmada ağırlığı olmaz, çünkü hangi düzenin geçtiği hesaplanamaz. Kursun her dersinde bir ölçü
tanımlanmasının nedeni budur.

#### Özet

- Bir biçem savunması üç parçadan oluşur: nitelik senaryosu, eşik ve ölçülen değer; biri eksikse
  ortada savunma değil tercih vardır.
- Karşılaştırma ortak temel ister: iki düzen aynı üç kuralı, aynı üç gönderiyi ve aynı sayaçları
  kullandı ve son gönderi için birebir aynı sonucu üretti.
- Tek birimli düzen sınır maliyetini sıfırda tuttu (geçiş 3, dönüşüm 0, bayt 0) ama birlikte
  yayınlanan kuralı 3'e, hacim durduğunda yanıtlanan isteği 0/3'e bıraktı.
- Üç birimli düzen aynı işi 9 geçiş, 18 dönüşüm ve 588 baytla yaptı; karşılığında birlikte
  yayınlanan kural 1, yanıtlanan istek 3/3, ayakta kalan birim 2/3 oldu.
- Üç eşiği birden geçen düzen sayısı 0 çıktı; bu bir ölçüm eksiği değil, çelişkili eşik kümesinin
  sonucudur ve kararı eşikleri sıralamaya çevirir.
- Kararı çeviren eşik hesaplandı: yalnız S2 bırakıldığında üç birimli düzen kalan iki senaryoyu
  geçiyor; S2 eşiği 100'den 588'e çıkmadıkça geçmiyor.

#### Kurs Kapanışı

Kurs, üç konuya yayılmış biçemleri tek bir soruyla ele aldı: aynı gönderi ücretlendirme ve
yönlendirme kitaplığı o düzende kurulduğunda hangi sayı küçülür, hangisi büyür. Derslerin bıraktığı
ölçüler aşağıdaki karar tablosunda toplanıyor.

| Ders | İyileşen nitelik | Ölçülen kazanç | Ödenen sayı |
|---|---|---|---|
| Katmanlı Mimari | sürdürülebilirlik | açık ilan okuma isteğini 3 katmandan 2'ye, 2 sınırdan 1'e indirdi | aynı istek iki ayrı yerden karşılanabilir hâle geldi |
| Altıgen ve Soğan Mimari | sürdürülebilirlik | onarımdan sonra halka kenarı 5'ten 3'e, iki kuralın ihlali 0'a indi | her kural ötekinin gördüğü kusura kör: altıgen 4, soğan 6 kenar yasaklıyor |
| Temiz Mimari | sürdürülebilirlik | çıktı modeli geçirildiğinde erişilebilir ad 10'dan 4'e, JSON 174'ten 81 karaktere indi | sınırda ayrı bir model türü ve bir dönüştürme noktası |
| Bileşen Tabanlı Mimari | sürdürülebilirlik | yeteneğe göre bölünmede dışa açılan ad 8'den 6'ya, birlikte yayın oranı 0.39'dan 0.27'ye indi | yüzeyleri atlayan tek tüketici dışa açılan adı 6'dan 10'a çıkardı |
| Mikroçekirdek ve Eklenti Mimarisi | sürdürülebilirlik | çekirdek 4 eklenti adı yerine 0 taşıyor, ithal kapanışı 5 dosyadan 1'e indi | adlar kapanışı 6 dosya olan bileşim köküne taşındı; sözleşme koşum anında denetlenir |
| Kara Tahta Mimarisi | genişletilebilirlik | dokuz dosyanın yedisinde başka birim adı 0; beşinci çözümleyici 1 dosya ekletti | aynı sonuç 3 tur, 5 yoklama ve 7 okuma harcadı; ilk yazan kazanır kuralı 24 sıralamada 3 ayrı tutar üretti |
| İstemci–Sunucu | sürdürülebilirlik | teslimat tarafının bildiği modül 3'ten 1'e, ad 4'ten 1'e indi | hata ithal anından koşum anına kaydı; üç değişimi öğrenmek 12 soru, 9'u boş |
| Eşler Arası | güvenilirlik | kritik birim yıldızda 1, halkada ve tam ağda 0 | bilinen komşu 10'dan 30'a; halkada 18 ileti ve 3 tur |
| Model–Görünüm Aileleri | sürdürülebilirlik | görünümün tanıdığı model adı 5'ten 0'a, ithal kapanışı 2'den 1'e indi | sınırdan geçen 62 bayt 115'e, değişen dosya 1'den 2'ye çıktı |
| Borular ve Süzgeçler | sınanabilirlik | altı adımın altısı tek başına koşturuldu, yeni adımı ekleyen dosya 0 alan adı tanıyor | süzgeç girişlerinden geçen 28 alanın 17'si okunmadan taşındı |
| Yayımla–Abone Ol | sürdürülebilirlik | yayımcının bildiği modül 3'ten 1'e, ithal kapanışı 4'ten 2 dosyaya indi | "bu olayı kim işliyor" sorusu 1 dosya yerine 7 modül tarattı |
| Mesaj Kuyrukları ve Akışlar | başarım verimliliği | yayımcı 60 iş biriminin 0'ını kendi zamanından harcadı | ikinci tüketiciyle bitiş sırası 3 yerde bozuldu, 8 ileti 9 kez teslim edildi |
| Tek Parça Mimari | başarım verimliliği | sınır geçişi 4, dönüşüm noktası 0, sınırdan geçen bayt 0 | 1 dosyalık değişiklik 4 dosya yayınlattı; kusurda ayakta kalan birim 0/2 |
| Servis Yönelimli Mimari | uyumluluk | öğrenilecek gövde biçimi 1'e indi | kanonik alan 10, istek gövdesi 427 bayt; `bolge` değişince 3 dosya |
| Mikroservis Mimarisi | güvenilirlik | yeniden yayınlanan dosya 1, ücret durduğunda 3 uçtan 2'si 200 döndürdü | 18 satır sunucu tekrarı, sonuç durumu 4, öğrenilecek sözleşme 3 |
| Olay Güdümlü Mimari | sürdürülebilirlik | üçüncü tüketici yayıncıda 0 satır düzenletti, tüketici düştüğünde 3/3 | dönüşüm noktası 0'dan 9'a, bayt 0'dan 476'ya; biriktirende sapma 12 |
| Sunucusuz Mimari | sürdürülebilirlik | koşum satırı 0, yeni bileşim için düzenlenen işlev dosyası 0 | soğukta kurulum 2'den 6'ya; modül durumunda sapma 5, depo 13 erişim |

Tablonun tek bir okuması var: sağ kolon hiçbir satırda boş değil. Her biçem bir niteliği ölçülebilir
biçimde iyileştirdi ve karşılığında başka bir sayıyı büyüttü. Sağ kolonun boş göründüğü bir karar,
ölçülmemiş bir karardır.

Bu kurs, Yazılım Tasarımı ve Mimari İlkeler müfredatının altı kursunun son basamağıdır ve basamakların
tamamı ölçeğin büyümesiyle sıralanır. Temiz Kod satırı ve adı ölçtü; Programlama Paradigmaları aynı
işin farklı hesaplama biçimlerinde nasıl yazıldığını; Tasarım İlkeleri modülün ve sınıfın sorumluluk
sınırını; Tasarım Kalıpları birlikte çalışan nesne kümelerinin yinelenen düzenlerini; Alan Odaklı
Tasarım bir uygulamanın alan modelini ve bağlam sınırlarını; bu kurs ise uygulamanın kendisinin
dağıtım birimlerine bölünüşünü. Her basamakta ölçünün birimi değişti: satır sayısından ad sayısına,
ad sayısından ithal kapanışına, ithal kapanışından sınır geçişine, sınır geçişinden birlikte
yayınlanan dosya ve ayakta kalan birim sayısına.

Bir basamak daha vardır ve orada soru bu kursun sorusundan ayrılır. Bu kurs biçemleri bir *tasarım
kararı* olarak karşılaştırdı: aynı kitaplığın düzenlenişleri, tek makinede modellenmiş sınırlarla.
Sonraki müfredat Sistem Tasarımı ve Dağıtık Sistemler aynı birimleri gerçek bir sistemin kısıtları
altında ele alır: ölçek, gecikme, tutarlılık ve arıza davranışı. Bu dersteki 588 bayt orada bir ağ
bütçesine, 3/3 yanıt oranı bir erişilebilirlik hedefine, hacim kuralının tuttuğu durum bir tutarlılık
modeline dönüşür. Biçem seçimi orada da aynı üç parçayla savunulur; değişen, eşiklerin nereden
geldiğidir.

