---
title: 'Servis Mimarileri'
source: 'https://academia.sh/tr/kurslar/servis-mimarileri'
language: tr
updated: '2026-08-17T04:38:55+00:00'
license: 'CC BY-SA 4.0'
lessons: 18
outcomes:
    - 'Tek parça, modüler ve dağıtık mimariler arasında ölçüte dayalı seçim yapar'
    - 'Servis sınırlarını alan modeline göre çizer'
    - 'Servisler arası iletişim ve tutarlılık kalıplarını uygular'
---

# Servis Mimarileri

Tek dağıtım biriminin bölünmesi: tek parça, modüler tek parça, servis yönelimli, mikroservis ve sunucusuz seçeneklerinin ölçüte dayalı karşılaştırılması, aşamalı ayrıştırma stratejileri, sınırın bağlaşım, veri sahipliği ve değişim hızıyla çizilmesi, eşzamanlı çağrının zincirleme gecikmesi ile hata yayılımı, olay tabanlı gevşek bağlanma, kenar sorumluluklarının toplanması, ağ ilgilerinin uygulamadan ayrılması, adres çözümleme ve dağıtık tutarlılıkta iki aşamalı işlemenin maliyeti, telafi edici iş akışları, giden kutusu, tüketici tarafı etkisizlik ile sözleşme evrimi.

## Mimari Seçenekler

Aynı uygulamanın beş biçimde kurulması ve ölçülmesi: tek parça uygulamanın güçlü yanları ile ölçek sınırları, sınırların kod içinde korunduğu modüler tek parça, kurumsal servis düşüncesi, bağımsız dağıtımın işletim maliyetini doğurduğu mikroservisler, olay tetiklemeli işlev modelinin kısıtları ve aşamalı ayrıştırmayla boğucu incir kalıbına dayanan geçiş stratejileri.

### Tek Parça Uygulama

Source: https://academia.sh/tr/kurslar/servis-mimarileri/tek-parca-uygulama

Çalışan bir tek parça ödünç sisteminin kurulması ve ölçülmesi: yedi modül, on ithal kenarı, tek süreç, sıfır ağ atlaması; yerel çağrının ucuzluğu, tek dağıtım biriminin getirdiği değiştirme kolaylığı ve aynı birimin çoğaltılmasıyla ortaya çıkan ölçek sınırı.

Önceki kurs, Eşzamansız İşleme, kapanışında tek bir varsayımı işaret etti: bütün kuyruklar,
teslim garantileri ve geri basınç düzenekleri **tek bir dağıtım biriminin** içinde kuruldu. Bu
kurs o birimi bölmeyi ele alır. Bölmeden önce bölünecek şeyin ne olduğu ortada durmalıdır.

Bu ders, kütüphane ödünç sisteminin çalışan bir **tek parça (monolith)** gerçekleştirmesini
kurar ve ölçer. Kurulan düzenek sonraki beş dersin tabanıdır: her ders onu bir adım böler ya
da bölmenin bedelini bu ölçülerin üzerinden sayar. Mimari biçemlerin tanımı Mimari Biçemler
kursunda yapıldı; buradaki iş tanım değil, gerçekleştirme ve ölçüdür.

#### Düzeneğin Sınırı

**MS1.** Durum tek bir JSON dosyasında tutulur. Gerçek bir dağıtımda burada paylaşılan bir
veri deposu olurdu; modelin taşıdığı özellik, durumun **süreç dışında ve tek yerde** olmasıdır.

**MS2.** Modüller aynı süreçte, yerel işlev çağrısıyla konuşur. Ağ gecikmesi, serileştirme ve
kısmi hata bu modelde yoktur; onlar dördüncü derste gerçek süreçlerle ölçülür.

#### Düzenek: Tek Parça Ödünç Sistemi

Sistem beş iş alanı taşır: katalog, üyelik, ödünç, bildirim ve ücretlendirme. Durumu tutan
ortak bir modül var.

```js
// depo.mjs — butun modullerin durumu; tek dosya, tek surec
import { existsSync, readFileSync, writeFileSync } from "node:fs";
const YOL = "veri.json";
const BASLANGIC = {
  kitap: { 1: { baslik: "Korluk", durum: "rafta" }, 2: { baslik: "Kum Kitabi", durum: "rafta" } },
  uye: { 4: { ad: "Emre", acik: 1, sinir: 3, borc: 0 },
         5: { ad: "Selin", acik: 3, sinir: 3, borc: 0 } },
  odunc: [],
};
export function oku() {
  if (existsSync(YOL) === false) writeFileSync(YOL, JSON.stringify(BASLANGIC));
  return JSON.parse(readFileSync(YOL, "utf8"));
}
export function yaz(d) { writeFileSync(YOL, JSON.stringify(d)); }
```

Üç iş modülü bu depoyu okur ve yazar; bildirim modülünün durumu yoktur.

```js
// katalog.mjs — kitap kayitlari
import { oku, yaz } from "./depo.mjs";
export function durum(kitapId) { return oku().kitap[kitapId].durum; }
export function oduncIsaretle(kitapId) {
  const d = oku();
  if (d.kitap[kitapId].durum !== "rafta") throw new Error("kitap rafta degil");
  d.kitap[kitapId].durum = "odunc";
  yaz(d);
}
```

```js
// uyelik.mjs — uye kayitlari ve odunc siniri
import { oku, yaz } from "./depo.mjs";
export function uygunMu(uyeId) { const u = oku().uye[uyeId]; return u.acik < u.sinir; }
export function oduncEkle(uyeId) { const d = oku(); d.uye[uyeId].acik += 1; yaz(d); }
```

```js
// ucretlendirme.mjs — gecikme ucreti
import { oku, yaz } from "./depo.mjs";
export const GUNLUK = 2;
export function ucret(gecikmeGun) { return gecikmeGun > 0 ? gecikmeGun * GUNLUK : 0; }
export function borcEkle(uyeId, tutar) { const d = oku(); d.uye[uyeId].borc += tutar; yaz(d); }
```

```js
// bildirim.mjs — uyeye bildirim
export function gonder(uyeId, metin) { console.log(`  bildirim -> uye ${uyeId}: ${metin}`); }
```

İş akışını yürüten modül dördünü de sırayla çağırır.

```js
// odunc.mjs — odunc verme is akisi; dort modulu sirayla cagirir
import { oku, yaz } from "./depo.mjs";
import * as katalog from "./katalog.mjs";
import * as uyelik from "./uyelik.mjs";
import * as ucretlendirme from "./ucretlendirme.mjs";
import * as bildirim from "./bildirim.mjs";

export function oduncVer(kitapId, uyeId, gecikmeGun = 0) {
  if (uyelik.uygunMu(uyeId) === false) throw new Error("odunc siniri asildi");
  katalog.oduncIsaretle(kitapId);
  uyelik.oduncEkle(uyeId);
  const tutar = ucretlendirme.ucret(gecikmeGun);
  if (tutar > 0) ucretlendirme.borcEkle(uyeId, tutar);
  const d = oku();
  d.odunc.push({ kitapId, uyeId });
  yaz(d);
  bildirim.gonder(uyeId, `kitap ${kitapId} odunc verildi`);
  return { kitapId, uyeId, tutar };
}
```

```js
// uygulama.mjs — tek giris noktasi; is akisini bastan sona kosturur
import { oduncVer } from "./odunc.mjs";
import { durum } from "./katalog.mjs";
const [kitapId = 1, uyeId = 4, gecikme = 0] = process.argv.slice(2).map(Number);
try {
  const s = oduncVer(kitapId, uyeId, gecikme);
  console.log(`odunc verildi: kitap ${s.kitapId} -> uye ${s.uyeId}, ucret ${s.tutar}`);
} catch (h) {
  console.log("basarisiz:", h.message);
}
console.log(`  katalog: kitap ${kitapId} = ${durum(kitapId)}`);
```

#### İş Akışının Koşumu

Dört istek dört yolu sınar: geçen bir ödünç, ödünçteki bir kitabın ikinci kez istenmesi,
sınırındaki bir üye ve gecikme ücreti çıkaran bir kayıt.

```sh
rm -f veri.json
node uygulama.mjs 1 4
echo "--- ayni kitap ikinci kez ---"
node uygulama.mjs 1 4
echo "--- sinirdaki uye ---"
node uygulama.mjs 2 5
echo "--- gecikmeli kayit ---"
node uygulama.mjs 2 4 3
```

```
  bildirim -> uye 4: kitap 1 odunc verildi
odunc verildi: kitap 1 -> uye 4, ucret 0
  katalog: kitap 1 = odunc
--- ayni kitap ikinci kez ---
basarisiz: kitap rafta degil
  katalog: kitap 1 = odunc
--- sinirdaki uye ---
basarisiz: odunc siniri asildi
  katalog: kitap 2 = rafta
--- gecikmeli kayit ---
  bildirim -> uye 4: kitap 2 odunc verildi
odunc verildi: kitap 2 -> uye 4, ucret 6
  katalog: kitap 2 = odunc
```

Üçüncü koşumda dikkat çeken şey, üyelik denetiminin katalogdan **önce** yapılmış olmasıdır:
sınır aşıldığı için kitap hiç işaretlenmedi. Bu sıralamayı ayakta tutan şey, iki denetimin de
aynı süreçte, aynı `oduncVer` gövdesinde durmasıdır. Beşinci koşumdan sonra bu sıralama bir
sözleşmeye dönüşecek; şimdilik bir `if` satırıdır.

#### Ölçü

Mimari tartışmasının taşıyacağı sayılar kaynak dosyalardan okunur.

```js
// olc.mjs — modul, ithal kenari ve is akisindaki modullerarasi cagri sayisi
import { readdirSync, readFileSync } from "node:fs";
const KENAR = /from "\.\/([\w-]+\.mjs)"/g;
const dosyalar = readdirSync(".").filter((a) => a.endsWith(".mjs") && a !== "olc.mjs").sort();
let toplam = 0;
for (const d of dosyalar) {
  const hedef = [...readFileSync(d, "utf8").matchAll(KENAR)].map((m) => m[1].replace(".mjs", ""));
  toplam += hedef.length;
  console.log(`${d.padEnd(18)} -> ${hedef.join(", ") || "(yok)"}`);
}
const akis = [...readFileSync("odunc.mjs", "utf8")
  .matchAll(/\b(katalog|uyelik|ucretlendirme|bildirim)\.\w+\(/g)];
console.log(`modul: ${dosyalar.length}   ithal kenari: ${toplam}`);
console.log(`is akisindaki modullerarasi cagri: ${akis.length}   ag atlamasi: 0`);
console.log(`surec: 1   dagitim birimi: 1   baslatma adimi: 1`);
```

```sh
node olc.mjs
```

```
bildirim.mjs       -> (yok)
depo.mjs           -> (yok)
katalog.mjs        -> depo
odunc.mjs          -> depo, katalog, uyelik, ucretlendirme, bildirim
ucretlendirme.mjs  -> depo
uyelik.mjs         -> depo
uygulama.mjs       -> odunc, katalog
modul: 7   ithal kenari: 10
is akisindaki modullerarasi cagri: 6   ag atlamasi: 0
surec: 1   dagitim birimi: 1   baslatma adimi: 1
```

Yedi modül, on ithal kenarı, altı modüller arası çağrı. Bu altı çağrının hepsi yerel: yığıt
üzerinde parametre geçirir, dönüş değerini doğrudan alır, hata `throw` ile yayılır. Ağ
atlaması sıfırdır. Sistemi ayağa kaldırmak tek adımdır; ödünç iş akışını uçtan uca koşturmak
için başka bir sürecin hazır olmasını beklemek gerekmez.

Bu üç sayı — altı yerel çağrı, sıfır atlama, bir dağıtım birimi — tek parçanın ucuzlattığı
şeyi tek başına anlatır. Bir çağrının yarı yolda kaybolması diye bir durum yoktur; ya işlev
döner ya hata atar. Katalog ile üyelik arasındaki tutarlılık için bir düzen kurmak gerekmez,
çünkü ikisi de aynı `veri.json` dosyasına aynı süreçten yazar. Beş alana yayılan bir
değişiklik tek bir derlemede sınanır ve tek bir dağıtımda yayına çıkar.

#### Ölçek Sınırı

Bedel, aynı birimin bölünememesinde görünür. Ödünç yolunun yükü arttığında yapılabilecek tek
şey, birimin tamamını çoğaltmaktır.

```sh
# olcek.sh — ayni tek parcanin uc kopyasi; her kopya butun modulleri tasir
rm -rf kopya1 kopya2 kopya3
for k in kopya1 kopya2 kopya3; do
  mkdir "$k"
  for m in depo katalog uyelik ucretlendirme bildirim odunc uygulama; do cp "$m.mjs" "$k/"; done
  ( cd "$k" && node uygulama.mjs 1 4 | grep '^odunc' | sed "s|^|$k: |" )
done
echo "dagitilan modul dosyasi: $(ls kopya*/*.mjs | wc -l | tr -d ' ')"
```

```
kopya1: odunc verildi: kitap 1 -> uye 4, ucret 0
kopya2: odunc verildi: kitap 1 -> uye 4, ucret 0
kopya3: odunc verildi: kitap 1 -> uye 4, ucret 0
dagitilan modul dosyasi: 21
```

İki sayı okunmalı. Birincisi: yalnız ödünç yoluna kapasite eklemek istenmesine karşın
dağıtılan modül dosyası 7'den 21'e çıktı. Bildirim ve ücretlendirme kodunun üç kopyası,
ihtiyaç duyulmadığı hâlde ayakta duruyor. Ölçekleme birimi **kod tabanının tamamıdır**;
darboğazın hangi modülde olduğu bu birimi küçültmez.

İkincisi, koşumun kendisi: üç kopya da aynı kitabı ödünç verdi. Her kopya kendi
`veri.json` dosyasını taşıdığı için üçü de kitabı rafta gördü. Gerçek bir dağıtımda durum
ortak bir depoya taşınır ve bu üçleme ortadan kalkar — ama o zaman da bütün kopyalar tek bir
depoya yüklenir. Tek parçanın ölçek sınırı buradadır: süreç çoğaltılabilir, durum çoğaltılamaz.

Değiştirme tarafında da bir sınır var. Dağıtım birimi bir tane olduğu için, ücret oranındaki
tek satırlık bir değişiklik de katalog ile üyeliği yeniden yayına çıkarır. Ölçüde bunun
karşılığı şudur: dokunulan dosya bir, yeniden dağıtılan modül yedi.

#### Üç Sütun

Bu kursun her dersi aynı işi iki gerçekleştirmede karşılaştırır ve üç sütunu doldurur.

| | Tek parça |
|---|---|
| Ucuzlattığı | Yerel çağrı, ortak işlem sınırı, tek derleme, tek dağıtım adımı |
| Pahalılaştırdığı | Ölçekleme birimi tüm kod tabanı; bir satırlık değişiklik yedi modülü yeniden yayınlar |
| Doğan arıza kipi | Bir modüldeki bellek sızıntısı ya da sonsuz döngü bütün alanları birlikte durdurur |

Üçüncü satır ölçülmedi, çünkü tek süreçte bunu ölçmenin yolu süreci çökertmektir. Kayıt olarak
bırakılıyor: dördüncü derste aynı arıza ayrı süreçlerde denendiğinde karşılaştırılacak.

#### Özet

- Kütüphane ödünç sistemi tek parça olarak kuruldu: 7 modül, 10 ithal kenarı, 1 süreç, 1
  dağıtım birimi; ödünç iş akışı uçtan uca koşuyor.
- İş akışındaki 6 modüller arası çağrının hepsi yerel; ağ atlaması sıfır, başlatma adımı bir.
- Tek işlem sınırı ve yerel çağrı, katalog ile üyelik arasındaki tutarlılığı ek bir düzen
  gerektirmeden sağlıyor.
- Ölçek sınırı çoğaltmada görünür: yalnız ödünç yoluna kapasite eklemek 7 modülü 21 dosyaya
  çıkardı, çünkü ölçekleme birimi kod tabanının tamamıdır.
- Tek dağıtım birimi, tek satırlık bir ücret değişikliğinde bile bütün modülleri yeniden
  yayına çıkarır.

#### Sonraki Adım

Ölçülen sayılardan biri henüz sorgulanmadı: `odunc.mjs` dört modülü de doğrudan ithal
ediyor, ama bunu engelleyen bir şey yok — `bildirim.mjs` de `katalog.mjs` dosyasını ithal
edebilir, katalog da üyeliğin içine uzanabilir. Tek parçanın kod içinde bir sınırı yoktur;
ithal grafiği kimse bakmazsa tamamlanmış bir grafiğe doğru büyür. Sonraki ders bu grafiği
ölçer, bir modülün başkasının içine uzandığı **sınır ihlallerini** sayar ve sınırın kod
içinde bir kuralla korunduğu düzeni kurar: süreç hâlâ tek, dağıtım birimi hâlâ tek, ama
ithal kenarları artık serbest değil.

### Modüler Tek Parça

Source: https://academia.sh/tr/kurslar/servis-mimarileri/moduler-tek-parca

Sınırların kod içinde korunması: alanlar arası ithal grafiğinin ölçülmesi, bir modülün başkasının içine uzandığı sınır ihlallerinin sayılması, kural konduğunda dokunulan dosyanın ve genişleyen genel yüzün ölçüsü; süreç ve dağıtım birimi hâlâ tek.

Önceki ders tek parçayı ölçtü: yedi modül, on ithal kenarı, tek süreç, tek dağıtım birimi.
Ölçünün açık bıraktığı yer, o on kenarın **hangi kurala göre** çizildiğiydi. Cevap şuydu:
hiçbir kurala göre. Bir modül başka bir modülün herhangi bir dosyasını ithal edebilir; hiçbir
şey engellemez.

Bu ders o boşluğu kapatır. Aynı ödünç iş akışı, alanları dizinlere ayrılmış olarak yeniden
kurulur; önce sınırların kendiliğinden aşındığı hâli ölçülür, sonra sınır bir kuralla
korunur. Sınırların kod içinde korunduğu bu düzene **modüler tek parça (modular monolith)**
denir. Süreç sayısı, dağıtım birimi sayısı ve iş akışının çıktısı **hiç değişmez**; değişen
tek şey ithal grafiğidir. Bu, mimarinin dağıtımdan bağımsız olarak da ölçülebildiği tek
yerdir.

Buradaki `tek parça`, Başarım Karşıt Kalıpları ve İzleme kursundaki **tek parça kalıcılık**
ile aynı sözcüğü taşır ama ayrı şeydir: orada tek olan veri deposu, burada tek olan dağıtım
birimidir.

#### Düzenek

**MS3.** Durum, tek koşum içinde çalışıldığı için bellekte tutulur. Birinci dersteki dosya
deposunun yerini alan bu sadeleştirme ölçüyü etkilemez: ölçülen şey ithal grafiğidir.

Beş alanın her biri bir dizin. Her dizinde dışa açılan `index.mjs` ile alanın iç dosyaları
bulunur.

```sh
# kur.sh — 01'deki ayni is akisi, alanlar dizin oldu; durum tek kosumda bellekte tutulur
rm -rf bozulmus && mkdir -p bozulmus/katalog bozulmus/uyelik bozulmus/ucretlendirme \
  bozulmus/bildirim bozulmus/odunc && cd bozulmus
cat > depo.mjs <<'JS'
export const veri = { kitap: { 1: { durum: "rafta" } },
                      uye: { 4: { ad: "Emre", acik: 1, sinir: 3, borc: 0 } } };
JS
cat > katalog/kayit.mjs <<'JS'
import { veri } from "../depo.mjs";
export const kitap = (id) => veri.kitap[id];
JS
cat > katalog/index.mjs <<'JS'
import { kitap } from "./kayit.mjs";
export const durum = (id) => kitap(id).durum;
JS
cat > uyelik/kayit.mjs <<'JS'
import { veri } from "../depo.mjs";
export const uye = (id) => veri.uye[id];
JS
cat > uyelik/index.mjs <<'JS'
import { uye } from "./kayit.mjs";
export const uygunMu = (id) => uye(id).acik < uye(id).sinir;
export const oduncEkle = (id) => { uye(id).acik += 1; };
JS
cat > ucretlendirme/index.mjs <<'JS'
import { uye } from "../uyelik/kayit.mjs";
export const ucret = (gun) => (gun > 0 ? gun * 2 : 0);
export const borcEkle = (id, tutar) => { uye(id).borc += tutar; };
JS
cat > bildirim/index.mjs <<'JS'
import { uye } from "../uyelik/kayit.mjs";
export const gonder = (id, metin) => console.log(`  bildirim -> ${uye(id).ad}: ${metin}`);
JS
cat > odunc/index.mjs <<'JS'
import { kitap } from "../katalog/kayit.mjs";
import * as uyelik from "../uyelik/index.mjs";
import * as ucretlendirme from "../ucretlendirme/index.mjs";
import * as bildirim from "../bildirim/index.mjs";
export function oduncVer(kitapId, uyeId, gecikmeGun = 0) {
  if (uyelik.uygunMu(uyeId) === false) throw new Error("odunc siniri asildi");
  if (kitap(kitapId).durum !== "rafta") throw new Error("kitap rafta degil");
  kitap(kitapId).durum = "odunc";
  uyelik.oduncEkle(uyeId);
  const tutar = ucretlendirme.ucret(gecikmeGun);
  if (tutar > 0) ucretlendirme.borcEkle(uyeId, tutar);
  bildirim.gonder(uyeId, `kitap ${kitapId} odunc verildi`);
  return tutar;
}
JS
cat > uygulama.mjs <<'JS'
import { oduncVer } from "./odunc/index.mjs";
import { durum } from "./katalog/index.mjs";
const tutar = oduncVer(1, 4, 3);
console.log(`odunc verildi: kitap 1 -> uye 4, ucret ${tutar}`);
console.log(`  katalog: kitap 1 = ${durum(1)}`);
JS
find . -name "*.mjs" | sort
node uygulama.mjs
```

```
./bildirim/index.mjs
./depo.mjs
./katalog/index.mjs
./katalog/kayit.mjs
./odunc/index.mjs
./ucretlendirme/index.mjs
./uyelik/index.mjs
./uyelik/kayit.mjs
./uygulama.mjs
  bildirim -> Emre: kitap 1 odunc verildi
odunc verildi: kitap 1 -> uye 4, ucret 6
  katalog: kitap 1 = odunc
```

Listeleme `bozulmus/` dizinine göredir. İş akışı çalışıyor, ücret hesaplanıyor, bildirim
gidiyor. Dizin ayrımı bakıldığında düzenli görünüyor.

#### Sınır İhlalinin Ölçüsü

Ölçülecek kural şudur: bir alan, başka bir alandan yalnız `index.mjs` dosyasını ithal
edebilir. Bunun dışındaki her ithal bir **sınır ihlali**dir — bir modülün başkasının içine
uzanması.

```js
// sinir.mjs — alan siniri kurali: baska alandan yalniz index.mjs ithal edilebilir
import { readdirSync, readFileSync, statSync } from "node:fs";
const kok = process.argv[2] ?? ".";
const alanlar = readdirSync(kok).filter((a) => statSync(`${kok}/${a}`).isDirectory()).sort();
let kenar = 0, ihlal = 0, yuz = 0;
for (const alan of alanlar) {
  for (const dosya of readdirSync(`${kok}/${alan}`).sort()) {
    if (dosya.endsWith(".mjs") === false) continue;
    const metin = readFileSync(`${kok}/${alan}/${dosya}`, "utf8");
    if (dosya === "index.mjs") yuz += [...metin.matchAll(/^export /gm)].length;
    for (const [, hedef] of metin.matchAll(/from "(\.[^"]+)"/g)) {
      kenar += 1;
      const p = hedef.split("/");
      if (p[0] === ".." && p.length === 3 && p[2] !== "index.mjs") {
        ihlal += 1;
        console.log(`IHLAL ${alan}/${dosya} -> ${hedef}`);
      }
    }
  }
}
console.log(`alan: ${alanlar.length}  ithal kenari: ${kenar}  genel yuz: ${yuz}  sinir ihlali: ${ihlal}`);
```

```sh
node sinir.mjs bozulmus
```

```
IHLAL bildirim/index.mjs -> ../uyelik/kayit.mjs
IHLAL odunc/index.mjs -> ../katalog/kayit.mjs
IHLAL ucretlendirme/index.mjs -> ../uyelik/kayit.mjs
alan: 5  ithal kenari: 10  genel yuz: 7  sinir ihlali: 3
```

Üç ihlal. Üçü de sonradan eklenmiş, tek satırlık, kimseyi rahatsız etmemiş ithallerdir.
Ücretlendirme üyenin borcunu artırmak için üye kaydına doğrudan uzandı; bildirim üyenin adını
almak için aynısını yaptı; ödünç, kitabın durumunu okumak için katalog kaydını açtı. Hiçbiri
hata üretmedi, çünkü aynı süreçte her şey erişilebilirdir.

Bunun bedeli, ihlal sayısında değil, değişimin yayılmasında görünür. `uyelik/kayit.mjs`
içindeki kayıt biçimi değişirse, üyelik alanının kendi `index.mjs` dosyasıyla birlikte iki
yabancı alan da düzeltilmek zorundadır.

#### Kuralın Konması

Düzeltme, dışa uzanan üç ithali alanın genel yüzüne çevirir: eksik olan işlemler ilgili
alanın `index.mjs` dosyasına eklenir, ihlal eden üç dosya bu yeni yüz üzerinden yazılır.

```sh
# duzelt.sh — kural konur: her alan disariya yalniz index.mjs uzerinden acilir
rm -rf moduler && cp -r bozulmus moduler && cd moduler
cat > katalog/index.mjs <<'JS'
import { kitap } from "./kayit.mjs";
export const durum = (id) => kitap(id).durum;
export const raftaMi = (id) => kitap(id).durum === "rafta";
export const oduncIsaretle = (id) => { kitap(id).durum = "odunc"; };
JS
cat > uyelik/index.mjs <<'JS'
import { uye } from "./kayit.mjs";
export const uygunMu = (id) => uye(id).acik < uye(id).sinir;
export const oduncEkle = (id) => { uye(id).acik += 1; };
export const ad = (id) => uye(id).ad;
export const borcEkle = (id, tutar) => { uye(id).borc += tutar; };
JS
cat > ucretlendirme/index.mjs <<'JS'
import { borcEkle as uyeyeBorcEkle } from "../uyelik/index.mjs";
export const ucret = (gun) => (gun > 0 ? gun * 2 : 0);
export const borcEkle = (id, tutar) => uyeyeBorcEkle(id, tutar);
JS
cat > bildirim/index.mjs <<'JS'
import { ad } from "../uyelik/index.mjs";
export const gonder = (id, metin) => console.log(`  bildirim -> ${ad(id)}: ${metin}`);
JS
cat > odunc/index.mjs <<'JS'
import * as katalog from "../katalog/index.mjs";
import * as uyelik from "../uyelik/index.mjs";
import * as ucretlendirme from "../ucretlendirme/index.mjs";
import * as bildirim from "../bildirim/index.mjs";
export function oduncVer(kitapId, uyeId, gecikmeGun = 0) {
  if (uyelik.uygunMu(uyeId) === false) throw new Error("odunc siniri asildi");
  if (katalog.raftaMi(kitapId) === false) throw new Error("kitap rafta degil");
  katalog.oduncIsaretle(kitapId);
  uyelik.oduncEkle(uyeId);
  const tutar = ucretlendirme.ucret(gecikmeGun);
  if (tutar > 0) ucretlendirme.borcEkle(uyeId, tutar);
  bildirim.gonder(uyeId, `kitap ${kitapId} odunc verildi`);
  return tutar;
}
JS
echo "yeniden yazilan dosya: 5"
node uygulama.mjs
```

```
yeniden yazilan dosya: 5
  bildirim -> Emre: kitap 1 odunc verildi
odunc verildi: kitap 1 -> uye 4, ucret 6
  katalog: kitap 1 = odunc
```

Çıktı birinci koşumun aynısı. Kullanıcı tarafında hiçbir şey değişmedi.

```sh
node sinir.mjs moduler
echo "uyelik ic dosyasina uzanan dis dosya -> bozulmus: $(grep -l 'uyelik/kayit.mjs' bozulmus/*/*.mjs | wc -l | tr -d ' ')  moduler: $(grep -l 'uyelik/kayit.mjs' moduler/*/*.mjs | wc -l | tr -d ' ')"
```

```
alan: 5  ithal kenari: 10  genel yuz: 11  sinir ihlali: 0
uyelik ic dosyasina uzanan dis dosya -> bozulmus: 2  moduler: 0
```

Üç sayı okunmalı. İhlal 3'ten 0'a indi. İthal kenarı **10'da kaldı**: kural kenar sayısını
azaltmaz, kenarların ucunu değiştirir; üç kenar bir alanın iç dosyasından o alanın genel
yüzüne kaydı. Genel yüz ise 7'den 11'e çıktı — dört yeni dışa verilen ad.

Son satır asıl kazancı verir. Üyelik alanının iç dosyasına dışarıdan uzanan dosya sayısı
2'den 0'a indi. `uyelik/kayit.mjs` içindeki kayıt biçimi değiştiğinde dokunulacak dosya,
bozulmuş sürümde 3 (kendi `index.mjs` dosyası artı iki yabancı alan), modüler sürümde 1'dir.

Ücretlendirme alanındaki `borcEkle`, üyeliğin aynı adlı işlevini çağıran bir sarmalayıcıya
dönüştü. Bu, kuralın doğrudan bedelidir: alanlar arası her yeni ihtiyaç bir dolaylılık
katmanı ekler ve genel yüzü genişletir.

#### Kuralın Ayakta Kalması

Bu düzenin bir zayıflığı var: kural derleyicinin değil, denetleyicinin kuralıdır.

```sh
# kacak.sh — moduler surume tek satirlik bir ithal eklenir; program calismaya devam eder
sed -i.yedek '1i\
import { uye } from "../uyelik/kayit.mjs";
' moduler/bildirim/index.mjs
rm -f moduler/bildirim/index.mjs.yedek
( cd moduler && node uygulama.mjs | head -n 1 )
node sinir.mjs moduler | tail -n 2
```

```
  bildirim -> Emre: kitap 1 odunc verildi
IHLAL bildirim/index.mjs -> ../uyelik/kayit.mjs
alan: 5  ithal kenari: 11  genel yuz: 11  sinir ihlali: 1
```

Program çalışmaya devam etti; hiçbir test kırılmadı. Yalnız denetleyici konuştu. İhlal
sayısı 0'dan 1'e, ithal kenarı 10'dan 11'e çıktı. Sınır ihlali bir çalışma zamanı hatası
değil, bir ölçüdür: ölçülmediği anda serbestçe büyür. Yerinde düzenlemede kullanılan `-i`
seçeneği GNU ve BSD sürümlerinde farklı davrandığı için burada yedek uzantısı verilip yedek
silinmiştir; iki ortamda da aynı sonucu üretir.

#### Üç Sütun

| | Modüler tek parça |
|---|---|
| Ucuzlattığı | Bir alanın iç yapısı değiştiğinde dokunulan dosya 3'ten 1'e indi; sınır sayılabilir bir ölçü oldu |
| Pahalılaştırdığı | Genel yüz 7'den 11'e çıktı; alanlar arası her ihtiyaç bir dolaylılık katmanı ekliyor |
| Doğan arıza kipi | Kural yalnız denetleyici koştuğunda geçerli; tek satırlık bir ithal hiçbir şeyi kırmadan sınırı sessizce geri alıyor |

Değişmeyen sayılar da kayda değer: süreç 1, dağıtım birimi 1, ağ atlaması 0. Modüler tek
parça bir dağıtım kararı değil, bir kod düzeni kararıdır. Bütün alanlar hâlâ birlikte yayına
çıkar ve bir alandaki sonsuz döngü hepsini birlikte durdurur.

#### Özet

- Alanlar dizinlere ayrıldığında bile ithal grafiği kendiliğinden aşınır: üç alan komşusunun
  iç dosyasına uzanmıştı, hiçbiri hata üretmiyordu.
- Sınır ihlali ölçülebilir bir birimdir: başka bir alandan `index.mjs` dışında bir dosyayı
  ithal eden her satır bir ihlaldir.
- Kural konduğunda 5 dosya yeniden yazıldı, ihlal 3'ten 0'a indi, ithal kenarı 10'da kaldı;
  kural kenarları azaltmaz, uçlarını genel yüze taşır.
- Bedel genel yüzün 7'den 11'e genişlemesi ve alanlar arası her ihtiyaçta bir sarmalayıcı
  eklenmesidir.
- Kural denetleyiciye bağlıdır; tek satırlık bir ithal programı kırmadan ihlali geri getirdi.

#### Sonraki Adım

Modüler tek parça sınırı kod içinde korudu, ama bir şeyi hiç sorgulamadı: `depo.mjs` bütün
alanların ortak veri yapısını tutmayı sürdürüyor. Katalog da üyelik de aynı nesnenin farklı
dallarına yazıyor, yani sınır ithal grafiğinde var, veride yok. Sonraki ders bu ortaklığı
büyütür: alanlar ayrı birimler hâline geldiğinde bile paylaşılan bir sözleşme ve ortak bir
veri modeli üzerinden konuşurlarsa ne olur — ortak katmandaki tek bir değişikliğin kaç birimi
dokunulur kıldığını ölçer.

### Servis Yönelimli Mimari

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

Kurumsal servis düşüncesinin ölçüsü: birimlerin ortak bir sözleşme ve kanonik veri modeli üzerinden konuşması, ortak katmana bağlı birim sayısının sayılması, kanonik bir alan adının değişmesinin kaç birimi dokunulur kıldığı ve sahibi güncellenip tüketicisi güncellenmediğinde doğan sessiz yanlış davranış.

Önceki ders sınırı ithal grafiğinde kurdu, ama `depo.mjs` bütün alanların ortak veri yapısını
tutmayı sürdürdü. Sınır kodda vardı, veride yoktu. Bu ders o ortaklığı büyütür: alanlar
kendi durumlarını taşıyan ayrı birimlere dönüşür, aralarında ortak bir yol üzerinden konuşur
ve konuşmanın biçimini **kurumsal ortak bir model** belirler.

Bu düzenin adı **servis yönelimli mimari** (service-oriented architecture). Biçemin tanımı Mimari Biçemler kursunda
yapıldı; burada ölçülen şey tanım değil, ortak katmanın koda getirdiği bağlaşımdır.

**MS4.** Birimler ayrı süreç değildir; hepsi aynı süreçte, bir ileti yolu üzerinden konuşur.
Ölçülen şey sözleşme bağlaşımıdır, ağ değil. Süreçlere bölme sonraki derste yapılır.

#### Ortak Katman

Ortak katman iki şey tanımlar: her iletinin taşıyacağı zarf ve kanonik üye kaydının alanları.

```js
// ortak/sozlesme.mjs — kurumsal ortak model: ileti zarfi ve kanonik uye alanlari
export const ZARF = ["kaynak", "islem", "govde"];
export const UYE_ALANLARI = ["uyeNo", "ad", "acikOdunc", "sinir", "borc"];
export function dogrula(ileti) {
  for (const a of ZARF) if (a in ileti === false) throw new Error(`zarf alani eksik: ${a}`);
  return ileti;
}
```

```js
// yol.mjs — kurumsal servis yolu; her ileti ortak sozlesmeye gore dogrulanir
import { dogrula } from "./ortak/sozlesme.mjs";
const birimler = new Map();
export const sayac = { ileti: 0 };
export function tanit(islem, islev) { birimler.set(islem, islev); }
export function gonder(ileti) {
  sayac.ileti += 1;
  return birimler.get(dogrula(ileti).islem)(ileti.govde);
}
```

Her birim kendi durumunu taşır ve yaptığı işi yola tanıtır. Doğrudan ithal yoktur; birimler
birbirinin adını değil, **işlem adını** bilir.

```js
// katalog.mjs — kitap birimi
import { tanit } from "./yol.mjs";
const kitaplar = { 1: { kitapNo: 1, baslik: "Korluk", durum: "rafta" },
                   2: { kitapNo: 2, baslik: "Kum Kitabi", durum: "rafta" } };
tanit("kitap.durum", ({ kitapNo }) => kitaplar[kitapNo].durum);
tanit("kitap.oduncIsaretle", ({ kitapNo }) => { kitaplar[kitapNo].durum = "odunc"; });
```

```js
// uyelik.mjs — kanonik uye kaydini tutan birim
import { tanit } from "./yol.mjs";
import { UYE_ALANLARI } from "./ortak/sozlesme.mjs";
const uyeler = {
  4: { uyeNo: 4, ad: "Emre", acikOdunc: 1, sinir: 3, borc: 0 },
  5: { uyeNo: 5, ad: "Selin", acikOdunc: 3, sinir: 3, borc: 0 },
};
export const eksikAlan = (u) => UYE_ALANLARI.filter((a) => a in u === false);
tanit("uye.getir", ({ uyeNo }) => uyeler[uyeNo]);
tanit("uye.oduncEkle", ({ uyeNo }) => { uyeler[uyeNo].acikOdunc += 1; });
tanit("uye.borcEkle", ({ uyeNo, tutar }) => { uyeler[uyeNo].borc += tutar; });
```

```js
// ucretlendirme.mjs — gecikme ucreti birimi
import { tanit } from "./yol.mjs";
tanit("ucret.hesapla", ({ gecikmeGun }) => (gecikmeGun > 0 ? gecikmeGun * 2 : 0));
```

```js
// bildirim.mjs — bildirim birimi; kanonik kayittan ad alanini okur
import { tanit, gonder } from "./yol.mjs";
tanit("bildirim.gonder", ({ uyeNo, metin }) => {
  const uye = gonder({ kaynak: "bildirim", islem: "uye.getir", govde: { uyeNo } });
  console.log(`  bildirim -> ${uye.ad}: ${metin}`);
});
```

Ödünç birimi iş akışını yürütür. Dikkat edilecek yer sınır denetimidir: kanonik kaydı
alıyor ve kuralı **kendisi** uyguluyor.

```js
// odunc.mjs — is akisi; kanonik uye kaydini okuyup kurali kendisi uygular
import { gonder, sayac } from "./yol.mjs";
export function oduncVer(kitapNo, uyeNo, gecikmeGun = 0) {
  const z = (islem, govde) => gonder({ kaynak: "odunc", islem, govde });
  const uye = z("uye.getir", { uyeNo });
  if (uye.acikOdunc >= uye.sinir) throw new Error("odunc siniri asildi");
  if (z("kitap.durum", { kitapNo }) !== "rafta") throw new Error("kitap rafta degil");
  z("kitap.oduncIsaretle", { kitapNo });
  z("uye.oduncEkle", { uyeNo });
  const tutar = z("ucret.hesapla", { gecikmeGun });
  if (tutar > 0) z("uye.borcEkle", { uyeNo, tutar });
  z("bildirim.gonder", { uyeNo, metin: `kitap ${kitapNo} odunc verildi` });
  return { tutar, ileti: sayac.ileti };
}
```

```js
// uygulama.mjs — butun birimleri yukler ve is akisini kosturur
import "./katalog.mjs"; import "./uyelik.mjs"; import "./ucretlendirme.mjs";
import "./bildirim.mjs";
import { oduncVer } from "./odunc.mjs";
for (const [kitapNo, uyeNo, gun] of [[1, 4, 3], [2, 5, 0]]) {
  try {
    const s = oduncVer(kitapNo, uyeNo, gun);
    console.log(`odunc verildi: kitap ${kitapNo} -> uye ${uyeNo}, ucret ${s.tutar}, ileti ${s.ileti}`);
  } catch (h) { console.log(`basarisiz (uye ${uyeNo}):`, h.message); }
}
```

```sh
node uygulama.mjs
```

```
  bildirim -> Emre: kitap 1 odunc verildi
odunc verildi: kitap 1 -> uye 4, ucret 6, ileti 8
basarisiz (uye 5): odunc siniri asildi
```

Birinci ölçü ileti sayısıdır: bir ödünç iş akışı **8 ileti** üretti. Birinci derste aynı iş
akışı 6 yerel çağrıyla yürüyordu. Fark iki yerden geliyor. Bildirim birimi artık üyenin adını
almak için ayrı bir ileti gönderiyor; ödünç birimi de `uygunMu` gibi bir davranış çağırmak
yerine kanonik kaydı çekip kuralı kendisi uyguluyor. Ortak model konuşmayı **davranıştan
veriye** kaydırdı.

#### Ortak Modele Bağlı Birim

Bağlaşımın ölçüsü, kanonik alan adlarına kaç birimden atıf yapıldığıdır.

```js
// bagalasim.mjs — ortak modeldeki alan adlarina hangi birimden atif var
import { readdirSync, readFileSync } from "node:fs";
import { UYE_ALANLARI } from "./ortak/sozlesme.mjs";
const disarida = ["bagalasim.mjs", "uygulama.mjs", "yol.mjs"];
const birimler = readdirSync(".").filter((a) => a.endsWith(".mjs") && disarida.includes(a) === false);
let bagli = 0;
for (const b of birimler.sort()) {
  const metin = readFileSync(b, "utf8");
  const bulunan = UYE_ALANLARI.filter((a) => new RegExp(`\\b${a}\\b`).test(metin));
  if (bulunan.length > 0) bagli += 1;
  console.log(`${b.padEnd(20)} kanonik alan atfi: ${bulunan.join(", ") || "(yok)"}`);
}
console.log(`birim: ${birimler.length}   ortak modele bagli birim: ${bagli}`);
```

```sh
node bagalasim.mjs
```

```
bildirim.mjs         kanonik alan atfi: uyeNo, ad
katalog.mjs          kanonik alan atfi: (yok)
odunc.mjs            kanonik alan atfi: uyeNo, acikOdunc, sinir
ucretlendirme.mjs    kanonik alan atfi: (yok)
uyelik.mjs           kanonik alan atfi: uyeNo, ad, acikOdunc, sinir, borc
birim: 5   ortak modele bagli birim: 3
```

Beş birimin üçü kanonik üye kaydının alan adlarını biliyor. Önceki derste bu sayı birdi:
`acik` alanını yalnız üyelik alanı tanıyordu, ödünç ona `uygunMu` diye soruyordu. Ortak
model, kaydın iç yapısını üç birime yaydı.

Buradaki ayrım şudur: `bildirim.mjs` ile `odunc.mjs` üye kaydına **sahip değil**, ama onun
biçimine bağlı. Veri sahipliği üyelik biriminde, veri bilgisi üç yerde.

#### Ortak Katmanın Değişmesi

Kanonik modelin bir alanı yeniden adlandırılsın. Değişikliği ortak katmanda ve alanın
sahibinde yapmak yeterli görünür.

```sh
# degistir.sh — kanonik alan adi degisir; yalniz ortak katman ve alanin sahibi guncellenir
sed -i.y 's/acikOdunc/acikOduncSayisi/g' ortak/sozlesme.mjs uyelik.mjs
rm -f ortak/sozlesme.mjs.y uyelik.mjs.y
echo "dokunulan dosya: 2"
node uygulama.mjs
```

```
dokunulan dosya: 2
  bildirim -> Emre: kitap 1 odunc verildi
odunc verildi: kitap 1 -> uye 4, ucret 6, ileti 8
  bildirim -> Selin: kitap 2 odunc verildi
odunc verildi: kitap 2 -> uye 5, ucret 0, ileti 15
```

İkinci istek geçti. Üye 5 sınırındaydı ve bir önceki koşumda reddedilmişti; şimdi ödünç
aldı. Hiçbir hata basılmadı, hiçbir yerde bir uyarı çıkmadı.

Sebep ödünç birimindeki tek satırdır: `uye.acikOdunc` artık `undefined` döndürüyor ve
`undefined >= 3` karşılaştırması `false` veriyor, yani sınır denetimi her zaman geçiyor.
Ortak modeldeki bir ad değişikliği, tüketicide **hata değil yanlış davranış** üretti.

Düzeltme, aynı değişikliğin tüketici birime de taşınmasıdır.

```sh
# duzelt.sh — ayni degisiklik tuketici birime de tasinir
sed -i.y 's/acikOdunc/acikOduncSayisi/g' odunc.mjs
rm -f odunc.mjs.y
echo "dokunulan dosya: 3 (ortak/sozlesme.mjs, uyelik.mjs, odunc.mjs)"
node uygulama.mjs
```

```
dokunulan dosya: 3 (ortak/sozlesme.mjs, uyelik.mjs, odunc.mjs)
  bildirim -> Emre: kitap 1 odunc verildi
odunc verildi: kitap 1 -> uye 4, ucret 6, ileti 8
basarisiz (uye 5): odunc siniri asildi
```

Ölçü şudur: ortak katmandaki bir alan adı değişikliği **3 dosyaya** dokundu ve bu üç dosya
birlikte yayına çıkmak zorunda. Birimler ayrı ayrı tanıtılmış olsa da, kanonik model onları
tek bir sürüm adımına bağlıyor. Yerinde düzenlemede `-i` seçeneği GNU ve BSD sürümlerinde
farklı davrandığı için yedek uzantısı verilip yedek silinmiştir.

#### Üç Sütun

| | Servis yönelimli mimari |
|---|---|
| Ucuzlattığı | Birimler birbirini ithal etmiyor; yeni bir birim eklemek yalnız bir işlem adı tanıtmakla oluyor, mevcut birimlerin hiçbiri değişmiyor |
| Pahalılaştırdığı | Ortak modele bağlı birim 1'den 3'e çıktı; kanonik bir alan adı değişikliği 3 dosyayı birlikte yayına zorluyor; iş akışı 6 çağrı yerine 8 ileti üretiyor |
| Doğan arıza kipi | Ortak modelin sahibi güncellenip tüketicisi güncellenmediğinde hata alınmıyor; sınır denetimi sessizce devre dışı kalıyor |

Üçüncü sütun bu dersin asıl kazancıdır. Ortak katman bir bağlaşım noktasıdır ve o noktadan
gelen kusurlar çoğunlukla çökme değil, **sessiz yanlış davranış** biçiminde görünür. Ödünç
birimi çalıştı, cevap döndürdü, kayıt yazdı; yalnız uygulamadığı bir kural vardı.

#### Özet

- Servis yönelimli düzende birimler birbirini ithal etmiyor, ortak bir yol üzerinden işlem
  adıyla konuşuyor; yeni birim eklemek mevcut birimleri değiştirmiyor.
- Ortak kanonik model konuşmayı davranıştan veriye kaydırdı: iş akışı 6 yerel çağrı yerine
  8 ileti üretti, çünkü tüketiciler kaydı çekip kuralı kendileri uyguluyor.
- Kanonik alan adlarını bilen birim sayısı 1'den 3'e çıktı; veri sahipliği tek birimde
  kalırken veri bilgisi üçe yayıldı.
- Ortak katmandaki bir alan adı değişikliği 3 dosyaya dokundu ve üçünü tek bir sürüm adımına
  bağladı.
- Sahibi güncellenip tüketicisi güncellenmediğinde sistem hata vermedi; sınırındaki üyeye
  ödünç verildi. Ortak model kusurları sessiz yanlış davranış olarak üretiyor.

#### Sonraki Adım

Bu düzende birimler ayrıldı, ama ayrılmanın bedeli hâlâ ödenmedi: hepsi aynı süreçte
koşuyor, `gonder` bir işlev çağrısı, ileti bir nesne. Bir birim çökerse hepsi çöker, bir
birim yeniden yayına çıkarsa hepsi çıkar. Sonraki ders aynı iş akışını **gerçekten ayrı
süreçlere** böler: her birim kendi bağlantı noktasında dinler, iletiler ağ üzerinden gider.
Ölçülecek şeyler süreç sayısı, uç sayısı, istek başına ağ atlaması, zincir gecikmesi, bir
değişikliğin dokunduğu dağıtım birimi ve sistemi ayağa kaldırma adımıdır.

### Mikroservisler

Source: https://academia.sh/tr/kurslar/servis-mimarileri/mikroservisler

Aynı ödünç iş akışının gerçekten ayrı süreçlere bölünmesi: beş süreç, sekiz uç, istek başına dokuz ağ atlaması, zincir gecikmesi; tek bir dağıtım biriminin diğerleri durmadan yeniden yayına çıkması ve bir sürecin düşmesiyle doğan yarım kalmış iş akışı.

Önceki derste birimler ayrıldı ama aynı süreçte kaldı: `gonder` bir işlev çağrısıydı, ileti
bir nesneydi. Bu ders aynı ödünç iş akışını **gerçekten ayrı süreçlere** böler. Her birim
kendi bağlantı noktasında dinler, kendi verisini tutar, kendi başına başlatılır ve
durdurulur. Her birimin kendi dağıtım birimi olduğu bu düzene **mikroservis** (microservice)
denir; biçemin tanımı Mimari Biçemler kursunda yapıldı, burada ölçülen şey bölmenin koşumda
ve kodda görünen bedelidir.

**MS5.** Beş süreç aynı makinede, geri döngü arayüzü üzerinden konuşur. Gerçek bir ağın
gecikmesi, paket kaybı ve bölünmesi bu düzenekte yoktur. Modelin taşıdığı özellikler
şunlardır: çağrı **süreç sınırını** geçer, serileştirilir, hedef süreç bağımsız olarak
durabilir ve bağımsız olarak yeniden başlatılabilir.

#### Beş Süreç

Her servis bağlantı noktasını komut satırından alır; komşularının adresini ortam
değişkeninden okur. Durum değiştiren uçlar `POST` ile çağrılır.

```js
// katalog-servisi.mjs — kitap birimi; kendi surecinde, kendi verisiyle
import { createServer } from "node:http";
const port = Number(process.argv[2]);
if (!port) { console.log("kullanim: node katalog-servisi.mjs <port>"); process.exit(0); }
const kitap = { 1: { durum: "rafta" }, 2: { durum: "rafta" }, 3: { durum: "rafta" } };
createServer((istek, yanit) => {
  const u = new URL(istek.url, "http://y");
  const no = u.searchParams.get("kitapNo");
  if (u.pathname === "/durum") return yanit.end(JSON.stringify({ durum: kitap[no].durum }));
  if (u.pathname === "/odunc-isaretle") { kitap[no].durum = "odunc"; return yanit.end("{}"); }
  yanit.statusCode = 404;
  yanit.end("{}");
}).listen(port, () => console.log(`katalog ${port}`));
```

```js
// uyelik-servisi.mjs — uye birimi; kendi surecinde, kendi verisiyle
import { createServer } from "node:http";
const port = Number(process.argv[2]);
if (!port) { console.log("kullanim: node uyelik-servisi.mjs <port>"); process.exit(0); }
const uye = { 4: { ad: "Emre", acikOdunc: 1, sinir: 5, borc: 0 },
              5: { ad: "Selin", acikOdunc: 3, sinir: 3, borc: 0 } };
createServer((istek, yanit) => {
  const u = new URL(istek.url, "http://y");
  const k = uye[u.searchParams.get("uyeNo")];
  if (k && u.pathname === "/getir") return yanit.end(JSON.stringify(k));
  if (k && u.pathname === "/odunc-ekle") { k.acikOdunc += 1; return yanit.end("{}"); }
  if (k && u.pathname === "/borc-ekle") {
    k.borc += Number(u.searchParams.get("tutar"));
    return yanit.end("{}");
  }
  yanit.statusCode = 404;
  yanit.end("{}");
}).listen(port, () => console.log(`uyelik ${port}`));
```

```js
// ucret-servisi.mjs — gecikme ucreti birimi; gunluk oran burada
import { createServer } from "node:http";
const port = Number(process.argv[2]);
if (!port) { console.log("kullanim: node ucret-servisi.mjs <port>"); process.exit(0); }
const GUNLUK = 2;
createServer((istek, yanit) => {
  const u = new URL(istek.url, "http://y");
  if (u.pathname !== "/hesapla") { yanit.statusCode = 404; return yanit.end("{}"); }
  const gun = Number(u.searchParams.get("gun"));
  yanit.end(JSON.stringify({ ucret: gun > 0 ? gun * GUNLUK : 0 }));
}).listen(port, () => console.log(`ucret ${port}`));
```

```js
// bildirim-servisi.mjs — bildirim birimi; uye adini uyelik surecinden ister
import { createServer } from "node:http";
const port = Number(process.argv[2]);
if (!port) { console.log("kullanim: node bildirim-servisi.mjs <port>"); process.exit(0); }
createServer(async (istek, yanit) => {
  const u = new URL(istek.url, "http://y");
  if (u.pathname !== "/gonder") { yanit.statusCode = 404; return yanit.end("{}"); }
  const y = await fetch(`http://127.0.0.1:${process.env.UYELIK}/getir?uyeNo=${u.searchParams.get("uyeNo")}`);
  console.log(`bildirim -> ${(await y.json()).ad}: ${u.searchParams.get("metin")}`);
  yanit.end("{}");
}).listen(port, () => console.log(`bildirim ${port}`));
```

Ödünç servisi, üçüncü dersteki `odunc.mjs` ile aynı sırayı yürütür. Tek fark, her adımın
artık bir ağ çağrısı olmasıdır. Yaptığı atlamaları sayar ve zincirin süresini kendi
günlüğüne yazar.

```js
// odunc-servisi.mjs — is akisini yuruten surec; digerlerini ag uzerinden cagirir
import { createServer } from "node:http";
const port = Number(process.argv[2]);
if (!port) { console.log("kullanim: node odunc-servisi.mjs <port>"); process.exit(0); }
const A = { katalog: process.env.KATALOG, uyelik: process.env.UYELIK,
            ucret: process.env.UCRET, bildirim: process.env.BILDIRIM };
let atlama = 0;
const cagir = async (hedef, yol, yontem = "GET") => {
  atlama += 1;
  return (await fetch(`http://127.0.0.1:${A[hedef]}${yol}`, { method: yontem })).json();
};
createServer(async (istek, yanit) => {
  const u = new URL(istek.url, "http://y");
  if (u.pathname !== "/odunc") { yanit.statusCode = 404; return yanit.end("{}"); }
  const uyeNo = u.searchParams.get("uyeNo"), kitapNo = u.searchParams.get("kitapNo");
  const gun = u.searchParams.get("gun") ?? 0;
  atlama = 0;
  const t0 = performance.now();
  try {
    const uye = await cagir("uyelik", `/getir?uyeNo=${uyeNo}`);
    if (uye.acikOdunc >= uye.sinir) throw new Error("odunc siniri asildi");
    if ((await cagir("katalog", `/durum?kitapNo=${kitapNo}`)).durum !== "rafta")
      throw new Error("kitap rafta degil");
    await cagir("katalog", `/odunc-isaretle?kitapNo=${kitapNo}`, "POST");
    await cagir("uyelik", `/odunc-ekle?uyeNo=${uyeNo}`, "POST");
    const { ucret } = await cagir("ucret", `/hesapla?gun=${gun}`);
    if (ucret > 0) await cagir("uyelik", `/borc-ekle?uyeNo=${uyeNo}&tutar=${ucret}`, "POST");
    await cagir("bildirim", `/gonder?uyeNo=${uyeNo}&metin=kitap+${kitapNo}`, "POST");
    console.log(`odunc ${kitapNo}/${uyeNo}: atlama ${atlama}, sure ${Math.round(performance.now() - t0)} ms`);
    yanit.end(JSON.stringify({ durum: "verildi", ucret, atlama }));
  } catch (h) {
    console.log(`odunc ${kitapNo}/${uyeNo}: HATA ${h.message}, atlama ${atlama}`);
    yanit.statusCode = 400;
    yanit.end(JSON.stringify({ durum: "basarisiz", hata: h.message, atlama }));
  }
}).listen(port, () => console.log(`odunc ${port}`));
```

#### Ayağa Kaldırma

```sh
# baslat.sh — bes surec ayaga kalkar; her surecin ciktisi kendi gunlugune yazilir
rm -f pid.txt
node katalog-servisi.mjs 8792 >katalog.log 2>&1 & echo "katalog $!" >>pid.txt
node uyelik-servisi.mjs 8793 >uyelik.log 2>&1 & echo "uyelik $!" >>pid.txt
node ucret-servisi.mjs 8794 >ucret.log 2>&1 & echo "ucret $!" >>pid.txt
UYELIK=8793 node bildirim-servisi.mjs 8795 >bildirim.log 2>&1 & echo "bildirim $!" >>pid.txt
KATALOG=8792 UYELIK=8793 UCRET=8794 BILDIRIM=8795 \
  node odunc-servisi.mjs 8791 >odunc.log 2>&1 & echo "odunc $!" >>pid.txt
for p in 8791 8792 8793 8794 8795; do
  for i in $(seq 40); do curl -s -o /dev/null "http://127.0.0.1:$p/" && break; sleep 0.2; done
done
echo "ayaga kalkan surec: $(wc -l <pid.txt | tr -d ' ')   baslatma adimi: 5 arka plan + 1 hazirlik beklemesi"
echo "uc: $(grep -oh '"/[a-z-]*"' ./*.mjs | sort -u | wc -l | tr -d ' ')   dagitim birimi: 5"
grep -oh '"/[a-z-]*"' ./*.mjs | sort -u | tr -d '"' | tr '\n' ' '
echo
```

```
ayaga kalkan surec: 5   baslatma adimi: 5 arka plan + 1 hazirlik beklemesi
uc: 8   dagitim birimi: 5
/borc-ekle /durum /getir /gonder /hesapla /odunc /odunc-ekle /odunc-isaretle
```

Birinci derste bu sayı birdi: `node uygulama.mjs`. Burada beş arka plan süreci ve bir
hazırlık beklemesi var; bağlantı noktaları ve komşu adresleri dışarıdan verilmek zorunda.

#### İstek Başına Atlama

```sh
curl -s "http://127.0.0.1:8791/odunc?kitapNo=1&uyeNo=4&gun=3"
echo
curl -s "http://127.0.0.1:8791/odunc?kitapNo=2&uyeNo=5"
echo
```

```
{"durum":"verildi","ucret":6,"atlama":7}
{"durum":"basarisiz","hata":"odunc siniri asildi","atlama":1}
```

Ödünç servisi 7 çağrı yaptı. Bildirim servisi üyenin adını almak için bir çağrı daha yaptı;
istemcinin ödünç servisine gönderdiği istekle birlikte **istek başına toplam 9 ağ atlaması**
oluyor. Üçüncü derste aynı iş akışı 8 ileti üretmişti — ileti sayısı değişmedi, her ileti
süreç sınırını geçer oldu.

Zincir sıralı ilerliyor: her çağrı bir öncekinin dönüşünü bekliyor, yani gecikmeler
toplanıyor. Ödünç servisi `odunc.log` dosyasına her istek için bir satır yazar; birinci
isteğin satırı bu koşumda `odunc 1/4: atlama 7, sure 21 ms` oldu.
Süre alanı makineye, çekirdeğe ve o andaki
yüke bağlıdır — yukarıdaki değer bu koşumda okunmuştur ve her koşumda değişir. Koşumdan
bağımsız nicelik **7 sıralı turdur** ve bu sayı ancak zincir kısaltılarak düşer. İkinci
istek üyelik denetiminde durduğu için tek atlamayla bitti: hata erken çıktığında zincirin
geri kalanı hiç kurulmuyor.

#### Bağımsız Dağıtım

Asıl kazanç şurada ölçülür: ücret oranı değişsin.

```sh
# yeniden.sh — yalniz ucret sureci degisir ve yeniden baslatilir; digerleri hic durmaz
kill "$(awk '$1 == "ucret" { print $2 }' pid.txt)"
sed -i.y 's/const GUNLUK = 2;/const GUNLUK = 5;/' ucret-servisi.mjs
rm -f ucret-servisi.mjs.y
node ucret-servisi.mjs 8794 >>ucret.log 2>&1 & echo "ucret $!" >>pid.txt
for i in $(seq 40); do curl -s -o /dev/null "http://127.0.0.1:8794/" && break; sleep 0.2; done
echo "yeniden baslatilan surec: 1 / 5   dokunulan dagitim birimi: 1"
curl -s "http://127.0.0.1:8791/odunc?kitapNo=2&uyeNo=4&gun=3"
echo
```

```
yeniden baslatilan surec: 1 / 5   dokunulan dagitim birimi: 1
{"durum":"verildi","ucret":15,"atlama":7}
```

Yeni oran yürürlükte. Katalog, üyelik, bildirim ve ödünç süreçleri hiç durmadı; bellekteki
durumlarını korudular. Birinci derste aynı tek satırlık değişiklik yedi modülü birlikte
yeniden yayına çıkarıyordu; burada bir dağıtım birimi yeniden başladı.

Bu kazanç sınırsız değil. Kanonik `acikOdunc` alanı iki dağıtım biriminde geçiyor — üyelik
ve ödünç. Onun yeniden adlandırılması, üçüncü derste tek birim içinde 3 dosyaya dokunan bir
değişikken, burada **iki ayrı dağıtım biriminin uyumlu bir sırayla yayına çıkmasını**
gerektirir. Bağımsız dağıtım, sözleşmenin değişmediği değişiklikler için bağımsızdır.

#### Kısmi Hata

```sh
# arizali.sh — bildirim sureci durur; ayni istek yolun sonunda kirilir
kill "$(awk '$1 == "bildirim" { print $2 }' pid.txt)"
sleep 0.5
curl -s "http://127.0.0.1:8791/odunc?kitapNo=3&uyeNo=4"
echo
curl -s "http://127.0.0.1:8792/durum?kitapNo=3"
echo
curl -s "http://127.0.0.1:8793/getir?uyeNo=4"
echo
```

```
{"durum":"basarisiz","hata":"fetch failed","atlama":6}
{"durum":"odunc"}
{"ad":"Emre","acikOdunc":4,"sinir":5,"borc":21}
```

İstek başarısız döndü, ama kitap ödünçte ve üyenin açık ödünç sayısı arttı. Altıncı atlamada
kırılan zincir, ilk beş atlamanın etkisini geri almadı. Tek parçada bu durum yoktu: bir
istisna bütün gövdeyi terk ediyor, hiçbir modül yarım bir işi kesinleştirmiş olmuyordu.

Aynı çıktının ikinci satırı başka bir şey söylüyor: katalog servisi hâlâ cevap veriyor.
Bildirim süreci düştüğünde sistemin tamamı durmadı — bildirime dokunmayan yollar çalışmaya
devam eder. Arıza artık **kısmi**dir; bu hem kazanç hem de yeni bir sorun, çünkü sistemin
durumu "ayakta" ve "düşük" arasında bir yerdedir.

```sh
# durdur.sh — butun surecleri durdurur
while read -r ad p; do kill "$p" 2>/dev/null; done <pid.txt
echo "durduruldu"
```

```
durduruldu
```

#### Üç Sütun

| | Mikroservisler |
|---|---|
| Ucuzlattığı | Bir oran değişikliği 5 dağıtım biriminin 1'ini yeniden başlattı; her birim kendi verisine sahip, ölçekleme birimi tek servis |
| Pahalılaştırdığı | İstek başına 9 ağ atlaması ve 7 sıralı tur; başlatma 1 adımdan 6 adıma çıktı; adresler dışarıdan verilmek zorunda |
| Doğan arıza kipi | Zincirin ortasında kırılan istek yarım kalmış durum bırakıyor; arıza kısmi, sistem ne tam ayakta ne tam düşük |

#### Özet

- Aynı ödünç iş akışı 5 ayrı sürece bölündü: 8 uç, 5 dağıtım birimi, istek başına 9 ağ
  atlaması, 7 sıralı tur.
- İleti sayısı üçüncü derse göre değişmedi; değişen şey her iletinin süreç sınırını geçmesi
  ve sıralı turların gecikmelerinin toplanmasıdır.
- Ayağa kaldırma 1 adımdan 6 adıma çıktı; her sürecin bağlantı noktası ve komşu adresleri
  dışarıdan verilmek zorunda.
- Ücret oranı değişikliği yalnız 1 dağıtım birimini yeniden başlattı; diğer dört süreç hiç
  durmadı. Sözleşmeye dokunan bir değişiklik ise iki birimi uyumlu sırayla yayına zorlar.
- Bildirim süreci düştüğünde istek başarısız döndü, ama kitap ödünçte ve üyenin sayacı artmış
  kaldı: zincirin ortasında kırılan iş akışı yarım bir durum bırakıyor.

#### Sonraki Adım

Bu beş süreç uzun ömürlüdür: istek gelsin gelmesin ayakta durur, durumlarını bellekte tutar
ve bir kez başlatılıp bırakılır. Sonraki ders dağıtım birimini bir adım daha küçültür ve
uzun ömürlülüğü kaldırır: birim, yalnız bir olay tetiklediğinde var olan bir işlevdir.
Ölçülecek şeyler, iki çağrı arasında bellekte hiçbir şey tutulamamasının getirdiği ek
çağrılar, işlev başına konan süre sınırının iş akışını bölmesi ve süreç içinde kalmış son
yerel çağrıların da ağ çağrısına dönüşmesidir.

### Sunucusuz Yaklaşım

Source: https://academia.sh/tr/kurslar/servis-mimarileri/sunucusuz-yaklasim

Olay tetiklemeli işlev modelinin kısıtlarının ölçülmesi: iki çağrı arasında bellekte hiçbir şey tutulamamasının getirdiği ek depo çağrıları, çağrı başına konan süre sınırının iş akışını bölmesi ve süreç içinde kalmış son yerel çağrıların da dış çağrıya dönüşmesi.

Önceki dersin beş süreci uzun ömürlüydü: bir kez başlatılıyor, istek gelsin gelmesin ayakta
duruyor ve üye kayıtlarını kendi belleğinde tutuyordu. Bu ders dağıtım birimini bir adım daha
küçültür ve uzun ömürlülüğü kaldırır. Birim artık bir süreç değil, **yalnız bir olay
tetiklediğinde var olan bir işlevdir**.

Bu düzenin adı **sunucusuz** (serverless). Biçemin tanımı ve ilk kurulum maliyeti Mimari Biçemler
kursunda ele alındı; burada ölçülen şey işlev modelinin **çalışma kısıtlarıdır**: iki çağrı
arasında bellekte hiçbir şey kalmaması, çağrı başına bir süre sınırı bulunması ve süreç
içinde kalmış son yerel çağrıların da dışarı çıkması.

**MS6.** İşlev çalıştırıcısı yerel bir modeldir: her olay için `node` ile yeni bir süreç
başlatır ve o sürece sabit bir süre sınırı uygular. Modelin taşıdığı üç özellik şudur —
çağrılar arasında bellek paylaşılmaz, aşılan süre sınırı işi ortasında keser, işlev dışındaki
her şeye ağ üzerinden erişilir.

#### Çalıştırıcı ve Depo

Durum bir yerde durmak zorunda. İşlevler tutamadığı için, tek uzun ömürlü süreç bir anahtar
deposudur.

```js
// depo-servisi.mjs — islevler arasinda durum tutan tek uzun omurlu surec
import { createServer } from "node:http";
const port = Number(process.argv[2]);
if (!port) { console.log("kullanim: node depo-servisi.mjs <port>"); process.exit(0); }
const kutu = { "kitap:1": { durum: "rafta" },
               "uye:4": { ad: "Emre", acikOdunc: 1, sinir: 5, borc: 0 },
               gecikme: ["g1", "g2", "g3"], islenen: [] };
let cagri = 0;
const kez = {};
createServer(async (istek, yanit) => {
  const u = new URL(istek.url, "http://y");
  const a = u.searchParams.get("a");
  const say = () => { cagri += 1; kez[a] = (kez[a] ?? 0) + 1; };
  if (u.pathname === "/al") { say(); return yanit.end(JSON.stringify(kutu[a] ?? null)); }
  if (u.pathname === "/koy") {
    say();
    let g = "";
    for await (const p of istek) g += p;
    kutu[a] = JSON.parse(g);
    return yanit.end("{}");
  }
  if (u.pathname === "/sayac") {
    const y = { cagri, uye: kez["uye:4"] ?? 0 };
    if (u.searchParams.get("sifirla")) { cagri = 0; for (const k in kez) delete kez[k]; }
    return yanit.end(JSON.stringify(y));
  }
  yanit.statusCode = 404;
  yanit.end("{}");
}).listen(port, () => console.log(`depo ${port}`));
```

```js
// calistirici.mjs — islev cagirici: her olay icin yeni surec, her cagriya sure siniri
import { spawnSync } from "node:child_process";
export const SINIR_MS = 400;
export function cagir(islev, ...olay) {
  const s = spawnSync("node", [islev, ...olay], { encoding: "utf8", timeout: SINIR_MS });
  return { kesildi: s.signal !== null, cikti: s.stdout.trim() };
}
```

#### Dört İşlev

Her işlev olayı komut satırından alır, işini yapar, sonucu basar ve ölür. Hiçbiri bir
sonraki çağrıya bir şey bırakamaz.

```js
// islev-uygunluk.mjs — olay tetiklemeli islev; her cagrida bellegi bos baslar
const D = process.env.DEPO;
if (!D) { console.log("DEPO ortam degiskeni gerekli"); process.exit(0); }
const uye = await (await fetch(`http://127.0.0.1:${D}/al?a=uye:${process.argv[2]}`)).json();
console.log(JSON.stringify({ uygun: uye.acikOdunc < uye.sinir }));
```

```js
// islev-isaretle.mjs — kitabi odunc isaretler, uyenin sayacini artirir
const D = process.env.DEPO;
if (!D) { console.log("DEPO ortam degiskeni gerekli"); process.exit(0); }
const [kitapNo, uyeNo] = process.argv.slice(2);
const yaz = (a, d) => fetch(`http://127.0.0.1:${D}/koy?a=${a}`, { method: "POST", body: JSON.stringify(d) });
const kitap = await (await fetch(`http://127.0.0.1:${D}/al?a=kitap:${kitapNo}`)).json();
if (kitap.durum !== "rafta") { console.log(JSON.stringify({ hata: "kitap rafta degil" })); process.exit(0); }
await yaz(`kitap:${kitapNo}`, { durum: "odunc" });
const uye = await (await fetch(`http://127.0.0.1:${D}/al?a=uye:${uyeNo}`)).json();
uye.acikOdunc += 1;
await yaz(`uye:${uyeNo}`, uye);
console.log(JSON.stringify({ durum: "odunc" }));
```

```js
// islev-ucret.mjs — gecikme ucretini hesaplar ve uyenin borcuna yazar
const D = process.env.DEPO;
if (!D) { console.log("DEPO ortam degiskeni gerekli"); process.exit(0); }
const [uyeNo, gun] = process.argv.slice(2).map(Number);
const ucret = gun > 0 ? gun * 2 : 0;
if (ucret > 0) {
  const uye = await (await fetch(`http://127.0.0.1:${D}/al?a=uye:${uyeNo}`)).json();
  uye.borc += ucret;
  await fetch(`http://127.0.0.1:${D}/koy?a=uye:${uyeNo}`, { method: "POST", body: JSON.stringify(uye) });
}
console.log(JSON.stringify({ ucret }));
```

```js
// islev-bildirim.mjs — uyenin adini depodan okur ve bildirimi basar
const D = process.env.DEPO;
if (!D) { console.log("DEPO ortam degiskeni gerekli"); process.exit(0); }
const [uyeNo, metin] = process.argv.slice(2);
const uye = await (await fetch(`http://127.0.0.1:${D}/al?a=uye:${uyeNo}`)).json();
console.log(`bildirim -> ${uye.ad}: ${metin}`);
```

```js
// akis.mjs — odunc is akisi dort islev cagrisina bolunur; her cagri yeni bir surec
import { cagir } from "./calistirici.mjs";
const D = process.env.DEPO;
if (!D) { console.log("DEPO ortam degiskeni gerekli"); process.exit(0); }
const sayac = async () => (await (await fetch(`http://127.0.0.1:${D}/sayac?sifirla=1`)).json());
await sayac();
const [kitapNo, uyeNo, gun] = ["1", "4", "3"];
let islev = 0;
const c = (ad, ...olay) => { islev += 1; return JSON.parse(cagir(ad, ...olay).cikti || "null"); };
const u = c("islev-uygunluk.mjs", uyeNo);
if (u.uygun) {
  c("islev-isaretle.mjs", kitapNo, uyeNo);
  const { ucret } = c("islev-ucret.mjs", uyeNo, gun);
  islev += 1;
  console.log(cagir("islev-bildirim.mjs", uyeNo, `kitap ${kitapNo}`).cikti);
  console.log(`odunc verildi: kitap ${kitapNo} -> uye ${uyeNo}, ucret ${ucret}`);
}
const s = await sayac();
console.log(`islev cagrisi: ${islev}   yeni surec: ${islev}   depo cagrisi: ${s.cagri}   uye:4 erisimi: ${s.uye}`);
```

```sh
# baslat.sh — durumu tutan tek uzun omurlu surec ayaga kalkar, sonra is akisi kosar
node depo-servisi.mjs 8796 >depo.log 2>&1 & echo $! >depo.pid
for i in $(seq 40); do curl -s -o /dev/null "http://127.0.0.1:8796/" && break; sleep 0.2; done
echo "uzun omurlu surec: 1 (depo)"
DEPO=8796 node akis.mjs
```

```
uzun omurlu surec: 1 (depo)
bildirim -> Emre: kitap 1
odunc verildi: kitap 1 -> uye 4, ucret 6
islev cagrisi: 4   yeni surec: 4   depo cagrisi: 8   uye:4 erisimi: 6
```

#### Durum Tutamamanın Bedeli

Son satır üç sayı taşıyor.

**Yeni süreç: 4.** Önceki derste bir ödünç isteği hiçbir süreç başlatmıyordu; beş süreç zaten
ayaktaydı ve isteği karşılıyordu. Burada her istek dört süreç doğurur ve dördü de biter. Bu,
dağıtım biriminin çağrı süresince var olmasının doğrudan sonucudur.

**Depo çağrısı: 8.** Mikroservis düzeninde üye kaydı üyelik sürecinin belleğindeydi; kayda
erişim süreç içi bir nesne okumasıydı. Burada hiçbir işlev kaydı taşıyamaz, bu yüzden
her işlev kendi çağrısında onu depodan çekmek zorundadır.

**`uye:4` erişimi: 6.** Aynı üye kaydı bir ödünç iş akışı içinde altı kez ağdan geçti:
uygunluk okudu, işaretle okuyup yazdı, ücret okuyup yazdı, bildirim okudu. Önceki derste
üyelik servisine yapılan çağrı üçtü ve üçü de bellekteki tek bir nesne üzerinde çalışıyordu.
Fark, işlev modelinin en somut maliyetidir: **bellekte tutulamayan her şey ağdan geçer.**

Buna bağlı ikinci bir sonuç var. Önceki derste `ucret-servisi` gecikme oranını kendi
belleğinde tutuyor ve hesabı süreç içinde yapıyordu; burada da hesap süreç içinde yapılıyor,
ama sonucu yazmak için üye kaydını çekmek ve geri yazmak gerekiyor. Süreç içinde kalmış son
yerel adımlar da dış çağrıya dönüştü.

#### Süre Sınırı

İkinci kısıt, çağrı başına konan süre sınırıdır. Gecikmiş üç kitabın bildirimini üreten bir
iş, kayıt başına 300 ms sürüyor; sınır 400 ms.

```js
// islev-gecikme.mjs — gecikme bildirimlerini tek cagrida sirayla isler
const D = process.env.DEPO;
if (!D) { console.log("DEPO ortam degiskeni gerekli"); process.exit(0); }
const kayitlar = await (await fetch(`http://127.0.0.1:${D}/al?a=gecikme`)).json();
const bitti = [];
for (const k of kayitlar) {
  await new Promise((c) => setTimeout(c, 300));
  bitti.push(k);
  await fetch(`http://127.0.0.1:${D}/koy?a=islenen`, { method: "POST", body: JSON.stringify(bitti) });
}
console.log(JSON.stringify({ islenen: bitti.length }));
```

```js
// islev-gecikme-tek.mjs — tek kayit isler; durum cagrilar arasinda depoda tutulur
const D = process.env.DEPO;
if (!D) { console.log("DEPO ortam degiskeni gerekli"); process.exit(0); }
const kayitlar = await (await fetch(`http://127.0.0.1:${D}/al?a=gecikme`)).json();
await new Promise((c) => setTimeout(c, 300));
const bitti = await (await fetch(`http://127.0.0.1:${D}/al?a=islenen`)).json();
bitti.push(kayitlar[Number(process.argv[2])]);
await fetch(`http://127.0.0.1:${D}/koy?a=islenen`, { method: "POST", body: JSON.stringify(bitti) });
console.log(JSON.stringify({ islenen: bitti.length }));
```

```js
// sure.mjs — ayni is once tek cagrida, sonra cagrilara bolunerek kosturulur
import { cagir, SINIR_MS } from "./calistirici.mjs";
const D = process.env.DEPO;
if (!D) { console.log("DEPO ortam degiskeni gerekli"); process.exit(0); }
const gec = (yol) => fetch(`http://127.0.0.1:${D}${yol}`);
const sifirla = () => fetch(`http://127.0.0.1:${D}/koy?a=islenen`, { method: "POST", body: "[]" });
const say = async () => (await (await gec("/sayac?sifirla=1")).json()).cagri;
const islenen = async () => (await (await gec("/al?a=islenen")).json()).length;

await sifirla(); await say();
const tek = cagir("islev-gecikme.mjs");
console.log(`tek cagri : kesildi=${tek.kesildi}  depo cagrisi=${await say()}  islenen=${await islenen()}/3  sinir=${SINIR_MS} ms`);

await sifirla(); await say();
let kesilen = 0;
for (const i of ["0", "1", "2"]) if (cagir("islev-gecikme-tek.mjs", i).kesildi) kesilen += 1;
console.log(`bolunmus  : cagri=3  kesilen=${kesilen}  depo cagrisi=${await say()}  islenen=${await islenen()}/3`);
```

```sh
DEPO=8796 node sure.mjs
```

```
tek cagri : kesildi=true  depo cagrisi=2  islenen=1/3  sinir=400 ms
bolunmus  : cagri=3  kesilen=0  depo cagrisi=9  islenen=3/3
```

Tek çağrıda iş yarım kaldı: üç kayıttan biri işlendi ve süreç ortasından kesildi. Kesilme bir
hata değil, çalıştırıcının kuralıdır — kod hatasız, iş eksik. Burada dikkat çeken şey,
kesilen çağrının **kısmi etki bırakmış** olmasıdır: bir kayıt depoya yazılmıştı.

Bölünmüş sürümde aynı iş üç çağrıya dağıldı ve tamamlandı. Bedeli depo çağrısı sayısında
görünüyor: 2'den 9'a çıktı. Sebebi, işin durumunun artık çağrılar arasında taşınamamasıdır —
her çağrı hem girdi listesini hem de o ana kadar işlenenleri yeniden okumak zorunda. Süre
sınırı iş akışını bölünce, bölünmenin maliyetini durum taşıma ödedi.

Bu sayılar iki nedenle ortama görece dayanıklıdır: kayıt başına iş 300 ms, sınır 400 ms
olarak seçildi; koşumdan bağımsız nicelikler kesilen çağrı sayısı, işlenen kayıt sayısı ve
depo çağrısı sayısıdır.

```sh
# durdur.sh — depo surecini durdurur
kill "$(cat depo.pid)" 2>/dev/null
echo "durduruldu"
```

```
durduruldu
```

#### Üç Sütun

| | Sunucusuz |
|---|---|
| Ucuzlattığı | Dağıtım birimi tek dosyaya indi; ayakta tutulan süreç 5'ten 1'e düştü, işlev çağrı dışında hiç var olmuyor |
| Pahalılaştırdığı | İstek başına 4 yeni süreç; aynı üye kaydına 6 ağ erişimi; iş bölününce depo çağrısı 2'den 9'a çıktı |
| Doğan arıza kipi | Süre sınırı işi ortasından keser: kod hatasız, sonuç eksik ve kısmi etki depoda kalmış olur |

#### Özet

- İşlev modelinde dağıtım birimi çağrı süresince var olur: bir ödünç iş akışı 4 işlev çağrısı
  ve 4 yeni süreç üretti; önceki derste bu sayı sıfırdı.
- İki çağrı arasında bellekte hiçbir şey kalmadığı için durum depoya taşındı; iş akışı 8 depo
  çağrısı yaptı ve tek bir üye kaydına 6 kez ağdan erişildi.
- Süreç içinde kalmış son yerel adımlar da dış çağrıya dönüştü; hesap süreç içinde yapılsa
  bile sonucu yazmak okuma ve yazma gerektiriyor.
- Çağrı başına süre sınırı işi ortasından kesti: üç kayıttan biri işlendi, kesilen çağrı geride
  kısmi etki bıraktı.
- İşi çağrılara bölmek sınırı aştırmadı, ama durum taşıma maliyetini doğurdu: depo çağrısı
  2'den 9'a çıktı.

#### Sonraki Adım

Bu noktada aynı ödünç iş akışı beş biçimde kuruldu ve her biri kendi üç sütununu yazdı. Geriye
tek bir soru kalıyor: elde çalışan bir tek parça varken bu biçimlerden birine **nasıl**
geçilir. Sonraki ders geçişin kendisini ele alır: yolun aşamalı olarak ayrıştırılması, boğucu
incir kalıbıyla eski ve yeni yolun bir süre birlikte koşması ve bu sürenin kod tarafındaki
bedeli — iki kod tabanında tutulan ortak mantık, çift bakım maliyeti ve geri alınabilirlik.

### Mimari Geçiş Stratejileri

Source: https://academia.sh/tr/kurslar/servis-mimarileri/mimari-gecis-stratejileri

Çalışan bir tek parçadan aşamalı ayrıştırmaya geçiş: boğucu incir kalıbının kod tarafındaki ölçüsü — iki kod tabanında tutulan ortak mantık, tek bir oran değişikliğinin iki dosyaya dokunması, yolların ayrışması ve yönlendirme anahtarıyla tek adımda geri alınabilirlik.

Beş biçim kuruldu ve her biri kendi üç sütununu yazdı. Elde çalışan bir tek parça varken bu
biçimlerden birine nasıl geçileceği ise hiç ele alınmadı. Bu ders geçişin kendisini ölçer.

Tek seferde bütün sistemi yeni bir biçime taşımak, ölçülen bütün maliyetleri aynı anda
üstlenmek demektir. Bunun yerine yol yol ayrıştırılır: bir iş akışının tek bir adımı yeni
bir birime taşınır, geri kalan tek parçada kalır ve trafiğin hangi yola gideceğine bir
anahtar karar verir. Taşınan yollar arttıkça eski gövde küçülür. Bu düzene **boğucu incir
geçişi** (strangler fig) denir; Vaka Çalışmaları kursunda trafiğin ölçüldüğü bu kalıbın
burada ölçülen yanı **kod tarafıdır**.

**MS7.** Yeni yol tek bir adımı taşır: gecikme ücreti hesabı. Ödünç iş akışının geri kalanı
tek parçada kalır. Ölçülen şey trafik payı değil, aynı mantığın iki kod tabanında tutulmasının
bedelidir.

#### Anahtar

Eski yol, birinci dersteki ücret kuralının kendisidir.

```js
// tekparca/ucret.mjs — eski yoldaki ucret kurali
// KURAL BASI
export const GUNLUK = 2;
export const ucret = (gun) => (gun > 0 ? gun * GUNLUK : 0);
// KURAL SONU
```

Yönlendirici iş akışıyla ücret adımı arasına girer. Anahtar kapalıyken çağrı yerel kalır,
açıkken ağ üzerinden yeni birime gider.

```js
// tekparca/yonlendirici.mjs — bogucu incir anahtari; adim eski ya da yeni yola gider
import { ucret as eskiUcret } from "./ucret.mjs";
export async function ucretHesapla(gun) {
  const p = process.env.YENI_UCRET;
  if (!p) return { ucret: eskiUcret(gun), yol: "eski" };
  return (await fetch(`http://127.0.0.1:${p}/hesapla?gun=${gun}`)).json();
}
```

```js
// tekparca/uygulama.mjs — odunc is akisi; yalniz ucret adimi yonlendiriciden gecer
import { ucretHesapla } from "./yonlendirici.mjs";
const durum = { kitap: "rafta", acikOdunc: 1, sinir: 5 };
const gun = Number(process.argv[2] ?? 3);
if (durum.acikOdunc >= durum.sinir) { console.log("odunc siniri asildi"); process.exit(0); }
durum.kitap = "odunc";
durum.acikOdunc += 1;
const { ucret, yol } = await ucretHesapla(gun);
console.log(`odunc verildi: kitap 1 -> uye 4, gecikme ${gun} gun, ucret ${ucret} (ucret yolu: ${yol})`);
```

Yeni birim, kuralı kendi kod tabanında taşır. Taşımak zorundadır: ayrı bir dağıtım birimi
olması, tek parçanın dosyalarını ithal edememesi demektir.

```js
// ucret-servisi.mjs — yeni yol; ayni kural ikinci kod tabaninda
import { createServer } from "node:http";
const port = Number(process.argv[2]);
if (!port) { console.log("kullanim: node ucret-servisi.mjs <port>"); process.exit(0); }
// KURAL BASI
export const GUNLUK = 2;
export const ucret = (gun) => (gun > 0 ? gun * GUNLUK : 0);
// KURAL SONU
createServer((istek, yanit) => {
  const gun = Number(new URL(istek.url, "http://y").searchParams.get("gun"));
  yanit.end(JSON.stringify({ ucret: ucret(gun), yol: "yeni" }));
}).listen(port, () => console.log(`ucret ${port}`));
```

```sh
node tekparca/uygulama.mjs 3
```

```
odunc verildi: kitap 1 -> uye 4, gecikme 3 gun, ucret 6 (ucret yolu: eski)
```

```sh
# baslat.sh — yeni ucret sureci ayaga kalkar, ayni istek anahtar acikken kosar
node ucret-servisi.mjs 8797 >ucret.log 2>&1 & echo $! >ucret.pid
for i in $(seq 40); do curl -s -o /dev/null "http://127.0.0.1:8797/" && break; sleep 0.2; done
YENI_UCRET=8797 node tekparca/uygulama.mjs 3
```

```
odunc verildi: kitap 1 -> uye 4, gecikme 3 gun, ucret 6 (ucret yolu: yeni)
```

İki yol da aynı sonucu verdi. Geçişin istenen durumu budur: yeni yol açıldığında dışarıdan
hiçbir fark görünmez. Fark yalnız `ucret yolu` alanında.

#### İki Kod Tabanı

Ölçülecek şey bu iki koşumun eşitliğinde değil, o eşitliği sürdürmenin bedelinde.

```js
// cift.mjs — ayni is kuralinin iki kod tabaninda tutuldugunu olcer
import { readFileSync } from "node:fs";
const kural = (yol) => {
  const s = readFileSync(yol, "utf8").split("\n");
  return s.slice(s.indexOf("// KURAL BASI") + 1, s.indexOf("// KURAL SONU"))
    .map((x) => x.trim()).filter(Boolean);
};
const eski = kural("tekparca/ucret.mjs"), yeni = kural("ucret-servisi.mjs");
const ayni = eski.filter((s) => yeni.includes(s));
console.log(`kural satiri: eski ${eski.length}, yeni ${yeni.length}, birebir ayni ${ayni.length}`);
console.log(`ayni mantigin tutuldugu kod tabani: 2   bir oran degisiminde dokunulacak dosya: 2`);
console.log(`yonlendirme anahtari: YENI_UCRET   geri alma adimi: 1   yeniden dagitim: 0`);
```

```sh
node cift.mjs
```

```
kural satiri: eski 2, yeni 2, birebir ayni 2
ayni mantigin tutuldugu kod tabani: 2   bir oran degisiminde dokunulacak dosya: 2
yonlendirme anahtari: YENI_UCRET   geri alma adimi: 1   yeniden dagitim: 0
```

Kuralın iki satırı da iki kod tabanında birebir duruyor. Geçiş süresince bu bir seçim değil,
zorunluluktur: eski yol hâlâ trafik alıyorsa mantığı taşımak zorundadır, yeni yol da kendi
kopyasını taşımak zorundadır çünkü ötekinin dosyalarına erişemez.

#### Çift Bakım

Bedel, kural değiştiğinde ortaya çıkar. Günlük oran yalnız yeni kod tabanında güncellensin.

```sh
# ayrik.sh — oran yalniz yeni kod tabaninda degisir; iki yol ayrisir
kill "$(cat ucret.pid)"
sleep 0.3
sed -i.y 's/export const GUNLUK = 2;/export const GUNLUK = 5;/' ucret-servisi.mjs
rm -f ucret-servisi.mjs.y
node ucret-servisi.mjs 8797 >>ucret.log 2>&1 & echo $! >ucret.pid
for i in $(seq 40); do curl -s -o /dev/null "http://127.0.0.1:8797/" && break; sleep 0.2; done
YENI_UCRET=8797 node tekparca/uygulama.mjs 3
node tekparca/uygulama.mjs 3
```

```
odunc verildi: kitap 1 -> uye 4, gecikme 3 gun, ucret 15 (ucret yolu: yeni)
odunc verildi: kitap 1 -> uye 4, gecikme 3 gun, ucret 6 (ucret yolu: eski)
```

Aynı üye, aynı kitap, aynı gecikme — iki farklı ücret. Hangi tutarın uygulanacağını iş
kuralı değil, isteğin hangi yola düştüğü belirliyor. Bu, geçiş dönemine özgü bir arıza
kipidir ve hiçbir sürecin çökmesini gerektirmez: iki kod tabanı da çalışıyor, ikisi de
kendince doğru.

Ölçü şudur: geçiş süresince ücret kuralına dokunan her değişiklik **iki dosyaya** yazılmak
zorundadır. Bu maliyet taşınan yol sayısıyla değil, taşıma süresinin uzunluğuyla çarpılır.
Yarım kalmış bir geçiş, kalıcı olarak iki katı bakım demektir.

#### Geri Alınabilirlik

Buna karşılık kalıbın asıl kazancı da aynı yerdedir.

```sh
# geri-al.sh — yonlendirme anahtari kapatilir; yeni servis durdurulmaz
echo "anahtar kapali, yeniden dagitim yok"
node tekparca/uygulama.mjs 3
echo "yeni servis hala ayakta: $(curl -s 'http://127.0.0.1:8797/hesapla?gun=3')"
```

```
anahtar kapali, yeniden dagitim yok
odunc verildi: kitap 1 -> uye 4, gecikme 3 gun, ucret 6 (ucret yolu: eski)
yeni servis hala ayakta: {"ucret":15,"yol":"yeni"}
```

Eski davranış tek adımda geri geldi: bir ortam değişkeni verilmedi, o kadar. Yeni servis
durdurulmadı, hiçbir şey yeniden dağıtılmadı, hiçbir dosya değişmedi. Eski kod silinmiş
olsaydı bu adımın karşılığı eski kod tabanını yeniden yayına çıkarmak olurdu.

Geri alınabilirliğin bedeli, eski yolun **silinmemiş** olmasıdır. Geçişin bitmiş sayılması,
anahtarın kalıcı olarak açılması ve eski kuralın silinmesiyle olur; o ana kadar çift bakım
sürer. İki maliyet aynı madalyonun iki yüzüdür.

```sh
# esitle.sh — ayni degisiklik eski kod tabanina da tasinir; iki yol yeniden ayni
sed -i.y 's/export const GUNLUK = 2;/export const GUNLUK = 5;/' tekparca/ucret.mjs
rm -f tekparca/ucret.mjs.y
echo "dokunulan dosya: 2 (tekparca/ucret.mjs, ucret-servisi.mjs)"
YENI_UCRET=8797 node tekparca/uygulama.mjs 3
node tekparca/uygulama.mjs 3
kill "$(cat ucret.pid)" 2>/dev/null
```

```
dokunulan dosya: 2 (tekparca/ucret.mjs, ucret-servisi.mjs)
odunc verildi: kitap 1 -> uye 4, gecikme 3 gun, ucret 15 (ucret yolu: yeni)
odunc verildi: kitap 1 -> uye 4, gecikme 3 gun, ucret 15 (ucret yolu: eski)
```

#### Üç Sütun

| | Aşamalı geçiş |
|---|---|
| Ucuzlattığı | Geri alma 1 adım, yeniden dağıtım 0; yeni biçimin maliyeti tek yolda sınanıyor, sistemin tamamı taşınmıyor |
| Pahalılaştırdığı | Aynı kural 2 kod tabanında; her değişiklik 2 dosyaya yazılıyor; yönlendirici bir ek dolaylılık katmanı |
| Doğan arıza kipi | Yalnız bir kod tabanı güncellendiğinde aynı istek iki farklı sonuç veriyor; hiçbir süreç çökmüyor, iki yol da kendince doğru |

#### Özet

- Boğucu incir geçişi bir iş akışının tek adımını yeni bir birime taşır, geri kalanı yerinde
  bırakır ve trafiği bir yönlendirme anahtarına bağlar.
- Yeni birim ayrı bir dağıtım birimi olduğu için kuralı kendi kod tabanında taşımak zorunda;
  ölçümde aynı iki satır iki kod tabanında birebir duruyor.
- Çift bakımın ölçüsü, oran değişikliğinin 2 dosyaya yazılmak zorunda olmasıdır; yalnız
  birine yazıldığında aynı istek 15 ve 6 gibi iki farklı sonuç verdi.
- Geri alma 1 adımdır ve yeniden dağıtım gerektirmez, çünkü eski yol hâlâ duruyor; bu kazancın
  bedeli çift bakımın sürmesidir.
- Geçiş, anahtarın kalıcı olarak açılması ve eski kuralın silinmesiyle biter; yarım kalmış bir
  geçiş kalıcı iki katı bakım demektir.

#### Sonraki Adım

Bu konu boyunca aynı ödünç iş akışı beş biçimde kuruldu — tek parça, modüler tek parça,
servis yönelimli düzen, mikroservisler, sunucusuz işlevler — ve altıncı derste bunlardan
birine geçmenin bedeli ölçüldü. Her ölçümde bir sayı değişti: ithal kenarı, sınır ihlali,
ortak modele bağlı birim, süreç, uç, ağ atlaması, depo çağrısı, dokunulan dosya.

Bütün bu ölçümlerin ortak bir varsayımı vardı ve hiçbir yerde sorgulanmadı: **sınırın nereden
geçtiği** verili kabul edildi. Katalog, üyelik, ödünç, bildirim ve ücretlendirme daha ilk
derste beş ayrı modül olarak çizildi; sonraki beş ders o çizgiyi olduğu gibi devraldı ve
yalnız çizginin hangi biçimde gerçekleştirileceğini tartıştı. Oysa asıl karar biçim değil,
çizginin yeridir. Ücretlendirme neden üyeliğin içinde değil? Bildirim neden ödünçten ayrı?

Bu soru şimdiye kadar hiçbir ölçüte bağlanmadı. Sonraki konu tam olarak bunu ele alır: bir
sınırın nereden geçmesi gerektiğine kodda ve koşumda görünen hangi kanıtla karar verilir.

## Servis Sınırları ve İletişim

Sınırın nereden geçeceği ve karşıdan nasıl konuşulacağı: bağlaşım, veri sahipliği ile değişim hızına dayanan sınır çizme ölçütleri, eşzamanlı çağrıların zincirleme gecikmesi ve hata yayılımı, olay tabanlı gevşek bağlanmanın kazandırdığı ile ödettiği, kenar sorumluluklarının ağ geçidinde toplanması, istemciye özel toplama katmanı, ağ ilgilerinin uygulamadan ayrılması ve adres çözümleme ile sağlık bilgisinin servis keşfinde birleşmesi.

### Sınır Çizme Ölçütleri

Source: https://academia.sh/tr/kurslar/servis-mimarileri/sinir-cizme-olcutleri

Sınırın nereden geçeceğinin koddan ölçülmesi: modüller arası ithal kenarının bağlaşım ölçüsü olarak sayılması, her tablonun sahibinin belirlenip sahibi olmayan yazmaların bulunması, değişiklik kaydından çıkarılan değişim birlikteliği ve üç aday sınır çiziminin aynı ölçütlerle karşılaştırılması.

Önceki konu tek parçadan sunucusuza kadar uzanan seçenekleri kurdu ve her birinin bedelini
dağıtım birimi, süreç ve dağıtım adımı cinsinden saydı. Seçeneklerin hepsinin ortak bir varsayımı
vardı: sınırın nereden geçtiği verili kabul edildi. Oysa bu listede en pahalı karar mimari
seçeneğin adı değil, sınırın yeridir. Yanlış yerden geçen bir sınır, seçilen mimari ne olursa
olsun her değişikliği iki tarafa birden dokundurur.

Bu ders sınırın yerini bir ölçüte bağlar ve ölçüt kodun içindedir. Alan modelleme, sınırlı bağlam
ve bağlam haritası Alan Odaklı Tasarım kursunda kuruldu; burada alan modeli öğretilmez. Buradaki
soru daha dar: elde çalışan bir modüler tek parça varken, hangi sayıya bakılarak sınır çizilir.
Through-line kütüphane ödünç sistemidir ve beş alan modülüne sahiptir: katalog, üyelik, ödünç,
bildirim, ücretlendirme.

#### Ölçülecek Uygulama

Aşağıdaki blok uygulamayı diske yazar ve bir kitabın ödünç verilmesi iş akışını koşturur. Modül
dosyaları bir haritadan üretilir; her modül hangi modülleri ithal ettiğini, hangi tablolara
yazdığını ve hangi tablonun sahibi olduğunu kendi kaynağında taşır.

```js
// kur.mjs — kutuphane odunc uygulamasini modul dosyalari olarak yazar ve is akisini kosturur
import { mkdirSync, writeFileSync } from "node:fs";

const MODUL = {                       // ithal: alan modulleri | yazar: tablolar | sahip: tablolar
  katalog:       { ithal: [],                    yazar: ["kitap", "nusha"], sahip: ["kitap", "nusha"] },
  uyelik:        { ithal: [],                    yazar: ["uye"],            sahip: ["uye"] },
  odunc:         { ithal: ["katalog", "uyelik"], yazar: ["odunc", "nusha"], sahip: ["odunc"] },
  ucretlendirme: { ithal: ["odunc"],             yazar: ["ucret", "odunc"], sahip: ["ucret"] },
  bildirim:      { ithal: ["uyelik", "odunc"],   yazar: ["bildirim"],       sahip: ["bildirim"] },
};

mkdirSync("uygulama", { recursive: true });
writeFileSync("uygulama/depo.mjs",
  "export const kayit = [];\n" +
  "export const yaz = (tablo, satir) => kayit.push({ tablo, ...satir });\n");

for (const [ad, m] of Object.entries(MODUL)) {
  writeFileSync(`uygulama/${ad}.mjs`,
    `// sahip: ${m.sahip.join(", ")}\n` +
    'import { yaz, kayit } from "./depo.mjs";\n' +
    m.ithal.map((i) => `import { ${i}Oku } from "./${i}.mjs";\n`).join("") +
    `export const ${ad}Oku = () => kayit.filter((k) => k.tablo === "${m.sahip[0]}");\n` +
    `export function ${ad}Islem(veri) {\n` +
    m.ithal.map((i) => `  ${i}Oku();\n`).join("") +
    m.yazar.map((t) => `  yaz("${t}", { adim: "${ad}", ...veri });\n`).join("") +
    "}\n");
}
writeFileSync("uygulama/uygulama.mjs",
  'import { kayit } from "./depo.mjs";\n' +
  'import { uyelikIslem } from "./uyelik.mjs";\n' +
  'import { katalogIslem } from "./katalog.mjs";\n' +
  'import { oduncIslem } from "./odunc.mjs";\n' +
  'import { bildirimIslem } from "./bildirim.mjs";\n' +
  'import { ucretlendirmeIslem } from "./ucretlendirme.mjs";\n' +
  'const istek = { uye: 7, kitap: 412 };\n' +
  "uyelikIslem(istek); katalogIslem(istek); oduncIslem(istek);\n" +
  "bildirimIslem(istek); ucretlendirmeIslem(istek);\n" +
  'export const yazilan = kayit.map((k) => `${k.adim}->${k.tablo}`);\n');

const { yazilan } = await import("./uygulama/uygulama.mjs");
console.log(`modul dosyasi: ${Object.keys(MODUL).length} alan modulu + depo + uygulama girisi`);
console.log(`is akisi (bir kitabin odunc verilmesi) ${yazilan.length} yazma uretti:`);
console.log(yazilan.join("  "));
```

```
modul dosyasi: 5 alan modulu + depo + uygulama girisi
is akisi (bir kitabin odunc verilmesi) 8 yazma uretti:
uyelik->uye  katalog->kitap  katalog->nusha  odunc->odunc  odunc->nusha  bildirim->bildirim  ucretlendirme->ucret  ucretlendirme->odunc
```

Başlangıç noktası budur: tek dağıtım birimi, tek süreç, sekiz yazma. Bu hâliyle sistem çalışıyor
ve hiçbir sınır yok. Sınır çizme kararı bundan sonra verilecek.

#### Koddan Okunan İki Ölçüt

İlk iki ölçüt kaynak dosyalardan doğrudan çıkarılır. **Bağlaşım** (coupling) burada bir sayıya
indirgenir: bir modülün başka bir alan modülünden yaptığı ithal, bir kenardır. **Veri sahipliği**
ikinci ölçüttür: her tablonun tek bir sahip modülü vardır ve sahibi olmayan bir modülün o tabloya
yazması ayrı sayılır.

```js
// olc.mjs — kur.mjs'in yazdigi uygulama/ dizinini okur; olcu koddan cikarilir
import { readFileSync, readdirSync } from "node:fs";

const dosya = readdirSync("uygulama").filter((d) => d !== "depo.mjs" && d !== "uygulama.mjs");
const M = {};
for (const d of dosya) {
  const t = readFileSync(`uygulama/${d}`, "utf8");
  M[d.replace(".mjs", "")] = {
    ithal: [...t.matchAll(/from "\.\/(\w+)\.mjs"/g)].map((m) => m[1]).filter((a) => a !== "depo"),
    yazar: [...t.matchAll(/yaz\("(\w+)"/g)].map((m) => m[1]),
    sahip: t.match(/^\/\/ sahip: (.+)$/m)[1].split(", "),
  };
}
const ad = Object.keys(M);
const sahibi = (tablo) => ad.find((a) => M[a].sahip.includes(tablo));
const kenar = ad.flatMap((a) => M[a].ithal.map((b) => [a, b]));
const disYazma = ad.flatMap((a) => M[a].yazar.filter((t) => sahibi(t) !== a).map((t) => [a, t]));

console.log(`${"modul".padEnd(15)}${"ithal ettigi".padEnd(18)}${"yazdigi".padEnd(15)}sahibi oldugu`);
for (const a of ad) {
  console.log(`${a.padEnd(15)}${(M[a].ithal.join(",") || "-").padEnd(18)}` +
    `${M[a].yazar.join(",").padEnd(15)}${M[a].sahip.join(",")}`);
}
console.log(`toplam: ${ad.length} modul, ${kenar.length} ithal kenari, ` +
  `${ad.reduce((s, a) => s + M[a].yazar.length, 0)} yazma, ` +
  `${disYazma.length} sahibi olmayan yazma (${disYazma.map(([a, t]) => `${a}->${t}`).join(", ")})`);
```

```
modul          ithal ettigi      yazdigi        sahibi oldugu
bildirim       uyelik,odunc      bildirim       bildirim
katalog        -                 kitap,nusha    kitap,nusha
odunc          katalog,uyelik    odunc,nusha    odunc
ucretlendirme  odunc             ucret,odunc    ucret
uyelik         -                 uye            uye
toplam: 5 modul, 5 ithal kenari, 8 yazma, 2 sahibi olmayan yazma (odunc->nusha, ucretlendirme->odunc)
```

Beş kenar ve iki sahibi olmayan yazma. Bu iki yazma tek dağıtım birimi içinde yalnızca bir düzen
kusurudur: ödünç modülü nüsha durumunu doğrudan değiştiriyor, ücretlendirme modülü ödünç kaydına
gecikme işareti koyuyor. İkisi de aynı süreçte, aynı işlemin içinde olduğu için bugün bir bedel
ödetmiyor. Sınır çizildiği anda bunlar **sınır ihlaline** dönüşür: bir servisin başka bir servisin
tablosuna yazması artık ya bir uca ya da paylaşılan bir tabloya bağımlılık demektir.

#### Üçüncü Ölçüt: Değişim Birlikteliği

Üçüncü ölçüt kaynak dosyalarda görünmez, kod tabanının geçmişinde durur. **Değişim birlikteliği**
(change coupling), iki modülün aynı değişiklikte birlikte değişme sıklığıdır. Ölçüt şudur: sık
birlikte değişen iki modülü ayıran bir sınır, her değişikliği iki dağıtım birimine yayar.

**SS1 — son çeyreğin değişiklik kaydı yirmi satırdır ve her satır bir değişikliğin dokunduğu
modülleri verir.** Gerekçe: sürüm geçmişindeki her değişiklik, dokunduğu dosyalardan modül kümesine
indirgenebilir; bu ders o indirgemenin sonucunu girdi olarak sabitler. Sayı ve dağılım değişirse
aşağıdaki sıralama da değişir; ölçütün kendisi değişmez.

```js
// aday.mjs — ayni dizini okur, uc aday sinir cizimini ayni olcutlerle karsilastirir
import { readFileSync, readdirSync } from "node:fs";

const M = {};
for (const d of readdirSync("uygulama").filter((x) => x !== "depo.mjs" && x !== "uygulama.mjs")) {
  const t = readFileSync(`uygulama/${d}`, "utf8");
  M[d.replace(".mjs", "")] = {
    ithal: [...t.matchAll(/from "\.\/(\w+)\.mjs"/g)].map((m) => m[1]).filter((a) => a !== "depo"),
    yazar: [...t.matchAll(/yaz\("(\w+)"/g)].map((m) => m[1]),
    sahip: t.match(/^\/\/ sahip: (.+)$/m)[1].split(", "),
  };
}
const ad = Object.keys(M);
const sahibi = (tablo) => ad.find((a) => M[a].sahip.includes(tablo));
const kenar = ad.flatMap((a) => M[a].ithal.map((b) => [a, b]));
const disYazma = ad.flatMap((a) => M[a].yazar.filter((t) => sahibi(t) !== a).map((t) => [a, t]));

// SS1: son ceyregin degisiklik kaydi — her satir bir degisiklik ve dokundugu moduller
const DEGISIM = [
  "gecikme ucreti orani: odunc ucretlendirme", "odunc suresini uzatma: odunc ucretlendirme",
  "ucret muafiyeti: ucretlendirme", "hatirlatma metni: bildirim",
  "iletisim tercihi: uyelik bildirim", "uyelik dondurma: uyelik odunc",
  "tur etiketi: katalog", "arama alani: katalog",
  "nusha durumu: katalog odunc", "rezervasyon sirasi: katalog odunc",
  "ceza esigi: odunc ucretlendirme", "uye adres alani: uyelik",
  "bildirim kanali: bildirim", "yenileme hatirlatmasi: uyelik bildirim",
  "gecikme gunu hesabi: odunc ucretlendirme", "nusha kaybi: katalog odunc ucretlendirme",
  "kapak gorseli: katalog", "kimlik dogrulama: uyelik",
  "iade akisi: odunc", "ucret iadesi: ucretlendirme",
].map((s) => [s.split(": ")[0], s.split(": ")[1].split(" ")]);

const cift = {};
for (const [, ms] of DEGISIM) {
  for (let i = 0; i < ms.length; i += 1) {
    for (let j = i + 1; j < ms.length; j += 1) {
      const c = [ms[i], ms[j]].sort().join(" + ");
      cift[c] = (cift[c] ?? 0) + 1;
    }
  }
}
console.log(`${DEGISIM.length} degisiklikte birlikte degisen ciftler (SS1):`);
for (const [c, n] of Object.entries(cift).sort((a, b) => b[1] - a[1])) {
  console.log(`  ${c.padEnd(30)}${n}`);
}

const ADAY = {
  "A referans/islem": [["katalog", "uyelik"], ["odunc", "ucretlendirme", "bildirim"]],
  "B varlik basina": [["katalog"], ["uyelik"], ["odunc"], ["ucretlendirme"], ["bildirim"]],
  "C degisim birlikteligi": [["odunc", "ucretlendirme"], ["katalog"], ["uyelik", "bildirim"]],
};
console.log(`\n${"aday".padEnd(24)}${"birim".padStart(6)}${"sinir gecen ithal".padStart(19)}` +
  `${"sahiplik ihlali".padStart(17)}${"tek birimde kalan".padStart(19)}${"dagitim".padStart(9)}`);
for (const [a, bolum] of Object.entries(ADAY)) {
  const birim = (m) => bolum.findIndex((b) => b.includes(m));
  const dokunulan = DEGISIM.map(([, ms]) => new Set(ms.map(birim)).size);
  const toplam = dokunulan.reduce((s, n) => s + n, 0);
  console.log(`${a.padEnd(24)}${String(bolum.length).padStart(6)}` +
    `${String(kenar.filter(([x, y]) => birim(x) !== birim(y)).length).padStart(19)}` +
    `${String(disYazma.filter(([x, t]) => birim(x) !== birim(sahibi(t))).length).padStart(17)}` +
    `${`${dokunulan.filter((n) => n === 1).length}/${DEGISIM.length}`.padStart(19)}` +
    `${String(toplam).padStart(9)}`);
}

// Ayni degisiklik, dort gerceklestirim: SS1'in ilk satiri
const [dad, dmod] = DEGISIM[0];
console.log(`\n"${dad}" degisikligi (${dmod.join(", ")}) dort gerceklestirimde:`);
console.log(`${"gerceklestirim".padEnd(24)}${"dokunulan dosya".padStart(16)}${"dagitim birimi".padStart(16)}` +
  `${"aga donusen ithal".padStart(19)}${"sinir gecen yazma".padStart(19)}`);
for (const [g, bolum] of Object.entries({ "moduler tek parca": [ad], ...ADAY })) {
  const birim = (m) => bolum.findIndex((b) => b.includes(m));
  console.log(`${g.padEnd(24)}${String(dmod.length).padStart(16)}` +
    `${String(new Set(dmod.map(birim)).size).padStart(16)}` +
    `${String(kenar.filter(([x, y]) => dmod.includes(x) && dmod.includes(y) && birim(x) !== birim(y)).length).padStart(19)}` +
    `${String(disYazma.filter(([x, t]) => dmod.includes(x) && birim(x) !== birim(sahibi(t))).length).padStart(19)}`);
}
```

```
20 degisiklikte birlikte degisen ciftler (SS1):
  odunc + ucretlendirme         5
  katalog + odunc               3
  bildirim + uyelik             2
  odunc + uyelik                1
  katalog + ucretlendirme       1

aday                     birim  sinir gecen ithal  sahiplik ihlali  tek birimde kalan  dagitim
A referans/islem             2                  3                1              14/20       26
B varlik basina              5                  5                2              10/20       31
C degisim birlikteligi       3                  3                1              16/20       24

"gecikme ucreti orani" degisikligi (odunc, ucretlendirme) dort gerceklestirimde:
gerceklestirim           dokunulan dosya  dagitim birimi  aga donusen ithal  sinir gecen yazma
moduler tek parca                      2               1                  0                  0
A referans/islem                       2               1                  0                  1
B varlik basina                        2               2                  1                  2
C degisim birlikteligi                 2               1                  0                  1
```

#### Üç Adayın Okunması

En sık birlikte değişen çift ödünç ile ücretlendirmedir: yirmi değişikliğin beşi ikisine birden
dokunuyor. Katalog ile ödünç üç kez, üyelik ile bildirim iki kez birlikte değişiyor.

**Aday B, her varlığa bir servis, en temiz görünen çizimdir ve ölçüde en pahalısıdır.** Beş ithal
kenarının beşi de sınırı geçiyor, iki sahiplik ihlalinin ikisi de sınır ihlaline dönüşüyor ve
yirmi değişikliğin yalnız onu tek birimde kalıyor. Yirmi değişikliğin toplam dağıtım sayısı 31.

**Aday A ile aday C bağlaşımda eşittir**, ikisi de üç kenar ve bir ihlal veriyor. Ayrım üçüncü
ölçütte çıkıyor: A değişikliklerin on dördünü, C on altısını tek birimde tutuyor; dağıtım toplamı
26'ya karşı 24. Fark, A'nın üyelik ile bildirimi ayırmasından geliyor — o çift iki kez birlikte
değişiyor ve A her seferinde iki birime dokunuyor.

Buradan çıkan kural şudur: **bağlaşım ve sahiplik ölçütleri aday sayısını daraltır, kararı değişim
birlikteliği verir.** İlk iki ölçüt kodun bugünkü hâlini, üçüncüsü kodun nasıl değiştiğini
ölçer; sınır ikincisine göre çizilir çünkü sınırın bedeli her değişiklikte ödenir.

#### Aynı Değişiklik, Dört Gerçekleştirim

Son tablo tek bir değişikliği izliyor: gecikme ücreti oranı, ödünç ile ücretlendirmeye dokunuyor.
Dokunulan dosya sayısı dört gerçekleştirimde de ikidir; sınır çizmek bir değişikliğin kod
hacmini azaltmaz. Değişen şey o iki dosyanın nereye ait olduğudur.

**Neyi ucuzlattı.** Modüler tek parçada ve C çiziminde değişiklik tek dağıtım biriminde kalıyor:
tek doğrulama, tek dağıtım, tek geri alma. C bunu tek parçadan farkı olmadan yaparken sistemi üç
bağımsız birime bölmüş oluyor; katalog değişiklikleri ödünç birimine hiç dokunmuyor.

**Neyi pahalılaştırdı.** B çiziminde aynı değişiklik iki dağıtım birimine dokunuyor, bir ithal
kenarı ağ çağrısına dönüşüyor ve iki yazma sınırı geçiyor. Ücretlendirmenin ödünç tablosuna
yazması artık kendi verisine yazma değil, başka bir birimin verisine yazmadır; ya o birimde bir uç
açılır ya da tablo iki birim tarafından paylaşılır. İkisi de yeni sözleşme demektir.

**Hangi yeni arıza kipi doğdu.** Tek parçada iki dosya birlikte yayımlanır ve arada tutarsız bir
an olmaz. İki dağıtım birimine bölündüğünde iki yayım arasında bir pencere açılır: yeni oran
ücretlendirmede yürürlüktedir ama ödünç eski alanı gönderiyordur. Bu, tek parçada bulunmayan bir
arıza kipidir ve sınırın kendisinden doğar. A ile C'de kalan tek sahiplik ihlali de aynı sınıfa
girer: ödünç modülünün nüsha durumunu yazması, katalog ayrı bir birime taşındığı anda ya bir
çağrıya dönüşecek ya da iki birimin aynı satırı yazmasına yol açacaktır.

#### Özet

- Sınırın yeri üç ölçüyle sınanır ve üçü de koddan okunur: ithal kenarı (bağlaşım), tablo başına
  sahip modül (veri sahipliği), değişikliklerin modül kümesi (değişim birlikteliği).
- Ölçülen uygulamada 5 alan modülü, 5 ithal kenarı ve 8 yazma var; yazmaların ikisi sahibi olmayan
  modülden geliyor ve tek dağıtım biriminde bedelsizken sınır çizilince sınır ihlaline dönüşüyor.
- Her varlığa bir servis çizimi üç ölçütte de en kötüsüdür: 5 kenarın 5'i sınırı geçiyor, 2
  ihlalin 2'si kalıyor, 20 değişikliğin yalnız 10'u tek birimde kalıyor ve dağıtım toplamı 31.
- Bağlaşımda eşit iki aday değişim birlikteliğiyle ayrılır: 14/20'ye karşı 16/20 ve 26'ya karşı 24
  dağıtım; sık birlikte değişen çifti ayıran sınır her değişiklikte iki birime dokunur.
- Aynı değişiklik dört gerçekleştirimde de 2 dosyaya dokunuyor; sınır dosya sayısını değil dağıtım
  birimi, ağ çağrısı ve sınırı geçen yazma sayısını değiştiriyor.

#### Sonraki Adım

Sınır bir ölçüte bağlandı, ama sınırın çizilmesiyle birlikte tabloda sessizce duran bir sayı
işlemeye başlıyor: ağa dönüşen ithal. Modüller arasında bir ithal kenarı, aynı süreçte bir işlev
çağrısıdır ve maliyeti yığıt üzerinde ölçülür. İki dağıtım birimi arasında aynı kenar bir ağ
çağrısıdır ve maliyeti tur cinsinden ölçülür. Sonraki ders bu dönüşümü gerçek süreçlerle kurar:
aynı ödünç verme iş akışı önce tek süreçte, sonra ayrı süreçlerde koşturulur ve zincirin uzunluğu
arttıkça uçtan uca turun nasıl büyüdüğü ölçülür. Aynı koşumda ikinci bir soru daha sorulur — bu
süreçlerden biri ayakta değilken gelen isteklerin hangi payı yanıtsız kalır.

### Eşzamanlı Servis Çağrıları

Source: https://academia.sh/tr/kurslar/servis-mimarileri/eszamanli-servis-cagrilari

Sınırın karşısına geçen çağrının gerçek süreçlerle ölçülmesi: zincir uzunluğuna göre uçtan uca turun büyümesi, dağıtılan çağrının aynı işi daha az turda bitirmesi, bir dış isteğin kaç iç isteğe dönüştüğünü veren çağrı çarpanı ve bir sürecin kapanmasında yanıtsız kalan giriş uçlarının payı.

Önceki ders sınırı bir ölçüte bağladı ve tabloda sessizce duran bir sayı bıraktı: ağa dönüşen
ithal. Aynı süreçteki bir ithal kenarı işlev çağrısıdır; iki dağıtım birimi arasındaki aynı kenar
ağ çağrısıdır. Bu ders o dönüşümü modelle değil, gerçekten ayrı işletim sistemi süreçleriyle
kurar ve farkı tur cinsinden sayar.

Zaman aşımı, yeniden deneme ve uçtan uca bütçenin zincire bölüştürülmesi Uygulama Katmanı ve
Servis Etkileşimi kursunda kuruldu ve ölçüldü; o aritmetik burada tekrarlanmaz. Buradaki üç ölçü
şudur: zincir uzunluğuna göre uçtan uca tur, bir dış isteğin doğurduğu iç istek sayısı ve bir
süreç kapandığında yanıtsız kalan isteklerin payı. Eşzamanlı çağrı, çağıranın yanıtı bekleyip
sonucu kendi yanıtına kattığı çağrıdır.

#### Aynı İş, İki Yerleşim

Ödünç verme iş akışı dört adımdan oluşur: ödünç kaydı, üyelik denetimi, ödenmemiş ücret denetimi
ve nüsha ayırma. Aşağıdaki servis tek bir kaynak dosyadır; hangi rolü üstleneceği komut satırı
değişkenlerinden gelir. Her adım aynı süreyi harcar, böylece ölçülen süre iş adımı sayısına
bölünebilir.

```js
// servis.mjs — <ad> <port> <kip: paralel|sirali> <adim> [ardil-port...]; uzun omurlu surec
import { createServer } from "node:http";
const [ad, port, kip, adim, ...ardil] = process.argv.slice(2);
if (!port) { console.log("kullanim: node servis.mjs <ad> <port> <kip> <adim> [ardil...]"); process.exit(1); }
const IS = 25;                        // bir is adiminin suresi (ms); olcum bu birime bolunur
let islenen = 0;
const cagir = async (p) => {          // ardilin durum kodu denetlenir: hata sinirin karsisina gecer
  const y = await fetch(`http://127.0.0.1:${p}/is`);
  if (y.status !== 200) throw new Error(`ardil ${p} durum ${y.status}`);
  return y.json();
};

createServer(async (istek, yanit) => {
  if (istek.url === "/sayac") { yanit.end(JSON.stringify({ ad, islenen })); return; }
  if (istek.url === "/sifirla") { islenen = 0; yanit.end("sifir"); return; }
  islenen += 1;
  for (let i = 0; i < Number(adim); i += 1) await new Promise((c) => setTimeout(c, IS));
  try {
    let alt = [];
    if (kip === "sirali") for (const p of ardil) alt.push(await cagir(p));
    else alt = await Promise.all(ardil.map(cagir));
    yanit.end(JSON.stringify({ ad, adim: Number(adim), alt }));
  } catch (e) {
    yanit.statusCode = 502;
    yanit.end(JSON.stringify({ ad, hata: e.message }));
  }
}).listen(Number(port));
```

Aynı dört adım iki yerleşimde koşacak. Bölünmüş yerleşimde dört süreç vardır: ödünç servisi üyelik
ile katalog servislerini çağırır, üyelik servisi ücretlendirme servisini çağırır. Tek parça
yerleşiminde dört adımın dördü de tek süreçte ardışık koşar; ödünç ucundaki `adim` değeri dörttür
ve hiç ardıl çağrısı yoktur. Üçüncü bir yerleşim karşılaştırma içindir: ödünç servisi iki ardılını
paralel yerine sırayla çağırır.

Ardıl çağrısında durum kodunun denetlendiği satır dersin geri kalanını belirler. Süreç içinde bir
adımın başarısızlığı özel durum olarak yukarı yayılır; sınırın karşısında ise yalnız bir durum
kodudur. Çağıran o kodu denetlemezse hata yayılmaz, yutulur: yanıt başarılı görünür ama içindeki
üyelik denetimi hiç yapılmamıştır.

#### Ölçüm

Ölçüm aracı üç kipte çalışır. Gecikme kipi her giriş ucuna üç istek gönderir ve ortancayı iş adımı
süresine bölerek turu bulur; süre ölçümü ortama bağlıdır, tur sayısı değildir. Çarpan kipi
sayaçları sıfırlar, tek uca beş istek gönderir ve süreçlerin işlediği istekleri toplar. Arıza kipi
her giriş ucuna birer istek atıp yanıtsız kalanları sayar.

**SS2 — dış istekler dört giriş ucuna eşit dağılır.** Gerekçe: etkilenen istek payı, kapanan
servise bağımlı giriş uçlarının trafikteki ağırlığıdır; ağırlıklar bilinmediğinde eşit dağılım
payı ölçülebilir kılar. Gerçek dağılım bilindiğinde aynı hesap ağırlıklarla yapılır.

```js
// olc.mjs — <kip: gecikme|carpan|ariza> [ad]; servis.mjs surecleri ayaktayken kosar
const IS = 25;                                   // servis.mjs ile ayni is adimi suresi
const UC = { ucretlendirme: 8941, uyelik: 8942, katalog: 8943, odunc: 8944,
  "sirali-odunc": 8945, "tek-surec": 8946 };
const ARIZA_GIRIS = ["ucretlendirme", "uyelik", "katalog", "odunc"];
const iste = async (p) => {
  const y = await fetch(`http://127.0.0.1:${p}/is`);
  if (y.status !== 200) throw new Error(`durum ${y.status}`);
  return y.json();
};
const dugum = (a) => 1 + (a.alt ?? []).reduce((s, x) => s + dugum(x), 0);
const adimlar = (a) => a.adim + (a.alt ?? []).reduce((s, x) => s + adimlar(x), 0);
const [kip, hedef] = process.argv.slice(2);

if (kip === "gecikme") {
  console.log(`${"giris".padEnd(14)}${"surec".padStart(7)}${"ag atlamasi".padStart(13)}` +
    `${"is adimi".padStart(10)}${"olculen tur".padStart(13)}`);
  let enBuyukEk = 0;
  for (const ad of Object.keys(UC)) {
    const sure = [];
    let agac;
    for (let i = 0; i < 3; i += 1) {
      const t = performance.now();
      agac = await iste(UC[ad]);
      sure.push(performance.now() - t);
    }
    const orta = sure.sort((a, b) => a - b)[1];
    const d = dugum(agac), tur = Math.floor(orta / IS);
    enBuyukEk = Math.max(enBuyukEk, orta - tur * IS);
    console.log(`${ad.padEnd(14)}${String(d).padStart(7)}${String(d - 1).padStart(13)}` +
      `${String(adimlar(agac)).padStart(10)}${String(tur).padStart(13)}`);
  }
  console.log(`olculen sure = tur x ${IS} ms + ek yuk; ek yuk bu kosumda ` +
    `${Math.ceil(enBuyukEk / IS) * IS} ms altinda (bir is adimindan az)`);
} else if (kip === "carpan") {
  for (const ad of Object.keys(UC)) await fetch(`http://127.0.0.1:${UC[ad]}/sifirla`);
  const N = 5;
  for (let i = 0; i < N; i += 1) await iste(UC.odunc);
  let toplam = 0;
  for (const ad of Object.keys(UC)) {
    const { islenen } = await (await fetch(`http://127.0.0.1:${UC[ad]}/sayac`)).json();
    toplam += islenen;
    if (islenen > 0) console.log(`  ${ad.padEnd(14)}${String(islenen).padStart(8)} istek`);
  }
  console.log(`odunc ucuna ${N} dis istek -> toplam ${toplam} islenen istek, ` +
    `cagri carpani x${toplam / N} (dis istek basina ${toplam / N - 1} ic istek)`);
} else if (kip === "ariza") {
  const dusen = [];
  for (const ad of ARIZA_GIRIS) {
    try { await iste(UC[ad]); } catch { dusen.push(ad); }
  }
  console.log(`${hedef.padEnd(16)}${(dusen.join(",") || "-").padEnd(36)}` +
    `${dusen.length}/${ARIZA_GIRIS.length}`);
}
```

Aşağıdaki kabuk bloğu altı süreci ayağa kaldırır, üç ölçümü koşturur, sonra sırayla üç servisi
kapatıp her defasında arıza kipini çalıştırır ve kapattığı servisi geri başlatır.

```bash
baslat() {
  case $1 in
    ucretlendirme) node servis.mjs ucretlendirme 8941 paralel 1 & ;;
    uyelik)        node servis.mjs uyelik 8942 paralel 1 8941 & ;;
    katalog)       node servis.mjs katalog 8943 paralel 1 & ;;
    odunc)         node servis.mjs odunc 8944 paralel 1 8942 8943 & ;;
  esac
}
for s in ucretlendirme uyelik katalog odunc; do baslat $s; done
node servis.mjs sirali-odunc 8945 sirali 1 8942 8943 &
node servis.mjs tek-surec 8946 paralel 4 &
sleep 1

node olc.mjs gecikme
echo
echo "cagri carpani:"
node olc.mjs carpan
echo
printf '%-16s%-36s%s\n' "kapali servis" "yanitsiz kalan giris" "etkilenen istek payi"
for s in ucretlendirme katalog odunc; do
  pkill -f "servis.mjs $s" ; sleep 0.3
  node olc.mjs ariza $s
  baslat $s ; sleep 0.5
done
pkill -f "node servis.mjs"
```

```
giris           surec  ag atlamasi  is adimi  olculen tur
ucretlendirme       1            0         1            1
uyelik              2            1         2            2
katalog             1            0         1            1
odunc               4            3         4            3
sirali-odunc        4            3         4            4
tek-surec           1            0         4            4
olculen sure = tur x 25 ms + ek yuk; ek yuk bu kosumda 25 ms altinda (bir is adimindan az)

cagri carpani:
  ucretlendirme        5 istek
  uyelik               5 istek
  katalog              5 istek
  odunc                5 istek
odunc ucuna 5 dis istek -> toplam 20 islenen istek, cagri carpani x4 (dis istek basina 3 ic istek)

kapali servis   yanitsiz kalan giris                etkilenen istek payi
ucretlendirme   ucretlendirme,uyelik,odunc          3/4
katalog         katalog,odunc                       2/4
odunc           odunc                               1/4
```

#### Zincir Uzunluğu Turu Belirliyor

İlk üç satır zinciri uzatarak okunuyor. Ücretlendirme ucuna gelen istek bir süreçte biter: sıfır
atlama, bir tur. Üyelik ucu ücretlendirmeyi çağırır: iki süreç, bir atlama, iki tur. Ödünç ucu
dört süreci birden işletir: üç atlama, dört iş adımı. **Zincir gecikmesi** burada tur cinsinden
görünür hâle geliyor: uçtan uca tur, zincirin ardışık adım sayısıdır ve ölçüm bunu geri veriyor.

Dördüncü ve altıncı satır dersin asıl karşılaştırmasıdır. Ödünç ucu dört iş adımını **üç** turda
bitiriyor, tek süreç aynı dört adımı **dört** turda. Kazanç bölünmeden gelmiyor, bölünmenin
açtığı fırsattan geliyor: üyelik dalı ile katalog dalı birbirini beklemek zorunda olmadığı için
paralel çağrılıyor ve uçtan uca süre iki dalın toplamı değil büyüğü oluyor. Beşinci satır bunu
doğruluyor — aynı dört süreç, aynı üç atlama, ama ardılları sırayla çağıran ödünç servisi dört
turda bitiriyor. **Dağıtılan çağrı bir turu geri kazandırır; sıralı çağrı kazanmaz.**

Süre ölçümü ortama bağlıdır ve loopback üzerinde bir atlamanın bedeli küçüktür; ek yükün tamamı
bir iş adımının altında kaldı. Gerçek bir ağda aynı üç atlama bu kadar ucuz değildir, ama tur
sayısı değişmez: tur, koşumdan bağımsız niceliktir ve gecikme bütçesi turlarla çarpılır.

#### Çarpan ve Etkilenen Pay

Çağrı çarpanı ikinci ölçüdür. Ödünç ucuna gelen beş dış istek, süreçlerde toplam yirmi istek
işletiyor: çarpan dört, yani her dış istek başına üç iç istek. Bu sayı doğrudan sınır kararının
sonucudur; önceki dersin ölçtüğü sınırı geçen ithal kenarları, koşum sırasında iç isteğe dönüşen
kenarlardır. Sınırı geçen kenar sayısı arttıkça çarpan artar ve her servis kendi kapasitesini
kendi trafiğine göre değil, çarpanla büyütülmüş trafiğe göre seçmek zorunda kalır.

Son tablo yeni arıza kipini sayıya çeviriyor. Ücretlendirme süreci kapandığında yalnız kendi ucu
değil, ona bağımlı olan üyelik ve ödünç uçları da yanıtsız kalıyor: dört giriş ucunun üçü, SS2
altında isteklerin yüzde yetmiş beşi. Katalog kapandığında iki uç, ödünç kapandığında bir uç
etkileniyor. **Etkilenen pay, kapanan servisin bağımlılık kapanışıdır**: zincirin dibindeki servis
en pahalıdır, çünkü üstündeki her uç ona bağlıdır. Tek parça yerleşiminde bu tablo tek satırdır —
süreç kapanırsa isteklerin tamamı düşer, ama kısmi arıza diye bir durum da yoktur.

Üçlü şöyle toparlanır. **Ucuzlayan:** dört iş adımı üç turda bitiyor ve her servis kendi başına
ölçeklenebiliyor; bir servisin kapanması bütün uçları birden düşürmüyor — zincirin tepesindeki
ödünç kapandığında dört giriş ucunun üçü ayakta kalıyor. **Pahalılaşan:** istek
başına üç ağ atlaması, dört süreç, dört kat işlenen istek ve dörde bölünmüş bir başlatma düzeni.
**Doğan arıza kipi:** kısmi arıza. Sistem artık ya tamamen ayakta ya tamamen kapalı değil; giriş
ucuna göre değişen bir yanıt verme oranı var ve ardıl hatasının durum kodu denetlenmezse bu oran
hiç görünmeden, eksik iş yapılmış başarılı yanıtlara dönüşüyor.

#### Özet

- Uçtan uca tur zincirin ardışık adım sayısıdır: ölçüm sıfır, bir ve üç atlamalı uçlarda sırasıyla
  1, 2 ve 3 tur verdi; ek yük bir iş adımının altında kaldı.
- Aynı dört iş adımı tek süreçte 4 turda, dört süreçte 3 turda bitiyor; kazanç bölünmenin değil,
  bölünmenin açtığı paralel çağrının sonucudur — ardılları sırayla çağıran yerleşim 4 turda bitiyor.
- Çağrı çarpanı sınırı geçen kenarların koşum karşılığıdır: 5 dış istek 20 işlenen isteğe dönüştü,
  dış istek başına 3 iç istek.
- Bir sürecin kapanmasında etkilenen istek payı, o servisin bağımlılık kapanışıdır: ücretlendirme
  için 3/4, katalog için 2/4, ödünç için 1/4.
- Sınır, hatayı özel durumdan durum koduna çevirir; çağıran durum kodunu denetlemezse arıza
  yayılmaz, yutulur ve başarılı görünen yanıt eksik iş içerir.

#### Sonraki Adım

Bu ölçümlerin hepsi tek bir varsayımın içindeydi: çağıran, ardılın işi bitene kadar bekliyor.
Çarpan da, tur da, etkilenen pay da o beklemeden doğuyor — ödünç servisi bildirimin gönderilmesini
beklediği için bildirim servisinin kapanması ödünç isteğini düşürüyor. Oysa iş akışındaki her adım
yanıtın verilebilmesi için tamamlanmak zorunda değil. Sonraki ders aynı iş akışını ikinci kez
kurar: yanıt yolundaki adımlardan biri çağrı olmaktan çıkıp bir olaya dönüşür ve tüketicisi
kendi zamanında işler. Ölçülecek olanlar da bellidir — yanıt yolundan düşen iş adımı, yayıncı ile
tüketici ayrı dağıtım birimleri olduğunda ortaya çıkan sözleşme yükü ve olayın yazılmasıyla
etkisinin görünmesi arasındaki tutarlılık penceresi.

### Eşzamansız Tümleştirme

Source: https://academia.sh/tr/kurslar/servis-mimarileri/eszamansiz-tumlestirme

Aynı iş akışının eşzamanlı ve olay tabanlı iki gerçekleştirmesinin ölçülmesi: yanıt yolundan düşen iş adımı ve birim sayısı, yayıncının tüketicileri tanımamasıyla doğan sözleşme yükü, olay gövdesindeki her alana bağlı dağıtım birimi sayısı ve yanıtın dönmesiyle etkinin görünmesi arasındaki tutarlılık penceresi.

Önceki dersin bütün sayıları tek bir varsayımdan doğuyordu: çağıran, ardılın işi bitene kadar
bekliyor. Bekleme kaldırıldığında hem tur hem de bağımlılık kapanışı değişir. Bu ders aynı ödünç
verme akışını ikinci kez kurar; bu kez bildirim ve ücretlendirme adımları yanıt yolundan çıkarılıp
bir olayın tüketicisi hâline getirilir ve iki gerçekleştirim aynı ölçülerle karşılaştırılır.

Kuyruk mekaniği, teslim garantisi, geri basınç ve rekabet eden tüketiciler Önbellekleme, Kuyruklar
ve Eşzamansız İşleme kursunda kuruldu ve ölçüldü; burada tekrarlanmaz. Ölçülen şey taşıyıcının
kendisi değil, sınırın iki yanındaki bağın nasıl değiştiğidir.

**SS3 — taşıyıcı, bir ekleme kütüğü ve sabit aralıklı yoklama olarak modellenir.** Gerekçe:
teslim ve sıralama güvenceleri ayrı bir kursta ölçüldüğü için burada taşıyıcının en yalın biçimi
yeterlidir; ölçülen üç nicelik (yanıt yolundaki birim, alan başına bağlı birim, tutarlılık
penceresi) taşıyıcının türünden bağımsızdır. Kütük gerçek bir dosyadır, yoklama gerçek bir süreçte
koşar; model olan yalnız taşıyıcının yalınlığıdır.

#### İki Gerçekleştirim, Tek Kaynak

Ödünç servisi iki kipte koşar. Eşzamanlı kipte iki ardılını çağırır ve ikisinin de yanıtını
bekler. Olay kipinde aynı gövdeyi kütüğe yazar ve döner. Fark tek satırdadır, ama o satır servisin
yapılandırmasını da değiştirir: olay kipinde ardıl adresi listesi boştur.

```js
// odunc.mjs — <kip: eszamanli|olay> <port> <bildirim-port> <ucret-port>; uzun omurlu surec
import { createServer } from "node:http";
import { appendFileSync } from "node:fs";
const [kip, port, pb, pu] = process.argv.slice(2);
if (!port) { console.log("kullanim: node odunc.mjs <kip> <port> <bildirim-port> <ucret-port>"); process.exit(1); }
const IS = 25;                                   // odunc kaydinin yazilmasi (ms)
const ARDIL = kip === "eszamanli" ? [pb, pu] : [];   // olay kipinde yayinci ardil adresi tasimaz
let sira = 0;

createServer(async (istek, yanit) => {
  if (istek.url === "/ardil") { yanit.end(JSON.stringify({ ardil: ARDIL.length })); return; }
  await new Promise((c) => setTimeout(c, IS));
  sira += 1;
  const olay = { tur: "odunc-verildi", sira: sira, uyeId: 7, nushaId: 400 + sira,
    gecikmeGunu: 0, baslik: "Kitap odunc verildi" };
  const govde = JSON.stringify(olay);
  if (kip === "olay") appendFileSync("olaylar.log", `${govde}\n`);
  else await Promise.all(ARDIL.map((p) =>
    fetch(`http://127.0.0.1:${p}/is`, { method: "POST", body: govde })));
  yanit.end(govde);
}).listen(Number(port));
```

Bildirim ve ücretlendirme birimleri de tek kaynaktan koşar ve iki rolü birden üstlenir: uç olarak
çağrıldıklarında gövdeyi istekten alırlar, tüketici olarak koştuklarında aynı gövdeyi kütükten
okurlar. İşin kendisi iki durumda da aynı işlevdir; değişen yalnız gövdenin nereden geldiğidir.

```js
// birim.mjs — <ad: bildirim|ucret> <port> <yokla: 0|1>; hem uc olarak hem tuketici olarak kosar
import { createServer } from "node:http";
import { appendFileSync, existsSync, readFileSync } from "node:fs";
const [ad, port, yokla] = process.argv.slice(2);
if (!port) { console.log("kullanim: node birim.mjs <ad> <port> <yokla>"); process.exit(1); }
const IS = 25, ARALIK = 200;                     // birimin isi ve yoklama araligi (ms)
const SOZLESME = {                               // her birimin olay govdesinden okudugu alanlar
  bildirim: (o) => `${o.uyeId} nolu uyeye ${o.baslik} bildirimi gitti`,
  ucret: (o) => `${o.uyeId} nolu uye, nusha ${o.nushaId}, ${o.gecikmeGunu} gun gecikme`,
};
const isle = async (o) => {
  await new Promise((c) => setTimeout(c, IS));
  appendFileSync(`etki-${ad}.log`, `${o.sira} ${SOZLESME[ad](o)}\n`);
};

createServer(async (istek, yanit) => {
  let s = "";
  for await (const p of istek) s += p;
  await isle(JSON.parse(s));
  yanit.end("tamam");
}).listen(Number(port));

let okunan = 0, mesgul = false;
if (yokla === "1") setInterval(async () => {
  if (mesgul || !existsSync("olaylar.log")) return;
  mesgul = true;
  const satir = readFileSync("olaylar.log", "utf8").split("\n").filter(Boolean);
  while (okunan < satir.length) { await isle(JSON.parse(satir[okunan])); okunan += 1; }
  mesgul = false;
}, ARALIK);
```

#### Ölçüm Aracı

Araç iki iş yapar. Koşum kipinde üç istek gönderip turu bulur, sonra etkilerin oturmasını bekler
ve bir istek daha göndererek yanıtın döndüğü anda yeni etkinin görünüp görünmediğine bakar. Aynı
gözlemi 600 milisaniye sonra yineler. Sözleşme kipinde hiçbir süreç ayakta değildir; yalnız iki
kaynak dosya okunur ve olay gövdesindeki her alanın kaç dağıtım birimini bağladığı sayılır.

```js
// olc.mjs — <kip: eszamanli|olay|sozlesme>; ilk iki kip surecler ayaktayken kosar
import { existsSync, readFileSync } from "node:fs";
const IS = 25, UC = { odunc: 8951 }, BIRIM = ["bildirim", "ucret"];
const bekle = (ms) => new Promise((c) => setTimeout(c, ms));
const etki = () => BIRIM.reduce((s, a) => s + (existsSync(`etki-${a}.log`)
  ? readFileSync(`etki-${a}.log`, "utf8").split("\n").filter(Boolean).length : 0), 0);
const iste = () => fetch(`http://127.0.0.1:${UC.odunc}/is`).then((y) => y.text());
const [kip] = process.argv.slice(2);

if (kip === "eszamanli" || kip === "olay") {
  const { ardil } = await (await fetch(`http://127.0.0.1:${UC.odunc}/ardil`)).json();
  const sure = [];
  for (let i = 0; i < 3; i += 1) {
    const t = performance.now();
    await iste();
    sure.push(performance.now() - t);
  }
  const tur = Math.floor(sure.sort((a, b) => a - b)[1] / IS);
  await bekle(600);
  const once = etki();
  await iste();
  const hemen = etki() > once;                   // yanit dondugu anda yeni etki gorunur mu
  await bekle(600);
  const sonra = etki() > once;
  console.log(`${kip.padEnd(12)}${String(tur).padStart(11)}${String(1 + ardil).padStart(23)}` +
    `${String(ardil).padStart(27)}${(hemen ? "evet" : "hayir").padStart(19)}` +
    `${(sonra ? "evet" : "hayir").padStart(19)}${`${etki()}/8`.padStart(13)}`);
} else if (kip === "sozlesme") {
  const olayBlok = readFileSync("odunc.mjs", "utf8").match(/const olay = \{([\s\S]*?)\};/)[1];
  const alan = [...olayBlok.matchAll(/(\w+):/g)].map((m) => m[1]);
  const kaynak = readFileSync("birim.mjs", "utf8");
  const tuketici = {};
  for (const m of kaynak.matchAll(/^ {2}(\w+): \(o\) => (.*)$/gm)) {
    tuketici[m[1]] = [...m[2].matchAll(/o\.(\w+)/g)].map((x) => x[1]);
  }
  const ortak = [...kaynak.replace(/^ {2}\w+: \(o\) => .*$/gm, "").matchAll(/o\.(\w+)/g)]
    .map((x) => x[1]);
  const tk = Object.keys(tuketici);
  console.log(`${"olay alani".padEnd(14)}${"yayinci".padStart(9)}${"okuyan tuketici".padStart(17)}` +
    `${"ortak kod".padStart(11)}${"bagli dagitim birimi".padStart(22)}`);
  let toplam = 0;
  for (const a of alan) {
    const okuyan = tk.filter((t) => tuketici[t].includes(a));
    const o = ortak.includes(a) ? 1 : 0;
    const bagli = 1 + (o ? tk.length : okuyan.length);
    toplam += bagli;
    console.log(`${a.padEnd(14)}${"1".padStart(9)}${String(okuyan.length).padStart(17)}` +
      `${String(o).padStart(11)}${String(bagli).padStart(22)}`);
  }
  console.log(`${alan.length} alanli olay govdesi, ${tk.length} tuketici birimi; ` +
    `alan basina ortalama bagli birim ${(toplam / alan.length).toFixed(2)}`);
  console.log(`hicbir tuketicinin okumadigi alan: ` +
    `${alan.filter((a) => tk.every((t) => !tuketici[t].includes(a)) && !ortak.includes(a)).join(", ")}`);
}
```

```bash
rm -f olaylar.log etki-bildirim.log etki-ucret.log
printf '%-12s%11s%23s%27s%19s%19s%13s\n' kip "yanit turu" \
  "yanit yolundaki birim" "yayincinin tanidigi ardil" "yanit aninda etki" "600 ms sonra" "etki satiri"
node birim.mjs bildirim 8952 0 &
node birim.mjs ucret 8953 0 &
node odunc.mjs eszamanli 8951 8952 8953 &
sleep 1
node olc.mjs eszamanli
pkill -f "node birim.mjs" ; pkill -f "node odunc.mjs" ; sleep 0.3
rm -f etki-bildirim.log etki-ucret.log

node birim.mjs bildirim 8952 1 &
node birim.mjs ucret 8953 1 &
node odunc.mjs olay 8951 8952 8953 &
sleep 1
node olc.mjs olay
pkill -f "node birim.mjs" ; pkill -f "node odunc.mjs" ; sleep 0.3
echo
node olc.mjs sozlesme
```

```
kip          yanit turu  yanit yolundaki birim  yayincinin tanidigi ardil  yanit aninda etki       600 ms sonra  etki satiri
eszamanli             2                      3                          2               evet               evet          8/8
olay                  1                      1                          0              hayir               evet          8/8

olay alani      yayinci  okuyan tuketici  ortak kod  bagli dagitim birimi
tur                   1                0          0                     1
sira                  1                0          1                     3
uyeId                 1                2          0                     3
nushaId               1                1          0                     2
gecikmeGunu           1                1          0                     2
baslik                1                1          0                     2
6 alanli olay govdesi, 2 tuketici birimi; alan basina ortalama bagli birim 2.17
hicbir tuketicinin okumadigi alan: tur
```

#### Yanıt Yolundan Düşen İş

İlk iki kolon kazancı veriyor. Eşzamanlı gerçekleştirmede yanıt iki tur sürüyor: ödünç kaydının
yazılması ve ardılların işi. Olay tabanlı gerçekleştirmede aynı istek bir turda dönüyor, çünkü
yanıt yolunda yalnız kütüğe bir satır ekleme kalıyor. Son kolon işin kaybolmadığını gösteriyor:
her iki gerçekleştirmede de dört istek sekiz etki satırı üretiyor.

Üçüncü kolon kazancın asıl büyüklüğünü veriyor. Yanıt yolundaki dağıtım birimi sayısı üçten bire
iniyor. Önceki dersin ölçüsüyle okunduğunda bunun anlamı açıktır: bildirim ve ücretlendirme
birimleri ödünç ucunun bağımlılık kapanışından çıkmıştır. Eşzamanlı gerçekleştirmede bu iki
birimden birinin kapanması ödünç isteğini düşürürken, olay tabanlı gerçekleştirmede kapanma yanıt
yolunda hiç görünmez.

#### Sözleşmenin Yayılması

Dördüncü kolon bedelin ilk yarısını veriyor. Eşzamanlı gerçekleştirmede yayıncı iki ardıl adresi
taşır: kimi çağırdığını bilir, çağrı yerinden okunabilir. Olay kipinde bu sayı sıfırdır. Yayıncı
artık tüketicilerini tanımaz; bu tam olarak istenen gevşek bağlanmadır ve tam olarak sözleşme
yükünün kaynağıdır.

İkinci tablo bu yükü sayıya çeviriyor. Olay gövdesinde altı alan var ve her alan ortalama 2,17
dağıtım birimini bağlıyor. Üyelik kimliğini iki tüketici birden okuyor: alanın adı ya da anlamı
değiştiğinde üç birim birden dokunulmak zorundadır. Sıra numarası hiçbir tüketicinin sözleşme
satırında görünmüyor ama ortak işleme kodunda okunuyor, dolayısıyla o da üç birimi bağlıyor.

Son satır eşzamansız tümleştirmenin karakteristik sorunudur: `tur` alanını hiçbir tüketici
okumuyor. Yayıncı gövdeye koyduğu her alanı sonsuza kadar savunmak zorundadır, çünkü hangi alanın
okunduğunu göremez. Eşzamanlı çağrıda bu bilgi çağrı yerinden okunabilirdi; kütüğün karşı tarafında
okunamaz. Sözleşme evrimi ve tüketici koruması bu kursun son konusunda ele alınacaktır.

#### Tutarlılık Penceresi

Beşinci ve altıncı kolon yeni arıza kipini gösteriyor. Eşzamanlı gerçekleştirmede yanıt döndüğü an
etki hazırdır: çağıran, ücret satırının yazıldığını bilerek döner. Olay tabanlı gerçekleştirmede
yanıt döndüğü anda etki yoktur ve 600 milisaniye sonra vardır. Aradaki aralık **tutarlılık
penceresi**dir ve bu düzenekte üst sınırı bellidir: yoklama aralığı artı tüketicinin iş süresi,
yani 225 milisaniye. Ölçüm bu sınırı iki uçtan doğruluyor — pencerenin başında etki yok, sonunda
var.

Pencerenin içinde sistem tutarsızdır ve tutarsızlık gözlenebilir: o aralıkta üyenin hesabını
okuyan bir istek ödünç kaydını görür, ücret satırını görmez. Bu, eşzamanlı gerçekleştirmede
bulunmayan bir durumdur ve kaldırılamaz; yalnızca daraltılabilir ve arayüzde karşılanabilir.
İkinci yeni arıza kipi de aynı yerden çıkar: tüketici kapalıyken olaylar kütükte birikir, yanıt
süresi hiç bozulmaz ve arıza yanıt yolunda görünmez. Sorun ancak biriken olaylara bakan biri
varsa fark edilir.

Üçlü şöyle toparlanır. **Ucuzlayan:** yanıt turu 2'den 1'e, yanıt yolundaki birim 3'ten 1'e indi;
iki tüketici birimi bağımlılık kapanışından çıktı. **Pahalılaşan:** sözleşme üç dağıtım birimine
yayıldı, alan başına ortalama 2,17 birim bağlandı ve yayıncının tanıdığı ardıl sayısı sıfıra
düştüğü için hangi alanın okunduğu koddan görünmez oldu. **Doğan arıza kipi:** tutarlılık
penceresi ve yanıt yolunda görünmeyen tüketici arızası.

#### Özet

- Aynı iş akışı olay tabanlı kurulduğunda yanıt turu 2'den 1'e, yanıt yolundaki dağıtım birimi
  3'ten 1'e iniyor; dört istek her iki gerçekleştirmede de 8 etki satırı üretiyor.
- Yayıncının tanıdığı ardıl sayısı 2'den 0'a düşüyor; gevşek bağlanmanın kaynağı da sözleşme
  yükünün kaynağı da bu sayıdır.
- Altı alanlı olay gövdesinde alan başına ortalama 2,17 dağıtım birimi bağlı; üyelik kimliği üç
  birimi birden bağlıyor ve `tur` alanını hiçbir tüketici okumuyor.
- Tutarlılık penceresi ölçümle iki uçtan doğrulandı: yanıt anında etki yok, 600 ms sonra var; üst
  sınır yoklama aralığı artı tüketici işidir.
- Eşzamansız tümleştirme tüketici arızasını yanıt yolundan çıkarır; arıza artık yanıt süresinde
  değil, biriken olaylarda görünür.

#### Sonraki Adım

İki konu boyunca sınırın iki yanı çalışıldı: sınırın nereden geçeceği, karşıya nasıl çağrı
yapılacağı ve bazı çağrıların hiç yapılmayıp olaya dönüştürülmesi. Bütün bu düzenlemelerde bir
taraf hiç ele alınmadı — dışarısı. İstemci hâlâ servislerin adreslerini tek tek biliyor, her
servis kendi kimlik denetimini kendi kodunda taşıyor ve aynı hız sınırı kuralı birden çok yerde
yazılı duruyor. Sonraki ders bu yinelenmeyi sayar: hangi kenar sorumluluğu kaç serviste kaç satır
olarak tekrarlanmış, bu sorumluluklar tek bir yere taşındığında kaç dosya ve kaç uç değişiyor ve
istek başına kaç atlama ekleniyor.

### API Ağ Geçidi

Source: https://academia.sh/tr/kurslar/servis-mimarileri/api-ag-gecidi

Kenar sorumluluklarının uygulama tarafında toplanmasının ölçülmesi: kimlik ve hız sınırı kodunun kaç serviste kaç satır olarak yinelendiği, sorumluluk ağ geçidine taşındığında değişen dosya, dışarıya açık uç ve istemci çağrısı sayısı, birleştirmenin turlara etkisi ve istek başına eklenen atlama.

Önceki iki ders sınırın iki yanını çalıştı: sınırın nereden geçeceği, karşıya nasıl çağrı
yapılacağı, hangi çağrının hiç yapılmayıp olaya dönüşeceği. Bir taraf hiç ele alınmadı — dışarısı.
İstemci servislerin adreslerini tek tek biliyor, her servis kendi kimlik denetimini kendi kodunda
taşıyor ve aynı hız sınırı kuralı birden çok yerde yazılı duruyor.

API ağ geçidi, ön yüz için backend ve kapı bekçisi kalıpları Trafik Katmanı kursunda kuruldu ve
trafik tarafında ölçüldü; o ölçüm burada tekrarlanmaz. Buradaki soru uygulama tarafındadır: aynı
kenar sorumluluğu kaç serviste kaç satır olarak duruyor, tek yere taşındığında kaç dosya ve kaç uç
değişiyor, istek başına kaç atlama ekleniyor.

Through-line ödünç sayfasıdır: bir sayfanın çizilmesi için katalog, üyelik ve ödünç servislerinin
üçünden de veri gerekir.

#### Aynı Sorumluluk, İki Yerleşim

Aşağıdaki blok aynı üç servisi iki biçimde yazar. Kenar sorumluluğunu taşıyan satırlar kaynakta
işaretlidir; yalın biçim aynı dosyalardan o satırların çıkarılmasıyla üretilir ve işaretli satırlar
tek bir ağ geçidi sürecinde bir kez yazılır. İki biçim de gerçekten koşan kaynaktır.

```js
// kur.mjs — ayni uc servisi iki bicimde yazar: kenar sorumlulugu serviste ve gecitte
import { mkdirSync, writeFileSync } from "node:fs";
const SERVIS = { katalog: 8961, uyelik: 8962, odunc: 8963 }, GECIT = 8960;

const servis = (ad, port) => [
  `// servis-${ad}.mjs — odunc sayfasinin bir parcasini ureten servis`,
  'import { createServer } from "node:http";',
  `const PORT = ${port}, IS = 25;`,
  "let islenen = 0, sifirla = () => {};",
  "const SAYAC = new Map(), SINIR = 3; sifirla = () => SAYAC.clear();          // kenar: hiz siniri",
  "createServer(async (istek, yanit) => {",
  '  if (istek.url === "/sayac") { yanit.end(String(islenen)); return; }',
  '  if (istek.url === "/sifirla") { islenen = 0; sifirla(); yanit.end("s"); return; }',
  '  const uye = istek.headers["x-uye"];                                       // kenar: kimlik',
  '  if (!uye) { yanit.statusCode = 401; yanit.end("kimlik yok"); return; }    // kenar: kimlik',
  "  const n = (SAYAC.get(uye) ?? 0) + 1; SAYAC.set(uye, n);                   // kenar: hiz siniri",
  '  if (n > SINIR) { yanit.statusCode = 429; yanit.end("hiz siniri"); return; } // kenar: hiz siniri',
  "  islenen += 1;",
  "  await new Promise((c) => setTimeout(c, IS));",
  `  yanit.end(JSON.stringify({ ad: "${ad}" }));`,
  "}).listen(PORT);",
];
const gecit = [
  "// gecit.mjs — kenar sorumluluklarini toplayan ve yanitlari birlestiren surec",
  'import { createServer } from "node:http";',
  `const PORT = ${GECIT}, ARDIL = [${Object.values(SERVIS).join(", ")}];`,
  "let sifirla = () => {};",
  "const SAYAC = new Map(), SINIR = 3; sifirla = () => SAYAC.clear();          // kenar: hiz siniri",
  "createServer(async (istek, yanit) => {",
  '  if (istek.url === "/sifirla") { sifirla(); yanit.end("s"); return; }',
  '  const uye = istek.headers["x-uye"];                                       // kenar: kimlik',
  '  if (!uye) { yanit.statusCode = 401; yanit.end("kimlik yok"); return; }    // kenar: kimlik',
  "  const n = (SAYAC.get(uye) ?? 0) + 1; SAYAC.set(uye, n);                   // kenar: hiz siniri",
  '  if (n > SINIR) { yanit.statusCode = 429; yanit.end("hiz siniri"); return; } // kenar: hiz siniri',
  "  const parca = await Promise.all(ARDIL.map((p) =>",
  '    fetch(`http://127.0.0.1:${p}/veri`).then((y) => y.json())));            // kenar: birlestirme',
  "  yanit.end(JSON.stringify({ birlesik: parca }));",
  "}).listen(PORT);",
];
const kenar = (satirlar) => satirlar.filter((s) => s.includes("// kenar:")).length;
const yaz = (dizin, dosya, satirlar) => {
  mkdirSync(dizin, { recursive: true });
  writeFileSync(`${dizin}/${dosya}`, `${satirlar.join("\n")}\n`);
  return satirlar.length;
};

let kenarliSatir = 0, kenarliIsaret = 0, yalinSatir = 0, yalinIsaret = 0;
for (const [ad, port] of Object.entries(SERVIS)) {
  const tam = servis(ad, port), sade = tam.filter((s) => !s.includes("// kenar:"));
  kenarliSatir += yaz("kenarli", `servis-${ad}.mjs`, tam);
  kenarliIsaret += kenar(tam);
  yalinSatir += yaz("yalin", `servis-${ad}.mjs`, sade);
  yalinIsaret += kenar(sade);
}
const gecitSatir = yaz("yalin", "gecit.mjs", gecit);
const n = Object.keys(SERVIS).length;
console.log(`${"bicim".padEnd(22)}${"dosya".padStart(7)}${"satir".padStart(7)}` +
  `${"kenar satiri".padStart(14)}${"kural degisince dokunulan".padStart(27)}${"disariya acik uc".padStart(18)}`);
console.log(`${"kenar servislerde".padEnd(22)}${String(n).padStart(7)}${String(kenarliSatir).padStart(7)}` +
  `${String(kenarliIsaret).padStart(14)}${String(n).padStart(27)}${String(n).padStart(18)}`);
console.log(`${"kenar gecitte".padEnd(22)}${String(n + 1).padStart(7)}` +
  `${String(yalinSatir + gecitSatir).padStart(7)}${String(yalinIsaret + kenar(gecit)).padStart(14)}` +
  `${"1".padStart(27)}${"1".padStart(18)}`);
console.log(`kenar sorumlulugu ${n} serviste ${kenarliIsaret / n} satir olarak yinelenmisti; ` +
  `gecitte ${kenar(gecit)} satirda tek kopya`);
```

```
bicim                   dosya  satir  kenar satiri  kural degisince dokunulan  disariya acik uc
kenar servislerde           3     48            15                          3                 3
kenar gecitte               4     48             6                          1                 1
kenar sorumlulugu 3 serviste 5 satir olarak yinelenmisti; gecitte 6 satirda tek kopya
```

Toplam satır sayısı iki biçimde de aynıdır; taşınan kod yok olmadı, yer değiştirdi. Değişen üç
sayı var: yinelenen kenar satırı 15'ten 6'ya indi, kimlik ya da hız sınırı kuralı değiştiğinde
dokunulacak dosya sayısı 3'ten 1'e düştü, dışarıya açık uç sayısı 3'ten 1'e indi. Ağ geçidinin
altıncı işaretli satırı servislerde hiç bulunmayan bir sorumluluktur: birleştirme.

#### Koşumdaki Fark

**SS4 — hız sınırı üye başına üç istektir.** Gerekçe: değer gerçek bir eşik değil, sınırın on iki
istekte görünür olmasını sağlayan bir ölçü büyüklüğüdür. Ölçülen nicelik sınırın değeri değil,
sınırın kaç yerde uygulandığı ve aynı kuralın dışarıya kaç istek hakkı tanıdığıdır.

```js
// olc.mjs — <olcum: tur|sinir> <kip: dogrudan|gecit>; surecler ayaktayken kosar
const IS = 25, S = [8961, 8962, 8963], GECIT = 8960;
const BASLIK = { headers: { "x-uye": "7" } };
let cagri = 0, servisCagrisi = 0;
const uc = new Set();
const al = async (p, yol) => {
  cagri += 1; uc.add(p);
  if (S.includes(p)) servisCagrisi += 1;
  const y = await fetch(`http://127.0.0.1:${p}${yol}`, BASLIK);
  await y.text();
  return y.status;
};
const kimliksiz = async (p, yol) => {         // x-uye basligi olmadan istek
  const y = await fetch(`http://127.0.0.1:${p}${yol}`);
  await y.text();
  return y.status;
};
const sifirla = async () => {
  for (const p of [...S, GECIT]) await fetch(`http://127.0.0.1:${p}/sifirla`).catch(() => {});
};
const ulasan = async () => {
  let t = 0;
  for (const p of S) t += Number(await (await fetch(`http://127.0.0.1:${p}/sayac`)).text());
  return t;
};
const [olcum, kip] = process.argv.slice(2);
const YOL = {
  "dogrudan-sirali": async () => { for (const p of S) await al(p, "/veri"); },
  "dogrudan-paralel": () => Promise.all(S.map((p) => al(p, "/veri"))),
  gecit: () => al(GECIT, "/sayfa"),
};

if (olcum === "tur") {
  console.log(`${"istemci yolu".padEnd(18)}${"istemci cagrisi".padStart(16)}${"ag atlamasi".padStart(13)}` +
    `${"dokunulan uc".padStart(14)}${"servise ulasan".padStart(16)}${"olculen tur".padStart(13)}`);
  for (const ad of Object.keys(YOL).filter((a) => a.startsWith(kip))) {
    const sure = [];
    for (let i = 0; i < 3; i += 1) {
      await sifirla();
      const t = performance.now();
      await YOL[ad]();
      sure.push(performance.now() - t);
    }
    await sifirla();
    cagri = 0; servisCagrisi = 0; uc.clear();
    await YOL[ad]();
    const u = await ulasan();
    console.log(`${ad.padEnd(18)}${String(cagri).padStart(16)}` +
      `${String(cagri + u - servisCagrisi).padStart(13)}${String(uc.size).padStart(14)}` +
      `${String(u).padStart(16)}${String(Math.floor(sure.sort((a, b) => a - b)[1] / IS)).padStart(13)}`);
  }
} else if (olcum === "sinir") {
  await sifirla();
  let kabul = 0, red = 0;
  for (let i = 0; i < 12; i += 1) {
    const durum = kip === "gecit" ? await al(GECIT, "/sayfa") : await al(S[i % S.length], "/veri");
    if (durum === 200) kabul += 1; else red += 1;
  }
  console.log(`hiz siniri (uye basina 3): 12 dis istek -> ${kabul} kabul, ${red} red, ` +
    `servise ulasan ${await ulasan()}`);
  await sifirla();
  const durum = kip === "gecit" ? [await kimliksiz(GECIT, "/sayfa")]
    : await Promise.all(S.map((p) => kimliksiz(p, "/veri")));
  console.log(`kimliksiz istek: ${durum.length} uc denetledi -> ` +
    `${durum.filter((d) => d === 401).length} kez 401, servise ulasan ${await ulasan()}`);
}
```

```bash
echo "-- kenar sorumlulugu her serviste --"
for a in katalog uyelik odunc; do node kenarli/servis-$a.mjs & done
sleep 1
node olc.mjs tur dogrudan
node olc.mjs sinir dogrudan
pkill -f "kenarli/servis" ; sleep 0.3

echo
echo "-- kenar sorumlulugu gecitte --"
for a in katalog uyelik odunc; do node yalin/servis-$a.mjs & done
node yalin/gecit.mjs &
sleep 1
node olc.mjs tur gecit
node olc.mjs sinir gecit
pkill -f "yalin/servis" ; pkill -f "yalin/gecit"
```

```
-- kenar sorumlulugu her serviste --
istemci yolu       istemci cagrisi  ag atlamasi  dokunulan uc  servise ulasan  olculen tur
dogrudan-sirali                  3            3             3               3            3
dogrudan-paralel                 3            3             3               3            1
hiz siniri (uye basina 3): 12 dis istek -> 9 kabul, 3 red, servise ulasan 9
kimliksiz istek: 3 uc denetledi -> 3 kez 401, servise ulasan 0

-- kenar sorumlulugu gecitte --
istemci yolu       istemci cagrisi  ag atlamasi  dokunulan uc  servise ulasan  olculen tur
gecit                            1            4             1               3            1
hiz siniri (uye basina 3): 12 dis istek -> 3 kabul, 9 red, servise ulasan 9
kimliksiz istek: 1 uc denetledi -> 1 kez 401, servise ulasan 0
```

#### Birleştirme ve Turlar

İlk iki satır istemcinin ödünç sayfasını kendisi topladığı durumdur. İstemci üç çağrı yapıyor, üç
ayrı ucu tanıyor ve üç servise ulaşıyor. Çağrıları sırayla yaparsa sayfa üç turda, paralel yaparsa
bir turda çiziliyor. Aradaki fark istemcinin elindedir ve sunucu tarafından denetlenemez: aynı
sayfanın gecikmesi istemcinin nasıl yazıldığına bağlıdır.

Üçüncü satır aynı sayfayı ağ geçidi üzerinden çiziyor. İstemci bir çağrı yapıyor, tek uç tanıyor,
servislere yine üç istek ulaşıyor ve sayfa bir turda dönüyor. Birleştirme sunucu tarafına
geçtiğinde en kötü durum en iyi duruma eşitlenir: paralel çağrıyı yapmayı istemcinin bilmesi
gerekmez, ağ geçidi zaten yapar. **Kazanılan şey ortalama gecikme değil, gecikmenin
denetlenebilirliğidir.**

Dördüncü kolon bedeli veriyor: istek başına ağ atlaması üçten dörde çıkıyor. Doğrudan yerleşimde
istemcinin üç çağrısı doğrudan servislere gidiyordu; geçitli yerleşimde bir dış atlama ve üç iç
atlama var. Ağ geçidi kendi işini yapmadığı için bu atlama bir tur eklemedi, ama gecikme
bütçesinden pay aldı.

#### Aynı Kural, İki Farklı Hak

Hız sınırı satırları dersin en keskin sayısını üretiyor. Kural iki yerleşimde birebir aynıdır:
üye başına üç istek. On iki istek gönderildiğinde doğrudan yerleşim dokuzunu kabul ediyor, geçitli
yerleşim üçünü.

Nedeni sınırın **birimidir**. Servis başına sayan bir sınır, üç servisli bir sistemde üyeye üç
değil dokuz istek hakkı tanır; üye isteklerini servislere dağıttığı sürece hiçbir sınıra
çarpmaz. Ağ geçidinde sayan sınır, sayfa isteğine göre sayar. Son kolon iki yerleşimin servis
yükünü eşitliyor: her iki durumda da servislere dokuz istek ulaşıyor. Yani aynı yazılı kural, aynı
servis yükü altında dışarıya üç kat farklı hak tanıyor. **Bir kenar kuralı, uygulandığı yer
değişmeden okunamaz.**

Kimlik satırı aynı şeyi denetim tarafında gösteriyor. Kimliksiz istek doğrudan yerleşimde üç ayrı
uçta üç kez reddediliyor, geçitli yerleşimde bir kez. Üç ret aynı kuralın üç ayrı kopyasıdır ve
kopyaların birbirinden ayrılması yalnız zaman meselesidir.

#### Üçlü

**Neyi ucuzlattı.** Yinelenen kenar satırı 15'ten 6'ya, kural değiştiğinde dokunulan dosya 3'ten
1'e, dışarıya açık uç 3'ten 1'e, istemci çağrısı 3'ten 1'e indi; sıralı çalışan bir istemcide
sayfa 3 turdan 1 tura düştü.

**Neyi pahalılaştırdı.** İstek başına atlama 3'ten 4'e çıktı, sisteme yeni bir dağıtım birimi ve
yeni bir süreç eklendi. Ağ geçidi üç servisin uçlarını kendi kaynağında taşır: bir servisin uç
yolu ya da yanıt biçimi değiştiğinde dokunulan dosya sayısı yeniden ikiye çıkar. Kenar
sorumluluğunda kazanılan tek dosya, sözleşme tarafında geri verilmiştir.

**Hangi yeni arıza kipi doğdu.** Dış trafiğin tamamı tek süreçten geçiyor. Önceki dersin ölçüsüyle
söylenirse, ağ geçidinin bağımlılık kapanışı bütün giriş uçlarıdır: kapandığında etkilenen istek
payı birdir. Doğrudan yerleşimde katalog kapandığında sayfa eksik çiziliyordu; geçitli yerleşimde
geçit kapandığında sayfa hiç çizilmiyor. İkinci arıza kipi daha sessizdir: hız sınırı ve kimlik
artık servislerde durmadığı için, geçidi atlayan bir çağrı hiçbir denetimden geçmez. Yalın
servisler kendilerini savunmaz; savunma tek bir yerin doğru yapılandırılmasına bağlıdır.

#### Özet

- Kenar sorumluluğu üç serviste 5'er satır olarak yinelenmişti (toplam 15); ağ geçidinde 6 satırda
  tek kopya oldu ve toplam satır sayısı değişmedi.
- Kimlik ya da hız sınırı kuralı değiştiğinde dokunulan dosya 3'ten 1'e, dışarıya açık uç 3'ten
  1'e, istemci çağrısı 3'ten 1'e indi.
- Birleştirme sunucuya geçtiğinde sıralı istemcinin 3 turu 1 tura indi; karşılığında istek başına
  ağ atlaması 3'ten 4'e çıktı ve yeni bir dağıtım birimi eklendi.
- Aynı "üye başına 3 istek" kuralı, servis başına sayıldığında 12 istekten 9'unu, ağ geçidinde
  sayıldığında 3'ünü kabul ediyor; servise ulaşan istek her iki durumda da 9.
- Ağ geçidi bütün dış trafiğin bağımlılık kapanışıdır ve yalın servisler kendilerini savunmaz;
  denetim tek bir yerin doğruluğuna bağlanmıştır.

#### Sonraki Adım

Ağ geçidi tek bir sayfayı topladı ve o sayfanın tek bir alıcısı olduğu varsayıldı. Kütüphanenin
ödünç sayfası ise birden çok yerde çiziliyor: raf başındaki dokunmatik uçbirim yalnız nüsha
durumunu ister, elde taşınan bir istemci küçük bir özet ister, personel ekranı üyenin bütün
geçmişini ister. Üçü de aynı geçitten geçtiğinde geçidin birleştirme kodu üç ayrı biçimi birden
taşımak zorunda kalır ve her istemci değişikliği ortak bir dosyaya dokunur. Sonraki ders bu yükü
sayar ve tek bir genel geçit yerine istemci başına ayrı bir toplama katmanı kurmanın kod tarafında
neyi ucuzlatıp neyi pahalılaştırdığını ölçer.

### Ön Yüz İçin Backend

Source: https://academia.sh/tr/kurslar/servis-mimarileri/on-yuz-icin-backend

İstemciye özel toplama katmanının gerçekleştirimi: tek genel uçtan beslenen iki istemcinin taşıdığı fazla veri ile yaptığı istek sayısının ölçülmesi, ayrı toplama katmanlarının bu iki sayıya etkisi ve ödediği yinelenen mantık satırı ile sessiz ayrışma riski.

Önceki ders kenar sorumluluklarını tek bir yerde topladı: doğrulama, sınırlama ve yönlendirme
artık her servisin içinde değil, önündeki tek katmanda duruyor. O katmanın sessiz kabulü şudur:
sınırın arkasındaki servisler ne döndürüyorsa istemciye o gider. Oysa aynı uca bağlanan
istemciler aynı yanıtı istemiyor. Kütüphane ödünç sisteminin web listesi bir üyenin bütün ödünç
kayıtlarını kitap adı, yazar ve raf koduyla birlikte gösteriyor; mobil özet ekranı yalnız iki
sayı ve bir başlık gösteriyor. İkisi de aynı uçtan besleniyor.

Bu dersin işi, o durumu ölçmek ve **ön yüz için backend** (backend for frontend) yerleşimiyle
karşılaştırmaktır. Kalıbın tanımı Trafik Katmanı kursunda kuruldu ve orada trafik tarafından
ölçüldü; burada ölçülen şey uygulama tarafıdır: istemcinin taşıdığı bayt, yaptığı istek ve
toplama katmanının kod olarak ödettiği tekrar.

Düzeneğin varsayımları: **SS10** iki istemci vardır (web listesi, mobil özet) ve üçüncü bir
istemci yoktur. **SS11** katalog, üyelik ve ödünç üç ayrı süreçtir; katalog kaydı on alan
taşır. **SS12** ölçüm süre ölçmez; istemci ağındaki istek sayısını, taşınan baytı ve alan
sayısını sayar.

#### Üç Aşağı Servis ve Tek Genel Uç

Üç servis tek dosyadan, üç ayrı süreç olarak koşuyor. Her biri kendi istek ve bayt sayacını
tutuyor; bu sayaçlar aşağı tarafın gördüğü yükü ölçmeye yarayacak.

```js
// bff/servis.mjs — uc asagi servis, tek dosya: node bff/servis.mjs katalog 8741
import { createServer } from "node:http";

const [ad, port] = [process.argv[2], Number(process.argv[3])];
const KITAP = {
  "978-0": { isbn: "978-0", ad: "Kayip Zamanin Izinde", yazar: "M. Proust", cevirmen: "R. Hakmen",
    yayinevi: "Yapi Kredi", basimYili: 1913, sayfa: 542, tur: "roman", dil: "tr", raf: "PQ-2631" },
  "978-1": { isbn: "978-1", ad: "Sessiz Ev", yazar: "O. Pamuk", cevirmen: null,
    yayinevi: "Iletisim", basimYili: 1983, sayfa: 320, tur: "roman", dil: "tr", raf: "PL-248" },
  "978-2": { isbn: "978-2", ad: "Algoritmalara Giris", yazar: "T. Cormen", cevirmen: "A. Yilmaz",
    yayinevi: "MIT", basimYili: 2009, sayfa: 1312, tur: "ders", dil: "tr", raf: "QA-76" },
  "978-3": { isbn: "978-3", ad: "Bozkirkurdu", yazar: "H. Hesse", cevirmen: "K. Sipal",
    yayinevi: "Can", basimYili: 1927, sayfa: 224, tur: "roman", dil: "tr", raf: "PT-2617" },
};
const UYE = { uyeNo: "U-41", ad: "Zeynep", soyad: "Aydin", eposta: "z.aydin@ornek.tr",
  telefon: "+90-555-0000", adres: "Kordon Cad. 12, Izmir", uyelikTarihi: "2019-04-02",
  uyelikTuru: "arastirmaci", cezaBakiyesi: 0, kartDurumu: "etkin" };
const ODUNC = [
  { isbn: "978-0", alinma: "2026-02-10", sonTeslim: "2026-03-10", uzatma: 1 },
  { isbn: "978-1", alinma: "2026-02-24", sonTeslim: "2026-03-24", uzatma: 0 },
  { isbn: "978-2", alinma: "2026-02-14", sonTeslim: "2026-03-14", uzatma: 2 },
  { isbn: "978-3", alinma: "2026-03-02", sonTeslim: "2026-04-01", uzatma: 0 },
];

let istek = 0, bayt = 0;                        // servis basina istek ve gonderilen govde bayti
const VER = { katalog: (u) => KITAP[u.split("/")[2]], uyelik: () => UYE, odunc: () => ODUNC };

createServer((q, y) => {
  if (q.url === "/sayac") {
    y.writeHead(200, { "Content-Type": "application/json" });
    return y.end(JSON.stringify({ ad, istek, bayt }));
  }
  const govde = JSON.stringify(VER[ad](q.url));
  istek += 1;
  bayt += Buffer.byteLength(govde);
  y.writeHead(200, { "Content-Type": "application/json" });
  y.end(govde);
}).listen(port, "127.0.0.1", () => console.log(`${ad} 127.0.0.1:${port}`));
```

Genel uç, üyelik ile ödünç kayıtlarını birleştirip olduğu gibi döndürüyor. Katalog verisini
eklemiyor, çünkü katalog başka bir sınırın arkasında ve bu uç ondan önce yazıldı. Sonuç şu:
kitap adını göstermek isteyen her istemci, listeyi aldıktan sonra katalog servisine kendisi
gitmek zorunda.

```js
// bff/genel-uc.mjs — tek genel uc 8744: iki istemci de burayi cagirir, ayni govdeyi alir
import { createServer } from "node:http";

const AL = async (u) => (await fetch(u)).json();

createServer(async (q, y) => {
  const uyeNo = q.url.split("/")[2];
  const uye = await AL("http://127.0.0.1:8742/uye/" + uyeNo);
  const odunc = await AL(`http://127.0.0.1:8743/uye/${uyeNo}/odunc`);
  const govde = JSON.stringify({ uye, odunc });
  y.writeHead(200, { "Content-Type": "application/json" });
  y.end(govde);
}).listen(8744, "127.0.0.1", () => console.log("genel-uc 127.0.0.1:8744"));
```

#### İki İstemci, İki Toplama Katmanı

İkinci gerçekleştirimde her istemcinin kendi toplama katmanı var. Web katmanı listeyi kurup
gecikme gününü hesaplıyor; mobil katmanı aynı veriden iki sayı ve bir başlık çıkarıyor. İkisi
de ayrı bir dağıtım birimidir ve birbirinin kodunu kullanmıyor.

```js
// bff/bff-web.mjs — web listesi icin toplama katmani 8745
import { createServer } from "node:http";

const BUGUN = "2026-03-16";
const KATALOG = "http://127.0.0.1:8741/kitap/";
const UYELIK = "http://127.0.0.1:8742/uye/";
const ODUNC = "http://127.0.0.1:8743/uye/";
const AL = async (u) => (await fetch(u)).json();
const gunFarki = (a, b) => Math.round((Date.parse(a) - Date.parse(b)) / 86400000);

createServer(async (q, y) => {
  const uyeNo = q.url.split("/")[2];
  const uye = await AL(UYELIK + uyeNo);
  const odunc = await AL(`${ODUNC}${uyeNo}/odunc`);
  const kitap = await Promise.all(odunc.map((o) => AL(KATALOG + o.isbn)));
  const satir = odunc.map((o, i) => ({
    baslik: kitap[i].ad, yazar: kitap[i].yazar, raf: kitap[i].raf,
    teslim: o.sonTeslim, gecikmeGunu: Math.max(0, gunFarki(BUGUN, o.sonTeslim)),
  }));
  const govde = JSON.stringify({ okuyucu: `${uye.ad} ${uye.soyad}`, satir });
  y.writeHead(200, { "Content-Type": "application/json" });
  y.end(govde);
}).listen(8745, "127.0.0.1", () => console.log("bff-web 127.0.0.1:8745"));
```

```js
// bff/bff-mobil.mjs — mobil ozet icin toplama katmani 8746
import { createServer } from "node:http";

const BUGUN = "2026-03-16";
const KATALOG = "http://127.0.0.1:8741/kitap/";
const UYELIK = "http://127.0.0.1:8742/uye/";
const ODUNC = "http://127.0.0.1:8743/uye/";
const AL = async (u) => (await fetch(u)).json();
const gunFarki = (a, b) => Math.round((Date.parse(a) - Date.parse(b)) / 86400000);

createServer(async (q, y) => {
  const uyeNo = q.url.split("/")[2];
  const uye = await AL(UYELIK + uyeNo);
  const odunc = await AL(`${ODUNC}${uyeNo}/odunc`);
  const gecikmis = odunc.filter((o) => gunFarki(BUGUN, o.sonTeslim) > 0).length;
  const yakin = odunc.slice().sort((a, b) => Date.parse(a.sonTeslim) - Date.parse(b.sonTeslim))[0];
  const kitap = await AL(KATALOG + yakin.isbn);
  const govde = JSON.stringify({ okuyucu: uye.ad, gecikmis,
    enYakin: { baslik: kitap.ad, kalanGun: gunFarki(yakin.sonTeslim, BUGUN) } });
  y.writeHead(200, { "Content-Type": "application/json" });
  y.end(govde);
}).listen(8746, "127.0.0.1", () => console.log("bff-mobil 127.0.0.1:8746"));
```

#### Ölçüm

Ölçüm betiği dört durumu koşuyor: iki istemci, iki gerçekleştirim. Her durumda istemcinin
yaptığı istek sayısını, aldığı baytı ve aldığı gövdedeki yaprak alan sayısını topluyor;
istemcinin gerçekten okuduğu adlara göre gövdeyi budayıp kullanılmayan baytı çıkarıyor. Son
sütun sızıntı ölçüsüdür: istemcinin okuduğu adlardan kaçı aşağı servislerin kendi alan adıdır.

```js
// bff/olc.mjs — iki istemci x iki gerceklestirme: istek, bayt, fazla veri, yinelenen satir
import { readFileSync } from "node:fs";

const al = async (u) => {
  const m = await (await fetch(u)).text();
  return { bayt: Buffer.byteLength(m), veri: JSON.parse(m) };
};
const SAYAC = [8741, 8742, 8743].map((p) => `http://127.0.0.1:${p}/sayac`);
const toplam = async () => (await Promise.all(SAYAC.map(al)))
  .reduce((s, x) => s + x.veri.istek, 0);

// Istemcilerin ekrana koymak icin gercekten okudugu alan adlari
const ALAN = {
  "web/genel uc": ["ad", "soyad", "isbn", "sonTeslim", "yazar", "raf"],
  "mobil/genel uc": ["ad", "isbn", "sonTeslim"],
  "web/bff": ["okuyucu", "satir"],
  "mobil/bff": ["okuyucu", "gecikmis", "enYakin"],
};
const SENARYO = {
  "web/genel uc": async () => {                    // genel uc + her kitap icin katalog cagrisi
    const g = await al("http://127.0.0.1:8744/uye/U-41/ozet");
    const k = await Promise.all(g.veri.odunc.map((o) => al(`http://127.0.0.1:8741/kitap/${o.isbn}`)));
    return { istek: 1 + k.length, bayt: g.bayt + k.reduce((s, x) => s + x.bayt, 0),
      govde: { ...g.veri, kitap: k.map((x) => x.veri) } };
  },
  "mobil/genel uc": async () => {                  // ayni genel uc + yalniz en yakin kitap
    const g = await al("http://127.0.0.1:8744/uye/U-41/ozet");
    const y = g.veri.odunc.slice().sort((a, b) => Date.parse(a.sonTeslim) - Date.parse(b.sonTeslim))[0];
    const k = await al(`http://127.0.0.1:8741/kitap/${y.isbn}`);
    return { istek: 2, bayt: g.bayt + k.bayt, govde: { ...g.veri, kitap: [k.veri] } };
  },
  "web/bff": async () => {
    const g = await al("http://127.0.0.1:8745/uye/U-41/liste");
    return { istek: 1, bayt: g.bayt, govde: g.veri };
  },
  "mobil/bff": async () => {
    const g = await al("http://127.0.0.1:8746/uye/U-41/ozet");
    return { istek: 1, bayt: g.bayt, govde: g.veri };
  },
};

const yaprak = (v) => Array.isArray(v) ? v.reduce((s, e) => s + yaprak(e), 0)
  : v && typeof v === "object" ? Object.values(v).reduce((s, e) => s + yaprak(e), 0) : 1;
const buda = (v, k) => {                           // yalniz istemcinin okudugu adlari birak
  if (Array.isArray(v)) return v.map((e) => buda(e, k));
  if (v && typeof v === "object") {
    const o = {};
    for (const [a, d] of Object.entries(v)) {
      if (k.has(a)) o[a] = d;
      else { const alt = buda(d, k); if (alt && Object.keys(alt).length) o[a] = alt; }
    }
    return o;
  }
  return null;
};
const anlamli = (dosya) => readFileSync(dosya, "utf8").split("\n")
  .map((s) => s.trim()).filter((s) => s && !s.startsWith("//") && /[A-Za-z]/.test(s));

// Asagi servislerin urettigi alan adlari: sizinti olcusunun tabani
const ham = await Promise.all(["8742/uye/U-41", "8743/uye/U-41/odunc", "8741/kitap/978-0"]
  .map((y) => al(`http://127.0.0.1:${y}`)));
const ASAGI = new Set(ham.flatMap((h) => Object.keys(Array.isArray(h.veri) ? h.veri[0] : h.veri)));

const satir = [];
for (const [ad, calistir] of Object.entries(SENARYO)) {
  const once = await toplam();
  const s = await calistir();
  const sonra = await toplam();
  const k = new Set(ALAN[ad]);
  satir.push({ ad, govde: s.govde, istek: s.istek, ic: sonra - once, bayt: s.bayt,
    gelen: yaprak(s.govde), kullanilan: yaprak(buda(s.govde, k)),
    fazla: s.bayt - Buffer.byteLength(JSON.stringify(buda(s.govde, k))),
    sizan: ALAN[ad].filter((a) => ASAGI.has(a)).length });
}

const B = ["istemci istegi", "asagi istek", "alinan B", "gelen yaprak", "kullanilan", "fazla B", "sizan ad"];
console.log(`${"istemci/gerceklestirme".padEnd(17)}${B.map((b) => b.padStart(15)).join("")}`);
for (const s of satir) {
  console.log(`${s.ad.padEnd(17)}${[s.istek, s.ic, s.bayt, s.gelen, s.kullanilan, s.fazla, s.sizan]
    .map((v) => String(v).padStart(15)).join("")}`);
}

console.log(`\nmobil/bff govdesi: ${JSON.stringify(satir.at(-1).govde)}`);
const w = anlamli("bff/bff-web.mjs"), m = anlamli("bff/bff-mobil.mjs");
const ortak = w.filter((s) => m.includes(s));
console.log(`\nbff-web anlamli satir = ${w.length}, bff-mobil = ${m.length}, ` +
  `ikisinde birebir ayni = ${ortak.length}`);
console.log(`genel uc dosyasi = ${anlamli("bff/genel-uc.mjs").length} satir, ` +
  `toplama katmani dosyasi = ${w.length + m.length} satir`);
console.log(`asagi servis alan adi = ${ASAGI.size}; toplama katmani = 2 dagitim birimi`);
```

```bash
for s in "servis.mjs katalog 8741" "servis.mjs uyelik 8742" "servis.mjs odunc 8743" \
         "genel-uc.mjs" "bff-web.mjs" "bff-mobil.mjs"; do
  node bff/$s > /dev/null &
done
for p in 8741 8742 8743 8744 8745 8746; do
  curl -s --retry 30 --retry-connrefused --retry-delay 0 -o /dev/null http://127.0.0.1:$p/sayac
done
node bff/olc.mjs
kill $(jobs -p)
```

```
istemci/gerceklestirme istemci istegi    asagi istek       alinan B   gelen yaprak     kullanilan        fazla B       sizan ad
web/genel uc                   5              6           1216             66             26            691              6
mobil/genel uc                 2              3            723             36             11            467              3
web/bff                        1              6            442             21             21              0              0
mobil/bff                      1              3             91              4              4              0              0

mobil/bff govdesi: {"okuyucu":"Zeynep","gecikmis":2,"enYakin":{"baslik":"Kayip Zamanin Izinde","kalanGun":-6}}

bff-web anlamli satir = 19, bff-mobil = 19, ikisinde birebir ayni = 13
genel uc dosyasi = 10 satir, toplama katmani dosyasi = 38 satir
asagi servis alan adi = 22; toplama katmani = 2 dagitim birimi
```

#### Sayıların Okunması

İlk iki satır tek genel ucun bedelini veriyor. Web listesi ekranı kurmak için beş istek yapıyor
ve 1216 bayt alıyor; bu baytın 691'i, yani yüzde 57'si hiçbir zaman ekrana çıkmıyor. Mobil özet
ekranı iki sayı ve bir başlık göstermek için iki istek yapıyor, 723 bayt alıyor ve bunun 467'si
kullanılmıyor. Alan tarafında oran daha keskin: mobil istemci otuz altı yaprak değer alıyor ve
on birini okuyor.

Alt iki satır toplama katmanının etkisini gösteriyor. İstemci isteği 5'ten ve 2'den 1'e iniyor.
Alınan bayt web tarafında 1216'dan 442'ye, mobil tarafında 723'ten 91'e düşüyor — mobil ekran
için taşınan veri sekizde bire iniyor. Fazla bayt iki durumda da 0'dır, çünkü katman gövdeyi
ekranın istediği biçimde kuruyor.

Aşağı istek sütunu ise değişmiyor: web tarafında iki gerçekleştirimde de 6, mobil tarafında 3.
Bu, kalıbın en çok yanlış anlaşılan yeridir. Toplama katmanı aşağı servislerin yükünü
azaltmıyor; aynı işi yapıyor, yalnız **nerede** yaptığını değiştiriyor. Beş gidiş-dönüş
istemcinin ağından iç ağa taşınıyor. Kazanç istemcinin ağındadır ve orada gidiş-dönüş bir
sunucu içi çağrıdan çok daha pahalıdır.

Sızan ad sütunu bağlaşımı ölçüyor. Genel uç yerleşiminde web istemcisinin okuduğu altı adın
altısı da aşağı servislerin kendi alan adıdır; `sonTeslim` adı ödünç servisinin sözlüğünden
gelir ve istemci koduna kadar iner. Toplama katmanı yerleşiminde bu sayı 0'dır: istemci
`teslim`, `gecikmeGunu`, `enYakin` gibi ekranın sözlüğünü görür. Ödünç servisi alanını yeniden
adlandırdığında ilk yerleşimde istemci kodu düzeltilip yeniden yayınlanmak zorundadır; ikinci
yerleşimde düzeltme katmanda kalır.

#### Yinelenen Mantık ve Sessiz Ayrışma

Bedel son üç satırdadır. Genel uç dosyası on anlamlı satır; iki toplama katmanı toplam otuz
sekiz satır. Bunların on üçü iki dosyada birebir aynıdır: üç servis adresi, `AL` yardımcısı,
`gunFarki`, `BUGUN` sabiti, üye ile ödünç çağrıları. Yani her istemci için bir katman kurmak,
aynı mantığı istemci sayısınca kopyalamak demektir. Üçüncü bir istemci geldiğinde on üç satır
üçüncü kez yazılacaktır.

Bu tekrarın doğurduğu arıza kipi, kopyanın kendisi değil, kopyalardan yalnız birinin
güncellenmesidir. Ödünç servisi `sonTeslim` alanını `teslimTarihi` diye yeniden adlandırıyor ve
yalnız web katmanı güncelleniyor.

```bash
sed 's/sonTeslim/teslimTarihi/g' bff/servis.mjs > bff/servis-v2.mjs   # odunc alani yeniden adlandirildi
sed 's/sonTeslim/teslimTarihi/g' bff/bff-web.mjs > bff/bff-web-v2.mjs # yalniz web katmani guncellendi
node bff/servis.mjs katalog 8741 > /dev/null &
node bff/servis.mjs uyelik 8742 > /dev/null &
node bff/servis-v2.mjs odunc 8743 > /dev/null &
node bff/bff-web-v2.mjs > /dev/null &
node bff/bff-mobil.mjs > /dev/null &
for p in 8741 8742 8743 8745 8746; do
  curl -s --retry 30 --retry-connrefused --retry-delay 0 -o /dev/null http://127.0.0.1:$p/sayac
done
echo "web  $(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8745/uye/U-41/liste) $(curl -s http://127.0.0.1:8745/uye/U-41/liste | head -c 96)"
echo "mobil $(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8746/uye/U-41/ozet) $(curl -s http://127.0.0.1:8746/uye/U-41/ozet)"
kill $(jobs -p)
```

```
web  200 {"okuyucu":"Zeynep Aydin","satir":[{"baslik":"Kayip Zamanin Izinde","yazar":"M. Proust","raf":"P
mobil 200 {"okuyucu":"Zeynep","gecikmis":0,"enYakin":{"baslik":"Kayip Zamanin Izinde","kalanGun":null}}
```

İki katman da 200 döndürüyor. Web listesi doğru; mobil özet ise `gecikmis` alanını 2 yerine 0,
`kalanGun` alanını -6 yerine `null` veriyor. Okunmayan alan `undefined` olduğu için tarih
karşılaştırması sessizce yanlış sonuç üretti ve hiçbir yerde hata kaydı oluşmadı. Tek genel uç
yerleşiminde aynı değişiklik iki istemciyi birden bozardı ve bu, fark edilmesi daha kolay bir
arızadır. Toplama katmanı yerleşiminde arıza **istemciler arasında ayrışma** biçiminde
görünür: bir ekran doğru, öteki sessizce yanlıştır.

Buradan çıkan kural, kalıbın sahipliğiyle ilgilidir. Toplama katmanı aşağı servisin değil,
ekranı yazan ekibin dağıtım birimidir; aşağı servisin sözleşmesi değiştiğinde katman sayısınca
düzeltme gerekir ve o düzeltmelerin hepsinin yapıldığını gösteren tek şey, katmanların
sözleşmeye karşı sınanmasıdır.

#### Özet

- Tek genel uç yerleşiminde web listesi 5 istek ve 1216 bayt, mobil özet 2 istek ve 723 bayt
  taşıyor; bu baytların 691 ve 467'si ekrana çıkmıyor.
- İstemciye özel toplama katmanı istek sayısını 1'e, alınan baytı 442 ve 91'e indiriyor; fazla
  bayt iki istemcide de 0 oluyor.
- Aşağı servislere ulaşan istek değişmiyor (6 ve 3): katman yükü azaltmıyor, gidiş-dönüşü
  istemcinin ağından iç ağa taşıyor.
- Bağlaşım ölçüsü: genel uçta istemcinin okuduğu 6 addan 6'sı aşağı servisin alan adı; toplama
  katmanında 0.
- Ödenen kod: iki katman 38 anlamlı satır, bunların 13'ü birebir aynı; ayrıca 2 yeni dağıtım
  birimi.
- Doğan arıza kipi: aşağı alan adı değişip yalnız bir katman güncellendiğinde iki istemci
  sessizce ayrışıyor — web 200 ve doğru, mobil 200 ve `gecikmis: 0`.

#### Sonraki Adım

Toplama katmanı da, önündeki ağ geçidi de aynı türden bir çözümdür: bir sorumluluğu servis
kodundan alıp ayrı bir yere koymak. İkisinin de aldığı sorumluluk **biçim** ile ilgiliydi —
hangi alan kime gider, hangi gövde nasıl kurulur. Oysa servis kodunun içinde biçimle ilgisi
olmayan başka bir yığın da duruyor: yeniden deneme döngüsü, zaman aşımı süresi, taşımanın
güvenliği ve her çağrının ölçütünü toplayan sayaçlar. Bu ders boyunca yazılan üç katmanın her
birinde `fetch` çağrısı çıplaktır; ne zaman aşımı vardır, ne yeniden deneme, ne de bir sayaç.
Sonraki ders bu işleri servis kodundan çıkarıp ayrı bir sürece taşıyan yerleşimi ele alır ve
ölçüyü kod tarafında kurar: uygulama kodundan kaç satır silindi, geriye hangi sorumluluk kaldı
ve ayrı süreçte koşmanın bedeli ne oldu.

### Servis Ağı

Source: https://academia.sh/tr/kurslar/servis-mimarileri/servis-agi

Ağ ilgilerinin uygulama kodundan çıkarılması: yeniden deneme, zaman aşımı, güvenli taşıma ve ölçüt toplamanın yan arabaya taşınmasıyla silinen satırın sayılması, geriye kalan sorumluluğun adlandırılması ve ayrı süreçte koşmanın ödettiği durak, süreç ile alan bilgisi kaybının ölçülmesi.

Önceki dersin bıraktığı yerde üç toplama katmanı vardı ve üçünün de dış çağrısı çıplaktı: ne
zaman aşımı, ne yeniden deneme, ne sayaç. Gerçek bir sistemde bu üç işin her servis kodunda
tekrar yazılması gerekir. Kütüphane ödünç sisteminde ödünç servisi ücret servisine gidiyor;
gidiş bir ağ üzerinden, taşıma güvenli, karşı taraf zaman zaman geçici hata döndürüyor.

Bu dersin sorusu şu: yeniden deneme, zaman aşımı, güvenli taşıma ve ölçüt toplama uygulama
kodundan çıkarılıp ana süreçle birlikte koşan ayrı bir sürece verilirse, uygulama kodundan kaç
satır silinir, geriye ne kalır ve bunun bedeli nedir. Yan araba ile elçinin tanımı Trafik
Katmanı kursunda kuruldu ve orada dağıtım birimi düzeyinde ölçüldü; burada ölçü kod tarafıdır.
Her servisin yanında böyle bir süreç koştuğunda ve hepsinin politikası tek bir yerden
verildiğinde ortaya çıkan yerleşime **servis ağı** (service mesh), politikanın verildiği yere
**denetim düzlemi** (control plane) denir.

Varsayımlar: **SS13** ücret servisi güvenli taşıma ile konuşur ve sertifika ölçüm sırasında
üretilir. **SS14** ücret servisi ilk iki okuma isteğini geçici hatayla reddeder; ödeme ucu
kaydı işler ama ilk yanıtı kaybolmuş sayılır. **SS15** ölçüm süre ölçmez; satır, süreç, durak
ve tahsilat sayar.

#### Ücret Servisi

Ücret servisinin iş ucu güvenli taşıma üzerinde. Yanına düz bir yönetim ucu konuldu; o uç
ölçüm düzeneğidir, sayaçları okur ve sıfırlar.

```js
// mesh/ucret.mjs — ucret servisi: is ucu 8751 guvenli tasima, yonetim ucu 8757 duz
import { createServer as tls } from "node:https";
import { createServer } from "node:http";
import { readFileSync } from "node:fs";

const sayac = { okuma: 0, odeme: 0, tahsil: 0 };
const sifirla = () => Object.keys(sayac).forEach((k) => (sayac[k] = 0));

tls({ key: readFileSync("mesh/anahtar.pem"), cert: readFileSync("mesh/sertifika.pem") },
  (q, y) => {
    if (q.url === "/ceza/ode") {
      sayac.odeme += 1;
      sayac.tahsil += 1;                       // kayit islenir; ilk yanit yolda kaybolmus sayilir
      const kod = sayac.odeme === 1 ? 503 : 200;
      y.writeHead(kod, { "Content-Type": "application/json" });
      return y.end(JSON.stringify({ tahsil: sayac.tahsil, tutar: 12.5 }));
    }
    sayac.okuma += 1;
    const kod = sayac.okuma <= 2 ? 503 : 200;  // ilk iki okuma istegi gecici olarak reddedilir
    y.writeHead(kod, { "Content-Type": "application/json" });
    y.end(JSON.stringify(kod === 200 ? { borc: 12.5 } : { hata: "gecici" }));
  }).listen(8751, "127.0.0.1", () => console.log("ucret 127.0.0.1:8751"));

createServer((q, y) => {
  if (q.url === "/sifirla") sifirla();
  y.writeHead(200, { "Content-Type": "application/json" });
  y.end(JSON.stringify(sayac));
}).listen(8757, "127.0.0.1");
```

#### Ağ İlgileri Uygulama Kodunda

İlk gerçekleştirimde ödünç servisi her şeyi kendisi yapıyor. Ağ ilgisi taşıyan satırlar
işaretli; ölçüm bu işareti sayacak.

```js
// mesh/odunc-kalin.mjs — butun ag ilgileri uygulama kodunda, 8752
import { createServer } from "node:http";
import { request } from "node:https";                                   // [tls]
import { readFileSync } from "node:fs";                                 // [tls]

const CA = readFileSync("mesh/sertifika.pem");                          // [tls]
const ZAMAN_ASIMI = 400;                                                // [zaman asimi]
const DENEME = 3;                                                       // [yeniden deneme]
const olcut = {};                                                       // [olcut]

const cagir = (yol, yontem) => new Promise((coz, red) => {              // [tls]
  const q = request({ host: "127.0.0.1", port: 8751, path: yol,         // [tls]
    method: yontem, ca: CA }, (y) => {                                  // [tls]
    let g = "";
    y.on("data", (p) => (g += p));
    y.on("end", () => coz({ kod: y.statusCode, govde: g }));
  });
  q.setTimeout(ZAMAN_ASIMI, () => q.destroy(new Error("zaman asimi"))); // [zaman asimi]
  q.on("error", red);                                                   // [zaman asimi]
  q.end();
});

const guvenli = (yontem) => yontem === "GET";     // yalniz etkisiz cagri yinelenir [yeniden deneme]
const dene = async (yol, yontem, ad) => {
  const o = (olcut[ad] ??= { istek: 0, deneme: 0, hata: 0 });            // [olcut]
  o.istek += 1;                                                         // [olcut]
  let son;
  for (let i = 0; i < (guvenli(yontem) ? DENEME : 1); i += 1) {         // [yeniden deneme]
    o.deneme += 1;                                                      // [olcut]
    son = await cagir(yol, yontem);
    if (son.kod < 500) break;                                           // [yeniden deneme]
  }
  if (son.kod >= 500) o.hata += 1;                                      // [olcut]
  return son;
};

const json = (y, kod, g) => {
  y.writeHead(kod, { "Content-Type": "application/json" });
  y.end(JSON.stringify(g));
};

createServer(async (q, y) => {
  const [kok, uye] = q.url.split("/").filter(Boolean);
  if (kok === "olcut") return json(y, 200, olcut);                       // [olcut]
  const is = kok === "ode" ? { yol: "/ceza/ode", yontem: "POST" }
    : { yol: `/ceza/${uye}`, yontem: "GET" };
  const s = await dene(is.yol, is.yontem, kok);
  json(y, s.kod, { islem: kok, kod: s.kod, govde: s.govde });
}).listen(8752, "127.0.0.1", () => console.log("odunc-kalin 127.0.0.1:8752"));
```

#### Aynı İş, İnce Uygulama ve Yan Araba

İkinci gerçekleştirimde iş mantığı satır satır aynı kalıyor; değişen tek şey `dene` işlevinin
gövdesi. Uygulama artık yerel bir adrese düz bir çağrı yapıyor.

```js
// mesh/odunc-ince.mjs — ag ilgileri uygulama disinda, 8753: yerel adrese duz cagri
import { createServer } from "node:http";

const dene = (yol, yontem) => fetch(`http://127.0.0.1:8754${yol}`, { method: yontem })
  .then(async (y) => ({ kod: y.status, govde: await y.text() }));

const json = (y, kod, g) => {
  y.writeHead(kod, { "Content-Type": "application/json" });
  y.end(JSON.stringify(g));
};

createServer(async (q, y) => {
  const [kok, uye] = q.url.split("/").filter(Boolean);
  const is = kok === "ode" ? { yol: "/ceza/ode", yontem: "POST" }
    : { yol: `/ceza/${uye}`, yontem: "GET" };
  const s = await dene(is.yol, is.yontem, kok);
  json(y, s.kod, { islem: kok, kod: s.kod, govde: s.govde });
}).listen(8753, "127.0.0.1", () => console.log("odunc-ince 127.0.0.1:8753"));
```

Silinen satırların hepsi yan arabaya taşınıyor. Yan arabanın gördüğü şey bir yol, bir yöntem ve
bir durum kodudur; hangi işin yapıldığını bilmez.

```js
// mesh/yan-araba.mjs — yan araba 8754: zaman asimi, yeniden deneme, guvenli tasima, olcut
import { createServer } from "node:http";
import { request } from "node:https";
import { readFileSync } from "node:fs";

const CA = readFileSync("mesh/sertifika.pem");
const ZAMAN_ASIMI = 400;
const DENEME = 3;
const YINELENEN_KOD = [502, 503, 504];      // politika durum kodudur; isin ne oldugu bilinmez
const olcut = {};

const cagir = (yol, yontem) => new Promise((coz, red) => {
  const q = request({ host: "127.0.0.1", port: 8751, path: yol, method: yontem, ca: CA }, (y) => {
    let g = "";
    y.on("data", (p) => (g += p));
    y.on("end", () => coz({ kod: y.statusCode, govde: g }));
  });
  q.setTimeout(ZAMAN_ASIMI, () => q.destroy(new Error("zaman asimi")));
  q.on("error", red);
  q.end();
});

createServer(async (q, y) => {
  if (q.url === "/olcut" || q.url === "/sifirla") {
    const g = JSON.stringify(olcut);
    if (q.url === "/sifirla") Object.keys(olcut).forEach((a) => delete olcut[a]);
    y.writeHead(200, { "Content-Type": "application/json" });
    return y.end(g);
  }
  const o = (olcut[q.url] ??= { istek: 0, deneme: 0, hata: 0 });
  o.istek += 1;
  let son;
  for (let i = 0; i < DENEME; i += 1) {
    o.deneme += 1;
    son = await cagir(q.url, q.method);
    if (!YINELENEN_KOD.includes(son.kod)) break;
  }
  if (son.kod >= 500) o.hata += 1;
  y.writeHead(son.kod, { "Content-Type": "application/json" });
  y.end(son.govde);
}).listen(8754, "127.0.0.1", () => console.log("yan-araba 127.0.0.1:8754"));
```

#### Ölçüm

Ölçüm iki yerleşimde de aynı iki işi koşuyor: bir borç sorgusu ve bir ceza ödemesi. Sonra
ücret servisinin sayaçlarını, iki taraftaki ölçütleri ve kod satırlarını okuyor.

```js
// mesh/olc.mjs — iki yerlesim ayni isi kosar: sonuc, deneme, tahsil ve silinen satir
import { readFileSync } from "node:fs";

const al = async (u) => JSON.parse(await (await fetch(u)).text());
const kod = async (u) => (await fetch(u)).status;
const anlamli = (d) => readFileSync(d, "utf8").split("\n").map((s) => s.trim())
  .filter((s) => s && !s.startsWith("//") && /[A-Za-z]/.test(s));
const ilgi = (d) => readFileSync(d, "utf8").split("\n")
  .filter((s) => /\/\/ \[/.test(s)).length;                 // ag ilgisi olarak isaretli satir

const YERLESIM = [["kalin uygulama", 8752, null], ["ince + yan araba", 8753, 8754]];
const sonuc = [];
for (const [ad, uygulama, arac] of YERLESIM) {
  await al("http://127.0.0.1:8757/sifirla");            // ucret sayaclari
  if (arac) await al(`http://127.0.0.1:${arac}/sifirla`);
  const borc = await kod(`http://127.0.0.1:${uygulama}/borc/U-41`);
  const ode = await kod(`http://127.0.0.1:${uygulama}/ode/U-41`);
  const sayac = await al("http://127.0.0.1:8757/sayac");
  const olcut = await al(`http://127.0.0.1:${arac ?? uygulama}/olcut`);
  sonuc.push({ ad, borc, ode, sayac, olcut, surec: arac ? 3 : 2, durak: arac ? 2 : 1 });
}

const B = ["borc kodu", "odeme kodu", "ucrete ulasan", "tahsil", "surec", "yerel durak"];
console.log(`${"yerlesim".padEnd(18)}${B.map((b) => b.padStart(14)).join("")}`);
for (const s of sonuc) {
  console.log(`${s.ad.padEnd(18)}${[s.borc, s.ode, s.sayac.okuma + s.sayac.odeme,
    s.sayac.tahsil, s.surec, s.durak].map((v) => String(v).padStart(14)).join("")}`);
}
console.log("");
for (const s of sonuc) {
  const d = Object.entries(s.olcut).map(([a, o]) => `${a} ${o.istek}/${o.deneme}/${o.hata}`);
  console.log(`${s.ad.padEnd(18)} olcut (istek/deneme/hata): ${d.join("  ")}`);
}

const SERVIS = 5;                        // katalog, uyelik, odunc, ucret, bildirim
const k = anlamli("mesh/odunc-kalin.mjs"), i = anlamli("mesh/odunc-ince.mjs");
const y = anlamli("mesh/yan-araba.mjs");
const silinen = k.filter((s) => !i.includes(s));
console.log(`\nodunc-kalin = ${k.length} satir, odunc-ince = ${i.length} satir, ` +
  `silinen = ${silinen.length}, eklenen = ${i.filter((s) => !k.includes(s)).length}`);
console.log(`ag ilgisi diye isaretli satir: uygulama kodunda ${ilgi("mesh/odunc-kalin.mjs")} -> 0`);
console.log(`${SERVIS} servis icin ag ilgisi satiri: uygulama tarafinda ${SERVIS * silinen.length}, ` +
  `yan araba tarafinda ${y.length} (tek dosya)`);
```

```bash
openssl req -x509 -newkey rsa:2048 -nodes -days 1 -subj "/CN=localhost" \
  -addext "subjectAltName=IP:127.0.0.1" -keyout mesh/anahtar.pem -out mesh/sertifika.pem 2>/dev/null
node mesh/ucret.mjs > /dev/null &
node mesh/odunc-kalin.mjs > /dev/null &
node mesh/odunc-ince.mjs > /dev/null &
node mesh/yan-araba.mjs > /dev/null &
for p in 8752 8753 8754 8757; do
  curl -s --retry 30 --retry-connrefused --retry-delay 0 -o /dev/null http://127.0.0.1:$p/olcut
done
node mesh/olc.mjs
kill $(jobs -p)
```

```
yerlesim               borc kodu    odeme kodu ucrete ulasan        tahsil         surec   yerel durak
kalin uygulama               200           503             4             1             2             1
ince + yan araba             200           200             5             2             3             2

kalin uygulama     olcut (istek/deneme/hata): borc 1/3/0  ode 1/1/1
ince + yan araba   olcut (istek/deneme/hata): /ceza/U-41 1/3/0  /ceza/ode 1/2/0

odunc-kalin = 38 satir, odunc-ince = 13 satir, silinen = 28, eklenen = 3
ag ilgisi diye isaretli satir: uygulama kodunda 18 -> 0
5 servis icin ag ilgisi satiri: uygulama tarafinda 140, yan araba tarafinda 33 (tek dosya)
```

#### Silinen Satır ve Kalan Sorumluluk

Uygulama kodu 38 anlamlı satırdan 13'e iniyor; 28 satır siliniyor, yerine 3 satır geliyor.
Silinen satırların 18'i ağ ilgisi diye işaretliydi ve uygulama kodunda bu işaretten hiç
kalmadı. Ölçünün asıl anlamı çarpanda: aynı 28 satır beş servisin her birinde yazılacağı için
uygulama tarafındaki toplam 140 satırdır; yan araba tarafında ise 33 satırlık tek bir dosya
vardır ve politikası dışarıdan verilir.

Geriye kalan sorumluluk üç maddedir ve hiçbiri yan arabaya devredilemez. Birincisi, hangi yola
hangi yöntemle gidileceği: `/ceza/ode` ucunun `POST` olduğu bilgisi uygulamadadır. İkincisi,
dönen durum kodunun iş açısından ne demek olduğu — 503 gören uygulama kullanıcıya ne
gösterecektir. Üçüncüsü ve en önemlisi, bir çağrının yinelenmesinin güvenli olup olmadığı.
Kalın gerçekleştirimde bu bilgi `guvenli` işlevinde tek satırdır; ince gerçekleştirimde o satır
silindi ve yerine hiçbir şey konmadı.

Bedel iki sütunda görünüyor. Süreç sayısı 2'den 3'e, isteğin dokunduğu yerel durak 1'den 2'ye
çıkıyor. Ücret servisine ulaşan istek 4'ten 5'e çıkıyor. Bu, Trafik Katmanı kursunun dağıtım
birimi başına süreç ölçüsünün kod tarafındaki karşılığıdır: aynı iş, bir durak fazlasıyla.

Ölçüt satırları kaybı gösteriyor. Kalın yerleşimde sayaçlar `borc` ve `ode` diye, yani işin
adıyla tutuluyor; ince yerleşimde `/ceza/U-41` ve `/ceza/ode` diye, yani ücret servisinin yol
sözlüğüyle. Yan araba "borç sorgusu" ile "ceza ödemesi" arasındaki farkı yalnız yola bakarak
tahmin edebilir. Üye numarası yola girdiği için her üye ayrı bir ölçüt anahtarı üretir; alan
bilgisi olmadan bu anahtarları toplamak da uygulama dışında bir işe dönüşür.

#### Yeniden Denemenin Bilmediği Şey

Tablonun en pahalı sütunu `tahsil` sütunudur. İki yerleşim de aynı iki isteği yaptı; kalın
yerleşimde ücret servisi bir tahsilat işledi, ince yerleşimde iki tahsilat işledi.

Nedeni politikanın konumudur. Ücret servisinin ödeme ucu kaydı işliyor ve ilk yanıtı 503
olarak dönüyor — yanıtın yolda kaybolduğu durumun taklidi. Kalın uygulamada `guvenli` işlevi
`POST` çağrısını yinelemeye kapatmıştı: ödeme bir kez denendi, uygulamaya 503 döndü, tahsil 1
oldu. Yan araba ise yalnız durum koduna bakıyor; 503 gördü, aynı isteği yineledi, ikinci
denemede 200 aldı ve uygulamaya 200 döndürdü. Ödünç servisi işlemin başarılı olduğunu sanıyor;
üyenin cezası iki kez tahsil edilmiş durumda ve hiçbir tarafta hata kaydı yok.

Doğan arıza kipi budur: **ağ katmanında verilen bir yeniden deneme kararı, iş katmanında
yinelemeye kapalı olan bir çağrıyı yineler.** Kalın yerleşimde böyle bir arıza kipi yoktu,
çünkü karar veren kodun elinde çağrının ne olduğu bilgisi vardı.

Çözüm, sorumluluğu bir ölçüde geri çağırmaktır. Yan arabanın politikası yönteme göre
daraltılabilir ya da uygulama isteğe bir tekillik anahtarı koyup ücret servisi yinelenen
tahsilatı düşebilir. İkisi de bedava değildir: birincisi ağ politikasının içine alan bilgisi
koyar, ikincisi ücret servisine defter tutma işi ekler. Ölçülen sonuç şudur: ağ ilgileri
uygulamadan çıkarılabilir, ama **hangi çağrının yinelenebilir olduğu** bilgisi çıkarılamaz;
yalnız yer değiştirir.

#### Özet

- Aynı iş iki yerleşimde koşturuldu: uygulama kodu 38 satırdan 13 satıra indi, 28 satır silindi
  ve 3 satır eklendi; ağ ilgisi diye işaretli 18 satırdan uygulamada hiçbiri kalmadı.
- Beş servisli sistemde aynı ilgi uygulama tarafında 140 satır tutar; yan araba tarafında 33
  satırlık tek dosya vardır ve politikası dışarıdan verilir.
- Uygulamada kalan sorumluluk: yol ile yöntem seçimi, durum kodunun iş anlamı ve bir çağrının
  yinelenmesinin güvenli olup olmadığı.
- Ödenen bedel: süreç 2'den 3'e, yerel durak 1'den 2'ye, ücret servisine ulaşan istek 4'ten
  5'e; ölçüt anahtarları işin adından yol sözlüğüne düştü.
- Doğan arıza kipi: durum koduna bakan yeniden deneme, yinelemeye kapalı ödeme çağrısını
  yineledi; tahsilat 1 yerine 2 oldu ve iki tarafta da hata kaydı oluşmadı.

#### Sonraki Adım

İki gerçekleştirimin de sessiz bir ortak kabulü vardı: ücret servisinin adresi `127.0.0.1:8751`
diye kodun içinde yazılıydı. Kalın uygulamada bu adres uygulama dosyasında, ince uygulamada yan
araba dosyasında duruyor; iki durumda da tek bir sabittir. Oysa bir servisin tek kopyası
olmayabilir, kopyalar gelip gidebilir ve giden bir kopyanın adresi bir süre daha geçerliymiş
gibi durabilir. Konunun son dersi adresin kodun dışından çözülmesini ele alır: bir kayıt defteri
süreci kurar, kayıtlara bir kira süresi verir, sağlık bilgisini yönlendirmeye bağlar ve bunu
sabit yapılandırmayla iki sayı üzerinden karşılaştırır — bir kopya eklendiğinde dağıtımda
dokunulan dosya ve bir kopya öldüğünde ölü adrese giden istek.

### Servis Keşfi

Source: https://academia.sh/tr/kurslar/servis-mimarileri/servis-kesfi

Adres çözümlemenin bir gerçekleştirme olarak kurulması: kira süreli kayıt defteri süreci, kopyaların kendini kaydetmesi ve sağlık bilgisinin yönlendirmeye bağlanması; sabit yapılandırmayla dağıtımda dokunulan dosya ve ölü adrese giden istek üzerinden karşılaştırma.

Önceki iki dersin de sessiz bir ortak kabulü vardı: karşı tarafın adresi kodun içinde sabit bir
dizedir. Ödünç servisi ücret servisine `127.0.0.1:8751` diye gitti; ondan önceki toplama
katmanları katalog servisine `127.0.0.1:8741` diye gitti. Tek kopyalı bir düzenekte bu doğrudur.
Kütüphane ödünç sisteminin katalog servisi ise tek kopya değildir: yük arttıkça kopya eklenir,
bir kopya çöker, bir kopya bakıma alınır. Sabit dizenin karşılığı olmayan bir adres, çağrının
kendisi kadar kesin bir arızadır.

Bu dersin işi adres çözümlemeyi bir **gerçekleştirme** olarak kurmaktır. Servis keşfi ile kayıt
defterinin ne olduğu Uygulama Katmanı ve Servis Etkileşimi kursunda tanımlandı; burada kayıt
defteri gerçekten bir süreç olarak koşar, kayıtlara bir kira süresi verilir, sağlık bilgisi
yönlendirmeye bağlanır ve sonuç sabit yapılandırmayla iki sayı üzerinden karşılaştırılır: bir
kopya eklendiğinde dağıtımda dokunulan dosya, bir kopya öldüğünde ölü adrese giden istek.

Varsayımlar: **SS16** katalog servisinin üç kopyası vardır ve üçü de aynı veriyi sunar.
**SS17** kayıt defterinde kira süresi 900 ms, kopyaların yenileme aralığı 300 ms'dir; bu
oranlar gerçek bir sistemdekinden küçüktür, ölçüm süresini kısaltmak içindir. **SS18** katalog
servisini çağıran üç istemci vardır (ödünç, bildirim, ücret) ve sabit yapılandırmada her biri
kendi adres dosyasını taşır.

#### Kayıt Defteri Süreci

Kayıt defteri, adres ile kayıt zamanını tutan tek bir süreçtir. Kaydın geçerliliği süreye
bağlıdır: son yenilemesinin üzerinden kira süresi geçmiş bir kayıt listeden düşer. Çözümleme
yanıtı iki sayı döndürür — diri kayıt sayısı ve yönlendirilebilir adresler. İkisinin ayrı
olmasının nedeni sağlık bilgisidir.

```js
// kesif/kayit-defteri.mjs — kayit defteri 8761: kira sureli kayit ve saglik bilgisi
import { createServer } from "node:http";

const KIRA = 900;                     // ms: yenilenmeyen kayit bu surenin sonunda listeden duser
const kayit = new Map();              // adres -> { ad, saglik, son yenileme }

createServer((q, y) => {
  const [kok, deger] = q.url.split("/").filter(Boolean);
  if (kok === "kayit") {                                   // /kayit/<ad>|<adres>|<saglik>
    const [ad, adres, saglik] = decodeURIComponent(deger).split("|");
    kayit.set(adres, { ad, saglik: saglik === "1", son: Date.now() });
  }
  const ad = kok === "cozumle" ? deger : "katalog";
  const diri = [...kayit.entries()]
    .filter(([, k]) => k.ad === ad && Date.now() - k.son < KIRA);
  y.writeHead(200, { "Content-Type": "application/json" });
  y.end(JSON.stringify({ kira: KIRA, kayit: diri.length,
    adres: diri.filter(([, k]) => k.saglik).map(([a]) => a) }));
}).listen(8761, "127.0.0.1", () => console.log("kayit-defteri 127.0.0.1:8761"));
```

Kayıt defterinde silme ucu yoktur. Bir kopya düzgün kapanırken kaydını sildirebilirdi, ama
çöken bir kopya bunu yapamaz; bu yüzden kaydın geçerliliği kopyanın iyi niyetine değil, süreye
bağlanır. Kayıt defteri hiçbir kopyaya kendisi soru sormaz.

#### Kaydolan Kopya

Her kopya ayağa kalkarken kendini kaydeder ve kira süresinin üçte biri kadar bir aralıkla
kaydını yeniler. Yenileme çağrısı sağlık bilgisini de taşır. `/bozul` ucu, sürecin yaşadığı ama
iş göremediği durumu üretmek içindir: süreç yenilemeyi sürdürür, bildirdiği sağlık bilgisi
değişir.

```js
// kesif/katalog.mjs — katalog kopyasi: node kesif/katalog.mjs 8762
import { createServer } from "node:http";

const port = Number(process.argv[2] ?? 8762);
const YENILEME = 300;                        // ms: kira suresinin ucte biri
let saglik = true;

const kaydol = () => fetch("http://127.0.0.1:8761/kayit/" +
  encodeURIComponent(`katalog|127.0.0.1:${port}|${saglik ? "1" : "0"}`)).catch(() => {});

createServer((q, y) => {
  if (q.url === "/bozul") saglik = false;    // surec yasiyor ve kaydini yeniliyor, is goremiyor
  const kod = saglik ? 200 : 503;
  y.writeHead(kod, { "Content-Type": "application/json" });
  y.end(JSON.stringify({ port, saglik }));
}).listen(port, "127.0.0.1", () => {
  kaydol();
  setInterval(kaydol, YENILEME).unref();
});
```

Yenileme aralığının kira süresinden küçük olması zorunludur; oran, bir kopyanın kaç yenileme
kaçırdıktan sonra listeden düşeceğini belirler. Burada oran üçtür: iki yenileme kaçıran kopya
hâlâ listededir, üçüncüsünü de kaçıran düşer.

#### İki Çözümleme Yolu, Aynı İş

Ölçüm betiği kayıt defterini ve üç kopyayı kendisi başlatır, sabit yapılandırma dosyasını
kendisi yazar ve beş aşamanın her birinde aynı altı isteği iki yolla dağıtır. Sabit yolda
adresler dosyadan okunur; keşif yolunda her aşamanın başında kayıt defterine sorulur.

```js
// kesif/olc.mjs — sabit yapilandirma ile kayit defteri ayni is uzerinde kosturulur
import { spawn } from "node:child_process";
import { writeFileSync, readFileSync } from "node:fs";

const bekle = (ms) => new Promise((c) => setTimeout(c, ms));
const surec = {};
const baslat = (p) => (surec[p] = spawn("node", ["kesif/katalog.mjs", String(p)], { stdio: "ignore" }));
const ISTEK = 6;                        // asama basina istek; adresler arasinda sirayla dagitilir
const ISTEMCI = 3;                      // katalogu cagiran servis: odunc, bildirim, ucret

const defter = spawn("node", ["kesif/kayit-defteri.mjs"], { stdio: "ignore" });
await bekle(400);
[8762, 8763, 8764].forEach(baslat);
writeFileSync("kesif/adresler.json",     // dagitim sirasinda elle yazilan yapilandirma dosyasi
  JSON.stringify([8762, 8763, 8764].map((p) => `127.0.0.1:${p}`)));
await bekle(500);

const cagir = async (adres) => {
  try { return (await fetch(`http://${adres}/kitap/978-0`)).status === 200; } catch { return false; }
};
const dagit = async (adres) => {
  let ok = 0;
  for (let i = 0; i < ISTEK && adres.length; i += 1) if (await cagir(adres[i % adres.length])) ok += 1;
  return ok;
};
const sabit = async () => {
  const a = JSON.parse(readFileSync("kesif/adresler.json", "utf8"));
  return { ok: await dagit(a), kayit: a.length, adres: a.length };
};
const kesif = async () => {
  const d = await (await fetch("http://127.0.0.1:8761/cozumle/katalog")).json();
  return { ok: await dagit(d.adres), kayit: d.kayit, adres: d.adres.length };
};

const asama = [];
const olc = async (ad) => asama.push({ ad, s: await sabit(), k: await kesif() });

await olc("hepsi diri");
surec[8764].kill();
await olc("kopya olduruldu (hemen)");
await bekle(1200);
await olc("kira suresi doldu");
await fetch("http://127.0.0.1:8763/bozul").catch(() => {});
await bekle(400);
await olc("kopya bozuk bildirdi");
baslat(8765);
await bekle(600);
await olc("yeni kopya eklendi");

const B = ["sabit b/i", "sabit adres", "defter b/i", "defter kayit", "defter adres"];
console.log(`${"asama".padEnd(26)}${B.map((b) => b.padStart(14)).join("")}`);
for (const a of asama) {
  console.log(`${a.ad.padEnd(26)}${[`${a.s.ok}/${ISTEK}`, a.s.adres, `${a.k.ok}/${ISTEK}`,
    a.k.kayit, a.k.adres].map((v) => String(v).padStart(14)).join("")}`);
}
console.log(`\nkira suresi 900 ms, yenileme araligi 300 ms; kesif penceresinde olu adrese ` +
  `giden istek = ${ISTEK - asama[1].k.ok}/${ISTEK}`);
console.log(`yeni kopya eklendiginde dokunulan yapilandirma dosyasi: sabit ${ISTEMCI}, defter 0; ` +
  `yeniden baslatilan istemci: sabit ${ISTEMCI}, defter 0`);
console.log(`kayit defteri = 1 ek surec, her cagri oncesi 1 ek cozumleme istegi`);

Object.values(surec).forEach((s) => s.kill());
defter.kill();
```

```
asama                          sabit b/i   sabit adres    defter b/i  defter kayit  defter adres
hepsi diri                           6/6             3           6/6             3             3
kopya olduruldu (hemen)              4/6             3           4/6             3             3
kira suresi doldu                    4/6             3           6/6             2             2
kopya bozuk bildirdi                 2/6             3           6/6             2             1
yeni kopya eklendi                   2/6             3           6/6             3             2

kira suresi 900 ms, yenileme araligi 300 ms; kesif penceresinde olu adrese giden istek = 2/6
yeni kopya eklendiginde dokunulan yapilandirma dosyasi: sabit 3, defter 0; yeniden baslatilan istemci: sabit 3, defter 0
kayit defteri = 1 ek surec, her cagri oncesi 1 ek cozumleme istegi
```

#### Sayıların Okunması

İlk aşamada iki yol da aynı sonucu veriyor: altı istek, altı başarı, üç adres. Keşfin hiçbir
katkısı görünmüyor ve bu doğrudur — her şey yolundayken sabit yapılandırma da doğru çalışır.
Keşfin ölçüsü ancak yerleşim değiştiğinde ortaya çıkar.

İkinci aşama, bir kopya öldürüldükten hemen sonra ölçülüyor. İki yol da 4/6 veriyor. Kayıt
defteri henüz üç adres döndürüyor, çünkü ölen kopyanın son yenilemesinin üzerinden kira süresi
geçmedi. Bu aralığa **keşif penceresi** denir ve ölçümdeki karşılığı 2/6'dır: pencerede giden
her altı istekten ikisi ölü bir adrese gitmiştir. Kayıt defteri, ölümü öğrenme süresini sıfıra
indirmez; yalnız sınırlar.

Üçüncü aşama pencerenin kapanmasını gösteriyor. Kira süresi dolduktan sonra kayıt defteri iki
adres döndürüyor ve altı istek iki adrese üçer dağılıyor: 6/6. Sabit yapılandırma ise hâlâ üç
adres taşıyor ve 4/6 vermeye devam ediyor. Bu fark keşfin bütün karşılığıdır: sabit
yapılandırmanın yanlışı, bir kişi dosyayı düzeltene kadar sürer.

Dördüncü aşama, sağlık bilgisinin neden ayrı bir bilgi olduğunu gösteriyor. `8763` kopyası
`/bozul` çağrısından sonra yaşamayı ve kaydını yenilemeyi sürdürüyor; kayıt defterindeki diri
kayıt sayısı 2 kalıyor, ama yönlendirilebilir adres 1'e düşüyor. Sürecin ayakta olması, işi
yapabildiği anlamına gelmez. Sabit yapılandırma bu ayrımı hiç yapamaz ve 2/6'ya iner: iki istek
ölü adrese, iki istek de 503 döndüren kopyaya gider.

Beşinci aşama dağıtım tarafını ölçüyor. Dördüncü bir kopya başlatıldığında kayıt defteri
tarafında hiçbir dosyaya dokunulmuyor; kopya kendini kaydediyor ve bir sonraki çözümlemede
yönlendirilebilir adres 2'ye çıkıyor. Sabit yapılandırma tarafında ise sonuç değişmiyor: yeni
kopya hiçbir isteği karşılamıyor, çünkü üç istemcinin adres dosyası hâlâ eski üçlüyü taşıyor.
Değişikliğin bedeli çıktının son satırlarındadır: dokunulan yapılandırma dosyası 3'e karşı 0,
yeniden başlatılan istemci 3'e karşı 0.

#### Kayıt Defterinin Bedeli ve Doğan Arıza Kipleri

Ödenen bedel üç kalemdir. Birincisi bir ek süreçtir ve bu süreç sıradan bir servis değildir:
kayıt defteri düştüğünde hiçbir istemci adres çözemez. Sabit yapılandırmanın tek üstünlüğü tam
buradadır — dosya, hiçbir şeye bağlı olmadan okunur. Bu yüzden kayıt defterine yaslanan
istemciler genellikle son çözümlemenin sonucunu ellerinde tutar; o zaman da elde tutulan liste
eskiyebilir ve keşif penceresi yeniden büyür.

İkincisi çözümleme isteğidir. Ölçümde her aşama başında bir çözümleme yapıldı; her çağrı öncesi
çözümleme yapılsaydı katalog servisine giden her istek iki isteğe dönüşecekti. Bu, bir önceki
dersteki yerel durak ölçüsünün keşif tarafındaki karşılığıdır ve çözümlemenin önbelleğe
alınmasını zorunlu kılar.

Üçüncüsü kira süresinin kendisidir ve iki yönü de arıza doğurur. Süre uzatılırsa keşif
penceresi büyür, yani ölü adrese giden istek artar. Süre kısaltılırsa kopyalar daha sık
yenileme yapar; kayıt defterine gelen yazma yükü kopya sayısıyla çarpılır ve ağdaki kısa bir
kesinti diri kopyaların listeden düşmesine yol açar. Listeden düşen kopya geri döndüğünde
istekler yeniden dağılır; bu salınım, kayıt defteri gerçekleştirmelerinin en tanıdık arızasıdır.

Doğan arıza kiplerinin ortak kaynağı şudur: kayıt defteri gerçeği değil, **gerçeğin gecikmeli
bir kopyasını** tutar. Sabit yapılandırmada gecikme sınırsızdır ve bir insanın dosyayı
düzeltmesine bağlıdır; kayıt defterinde gecikme kira süresiyle sınırlıdır. Keşfin sattığı şey
doğruluk değil, **yanlışın ömrüne konan üst sınırdır.**

#### Özet

- Kayıt defteri kira süreli kayıt tutar; silme ucu yoktur, çünkü çöken bir kopya kaydını
  sildiremez. Kopyalar kira süresinin üçte biri aralıkla kaydını yeniler.
- Her şey yolundayken iki yol da 6/6 verir; fark yalnız yerleşim değiştiğinde ortaya çıkar.
- Bir kopya öldürüldükten hemen sonra iki yol da 4/6 verir: keşif penceresinde altı istekten
  ikisi ölü adrese gider. Kira süresi dolduktan sonra kayıt defteri 6/6'ya döner, sabit
  yapılandırma 4/6'da kalır.
- Sağlık bilgisi diri kayıttan ayrıdır: bozuk bildiren kopya kayıtta kalır (kayıt 2) ama
  yönlendirmeden düşer (adres 1); sabit yapılandırma bu ayrımı yapamaz ve 2/6'ya iner.
- Yeni kopya eklendiğinde dokunulan yapılandırma dosyası 3'e karşı 0, yeniden başlatılan
  istemci 3'e karşı 0'dır.
- Ödenen bedel: 1 ek süreç ve tek arıza noktası, çağrı başına 1 ek çözümleme isteği, kira
  süresinin iki yönlü arızası (uzun süre keşif penceresini, kısa süre yenileme yükünü ve
  salınımı büyütür).

#### Sonraki Adım

Bu konu sınırın nereden geçeceğiyle başladı, sınırın iki yanının nasıl konuşacağıyla sürdü ve
adresin nasıl çözüleceğiyle bitti. Sınır çizildi, eşzamanlı ve eşzamansız iletişim kuruldu,
kenar sorumlulukları toplandı, istemciye özel katman eklendi, ağ ilgileri uygulamadan çıkarıldı
ve adres kodun dışından çözüldü. Yedi dersin ölçtüğü her şeyin altında duran bir kabul ise hiç
sınanmadı: bir işin ortasında sınırın iki yanındaki veri, sanki tek bir işlemin güvencesi
altındaymış gibi ele alındı. Ödünç servisi kaydı yazdı ve ücret servisi cezayı tahsil etti;
ikisi ayrı süreçlerde, ayrı veriler üzerinde ve ayrı zamanlarda oldu. Bir önceki dersin çift
tahsilat ölçümü bu kabulün ilk çatlağıydı. Sonraki konu tam olarak bunu ele alır: bir iş akışı
birden çok servisin verisine dokunduğunda, hepsinin birlikte olması ya da hiçbirinin olmaması
nasıl sağlanır, sağlanamadığında ne olur ve bunun bedeli hangi birimlerle ölçülür.

## Dağıtık Tutarlılık

Sınırın iki yanında doğruluğun korunması: iki aşamalı işlemenin maliyeti ve alternatifleri, telafi edici işlemlerle yürütülen iş akışları, veritabanı yazımı ile mesaj yayınının atomikliğini sağlayan giden kutusu kalıbı, olay sıralaması ile yinelenmenin tüketici tarafında etkisizlikle karşılanması ve şema uyumluluğuyla tüketiciyi koruyan sözleşme evrimi.

### Dağıtık İşlem Alternatifleri

Source: https://academia.sh/tr/kurslar/servis-mimarileri/dagitik-islem-alternatifleri

İki aşamalı işlemenin gerçekleştirim maliyeti: oy ile karar arasında tutulan kilidin süresi, o sürede bloke kalan bağlantı ve reddedilen istek sayısı, eşgüdümcü düştüğünde katılımcılarda askıda kalan işlem — ve aynı işin tek turlu gerçekleştirmesiyle karşılaştırılması.

Önceki konu sınırı çizdi, sınırın iki yanı arasındaki iletişimi kurdu ve adresin nasıl
çözüleceğini gösterdi. Bu üç işin hiçbiri veriye dokunmadı. Katalog servisi kitabı, üyelik
servisi ödünç sayacını kendi deposunda tutuyor; ama "ödünç ver" isteği hâlâ, iki yazmanın da
birlikte olacağı ya da hiçbirinin olmayacağı varsayımıyla yazılıyor. O varsayımın dayanağı
tek bir işlem sınırıydı ve sınır bölündüğünde kalktı.

Bu kaybın kendisi M16/K04'ün `dağıtık işlem sorunu` dersinde kurulmuş ve ölçülmüştür: iki
ayrı depoya yapılan yazmalarda ikinci çağrı başarısız olunca birincisi kesinleşmiş kalır. O
ölçüm burada tekrarlanmıyor. Burada ölçülen, o dersin bir cümleyle geçtiği klasik çözümün —
**iki aşamalı işleme** — gerçekleştirim tarafındaki bedelidir: kilidin ne kadar süre
tutulduğu, o sürede kaç isteğin bloke olduğu ve eşgüdümcü düştüğünde geride ne kaldığı.

#### Düzenek

İki servis **gerçekten ayrı iki süreçtir**; her biri kendi `node:sqlite` dosyasını açar ve
aralarında yalnız yerel ağ üzerinden çağrı vardır.

**DT1.** Depo, yazma kilidini satır düzeyinde değil dosya düzeyinde tutar. Bu yüzden aşağıda
sayılan "bloke istek" değeri bir **üst sınırdır**; satır kilitleyen bir depoda bloke olan
küme, hazırlanmış işlemin dokunduğu satırlarla sınırlı kalır. Ölçümün biçimi — karar
beklenirken kaynağın tutuluyor olması — iki durumda da aynıdır, büyüklüğü farklıdır.

**DT2.** Üyelik servisi oy vermeden önce sabit 400 ms harcar. Bu, yavaş katılımcının
modelidir; gerçek bir ağdan ölçülmüş bir gecikme değildir. Yani kilit penceresinin
büyüklüğü seçilmiş bir niceliktir; ölçülen şey, o büyüklükte bir pencerede ne olduğudur.

```sh
# kur.sh — onceki kosumu kapatir, iki servisin ayri depolarini bastan kurar
pkill -f katilimci.mjs 2>/dev/null; sleep 0.3
rm -f katalog.db* uyelik.db* servis.log
sqlite3 katalog.db >/dev/null <<'SQL'
PRAGMA journal_mode=WAL;
CREATE TABLE kitap (kitap_id INTEGER PRIMARY KEY, baslik TEXT NOT NULL, durumu TEXT NOT NULL);
INSERT INTO kitap VALUES (1,'Korluk','rafta'),(2,'Tutunamayanlar','rafta'),(3,'Kum Kitabi','rafta');
SQL
sqlite3 uyelik.db >/dev/null <<'SQL'
PRAGMA journal_mode=WAL;
CREATE TABLE uye (uye_id INTEGER PRIMARY KEY, ad TEXT NOT NULL, acik_odunc INTEGER NOT NULL, sinir INTEGER NOT NULL);
INSERT INTO uye VALUES (4,'Emre',2,3),(5,'Selin',3,3),(6,'Burak',0,9);
SQL
```

Katılımcı tarafı her iki servis için aynı modüldür; iş kuralı ve depo dışarıdan verilir.
Ayırt edici nokta `/hazirla` ucudur: yazma yapılır, işlem **kesinleştirilmez**, bağlantı
karar gelene kadar tutulur.

```js
// katilimci.mjs — bir servisin katilimci tarafi; her servis ayri surec, ayri depo
import { createServer } from "node:http";
import { DatabaseSync } from "node:sqlite";

const [ad, dosya, port, isSql, rakipSql] = process.argv.slice(2);
const yavasMs = Number(process.env.YAVAS_MS ?? 0);
const havuz = Array.from({ length: 2 }, () => {
  const db = new DatabaseSync(dosya);
  db.exec("PRAGMA busy_timeout = 50");
  return { db, mesgul: false };
});
const askida = new Map();
const bekle = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
const bos = () => havuz.find((b) => !b.mesgul);

createServer((istek, yanit) => {
  const u = new URL(istek.url, "http://y");
  const y = (o) => yanit.end(JSON.stringify(o) + "\n");
  const islem = u.searchParams.get("islem");
  const anahtar = Number(u.searchParams.get("anahtar"));

  if (u.pathname === "/hazirla" || u.pathname === "/tekil") {
    const b = bos();
    if (!b) return y({ oy: "hayir", neden: "bos baglanti yok" });
    b.mesgul = true;
    if (yavasMs) bekle(yavasMs);
    b.db.exec("BEGIN IMMEDIATE");
    if (b.db.prepare(isSql).run(anahtar).changes !== 1) {
      b.db.exec("ROLLBACK"); b.mesgul = false; return y({ oy: "hayir", neden: "kosul saglanmadi" });
    }
    if (u.pathname === "/tekil") { b.db.exec("COMMIT"); b.mesgul = false; return y({ oy: "evet" }); }
    askida.set(islem, b);                  // kilit bu yanittan sonra da tutulmaya devam ediyor
    return y({ oy: "evet" });
  }
  if (u.pathname === "/kesinlestir" || u.pathname === "/iptal") {
    const b = askida.get(islem);
    if (!b) return y({ sonuc: "bilinmeyen islem" });
    b.db.exec(u.pathname === "/kesinlestir" ? "COMMIT" : "ROLLBACK");
    b.mesgul = false; askida.delete(islem);
    return y({ sonuc: u.pathname.slice(1) });
  }
  if (u.pathname === "/rakip") {           // isleme katilmayan, bagimsiz bir yerel yazma
    const b = bos();
    if (!b) return y({ sonuc: "bloke", neden: "bos baglanti yok" });
    b.mesgul = true;
    try { b.db.exec("BEGIN IMMEDIATE"); b.db.prepare(rakipSql).run(anahtar); b.db.exec("COMMIT");
          return y({ sonuc: "yazildi" }); }
    catch (h) { return y({ sonuc: "bloke", neden: h.code ?? h.message }); }
    finally { b.mesgul = false; }
  }
  if (u.pathname === "/askida")
    return y({ servis: ad, askida: [...askida.keys()], tutulan: havuz.filter((b) => b.mesgul).length });
  y({ hata: "bilinmeyen uc" });
}).listen(Number(port), () => console.log(`${ad} ayakta: ${port}`));
```

```sh
# baslat.sh — iki servis, iki ayri surec, iki ayri depo; uyelik yavas katilimci olarak kurulur
node katilimci.mjs katalog katalog.db 8801 \
  "UPDATE kitap SET durumu='odunc' WHERE kitap_id=? AND durumu='rafta'" \
  "UPDATE kitap SET durumu = CASE durumu WHEN 'rafta' THEN 'odunc' ELSE 'rafta' END WHERE kitap_id=?" \
  >>servis.log 2>&1 &
YAVAS_MS=400 node katilimci.mjs uyelik uyelik.db 8802 \
  "UPDATE uye SET acik_odunc=acik_odunc+1 WHERE uye_id=? AND acik_odunc<sinir" \
  "UPDATE uye SET acik_odunc=acik_odunc WHERE uye_id=?" \
  >>servis.log 2>&1 &
sleep 1
```

#### Aynı İş, İki Gerçekleştirme

Eşgüdümcü tek dosyadır ve aynı iş akışını iki biçimde yürütür. `--sirayla` kipi her servisin
kendi işlemini hemen kesinleştirmesidir — M16/K04'te tutarsızlığı ölçülen düzen. Öntanımlı
kip iki turludur: önce bütün katılımcılardan oy toplanır, sonra karar dağıtılır.

```js
// esgudumcu.mjs — ayni is, iki gerceklestirme: --iki-asamali ve --sirayla
const kip = process.argv.includes("--sirayla") ? "sirayla" : "iki-asamali";
const cok = process.argv.includes("--cok");          // karar dagitilmadan once esgudumcu duser
const [kitapId, uyeId] = process.argv.slice(2).filter((a) => !a.startsWith("--")).map(Number);
const islem = `i-${kitapId}-${uyeId}`;
const katilimcilar = [
  { ad: "katalog", uc: "http://localhost:8801", anahtar: kitapId },
  { ad: "uyelik",  uc: "http://localhost:8802", anahtar: uyeId },
];
let mesaj = 0;
const cagir = async (k, yol) => {
  mesaj += 1;
  const c = await fetch(`${k.uc}/${yol}?islem=${islem}&anahtar=${k.anahtar}`);
  return c.json();
};
const ms = (a, b) => (b - a).toFixed(1);

if (kip === "sirayla") {                              // tek tur: her servis kendi isini kesinlestirir
  const t0 = performance.now();
  for (const k of katilimcilar) {
    const c = await cagir(k, "tekil");
    console.log(`  ${k.ad}: ${c.oy}${c.neden ? " (" + c.neden + ")" : ""}`);
  }
  console.log(`sirayla | tur=1 mesaj=${mesaj} | esgudumcu kilit penceresi=0 ms | sure=${ms(t0, performance.now())} ms`);
} else {                                              // iki tur: once oy, sonra karar
  const oylar = [];
  const t0 = performance.now();
  for (const k of katilimcilar) {
    const c = await cagir(k, "hazirla");
    oylar.push([k, c, performance.now()]);
    console.log(`  ${k.ad}: oy=${c.oy}${c.neden ? " (" + c.neden + ")" : ""}`);
  }
  const karar = oylar.every(([, c]) => c.oy === "evet") ? "kesinlestir" : "iptal";
  if (cok) {
    console.log(`esgudumcu karar dagitmadan dustu (karar=${karar} hicbir katilimciya ulasmadi)`);
    process.exit(1);
  }
  for (const [k, c] of oylar) if (c.oy === "evet") await cagir(k, karar);
  const t1 = performance.now();
  const [, , tOy] = oylar[0];
  console.log(`iki asamali | tur=2 mesaj=${mesaj} | karar=${karar}`);
  console.log(`  katalog kilit penceresi=${ms(tOy, t1)} ms | toplam=${ms(t0, t1)} ms (bu kosumda)`);
}
```

Bloke kalan kaynağı sayabilmek için sisteme, işleme katılmayan sıradan bir yük verilir.

```js
// rakip.mjs — isleme katilmayan istekler; pencere boyunca duzenli araliklarla yazmayi dener
const [uc, anahtar, adet, aralik] = process.argv.slice(2);
let yazildi = 0, bloke = 0, toplamBekleme = 0;
for (let n = 0; n < Number(adet); n++) {
  const t = performance.now();
  const c = await (await fetch(`${uc}/rakip?anahtar=${anahtar}`)).json();
  toplamBekleme += performance.now() - t;
  c.sonuc === "yazildi" ? (yazildi += 1) : (bloke += 1);
  await new Promise((r) => setTimeout(r, Number(aralik)));
}
console.log(`rakip: istek=${adet} yazildi=${yazildi} bloke=${bloke}` +
            ` | ortalama bekleme=${(toplamBekleme / Number(adet)).toFixed(1)} ms (bu kosumda)`);
```

#### Kilit Penceresinde Bloke Kalan Kaynak

Aynı yük, aynı iş, iki gerçekleştirme. Yük katalog servisine gider; ödünç isteğiyle ilgisi
yoktur, yalnız aynı depoya yazmak ister.

```sh
sh kur.sh; sh baslat.sh
node rakip.mjs http://localhost:8801 2 40 10 &
node esgudumcu.mjs 1 4
wait
```

```
  katalog: oy=evet
  uyelik: oy=evet
iki asamali | tur=2 mesaj=4 | karar=kesinlestir
  katalog kilit penceresi=459.5 ms | toplam=489.6 ms (bu kosumda)
rakip: istek=40 yazildi=34 bloke=6 | ortalama bekleme=12.4 ms (bu kosumda)
```

```sh
sh kur.sh; sh baslat.sh
node rakip.mjs http://localhost:8801 2 40 10 &
node esgudumcu.mjs 1 4 --sirayla
wait
```

```
  katalog: evet
  uyelik: evet
sirayla | tur=1 mesaj=2 | esgudumcu kilit penceresi=0 ms | sure=443.0 ms
rakip: istek=40 yazildi=40 bloke=0 | ortalama bekleme=2.6 ms (bu kosumda)
```

Koşumdan bağımsız iki nicelik burada belirleyicidir: tur sayısı 2'ye karşı 1, mesaj sayısı
4'e karşı 2. Katılımcı sayısı arttıkça mesaj sayısı `2n` olarak büyür ve pencere **en yavaş**
katılımcının hızına bağlanır.

Koşuma bağlı olanlar da tutarlıdır: katalog kilidi 459,5 ms tutuldu ve o pencerede 40 rakip
isteğin 6'sı bloke oldu, ortalama bekleme 2,6 ms'ten 12,4 ms'e çıktı. Katalog kendi işini
30,1 ms'de bitirmişti; kilidi 459,5 ms tutmasının nedeni kendi işi değil, **başka bir servisin
oy vermesini bekliyor olmasıdır**.

#### Eşgüdümcü Düştüğünde

İki aşamalı işlemenin asıl bedeli buradadır. Katılımcı "evet" dedikten sonra tek başına karar
veremez: kesinleştirse öteki iptal etmiş olabilir, iptal etse öteki kesinleştirmiş olabilir.
Kararı yalnız eşgüdümcü bilir.

```sh
sh kur.sh; sh baslat.sh
node esgudumcu.mjs 1 4 --cok
echo "-- katilimcilarin gorunumu --"
curl -s "http://localhost:8801/askida"; curl -s "http://localhost:8802/askida"
echo "-- disaridan okuma ve rakip yazma --"
sqlite3 katalog.db "SELECT kitap_id, durumu FROM kitap WHERE kitap_id=1;"
node rakip.mjs http://localhost:8801 2 10 10
echo "-- karar disaridan verilirse --"
ISLEM=$(curl -s "http://localhost:8801/askida" | sed 's/.*\["//;s/"\].*//')
curl -s "http://localhost:8801/kesinlestir?islem=$ISLEM"
curl -s "http://localhost:8802/kesinlestir?islem=$ISLEM"
node rakip.mjs http://localhost:8801 2 10 10
```

```
  katalog: oy=evet
  uyelik: oy=evet
esgudumcu karar dagitmadan dustu (karar=kesinlestir hicbir katilimciya ulasmadi)
-- katilimcilarin gorunumu --
{"servis":"katalog","askida":["i-1-4"],"tutulan":1}
{"servis":"uyelik","askida":["i-1-4"],"tutulan":1}
-- disaridan okuma ve rakip yazma --
1|rafta
rakip: istek=10 yazildi=0 bloke=10 | ortalama bekleme=67.4 ms (bu kosumda)
-- karar disaridan verilirse --
{"sonuc":"kesinlestir"}
{"sonuc":"kesinlestir"}
rakip: istek=10 yazildi=10 bloke=0 | ortalama bekleme=3.8 ms (bu kosumda)
```

İki serviste de **1'er askıda işlem**, ikişer bağlantılık havuzların **1'er bağlantısı**
tutulmuş durumda. Kitabın durumu dışarıdan hâlâ `rafta` görünüyor — yazma görünmez, ama
kilit gerçek: 10 rakip isteğin **10'u** bloke oldu ve ortalama bekleme 67,4 ms'e çıktı. Bu
sayı eşgüdümcü ayağa kalkana kadar düşmez. Kilit süresi artık bir gecikme değil, bir kesinti
uzunluğudur.

Son iki çağrı kararı dışarıdan verir. Bu adım gerçek sistemlerde bir insanın ya da bir
kurtarma yordamının işidir ve **doğru kararı bilmek zorundadır**; askıda kalan işlemin
kesinleştirilmesi mi iptali mi gerektiği, katılımcıların elindeki bilgiden çıkarılamaz.

#### Karşılaştırma

| Ölçü | Sırayla yerel kesinleştirme | İki aşamalı işleme |
|---|---|---|
| Tur sayısı | 1 | 2 |
| Mesaj sayısı (2 katılımcı) | 2 | 4 |
| Kilit penceresi | 0 ms | 459,5 ms (bu koşumda) |
| Pencerede bloke rakip istek | 0 / 40 | 6 / 40 (bu koşumda) |
| Eşgüdümcü düşerse askıda işlem | 0 | 2 (servis başına 1) |
| Eşgüdümcü düşerse bloke rakip istek | 0 / 10 | 10 / 10 |

**Neyi ucuzlattı:** iki aşamalı işleme, kısmi sonucu ortadan kaldırdı. `--sirayla` kipinde
katalog kesinleşip üyelik reddedildiğinde ortada sahibi olmayan bir ödünç kitap kalır;
iki aşamalı kipte hiçbir katılımcı ötekinin oyunu görmeden kesinleştirmediği için bu durum
oluşmaz. Uygulama kodunda telafi mantığı yazılmaz.

**Neyi pahalılaştırdı:** tur sayısı ikiye, mesaj sayısı `2n`'e çıktı; kaynak, sistemin en
yavaş katılımcısı kadar süre tutuldu ve o sürede işle ilgisiz istekler reddedildi.

**Hangi yeni arıza kipi doğdu:** *belirsiz durum*. Oyunu vermiş ama kararı almamış katılımcı,
kilidi süresiz tutar; kaynağı serbest bırakmanın tek yolu dışarıdan verilecek bir karardır ve
o kararı verecek olan, doğru yanıtı yerel bilgiyle bulamaz. Bu, sırayla kesinleştirmede
bulunmayan bir kiptir.

Alternatifler bu bedelin nereye taşınacağını seçer. Telafi eden düzen kilidi hiç tutmaz,
tutarsızlığı bir süre görünür kılar ve sıralamayı kısıtlar. Giden kutusu düzeni ise
atomikliği tek bir deponun içine hapseder ve yayını ayrı bir işe devreder. İkisi de "ya hep
ya hiç"i bırakıp "sonunda doğru"yu seçer.

#### Özet

- İki aşamalı işleme iki servis süreci üzerinde kuruldu; oy turu ile karar turu arasında
  katalog servisi kilidini 459,5 ms tuttu, oysa kendi işi 30,1 ms sürmüştü.
- Pencerenin uzunluğunu en yavaş katılımcı belirler; koşumdan bağımsız maliyet 1 tur yerine
  2 tur ve `n` mesaj yerine `2n` mesajdır.
- O pencerede işle ilgisiz 40 isteğin 6'sı bloke oldu (bu koşumda) ve ortalama bekleme neredeyse
  beş katına çıktı;
  tek turlu gerçekleştirmede bloke istek sayısı sıfırdı.
- Eşgüdümcü kararı dağıtmadan düştüğünde her katılımcıda 1 askıda işlem ve 1 tutulu bağlantı
  kaldı; rakip isteklerin tamamı bloke oldu ve bu durum dışarıdan karar verilene kadar sürdü.
- Askıda kalan işlemin kesinleştirilmesi mi iptali mi gerektiği katılımcıların yerel
  bilgisinden çıkarılamaz; bu, tek turlu gerçekleştirmede olmayan yeni bir arıza kipidir.

#### Sonraki Adım

Kilit penceresini sıfırlamanın yolu, adımları hazırlamak yerine **tek tek kesinleştirmek** ve
başarısızlıkta tamamlananları geri sarmaktır. M16/K04 bu düzenin bir telafi adımını kurmuştu,
M19/K05 telafi edici işlemin bedelini ölçmüştü. Geriye ikisinin de değinmediği bir kısıt
kalıyor: telafi edilebilirliği eşit olmayan adımlar bir sıraya dizilmek zorundadır ve o sıra
keyfî değildir. Sonraki ders aynı ödünç akışını iki farklı adım sırasıyla koşturup her sırada
geri alınamayan yan etkiyi sayar, sonra aynı akışı koreografi ve yürütücü olarak iki kez
gerçekleştirip kod tarafındaki farkı — dokunulan servis ve bilinen adım sayısını — ölçer.

### Saga Kalıbı

Source: https://academia.sh/tr/kurslar/servis-mimarileri/saga-kalibi

Telafi edici işlemlerle yürüyen iş akışının iki gerçekleştirim kısıtı: adım sırasının telafi edilebilirliğe göre seçilmesi ve her hata noktasında geride kalan geri alınamayan yan etkinin sayılması; koreografi ile yürütücünün kod tarafındaki farkı — akış bilgisini taşıyan dosya, adlandırılan adım, bir adım eklenince dokunulan dosya.

Önceki ders iki aşamalı işlemenin bedelini gösterdi: kaynak, en yavaş katılımcının hızı kadar
tutuldu ve eşgüdümcü düştüğünde askıda kaldı. Saga kalıbı bu pencereyi tamamen kaldırır. Her
adım kendi işlemini hemen kesinleştirir; bir adım başarısız olursa tamamlananlar tersten
**telafi edilir**.

Telafi adımının kendisi M16/K04'ün `dağıtık işlem sorunu` dersinde kurulmuştur, telafi edici
işlemin bedeli M19/K05'in `dağıtık doğruluk` konusunda ölçülmüştür. İkisi de burada
tekrarlanmıyor. Bu dersin ölçtüğü şey ikisinin de değinmediği bir kısıttır: **adımların
sırası**. Sıra keyfî değildir, çünkü her adım geri alınabilir değildir; ve sıranın seçimi,
başarısızlık anında sistemden dışarı çıkmış ve geri çağrılamayan etkinin sayısını belirler.

#### Düzenek

**DT3.** Bu derste servisler ayrı süreçler değil, aynı süreçte çalışan ve **ayrı depolara**
yazan modüllerdir. Modelin taşıdığı özellik, her adımın kendi işlem sınırında kesinleşmesi ve
ortak bir geri alma çağrısının bulunmamasıdır. Ağ gecikmesi ve kısmi hata modelde yoktur.

**DT4.** Bildirim adımının dış etkisi, bir dosyaya satır yazmakla modellenir. Satırı silmek
teslimi geri almadığı için bu adımın telafisi **yoktur**; yapılabilecek tek şey ikinci bir
bildirimle düzeltmedir.

```sh
# kur.sh — dort servisin ayri depolari ve dis dunyaya giden bildirim gunlugu
rm -f katalog.db uyelik.db ucret.db gonderilen.log
sqlite3 katalog.db "CREATE TABLE kitap (kitap_id INTEGER PRIMARY KEY, durumu TEXT NOT NULL);
                    INSERT INTO kitap VALUES (1,'rafta'),(2,'rafta');"
sqlite3 uyelik.db  "CREATE TABLE uye (uye_id INTEGER PRIMARY KEY, acik_odunc INTEGER NOT NULL);
                    INSERT INTO uye VALUES (4,0);"
sqlite3 ucret.db   "CREATE TABLE hareket (id INTEGER PRIMARY KEY, uye_id INTEGER, tutar INTEGER);"
```

```js
// adimlar.mjs — odunc akisinin dort adimi; her adimin telafisi ya vardir ya yoktur
import { DatabaseSync } from "node:sqlite";
import { appendFileSync, readFileSync, writeFileSync } from "node:fs";

const ac = (d) => new DatabaseSync(d);
const katalogDb = ac("katalog.db"), uyelikDb = ac("uyelik.db"), ucretDb = ac("ucret.db");
const gonder = (satir) => appendFileSync("gonderilen.log", satir + "\n");

export const adimlar = {
  "katalog.ayirt": {
    yap: (i) => katalogDb.prepare("UPDATE kitap SET durumu='odunc' WHERE kitap_id=?").run(i.kitap),
    telafi: (i) => katalogDb.prepare("UPDATE kitap SET durumu='rafta' WHERE kitap_id=?").run(i.kitap) },
  "ucret.tahsil": {
    yap: (i) => ucretDb.prepare("INSERT INTO hareket (uye_id,tutar) VALUES (?,5)").run(i.uye),
    telafi: (i) => ucretDb.prepare("INSERT INTO hareket (uye_id,tutar) VALUES (?,-5)").run(i.uye) },
  "uyelik.oduncEkle": {
    yap: (i) => uyelikDb.prepare("UPDATE uye SET acik_odunc=acik_odunc+1 WHERE uye_id=?").run(i.uye),
    telafi: (i) => uyelikDb.prepare("UPDATE uye SET acik_odunc=acik_odunc-1 WHERE uye_id=?").run(i.uye) },
  "bildirim.gonder": {                       // sistemin disina cikar: geri alinamaz
    yap: (i) => gonder(`uye ${i.uye}: kitap ${i.kitap} hazir`),
    telafi: null, duzeltme: (i) => gonder(`uye ${i.uye}: kitap ${i.kitap} icin onceki bildirim gecersiz`) },
};

export function sifirla() {
  katalogDb.exec("UPDATE kitap SET durumu='rafta'");
  uyelikDb.exec("UPDATE uye SET acik_odunc=0");
  ucretDb.exec("DELETE FROM hareket");
  writeFileSync("gonderilen.log", "");
}
export const gonderim = () =>
  readFileSync("gonderilen.log", "utf8").split("\n").filter((s) => s.length > 0);
```

Dört adımın üçünün telafisi var, birinin yok. Ayırt edici tek fark budur ve bütün sıralama
kısıtı bu farktan doğar.

#### Sıralama Kısıtı

Aynı dört adım iki farklı sıraya dizilir ve her sıra bütün hata noktaları için ayrı ayrı
koşturulur. Ölçülen nicelik, telafi bittikten sonra geride kalan **geri alınamayan yan
etki** sayısıdır.

```js
// tarama.mjs — ayni dort adim, iki farkli sira; her hata noktasi ayri ayri taranir
import { adimlar, sifirla, gonderim } from "./adimlar.mjs";

const siralar = {
  "A (bildirim erken)": ["katalog.ayirt", "bildirim.gonder", "ucret.tahsil", "uyelik.oduncEkle"],
  "B (bildirim sonda)": ["katalog.ayirt", "ucret.tahsil", "uyelik.oduncEkle", "bildirim.gonder"],
};

function kos(sira, hataAdimi, istek) {
  sifirla();
  const yapilan = [];
  let telafi = 0, geriAlinamayan = 0, duzeltme = 0;
  try {
    for (const ad of sira) {
      if (yapilan.length + 1 === hataAdimi) throw new Error(`${ad} basarisiz`);
      adimlar[ad].yap(istek); yapilan.push(ad);
    }
  } catch {
    for (const ad of [...yapilan].reverse()) {
      const a = adimlar[ad];
      if (a.telafi) { a.telafi(istek); telafi += 1; }
      else { geriAlinamayan += 1; if (a.duzeltme) { a.duzeltme(istek); duzeltme += 1; } }
    }
  }
  return { tamamlanan: yapilan.length, telafi, geriAlinamayan, duzeltme, gonderim: gonderim().length };
}

for (const [ad, sira] of Object.entries(siralar)) {
  console.log(`\n${ad}: ${sira.join(" > ")}`);
  console.log("hata_adimi tamamlanan telafi geri_alinamayan duzeltme toplam_gonderim");
  let toplam = 0;
  for (let k = 1; k <= sira.length; k++) {
    const s = kos(sira, k, { kitap: 1, uye: 4 });
    toplam += s.geriAlinamayan;
    console.log(`     ${k}          ${s.tamamlanan}        ${s.telafi}           ` +
                `${s.geriAlinamayan}            ${s.duzeltme}          ${s.gonderim}`);
  }
  console.log(`toplam geri alinamayan yan etki: ${toplam}`);
}
```

```sh
sh kur.sh
node tarama.mjs
```

```
A (bildirim erken): katalog.ayirt > bildirim.gonder > ucret.tahsil > uyelik.oduncEkle
hata_adimi tamamlanan telafi geri_alinamayan duzeltme toplam_gonderim
     1          0        0           0            0          0
     2          1        1           0            0          0
     3          2        1           1            1          2
     4          3        2           1            1          2
toplam geri alinamayan yan etki: 2

B (bildirim sonda): katalog.ayirt > ucret.tahsil > uyelik.oduncEkle > bildirim.gonder
hata_adimi tamamlanan telafi geri_alinamayan duzeltme toplam_gonderim
     1          0        0           0            0          0
     2          1        1           0            0          0
     3          2        2           0            0          0
     4          3        3           0            0          0
toplam geri alinamayan yan etki: 0
```

Aynı adımlar, aynı iş, aynı telafi kodu. Fark yalnız sıradadır ve sonuç dört hata noktasında
**2'ye karşı 0**'dır. A sırasında bildirim ikinci adım olduğu için, ondan sonraki her
başarısızlıkta üyeye yanlış bir haber gitmiş olur; geri alınamadığı için tek yapılabilen,
üyeye ikinci bir ileti daha göndermektir. Toplam gönderim `0` değil `2`'dir: iş
gerçekleşmediği hâlde iki teslim yapılmıştır.

B sırası bunu sıfıra indirir ama bedelsiz değildir. B'nin son satırında telafi sayısı **3**;
A'nın aynı satırında **2**. Telafi edilemeyen adım sona alındığında, o adım başarısız
olduğunda geri sarılacak iş en fazladır. Yani sıralama kararı iki nicelik arasında yapılır:
**geri alınamayan yan etki** ile **geri sarılan tamamlanmış adım**. Birincisi kullanıcıya
görünür ve düzeltilemez, ikincisi yalnız pahalıdır. Kural bu asimetriden çıkar: telafi
edilemeyen adımlar sıranın sonuna konur.

#### Aynı Akış, İki Gerçekleştirme

Sıra belirlendikten sonra geriye onu kimin yürüteceği kalır. İki gerçekleştirme vardır ve
ikisi de aynı sonucu üretir. **Yürütücü** kipinde akış tek bir modülde durur; servis modülleri
sıradaki yerlerini bilmez.

```js
// yurutucu.mjs — akisin tamami tek modulde; servis modulleri akisi hic bilmez
import { adimlar } from "./adimlar.mjs";
export const sira = ["katalog.ayirt", "ucret.tahsil", "uyelik.oduncEkle", "bildirim.gonder"];
export function yurut(istek) {
  const yapilan = [];
  for (const ad of sira) { adimlar[ad].yap(istek); yapilan.push(ad); }
  return yapilan;                       // telafi sirasi da bu listeden okunur
}
```

**Koreografi** kipinde merkezî bir yürütücü yoktur; her servis kendinden sonrakini tetikler.
Telafi de geriye doğru yürüdüğü için her düğüm önceki komşusunu da bilmek zorundadır.

```js
// d-katalog.mjs — katalog dugumu: kendi adimi ve akistaki iki komsusu
export const dugum = { ad: "katalog", adim: "katalog.ayirt", onceki: null, sonraki: "ucret" };
```

```js
// d-ucret.mjs — ucret dugumu: kendi adimi ve akistaki iki komsusu
export const dugum = { ad: "ucret", adim: "ucret.tahsil", onceki: "katalog", sonraki: "uyelik" };
```

```js
// d-uyelik.mjs — uyelik dugumu: kendi adimi ve akistaki iki komsusu
export const dugum = { ad: "uyelik", adim: "uyelik.oduncEkle", onceki: "ucret", sonraki: "bildirim" };
```

```js
// d-bildirim.mjs — bildirim dugumu: kendi adimi ve akistaki iki komsusu
export const dugum = { ad: "bildirim", adim: "bildirim.gonder", onceki: "uyelik", sonraki: null };
```

```js
// koreografi.mjs — merkezi yurutucu yok; veri yolu sirayi bilmez, her dugum komsusunu bilir
import { adimlar } from "./adimlar.mjs";
import { dugum as a } from "./d-katalog.mjs";
import { dugum as b } from "./d-ucret.mjs";
import { dugum as c } from "./d-uyelik.mjs";
import { dugum as d } from "./d-bildirim.mjs";
const yol = Object.fromEntries([a, b, c, d].map((x) => [x.ad, x]));
export function baslat(ilkDugum, istek) {
  const yapilan = [];
  for (let x = yol[ilkDugum]; x; x = x.sonraki ? yol[x.sonraki] : null) {
    adimlar[x.adim].yap(istek); yapilan.push(x.adim);   // her dugum bir sonrakini tetikler
  }
  return yapilan;
}
```

`koreografi.mjs` dört düğümü de içeri alır, ama sırayı bilmez: hangi düğümün nereye
bağlandığı düğümlerin kendi dosyalarındadır. Modül bir kayıt defteridir, akış tanımı değil.

#### Kod Tarafındaki Fark

Ölçüm kaynağın kendisinden okunur: her dosyada, kendisi dışındaki kaç servisin adı geçiyor?

```js
// olc-kod.mjs — akis bilgisinin kaynak metinde kac dosyaya dagildigini sayar
import { readFileSync } from "node:fs";
import { sifirla, gonderim } from "./adimlar.mjs";
import { yurut, sira } from "./yurutucu.mjs";
import { baslat } from "./koreografi.mjs";
const servisler = ["katalog", "ucret", "uyelik", "bildirim"];
const kenar = ["ucret", "uyelik"];                 // yeni adim bu ikisinin arasina girecek

function olc(etiket, dosyalar) {
  let adlandirilan = 0, tasiyan = 0, dokunulan = 0;
  for (const f of dosyalar) {
    const metin = readFileSync(f, "utf8");
    const kendi = f.replace(/^d-/, "").replace(/\.mjs$/, "");
    const n = servisler.filter((s) => s !== kendi && metin.includes(s)).length;
    adlandirilan += n; if (n > 0) tasiyan += 1;
    if (kenar.every((s) => metin.includes(s))) dokunulan += 1;
  }
  console.log(`${etiket}: akis adi tasiyan dosya=${tasiyan}/${dosyalar.length}` +
    ` adlandirilan adim=${adlandirilan} | akisi okumak icin acilacak dosya=${tasiyan}` +
    ` | araya adim eklenince dokunulan mevcut dosya=${dokunulan}`);
}

sifirla(); const y = yurut({ kitap: 1, uye: 4 });
console.log(`yurutucu  sonuc: adim=${y.length} gonderim=${gonderim().length} [${y.join(",")}]`);
sifirla(); const k = baslat("katalog", { kitap: 1, uye: 4 });
console.log(`koreografi sonuc: adim=${k.length} gonderim=${gonderim().length} [${k.join(",")}]`);
console.log(`ayni sonuc mu: ${JSON.stringify(y) === JSON.stringify(k)}`);
olc("yurutucu ", ["yurutucu.mjs"]);
olc("koreografi", ["d-katalog.mjs", "d-ucret.mjs", "d-uyelik.mjs", "d-bildirim.mjs"]);
console.log(`akis uzunlugu (iki gerceklestirmede de): ${sira.length} adim`);
```

```sh
sh kur.sh
node olc-kod.mjs
```

```
yurutucu  sonuc: adim=4 gonderim=1 [katalog.ayirt,ucret.tahsil,uyelik.oduncEkle,bildirim.gonder]
koreografi sonuc: adim=4 gonderim=1 [katalog.ayirt,ucret.tahsil,uyelik.oduncEkle,bildirim.gonder]
ayni sonuc mu: true
yurutucu : akis adi tasiyan dosya=1/1 adlandirilan adim=4 | akisi okumak icin acilacak dosya=1 | araya adim eklenince dokunulan mevcut dosya=1
koreografi: akis adi tasiyan dosya=4/4 adlandirilan adim=6 | akisi okumak icin acilacak dosya=4 | araya adim eklenince dokunulan mevcut dosya=2
akis uzunlugu (iki gerceklestirmede de): 4 adim
```

Koşum tarafı özdeş: dört adım, bir gönderim, aynı adım listesi. Fark tümüyle kod tarafındadır.
Yürütücüde akış bilgisi 1 dosyada ve 4 adlandırma olarak durur. Koreografide 4 dosyaya
dağılır ve 6 adlandırmaya çıkar; fazlalık, telafinin geriye yürüyebilmesi için gereken
`onceki` kenarlarından gelir. Yürütücünün `yapilan` listesi tek bir yerde biriktiği için o
kenarlara ihtiyaç duymaz.

Araya yeni bir adım eklenmesi yürütücüde 1, koreografide 2 mevcut dosyaya dokunur: yeni adımın
öncesindeki düğümün `sonraki` alanı ile sonrasındaki düğümün `onceki` alanı birlikte
değişmek zorundadır. Buna karşılık koreografide hiçbir modül bütün akışı bilmez; bir servisin
değişmesi yürütücüyü değiştirmez ve akışa yeni bir dinleyici eklemek hiçbir mevcut dosyaya
dokunmadan yapılabilir.

**Neyi ucuzlattı:** saga, iki aşamalı işlemenin kilit penceresini sıfırladı; her adım kendi
işlemini hemen kesinleştirdiği için hiçbir kaynak başka bir servisin yanıtı beklenirken
tutulmaz. Yürütücü kipi ayrıca akışın tamamını tek dosyada okunur kıldı.

**Neyi pahalılaştırdı:** her adım için ikinci bir gerçekleştirme — telafi — yazılmak zorunda.
Adım sırası artık serbest değil; telafi edilebilirliğe göre kısıtlı. Koreografi kipinde akış
bilgisi 1 dosyadan 4 dosyaya, 4 adlandırmadan 6 adlandırmaya dağıldı.

**Hangi yeni arıza kipi doğdu:** *yarım kalmış akış*. Telafi edilemeyen adım tamamlandıktan
sonra çıkan hata, sistemi ne başlangıç ne bitiş durumunda bırakır; A sırasında bu, iş hiç
olmadığı hâlde üyeye giden 2 teslim olarak ölçüldü. Yürütücü kipinde ek olarak yürütücünün
kendisi tek arıza noktasıdır: `yapilan` listesi onunla birlikte kaybolursa telafi sırası
kimsede kalmaz.

#### Özet

- Saga, iki aşamalı işlemenin kilit penceresini kaldırır; karşılığında her adımın telafisini
  yazmayı ve adımları telafi edilebilirliğe göre sıralamayı zorunlu kılar.
- Aynı dört adım iki sıraya dizildiğinde, dört hata noktası boyunca geri alınamayan yan etki
  A sırasında 2, B sırasında 0 çıktı; A'da iş hiç olmadığı hâlde 2 teslim yapıldı.
- Bedel karşılıklıdır: B sırasında son adımın başarısızlığı 3 telafi çağrısı gerektirdi,
  A'da 2. Telafi edilemeyen adım sona konur, çünkü geri sarılan iş pahalıdır ama düzeltilir;
  geri alınamayan etki düzeltilmez.
- Yürütücü ve koreografi aynı sonucu üretti — 4 adım, 1 gönderim, aynı adım listesi — fark
  yalnız kodda: akış bilgisi 1 dosya ve 4 adlandırmaya karşı 4 dosya ve 6 adlandırma.
- Araya adım eklemek yürütücüde 1, koreografide 2 mevcut dosyaya dokunur; buna karşılık
  koreografide hiçbir modül akışın tamamını bilmez ve yeni dinleyici eklemek mevcut dosyalara
  dokunmaz.

#### Sonraki Adım

Bu derste her adım "yap" çağrısını doğrudan yaptı; adımın kesinleşmesiyle bir sonrakine haber
verilmesi arasında hiçbir boşluk yokmuş gibi davranıldı. Gerçekte bu iki iş ayrıdır: servis
kendi deposuna yazar, sonra iletiyi yayımlar — ve ikisinin arasında düşebilir. Sonraki ders
bu boşluğu ölçer: yayıncı çöktüğünde kaç ileti kaybolur, giden kutusuyla aynı yük altında kaç
ileti ikinci kez teslim edilir, aktarıcının gecikmesi ne kadardır ve kutuda kaç satır birikir.

### Giden Kutusu Kalıbı

Source: https://academia.sh/tr/kurslar/servis-mimarileri/giden-kutusu-kalibi

Veritabanı yazması ile ileti yayınının aynı işleme alınmasının ölçülen bedeli: yayıncı çöktüğünde giden kutusuz kaybolan ileti, giden kutusuyla ikinci kez teslim edilen ileti, yoklama aralığının belirlediği aktarıcı gecikmesi ve aktarıcı durduğunda tabloda biriken satır.

Önceki derste her adım bir sonrakini doğrudan çağırdı: katalog kendi deposuna yazdı ve hemen
ardından ödünç bilgisini üyeliğe bildirdi. Kod tek satırdı, ama iki iş vardı — biri yerel
işlemin içinde, öteki dışında. Aralarına düşen süreç, işi yapmış ama kimseye söylememiş olur.

M16/K04'ün `dağıtık işlem sorunu` dersi giden kutusu düzenini kurmuş ve tek bir iletinin iki
kez teslim edildiğini göstermişti. O ölçüm burada tekrarlanmıyor. Buradaki ölçü **bir yük
üzerinde**dir: yayıncı çöktüğünde kaç ileti kayboluyor, giden kutusuyla aynı çökme dizisinde
kaç ileti ikinci kez teslim ediliyor, aktarıcının gecikmesi ne oluyor ve tabloda kaç satır
birikiyor.

#### Düzenek

**DT5.** Yayıncının çökmesi süreç gerçekten öldürülerek değil, kesinleştirme ile yayın
arasındaki noktada tohumlu bir üreteçten çekilen kararla modellenir. Tohum `20260731`, çökme
oranı `0,10`. Karar dizisi **teslim denemesi numarasına** göre okunduğu için, `k`'inci teslim
denemesi iki gerçekleştirmede de aynı kaderi paylaşır.

**DT6.** Aktarıcı tetiklenmez, kutuyu yoklar. Ölçülen gecikme bu tercihin doğrudan sonucudur;
tetiklemeli bir aktarıcıda gecikmenin alt sınırı yoklama aralığına bağlı olmaz.

```sh
# kur.sh — katalog kendi durumunu ve giden kutusunu tutar, uyelik teslimleri kaydeder
rm -f katalog.db* uyelik.db*
sqlite3 katalog.db "PRAGMA journal_mode=WAL;
  CREATE TABLE hareket (id INTEGER PRIMARY KEY, kitap_id INTEGER, uye_id INTEGER);
  CREATE TABLE giden_kutusu (ileti_id INTEGER PRIMARY KEY, kitap_id INTEGER, uye_id INTEGER,
                             yazilma INTEGER NOT NULL, durum TEXT NOT NULL DEFAULT 'bekliyor');" >/dev/null
sqlite3 uyelik.db "PRAGMA journal_mode=WAL;
  CREATE TABLE teslim (sira INTEGER PRIMARY KEY, ileti_id INTEGER, varis INTEGER);" >/dev/null
```

```js
// uretec.mjs — kendi yazdigimiz uretec; tohum gorunur, dizi her kosumda ayni
export function uretec(tohum) {
  let d = tohum >>> 0;
  return () => { d = (Math.imul(d, 1103515245) + 12345) >>> 0; return (d >>> 8) / 16777216; };
}
```

Üyelik tarafındaki `teslim` tablosu her teslimi ayrı satır olarak biriktirir; yinelenen bir
teslim aynı `ileti_id` ile ikinci kez görünür. Kaybolan ileti ile yinelenen ileti bu tek
tablodan sayılır.

#### Aynı Yük, İki Gerçekleştirme

İki yüz ödünç isteği. Birinci gerçekleştirme durumu kesinleştirir, sonra yayımlar. İkincisi
durumu ve iletiyi tek işlemde yazar, yayını ayrı bir geçişe bırakır.

```js
// yayinci.mjs — ayni yuk, iki gerceklestirme: --dogrudan ve --giden-kutusu
import { DatabaseSync } from "node:sqlite";
import { uretec } from "./uretec.mjs";

const kip = process.argv.includes("--dogrudan") ? "dogrudan" : "giden-kutusu";
const N = 200, TOHUM = 20260731, ORAN = 0.10;
const zar = uretec(TOHUM);
// deneme numarasina gore sabit cokme dizisi: k'inci teslim denemesi iki kipte de ayni kaderi paylasir
const coker = Array.from({ length: N * 4 }, () => zar() < ORAN);
let deneme = 0, cokme = 0;

const katalog = new DatabaseSync("katalog.db");
const uyelik = new DatabaseSync("uyelik.db");
const teslimEt = (iletiId) =>
  uyelik.prepare("INSERT INTO teslim (ileti_id, varis) VALUES (?,?)").run(iletiId, Date.now());

if (kip === "dogrudan") {
  for (let n = 1; n <= N; n++) {
    katalog.exec("BEGIN IMMEDIATE");
    const r = katalog.prepare("INSERT INTO hareket (kitap_id, uye_id) VALUES (?,4)").run(n);
    katalog.exec("COMMIT");
    if (coker[deneme++]) { cokme += 1; continue; }   // surec burada duser: ileti hic uretilmedi
    teslimEt(Number(r.lastInsertRowid));
  }
} else {
  for (let n = 1; n <= N; n++) {
    katalog.exec("BEGIN IMMEDIATE");                 // durum ve ileti tek islemde
    katalog.prepare("INSERT INTO hareket (kitap_id, uye_id) VALUES (?,4)").run(n);
    katalog.prepare("INSERT INTO giden_kutusu (kitap_id, uye_id, yazilma) VALUES (?,4,?)")
      .run(n, Date.now());
    katalog.exec("COMMIT");
  }
  for (let gecis = 0; gecis < 500; gecis++) {        // aktarici gecisleri
    const bekleyen = katalog.prepare(
      "SELECT ileti_id FROM giden_kutusu WHERE durum='bekliyor' ORDER BY ileti_id").all();
    if (bekleyen.length === 0) break;
    for (const i of bekleyen) {
      teslimEt(i.ileti_id);
      if (coker[deneme++]) { cokme += 1; break; }    // isaretleme oncesi duser: yeniden teslim edilecek
      katalog.prepare("UPDATE giden_kutusu SET durum='gonderildi' WHERE ileti_id=?").run(i.ileti_id);
    }
  }
}

const satir = uyelik.prepare("SELECT COUNT(*) c FROM teslim").get().c;
const benzersiz = uyelik.prepare("SELECT COUNT(DISTINCT ileti_id) c FROM teslim").get().c;
const kutu = katalog.prepare("SELECT COUNT(*) c FROM giden_kutusu").get().c;
console.log(`${kip.padEnd(13)}| uretilen=${N} cokme=${cokme} | teslim satiri=${satir}` +
  ` benzersiz=${benzersiz} kaybolan=${N - benzersiz} yinelenen=${satir - benzersiz}` +
  ` | kutuda satir=${kutu}`);
```

```sh
sh kur.sh
node yayinci.mjs --dogrudan
sh kur.sh
node yayinci.mjs --giden-kutusu
```

```
dogrudan     | uretilen=200 cokme=22 | teslim satiri=178 benzersiz=178 kaybolan=22 yinelenen=0 | kutuda satir=0
giden-kutusu | uretilen=200 cokme=24 | teslim satiri=224 benzersiz=200 kaybolan=0 yinelenen=24 | kutuda satir=200
```

Doğrudan yayında 200 durum değişikliğinin 22'si için hiçbir ileti üretilmedi. Bu 22 kitap
katalogda ödünç göründü, üyelik tarafında hiç görünmedi ve **bu durumdan hiçbir kayıt
kalmadı**: kutuda 0 satır var, yani sistemde "gönderilmesi gereken ama gönderilmemiş" diye
bakılabilecek bir yer yok. Kayıp sessizdir.

Giden kutusunda kaybolan sıfır. Çökme sayısı 22 değil 24; çünkü bu kip daha çok teslim
denemesi yaptı (224'e karşı 200) ve ilk 200 denemenin kaderi doğrudan kiple aynı olsa da
sonraki 24 denemede 2 çökme daha çıktı. Her çökme tam olarak bir ikinci teslim doğurdu:
`224 − 200 = 24` yinelenen. Kayıp, yinelenmeye dönüştü.

Üçüncü sayı gözden kaçmamalı: kutuda 200 satır var. Bunlar teslim edilmiş, işi bitmiş
satırlardır ve tablo kendiliğinden küçülmez.

#### Aktarıcı Gecikmesi

Yukarıdaki geçişler ardışıktı. Gerçekte aktarıcı ayrı koşar ve kutuyu belirli aralıklarla
yoklar; aralık, iletinin yazılmasıyla teslimi arasındaki süreyi doğrudan belirler.

```js
// yazici.mjs — durum degisikligini ve iletiyi tek islemde, duzenli araliklarla kutuya yazar
import { DatabaseSync } from "node:sqlite";
const [adet, aralik] = process.argv.slice(2).map(Number);
const db = new DatabaseSync("katalog.db");
db.exec("PRAGMA busy_timeout = 500");
for (let n = 1; n <= adet; n++) {
  db.exec("BEGIN IMMEDIATE");
  db.prepare("INSERT INTO hareket (kitap_id, uye_id) VALUES (?,4)").run(n);
  db.prepare("INSERT INTO giden_kutusu (kitap_id, uye_id, yazilma) VALUES (?,4,?)").run(n, Date.now());
  db.exec("COMMIT");
  await new Promise((r) => setTimeout(r, aralik));
}
console.log(`yazici: ${adet} ileti, ${aralik} ms arayla yazildi`);
```

```js
// aktarici.mjs — kutuyu belirli araliklarla yoklar; gecikmeyi ve bos yoklamayi olcer
import { DatabaseSync } from "node:sqlite";
const [aralik, sure] = process.argv.slice(2).map(Number);
const katalog = new DatabaseSync("katalog.db"); katalog.exec("PRAGMA busy_timeout = 500");
const uyelik = new DatabaseSync("uyelik.db");
const gecikmeler = [];
let yoklama = 0, bosYoklama = 0, enCokBekleyen = 0;
const bitis = Date.now() + sure;
while (Date.now() < bitis) {
  const bekleyen = katalog.prepare(
    "SELECT ileti_id, yazilma FROM giden_kutusu WHERE durum='bekliyor' ORDER BY ileti_id").all();
  yoklama += 1;
  if (bekleyen.length === 0) bosYoklama += 1;
  enCokBekleyen = Math.max(enCokBekleyen, bekleyen.length);
  for (const i of bekleyen) {
    const simdi = Date.now();
    uyelik.prepare("INSERT INTO teslim (ileti_id, varis) VALUES (?,?)").run(i.ileti_id, simdi);
    katalog.exec("BEGIN IMMEDIATE");
    katalog.prepare("UPDATE giden_kutusu SET durum='gonderildi' WHERE ileti_id=?").run(i.ileti_id);
    katalog.exec("COMMIT");
    gecikmeler.push(simdi - i.yazilma);
  }
  await new Promise((r) => setTimeout(r, aralik));
}
gecikmeler.sort((a, b) => a - b);
const ortanca = gecikmeler[Math.floor(gecikmeler.length / 2)] ?? 0;
console.log(`aktarici(aralik=${aralik} ms): yoklama=${yoklama} bos yoklama=${bosYoklama}` +
  ` teslim=${gecikmeler.length} en cok bekleyen satir=${enCokBekleyen}` +
  ` | gecikme ortanca=${ortanca} ms en buyuk=${gecikmeler.at(-1) ?? 0} ms (bu kosumda)`);
```

```sh
# ayni yazma hizi (60 ileti, 50 ms arayla), iki farkli yoklama araligi
for ARALIK in 20 250; do
  sh kur.sh
  node yazici.mjs 60 50 &
  node aktarici.mjs $ARALIK 4500
  wait
  sqlite3 katalog.db "SELECT durum, COUNT(*) FROM giden_kutusu GROUP BY durum;"
done
```

```
yazici: 60 ileti, 50 ms arayla yazildi
aktarici(aralik=20 ms): yoklama=204 bos yoklama=144 teslim=60 en cok bekleyen satir=1 | gecikme ortanca=13 ms en buyuk=23 ms (bu kosumda)
gonderildi|60
yazici: 60 ileti, 50 ms arayla yazildi
aktarici(aralik=250 ms): yoklama=18 bos yoklama=4 teslim=60 en cok bekleyen satir=5 | gecikme ortanca=131 ms en buyuk=252 ms (bu kosumda)
gonderildi|60
```

Koşumdan bağımsız olan taraf nettir: iki koşumda da 60 ileti yazıldı ve 60'ı teslim edildi.
Değişen, aynı işin kaç sorguyla ve ne kadar bekleyerek yapıldığıdır. Yirmi milisaniyelik
aralıkta 204 yoklamanın 144'ü boştur (bu koşumda) — teslim başına yaklaşık 3,4 sorgu, onda
yedisi karşılıksız. İki yüz elli milisaniyelik aralıkta yoklama 18'e iner ve boş yoklama
neredeyse kalmaz, ama ortanca gecikme 13 ms'ten 131 ms'e çıkar ve bir geçişte kutuda en çok 1 yerine 5 satır
birikir. Aralık, boş sorgu ile gecikme arasında doğrudan bir takastır.

#### Aktarıcı Durduğunda

Aktarıcı yayıncıdan bağımsız bir süreç olduğu için ayrı düşebilir. Bu, giden kutusunun
kazancının en görünür olduğu ve bedelinin en somutlaştığı durumdur.

```sh
sh kur.sh
node yazici.mjs 60 20
sqlite3 katalog.db "SELECT durum, COUNT(*) FROM giden_kutusu GROUP BY durum;"
node aktarici.mjs 20 1500
sqlite3 katalog.db "SELECT durum, COUNT(*) FROM giden_kutusu GROUP BY durum;"
sqlite3 katalog.db "DELETE FROM giden_kutusu WHERE durum='gonderildi';
                    SELECT 'budamadan sonra satir: ' || COUNT(*) FROM giden_kutusu;"
```

```
yazici: 60 ileti, 20 ms arayla yazildi
bekliyor|60
aktarici(aralik=20 ms): yoklama=68 bos yoklama=67 teslim=60 en cok bekleyen satir=60 | gecikme ortanca=743 ms en buyuk=1382 ms (bu kosumda)
gonderildi|60
budamadan sonra satir: 0
```

Aktarıcı yokken 60 satır kutuda birikti ve hiçbiri kaybolmadı. Aktarıcı geri geldiğinde
hepsini tek geçişte teslim etti; bedeli, ortanca gecikmenin 13 ms'ten 743 ms'e, en büyük
gecikmenin 1382 ms'e çıkmasıdır (bu koşumda). Aktarıcı ne kadar uzun durursa biriken satır ve yakalama
gecikmesi o kadar büyür — bu, izlenmesi gereken bir niceliktir, kendiliğinden sınırlı değildir.

Son satır tablonun büyümesini gösterir: teslim edilen 60 satır tabloda kalır ve ancak açık bir
budama ile silinir. Giden kutusu, iş kuralıyla ilgisi olmayan bir tabloyu yazma yoluna ekler;
o tablonun temizlenmesi de bir iştir.

#### Üç Sütun

**Neyi ucuzlattı:** durum değişikliği ile iletinin birlikte yazılması, "yapıldı ama
duyurulmadı" durumunu ortadan kaldırdı. Aynı çökme dizisinde doğrudan yayın 22 ileti
kaybederken giden kutusu 0 kaybetti; üstelik kayıp sessiz değil, kutuda bekleyen bir satır
olarak görünür.

**Neyi pahalılaştırdı:** her ödünç isteği artık iki tabloya yazıyor; ayrıca yeni bir süreç
(aktarıcı) ve yeni bir ayar (yoklama aralığı) var. Teslim başına 3,4 sorguya kadar çıkan boş
yoklama, gecikmeyi kısaltmanın bedelidir. Kutu kendiliğinden küçülmez; budama ayrı bir iştir.

**Hangi yeni arıza kipi doğdu:** iki tane. Birincisi **ikinci teslim**: aktarıcının teslim ile
işaretleme arasında düşmesi 24 iletinin iki kez ulaşmasına yol açtı. İkincisi **aktarıcı
gerilemesi**: aktarıcı durduğunda sistem hata vermez, yalnız geç kalır; ölçüm yapılmazsa
biriken satır ve büyüyen gecikme fark edilmez.

#### Özet

- Aynı çökme dizisi altında doğrudan yayın 200 durum değişikliğinin 22'si için hiç ileti
  üretmedi; giden kutusu hiç ileti kaybetmedi.
- Kaybın yerine yinelenme geçti: giden kutusu 224 teslim yaptı, bunların 24'ü ikinci
  teslimdir; her çökme tam olarak bir yinelenme doğurdu.
- Yoklama aralığı gecikme ile boş sorgu arasında takastır: bu koşumda 20 ms'de ortanca gecikme
  13 ms ve 204 yoklamanın 144'ü boş; 250 ms'de gecikme 131 ms'e çıkar, yoklama 18'e iner.
- Aktarıcı durduğunda 60 satır kutuda birikti, hiçbiri kaybolmadı; yakalama sırasında ortanca
  gecikme 743 ms, en büyük 1382 ms oldu (bu koşumda).
- Teslim edilen satırlar tabloda kalır (200 ve 60 satır); giden kutusu açık bir budama işi
  gerektirir.

#### Sonraki Adım

Giden kutusunun bıraktığı 24 ikinci teslim, yayıncı tarafında çözülemez: aktarıcı ile hedef
ayrı depolarda olduğu sürece "gönderdim" bilgisi ile "işledim" bilgisi aynı işleme giremez.
Geriye tek yol kalıyor — bedeli tüketiciye taşımak. Sonraki ders iletileri hem **yinelenmiş**
hem de **sırası bozulmuş** olarak tüketiciye verir, iki tüketici gerçekleştirmesinin ürettiği
yan etkiyi sayar ve sürüm damgasının eski iletiyi nasıl attığını ölçer.

### Olay Sıralama ve Yinelenme

Source: https://academia.sh/tr/kurslar/servis-mimarileri/olay-siralama-ve-yinelenme

Tüketici tarafında etkisizlik: aynı olay akışı yinelenmiş ve sırası bozulmuş olarak teslim edildiğinde düz tüketicinin ürettiği fazladan yan etki ile yanlış biten görünüm, sürüm damgasının eski iletiyi atması ve etkisiz tüketicinin sorgu olarak ölçülen bedeli.

Önceki ders yayıncı tarafını kapattı: giden kutusu hiçbir iletiyi kaybetmedi, ama 24 iletiyi
iki kez teslim etti. Bu artık yayıncının çözebileceği bir sorun değil. Aktarıcının "gönderdim"
kaydı ile tüketicinin "işledim" kaydı ayrı depolarda durduğu sürece ikisi aynı işleme giremez.
Bedel tüketiciye geçer.

Tüketici tarafında iki ayrı bozulma vardır ve etkileri farklıdır. Birincisi **yinelenme**:
aynı ileti iki kez gelir. İkincisi **sıra bozulması**: geç kalan bir ileti, kendinden sonraki
bir iletinin ardından varır. M19/K05'in `dağıtık doğruluk` konusu tekillik anahtarını ve
anahtar defterini kurmuş ve ölçmüştür; o ölçüm burada tekrarlanmıyor. Anahtar defteri
yinelenmeyi keser ama sıra bozulmasına karşı bir şey yapmaz — bu ders ikisini ayırır ve
ikisini birden karşılayan bir gerçekleştirmenin bedelini ölçer.

#### Düzenek

**DT7.** Ağın bozulması tohumlu bir üreteçle modellenir: tohum `20260731`, gecikme oranı
`0,20`, yinelenme oranı `0,15`. Aynı oranlarda teslim akışı bütün koşumlarda birebir aynıdır;
dolayısıyla koşumlar arasındaki fark yalnız **tüketicinin** gerçekleştirmesinden gelir.

**DT8.** Tüketici süreç içi bir modüldür ve kendi deposuna yazar. Sorgu sayısı depodan değil,
kodun kendi sayacından okunur; ölçülen şey tüketicinin **yaptığı iş miktarıdır**.

```sh
# kur.sh — tuketici tarafinin kendi deposu: gorunum tablosu ve yan etki gunlugu
rm -f gorunum.db*
sqlite3 gorunum.db "PRAGMA journal_mode=WAL;
  CREATE TABLE kitap_gorunum (kitap_id INTEGER PRIMARY KEY, durumu TEXT, son_surum INTEGER DEFAULT 0);
  CREATE TABLE bildirim (id INTEGER PRIMARY KEY, kitap_id INTEGER, durumu TEXT);" >/dev/null
```

Katalog her kitap için artan sürümlü olaylar üretir: tek sürümler ödünç verme, çift sürümler
iade. Doğru sıra bu yüzden tek bir sayıdan okunabilir; sıra bozulması da öyle.

```js
// akis.mjs — olay gunlugunu uretir, sonra teslim sirasini bozar ve iletileri yineler
export function uretec(tohum) {                     // tohum gorunur, dizi her kosumda ayni
  let d = tohum >>> 0;
  return () => { d = (Math.imul(d, 1103515245) + 12345) >>> 0; return (d >>> 8) / 16777216; };
}

export function gunluk(kitap, surumSayisi) {        // katalogun urettigi dogru sirali gunluk
  const g = [];
  for (let s = 1; s <= surumSayisi; s++)
    for (let k = 1; k <= kitap; k++)
      g.push({ ileti_id: g.length + 1, kitap: k, surum: s, durumu: s % 2 ? "odunc" : "rafta" });
  return g;
}

export function teslimSirasi(g, tohum, gecikme, yineleme) {
  const zar = uretec(tohum), cikti = [], bekleyen = [];
  for (const o of g) {
    if (zar() < gecikme) { bekleyen.push(o); continue; }        // bu ileti gec kalacak
    cikti.push(o);
    if (bekleyen.length && zar() < 0.10) cikti.push(bekleyen.shift());
    if (zar() < yineleme) cikti.push(o);                        // en az bir kez teslim: kopya
  }
  cikti.push(...bekleyen);                                      // en gec kalanlar en sonda
  return cikti;
}
```

Tüketici tek dosyadır ve iki gerçekleştirme içerir. `--duz` geleni olduğu gibi uygular.
`--damgali` her kitap için son işlediği sürümü saklar ve o sürümden büyük olmayan iletiyi
atar.

```js
// tuketici.mjs — ayni akis, iki tuketici gerceklestirmesi: --duz ve --damgali
import { DatabaseSync } from "node:sqlite";
import { gunluk, teslimSirasi } from "./akis.mjs";

const damgali = process.argv.includes("--damgali");
const KITAP = 10, SURUM = 300, TOHUM = 20260731;
const gecikme = Number(process.env.GECIKME ?? 0.20);   // sirayi bozan iletilerin orani
const yineleme = Number(process.env.YINELEME ?? 0.15);  // ikinci kez teslim edilenlerin orani
const dogru = gunluk(KITAP, SURUM);
const akis = teslimSirasi(dogru, TOHUM, gecikme, yineleme);

const db = new DatabaseSync("gorunum.db");
const okuSurum = db.prepare("SELECT son_surum FROM kitap_gorunum WHERE kitap_id = ?");
const yaz = db.prepare(
  "INSERT INTO kitap_gorunum (kitap_id, durumu, son_surum) VALUES (?,?,?)" +
  " ON CONFLICT(kitap_id) DO UPDATE SET durumu = excluded.durumu, son_surum = excluded.son_surum");
const bildir = db.prepare("INSERT INTO bildirim (kitap_id, durumu) VALUES (?,?)");

let islenen = 0, atilan = 0, sorgu = 0, eskiVaris = 0, atilanKopya = 0, atilanEski = 0;
const gorulen = new Map(), gorulenIleti = new Set();
const t0 = performance.now();
db.exec("BEGIN IMMEDIATE");
for (const o of akis) {
  if ((gorulen.get(o.kitap) ?? 0) > o.surum) eskiVaris += 1;    // sirasi bozulmus varis
  gorulen.set(o.kitap, Math.max(gorulen.get(o.kitap) ?? 0, o.surum));
  if (damgali) {
    const s = okuSurum.get(o.kitap); sorgu += 1;                // ek okuma: surum damgasi
    if (s && s.son_surum >= o.surum) {
      atilan += 1; gorulenIleti.has(o.ileti_id) ? (atilanKopya += 1) : (atilanEski += 1);
      continue;
    }
  }
  gorulenIleti.add(o.ileti_id);
  yaz.run(o.kitap, o.durumu, o.surum); sorgu += 1;
  bildir.run(o.kitap, o.durumu); sorgu += 1;
  islenen += 1;
}
db.exec("COMMIT");
const sure = performance.now() - t0;

const sonDurum = new Map(dogru.map((o) => [o.kitap, o.durumu]));
let yanlis = 0;
for (const r of db.prepare("SELECT kitap_id, durumu FROM kitap_gorunum").all())
  if (sonDurum.get(r.kitap_id) !== r.durumu) yanlis += 1;
const yanEtki = db.prepare("SELECT COUNT(*) c FROM bildirim").get().c;

console.log(`${damgali ? "damgali" : "duz    "} (gecikme=${gecikme} yineleme=${yineleme})` +
  ` | gunluk=${dogru.length} teslim=${akis.length}` +
  ` sirasi bozuk varis=${eskiVaris} | islenen=${islenen} atilan=${atilan} (kopya=${atilanKopya} eski=${atilanEski})` +
  ` yan etki=${yanEtki} yanlis biten kitap=${yanlis}/${KITAP}` +
  ` | sorgu=${sorgu} sure=${sure.toFixed(1)} ms (bu kosumda)`);
```

#### İki Bozulma, İki Farklı Zarar

İlk iki koşum bozulmaları tek tek uygular; üçüncüsü ikisini birden verir; dördüncüsü aynı
akışı sürüm damgalı tüketiciye verir.

```sh
sh kur.sh; GECIKME=0 node tuketici.mjs --duz
sh kur.sh; YINELEME=0 node tuketici.mjs --duz
sh kur.sh; node tuketici.mjs --duz
sh kur.sh; node tuketici.mjs --damgali
```

```
duz     (gecikme=0 yineleme=0.15) | gunluk=3000 teslim=3452 sirasi bozuk varis=0 | islenen=3452 atilan=0 (kopya=0 eski=0) yan etki=3452 yanlis biten kitap=0/10 | sorgu=6904 sure=2.8 ms (bu kosumda)
duz     (gecikme=0.2 yineleme=0) | gunluk=3000 teslim=3000 sirasi bozuk varis=585 | islenen=3000 atilan=0 (kopya=0 eski=0) yan etki=3000 yanlis biten kitap=4/10 | sorgu=6000 sure=2.3 ms (bu kosumda)
duz     (gecikme=0.2 yineleme=0.15) | gunluk=3000 teslim=3359 sirasi bozuk varis=585 | islenen=3359 atilan=0 (kopya=0 eski=0) yan etki=3359 yanlis biten kitap=4/10 | sorgu=6718 sure=2.5 ms (bu kosumda)
damgali (gecikme=0.2 yineleme=0.15) | gunluk=3000 teslim=3359 sirasi bozuk varis=585 | islenen=2415 atilan=944 (kopya=359 eski=585) yan etki=2415 yanlis biten kitap=0/10 | sorgu=8189 sure=3.4 ms (bu kosumda)
```

Birinci satır yalnız yinelenmedir. Üç bin olay 3452 kez teslim edildi ve tüketici 3452 yan
etki üretti: **452 fazladan bildirim**. Buna karşılık son durum doğru — çünkü aynı iletinin
ikinci kez uygulanması görünümü değiştirmez, yalnız bildirimi tekrarlar. Yinelenmenin zararı
**yan etkidedir**, durumda değil.

İkinci satır yalnız sıra bozulmasıdır. Teslim sayısı günlüğe eşit, yan etki sayısı doğru
(3000), ama 585 ileti kendinden sonrakinin ardından vardı ve **10 kitabın 4'ü yanlış durumda
bitti**. Sıra bozulmasının zararı **durumdadır**, yan etki sayısında değil.

İki bozulma birlikte verildiğinde ikisi de görünür: 3359 yan etki ve yine 4 yanlış kitap.
Anahtar defteriyle yinelenme kesilse bile bu 4 kitap yanlış kalırdı; defter aynı iletiyi ikinci
kez tanır, geç kalan **başka** bir iletiyi tanımaz.

#### Sürüm Damgası

Dördüncü satır aynı akışı sürüm damgalı tüketiciye verir. Sonuç: yanlış biten kitap **0**, yan
etki 3359'dan 2415'e iniyor, 944 ileti atılıyor.

Atılanların ayrımı önemlidir: `944 = 359 kopya + 585 eski`. Kopyalar aynı iletinin ikinci
gelişidir; atılmaları saf kazançtır. **Eskiler ise gerçek olaylardır** — sadece geç kalmış
oldukları için üzerlerine daha yeni bir sürüm yazılmıştır. Sürüm damgası onları da atar,
çünkü bir sürümü geriye almanın anlamı yoktur.

Bu yüzden yan etki sayısı 3000 değil 2415'tir. Görünüm doğru bitti, ama 585 gerçek durum
değişikliği için bildirim **hiç gönderilmedi**. Bu, tek bir mekanizmayla iki bozulmayı birden
karşılamanın bedelidir: sıra bozulmasını düzelten kural, geç kalan bilgiyi de siler.

Ayrım, tüketicinin ne yaptığına bağlıdır. Görünüm tablosu gibi yalnız **son durumu** taşıyan
bir tüketici için bu doğru davranıştır; atılan ileti hiçbir şey kaybettirmez. Her geçişe tepki
vermek zorunda olan bir tüketici — gecikme ücreti hesaplayan ya da her hareketi deftere yazan
bir servis — için 585 kayıp gerçek bir kayıptır ve o tüketici sürüm damgasıyla değil, her
iletiyi ayrı ayrı tanıyan bir kayıtla çalışmak zorundadır.

#### Etkisiz Tüketicinin Bedeli

Bedelin koşumdan bağımsız ölçüsü sorgu sayısıdır. Düz tüketici teslim başına iki yazma yapar:
`3359 × 2 = 6718`. Damgalı tüketici her teslim için önce bir okuma yapar (`3359`), sonra yalnız
işlediklerine iki yazma (`2415 × 2 = 4830`); toplam `8189`. Fark `+1471` sorgu, yani teslim
başına yaklaşık `0,44` ek sorgu. Süre farkı da aynı yöndedir (2,5 ms'e karşı 3,4 ms), ama asıl
sabit maliyet okumadır: **her ileti, atılacak olsa bile önce sorgulanır.**

Depo tarafındaki maliyet de görünürdür: `kitap_gorunum` tablosuna `son_surum` sütunu eklendi ve
her yazmada güncellendi. Sürüm damgası, tüketicinin şemasına giren kalıcı bir alandır; iletiyi
üreten servisin de her olayda artan bir sürüm yayımlamayı **sözleşmesinin parçası** yapması
gerekir.

**Neyi ucuzlattı:** sürüm damgası, yinelenmiş ve sırası bozulmuş bir akıştan doğru son durumu
çıkardı — 4 yanlış kitap 0'a indi — ve fazladan bildirimleri sıfırladı. Yayıncı tarafında
hiçbir garanti yükseltilmeden yapıldı; giden kutusunun "en az bir kez" teslimi olduğu gibi
kaldı.

**Neyi pahalılaştırdı:** tüketici şemasına bir sütun, her iletiye bir okuma (`+1471` sorgu)
eklendi; üstelik yayıncı artık her olayda artan bir sürüm yayımlamakla yükümlü.

**Hangi yeni arıza kipi doğdu:** *sessiz atma*. Tüketici 585 gerçek olayı hiçbir hata
üretmeden yuttu. Görünüm doğru olduğu için bu kayıp durumdan anlaşılmaz; yalnız yan etki
sayısı ile günlük uzunluğu karşılaştırılırsa görünür. Sürüm alanı yanlış üretilirse — örneğin
iki servis aynı kayda ayrı sayaçlarla yazarsa — atılan iletiler artar ve kimse fark etmez.

#### Özet

- Yinelenme ile sıra bozulması farklı zararlar verir: yalnız yinelenmede yan etki 3000 yerine
  3452 çıktı ama son durum doğruydu; yalnız sıra bozulmasında yan etki sayısı doğruydu ama 10
  kitabın 4'ü yanlış durumda bitti.
- Bu yüzden yinelenmeyi kesen bir kayıt tek başına yetmez; geç kalan ileti farklı bir iletidir
  ve tanınamaz.
- Sürüm damgalı tüketici aynı akıştan doğru son durumu çıkardı (0/10 yanlış) ve 944 iletiyi
  attı: 359 kopya, 585 eski.
- Atılan eskiler gerçek olaylardır; yan etki 3000 yerine 2415 kaldı. Son durumu taşıyan bir
  tüketici için bu doğru, her geçişe tepki veren bir tüketici için kayıptır.
- Bedel teslim başına yaklaşık 0,44 ek sorgu (`6718` yerine `8189`), tüketici şemasında bir
  sütun ve yayıncı sözleşmesinde artan sürüm yükümlülüğüdür.

#### Sonraki Adım

Bu dersin çözümü sessizce bir söz verdi: yayıncı her olayda artan bir `surum` alanı
yayımlayacak, tüketici o alanı okuyacak. Aynı söz önceki derslerde de verilmişti — `ileti_id`
kalıcı olacak, `kitap` alanı hep aynı anlamı taşıyacak. Bu alanlar artık iki ayrı ekibin
birlikte tuttuğu bir sözleşmedir ve sistemin doğruluğu ona bağlıdır. Servisler ayrı dağıtım
birimleri olduğu için sözleşme tek bir anda değişemez: bir süre iki sürüm birlikte koşar.
Sonraki ders sözleşmenin nasıl değişebileceğini ele alır — alan ekleme ve kaldırma sırası,
iki sürümün birlikte koşması ve tüketicinin korunması.

### Sözleşme Evrimi

Source: https://academia.sh/tr/kurslar/servis-mimarileri/sozlesme-evrimi

Bir sözleşme alanının eklenmesi, kaldırılması, adının değişmesi ve zorunlu kılınması: her adımın iki yönde kaç tüketiciyi kırdığı, kırılmanın sessiz mi gürültülü mü olduğu, korumalı okumanın hangi yönü kurtardığı ve geçiş sırasının dağıtım adımı, kırılan çağrı ve taşınan fazla alan olarak ölçülmesi.

Önceki ders bir söz verdi: yayıncı her olayda artan bir `surum` alanı yayımlayacak, tüketici
onu okuyacak. Aynı türden sözler kurs boyunca birikti: `ileti_id` kalıcı olacak, `gun` hep
aynı anlamı taşıyacak. Bu alanlar ayrı dağıtım birimlerinin ortak tuttuğu bir sözleşmedir.

Sorun sözleşmenin varlığı değil, değişmesidir. Servisler ayrı birimler olduğu için sözleşme
tek bir anda değişemez: üretici ile tüketiciler farklı zamanlarda yayına çıkar ve arada iki
sürümün birlikte koştuğu bir pencere açılır. M21/K03 bu sözleşmenin **test** tarafını,
M19/K03 `genişlet–daralt` kalıbını kurmuştu; ikisi de burada tekrarlanmıyor. Buranın konusu
evrim **adımlarının kendisi**: hangi değişiklik kimi kırıyor, sessiz mi kırıyor ve adımlar
hangi sırayla atılırsa pencere zararsız geçiyor.

#### Düzenek

**DT9.** Üretici ve tüketiciler süreç içi işlevlerdir; ölçülen şey sözleşmenin biçimidir,
teslim mekanizması değil.

**DT10.** Kırılma ölçütü: tüketicinin ürettiği iş sonucunun, değişiklikten önceki doğru
değerden farklı olması. Değer farkı **sessiz yanlış**, atılan bir özel durum **hata** sayılır.

**DT11.** Pencere, iki dağıtım adımı arasında akan 50 ileti olarak sabitlenmiştir. Gerçek
uzunluk dağıtım hızına bağlıdır; sabit tutulduğu için planlar karşılaştırılabilir.

#### Dört Adım, İki Yön

Ödünç servisi `odunc_verildi` iletisini yayımlıyor, ücretlendirme gecikme ücretini
hesaplıyor. Dört değişiklik türünün her biri iki yönde sınanır: eski tüketici yeni iletiyi
okuduğunda ve yeni tüketici eski iletiyi okuduğunda.

```js
// uyumluluk.mjs — dort evrim adimi, iki yon: eski tuketici yeni iletiyi, yeni tuketici eskiyi okur
const temel = { ileti_id: 1, kitap: 101, uye: 4 };

const adimlar = [
  { ad: "alan ekleme   ", dogru: 28,
    eskiIleti: { ...temel, gun: 14 },
    yeniIleti: { ...temel, gun: 14, gunluk_ucret: 2 },
    eskiT: (i) => 2 * i.gun,
    yeniT: (i) => i.gunluk_ucret * i.gun,
    korumaliT: (i) => (i.gunluk_ucret ?? 2) * i.gun },
  { ad: "alan kaldirma ", dogru: 336,
    eskiIleti: { ...temel, gun: 14, saat: 336 },
    yeniIleti: { ...temel, gun: 14 },
    eskiT: (i) => i.saat,
    yeniT: (i) => i.gun * 24,
    korumaliT: (i) => i.saat ?? i.gun * 24 },
  { ad: "ad degistirme ", dogru: 28,
    eskiIleti: { ...temel, gun: 14 },
    yeniIleti: { ...temel, sure_gun: 14 },
    eskiT: (i) => 2 * i.gun,
    yeniT: (i) => 2 * i.sure_gun,
    korumaliT: (i) => 2 * (i.sure_gun ?? i.gun) },
  { ad: "zorunlu yapma ", dogru: 28,
    eskiIleti: { ...temel, gun: 14 },
    yeniIleti: { ...temel, gun: 14, gunluk_ucret: 2 },
    eskiT: (i) => 2 * i.gun,
    yeniT: (i) => { if (i.gunluk_ucret === undefined) throw new Error("gunluk_ucret zorunlu");
                    return i.gunluk_ucret * i.gun; },
    korumaliT: (i) => { if (i.gunluk_ucret === undefined) throw new Error("gunluk_ucret zorunlu");
                        return i.gunluk_ucret * i.gun; } },
];

const dene = (f, ileti, dogru) => {
  try { return f(ileti) === dogru ? "tamam" : "sessiz yanlis"; }
  catch { return "hata"; }
};
const yaz = (s) => s.padEnd(13);

const say = { tamam: 0, sessiz: 0, hata: 0 }, korumali = { tamam: 0, sessiz: 0, hata: 0 };
const ekle = (k, s) => { k[s === "tamam" ? "tamam" : s === "hata" ? "hata" : "sessiz"] += 1; };

console.log("adim            | eski T <- yeni ileti | yeni T <- eski ileti | korumali T <- eski ileti");
for (const a of adimlar) {
  const yon1 = dene(a.eskiT, a.yeniIleti, a.dogru);
  const yon2 = dene(a.yeniT, a.eskiIleti, a.dogru);
  const yon3 = dene(a.korumaliT, a.eskiIleti, a.dogru);
  ekle(say, yon1); ekle(say, yon2); ekle(korumali, yon1); ekle(korumali, yon3);
  console.log(`${a.ad} | ${yaz(yon1)}        | ${yaz(yon2)}        | ${yaz(yon3)}`);
}
console.log(`duz tuketici : tamam=${say.tamam} sessiz yanlis=${say.sessiz} hata=${say.hata} (8 birlesim)`);
console.log(`korumali     : tamam=${korumali.tamam} sessiz yanlis=${korumali.sessiz} hata=${korumali.hata} (8 birlesim)`);
```

```sh
node uyumluluk.mjs
```

```
adim            | eski T <- yeni ileti | yeni T <- eski ileti | korumali T <- eski ileti
alan ekleme    | tamam                | sessiz yanlis        | tamam        
alan kaldirma  | sessiz yanlis        | tamam                | tamam        
ad degistirme  | sessiz yanlis        | sessiz yanlis        | tamam        
zorunlu yapma  | tamam                | hata                 | hata         
duz tuketici : tamam=3 sessiz yanlis=4 hata=1 (8 birlesim)
korumali     : tamam=5 sessiz yanlis=2 hata=1 (8 birlesim)
```

Sekiz birleşimin yalnız 3'ü sorunsuz. Asıl bulgu, kalan 5'in dağılımıdır: **4 sessiz yanlış,
1 hata.** Eksik bir alan `undefined` olur, hesaba girer ve sonuç sessizce bozulur; hiçbir
yerde hata kaydı oluşmaz. Sözleşmeyi kıran değişikliklerin çoğu gürültü çıkarmaz.

Tek gürültülü satır "zorunlu yapma"dır ve gürültünün nedeni değişikliğin türü değil,
tüketicinin alanı **açıkça denetlemesi**dir. Sessizlik sözleşmenin değil, doğrulamasızlığın
sonucudur.

Sıra kuralı bu tablodan doğrudan okunur. Alan eklemede kırılan yön "yeni tüketici ← eski
ileti"dir; öyleyse **önce üretici** yayına çıkar. Alan kaldırmada kırılan yön "eski tüketici
← yeni ileti"dir; öyleyse **önce tüketici** yayına çıkar. Ad değiştirme ikisinin toplamıdır ve
her iki yön de kırılır: tek bir sıra onu güvenli yapamaz.

#### Tüketici Koruması

Son sütun, yeni tüketicinin eski adı da okuyacak biçimde yazılmasıdır. Sessiz yanlış 4'ten
2'ye, tamam 3'ten 5'e çıkıyor. Kurtarılan iki birleşim de aynı yöndedir: **yeni tüketici ←
eski ileti.** Kalan 2 sessiz yanlış öteki yöndedir ve tüketici kodu onları kurtaramaz — o iletiyi
okuyan tüketici zaten yayına çıkmış eski koddur, ona dokunulamaz.

Buradan kursun en dar kuralı çıkar: **tüketici koruması yalnız bir yönü kapatır; öteki yönü
kapatmak üreticinin işidir.** Üretici, eski adı bir süre daha göndermeye devam ederek kapatır.

#### Geçiş Sırası

Ad değişikliği üç planla koşturulur. Dört tüketicinin ikisi alanı okuyor, ikisi okumuyor.

```js
// gecis.mjs — ayni ad degisikligi (gun -> sure_gun), uc gecis plani; pencereler sayilir
const PENCERE = 50;                       // iki adim arasinda akan ileti sayisi

const ileti = {
  v1:    (n) => ({ ileti_id: n, kitap: 101, uye: 4, gun: 14 }),
  gecis: (n) => ({ ileti_id: n, kitap: 101, uye: 4, gun: 14, sure_gun: 14 }),
  v2:    (n) => ({ ileti_id: n, kitap: 101, uye: 4, sure_gun: 14 }),
};
const tuketici = {                        // ilk ikisi alani hic okumuyor, son ikisi okuyor
  uyelik:   { dogru: 4,   v1: (i) => i.uye,     v2: (i) => i.uye },
  bildirim: { dogru: 101, v1: (i) => i.kitap,   v2: (i) => i.kitap },
  ucret:    { dogru: 28,  v1: (i) => 2 * i.gun, v2: (i) => 2 * i.sure_gun },
  gorunum:  { dogru: 14,  v1: (i) => i.gun,     v2: (i) => i.sure_gun },
};
const planlar = {
  "tek adim, once uretici ": [["uretici", "v2"], ["ucret", "v2"], ["gorunum", "v2"]],
  "tek adim, once tuketici": [["ucret", "v2"], ["gorunum", "v2"], ["uretici", "v2"]],
  "genislet-daralt        ": [["uretici", "gecis"], ["ucret", "v2"], ["gorunum", "v2"],
                              ["uretici", "v2"]],
};

for (const [ad, plan] of Object.entries(planlar)) {
  const durum = { uretici: "v1", uyelik: "v1", bildirim: "v1", ucret: "v1", gorunum: "v1" };
  let kirilan = 0, cagri = 0, fazlaAlan = 0, fazlaBayt = 0;
  for (const [birim, surum] of plan) {
    durum[birim] = surum;                 // bir dagitim adimi
    for (let n = 1; n <= PENCERE; n++) {
      const i = ileti[durum.uretici](n);
      if (durum.uretici === "gecis") {          // iki ad birlikte tasiniyor
        fazlaAlan += Object.keys(i).length - Object.keys(ileti.v2(n)).length;
        fazlaBayt += JSON.stringify(i).length - JSON.stringify(ileti.v2(n)).length;
      }
      for (const [t, k] of Object.entries(tuketici)) {
        cagri += 1;
        let s; try { s = k[durum[t]](i); } catch { s = null; }
        if (s !== k.dogru) kirilan += 1;
      }
    }
  }
  console.log(`${ad} | dagitim adimi=${plan.length} tuketici cagrisi=${cagri}` +
    ` | kirilan cagri=${kirilan} | tasinan fazla alan=${fazlaAlan} fazla bayt=${fazlaBayt}`);
}
const okuyan = Object.values(tuketici).filter((k) => String(k.v1).includes("gun")).length;
console.log(`pencere basina ileti=${PENCERE} | tuketici=${Object.keys(tuketici).length}` +
  ` | alani okuyan tuketici=${okuyan}` +
  ` | gecis surumunde fazla alan=${Object.keys(ileti.gecis(1)).length - Object.keys(ileti.v2(1)).length}`);
```

```sh
node gecis.mjs
```

```
tek adim, once uretici  | dagitim adimi=3 tuketici cagrisi=600 | kirilan cagri=150 | tasinan fazla alan=0 fazla bayt=0
tek adim, once tuketici | dagitim adimi=3 tuketici cagrisi=600 | kirilan cagri=150 | tasinan fazla alan=0 fazla bayt=0
genislet-daralt         | dagitim adimi=4 tuketici cagrisi=800 | kirilan cagri=0 | tasinan fazla alan=150 fazla bayt=1350
pencere basina ileti=50 | tuketici=4 | alani okuyan tuketici=2 | gecis surumunde fazla alan=1
```

İki tek adımlı plan **aynı sayıyı** verdi: 600 tüketici çağrısının 150'si kırıldı. Sıra
kırılmanın yerini değiştirir, miktarını değiştirmez — çünkü ad değiştirme her iki yönde de
kırar ve hangi taraf önce giderse öteki taraf o kadar süre yanlış okur.

Genişlet–daralt planı kırılan çağrıyı **0**'a indirir. Bedeli üç kalemdir: dağıtım adımı 3'ten
4'e çıktı, pencere boyunca 150 ileti fazladan bir alan taşıdı (1350 bayt) ve üretici o süre
boyunca aynı bilgiyi iki adla üretmek zorunda kaldı. Çift kodun nerede duracağı bir seçimdir:
üreticide tutulursa 1 birim iki adı birlikte taşır, tüketicide tutulursa alanı okuyan 2 birim
korumalı okuma satırını taşır. Ölçüm ikinci seçeneğin daha pahalı olduğunu söyler — okuyan
tüketici sayısı arttıkça fark büyür.

Dördüncü adım — daraltma — atlanabilir görünür. Atlandığında sistem çalışmaya devam eder;
sözleşmede ölü bir alan kalır ve onu kimin okuduğunu kimse bilmez.

#### Özet

- Sekiz değişiklik–yön birleşiminin 3'ü sorunsuz, 4'ü sessiz yanlış, 1'i hata verdi;
  sözleşmeyi kıran değişikliklerin çoğu gürültü çıkarmaz.
- Tek gürültülü satır, tüketicinin alanı açıkça denetlediği satırdır; sessizlik sözleşmenin
  değil doğrulamasızlığın sonucudur.
- Sıra kuralı ölçümden okunur: alan eklemede önce üretici, alan kaldırmada önce tüketici
  yayına çıkar. Ad değiştirme ikisinin toplamıdır ve tek bir sıra onu güvenli yapamaz.
- Korumalı okuma sessiz yanlışı 4'ten 2'ye indirdi; kurtardığı iki birleşim de "yeni
  tüketici ← eski ileti" yönündedir. Öteki yön yalnız üreticinin eski adı bir süre daha
  göndermesiyle kapanır.
- Ad değişikliğinde iki tek adımlı plan da 600 çağrının 150'sini kırdı; genişlet–daralt 0
  kırdı. Bedeli 1 ek dağıtım adımı, 150 fazla alan ve 1350 fazla bayttır.
- Daraltma adımı atlandığında hiçbir şey kırılmaz; sözleşmede kalıcı olarak ölü bir alan kalır.

#### Kurs Kapanışı

Kurs boyunca aynı kütüphane ödünç sistemi on sekiz kez, on sekiz farklı biçimde kuruldu. Her
ders aynı üç sütunu yazdı.

| Ders | Neyi ucuzlattı | Neyi pahalılaştırdı | Doğan arıza kipi |
|---|---|---|---|
| Tek Parça Uygulama | Yerel çağrı, ortak işlem sınırı, tek dağıtım adımı | Ölçekleme birimi tüm kod tabanı; bir satırlık değişiklik 7 modülü yeniden yayınlar | Bir modüldeki sızıntı ya da sonsuz döngü bütün alanları birlikte durdurur |
| Modüler Tek Parça | Bir alanın iç yapısı değişince dokunulan dosya 3'ten 1'e indi | Genel yüz 7'den 11'e çıktı; her alanlar arası ihtiyaç bir dolaylılık ekliyor | Kural yalnız denetleyici koşarken geçerli; tek satırlık ithal sınırı sessizce geri alıyor |
| Servis Yönelimli Mimari | Birimler birbirini ithal etmiyor; yeni birim eklemek mevcutları değiştirmiyor | Ortak modele bağlı birim 1'den 3'e; bir alan adı değişikliği 3 dosyayı birlikte yayına zorluyor | Sahibi güncellenip tüketicisi güncellenmediğinde hata alınmıyor, sınır denetimi sessizce kalkıyor |
| Mikroservisler | Bir oran değişikliği 5 dağıtım biriminin 1'ini yeniden başlattı | İstek başına 9 ağ atlaması, 7 sıralı tur; başlatma 1 adımdan 6 adıma | Zincirin ortasında kırılan istek yarım durum bırakıyor; arıza kısmi |
| Sunucusuz Yaklaşım | Ayakta tutulan süreç 5'ten 1'e düştü; dağıtım birimi tek dosya | İstek başına 4 yeni süreç; depo çağrısı 2'den 9'a çıktı | Süre sınırı işi ortasından keser: kod hatasız, sonuç eksik, kısmi etki depoda kalır |
| Mimari Geçiş Stratejileri | Geri alma 1 adım, yeniden dağıtım 0; yeni biçim tek yolda sınanıyor | Aynı kural 2 kod tabanında; her değişiklik 2 dosyaya yazılıyor | Yalnız bir kod tabanı güncellenince aynı istek iki farklı sonuç veriyor |
| Sınır Çizme Ölçütleri | İyi çizilen sınırda değişiklik tek dağıtım biriminde kalıyor: tek doğrulama, tek geri alma | Kötü çizilende aynı değişiklik 2 birime dokunuyor; 1 ithal kenarı ağ çağrısına, 2 yazma sınır ihlaline dönüyor | İki yayım arasında pencere: yeni oran bir birimde yürürlükte, öteki eski alanı gönderiyor |
| Eşzamanlı Servis Çağrıları | Dört iş adımı üç turda bitiyor; zincirin tepesindeki servis kapandığında dört giriş ucunun üçü ayakta kalıyor | İstek başına 3 ağ atlaması, 4 süreç, 4 kat işlenen istek | Kısmi arıza; ardılın durum kodu denetlenmezse eksik iş yapılmış başarılı yanıt |
| Eşzamansız Tümleştirme | Yanıt turu 2'den 1'e, yanıt yolundaki birim 3'ten 1'e indi | Sözleşme 3 dağıtım birimine yayıldı; alan başına 2,17 birim bağlandı | Tutarlılık penceresi ve yanıt yolunda hiç görünmeyen tüketici arızası |
| API Ağ Geçidi | Yinelenen kenar satırı 15'ten 6'ya; dokunulan dosya ve istemci çağrısı 3'ten 1'e | İstek başına atlama 3'ten 4'e; yeni bir dağıtım birimi; uç yolu değişince dokunulan dosya yine 2 | Ağ geçidi bütün dış trafiğin bağımlılık kapanışı; yalın servisler kendini savunmuyor |
| Ön Yüz İçin Backend | İstek 1'e, fazla bayt 0'a indi; istemcinin okuduğu aşağı alan adı 6'dan 0'a | 2 yeni dağıtım birimi, 38 satırın 13'ü birebir aynı; aşağı servise ulaşan istek değişmiyor | Aşağı alan adı değişip tek katman güncellenince istemciler sessizce ayrışıyor |
| Servis Ağı | Uygulama kodu 38 satırdan 13'e; ağ ilgisi diye işaretli 18 satır uygulamada kalmadı | Süreç 2'den 3'e, yerel durak 1'den 2'ye, ücret servisine ulaşan istek 4'ten 5'e | Durum koduna bakan yeniden deneme, yinelemeye kapalı ödeme çağrısını yineledi: tahsilat 2 |
| Servis Keşfi | Yeni kopya eklenince dokunulan yapılandırma 3'e karşı 0; kira dolunca 6/6'ya dönüş | 1 ek süreç ve tek arıza noktası, çağrı başına 1 ek çözümleme isteği | Kayıt defteri gerçeğin gecikmeli kopyasını tutar; keşif penceresinde 6 isteğin 2'si ölü adrese gidiyor |
| Dağıtık İşlem Alternatifleri | Kısmi sonuç ortadan kalktı; uygulama kodunda telafi mantığı yazılmıyor | Tur 1'den 2'ye, mesaj `n`'den `2n`'e; kilit 459,5 ms tutuldu, 40 isteğin 6'sı bloke | Belirsiz durum: oy vermiş ama karar almamış katılımcı kilidi süresiz tutar |
| Saga Kalıbı | Kilit penceresi sıfırlandı; hiçbir kaynak başka servisin yanıtı beklenirken tutulmuyor | Her adıma bir telafi; sıra telafi edilebilirliğe bağlandı; koreografide akış bilgisi 1 dosyadan 4'e | Yarım kalmış akış: telafi edilemeyen adımdan sonraki hata 2 geri alınamayan teslim bıraktı |
| Giden Kutusu Kalıbı | "Yapıldı ama duyurulmadı" kalktı: 22 kayıp 0'a indi ve kayıp sessiz olmaktan çıktı | Her istek iki tabloya yazıyor; yeni bir süreç ve yoklama aralığı; kutu budanmazsa büyüyor | İkinci teslim (24 ileti) ve aktarıcı gerilemesi: sistem hata vermez, yalnız geç kalır |
| Olay Sıralama ve Yinelenme | Bozulmuş akıştan doğru son durum: 4 yanlış kitap 0'a, fazladan bildirim 0'a | Tüketici şemasına bir sütun, ileti başına bir okuma (+1471 sorgu), yayıncıya sürüm yükümlülüğü | Sessiz atma: 585 gerçek olay hiçbir hata üretmeden yutuldu |
| Sözleşme Evrimi | Genişlet–daralt kırılan çağrıyı 150'den 0'a; korumalı okuma sessiz yanlışı 4'ten 2'ye indirdi | 3 yerine 4 dağıtım adımı; pencerede 150 fazla alan ve 1350 fazla bayt taşındı | Daraltma atlanınca hiçbir şey kırılmaz; sözleşmede ölü bir alan kalıcı olarak kalır |

Tablo kursun kuralını taşıyor: **bir mimari ancak bu üç sütunla savunulur.** "Daha
ölçeklenebilir" bir karar değildir; hangi birimin ölçeklendiğini, karşılığında kaç ağ
atlamasının ve kaç dağıtım adımının eklendiğini ve hangi yeni arıza kipinin doğduğunu
söylemeyen bir cümle mimari bir gerekçe sayılmaz. Yukarıdaki on sekiz satırın hiçbirinde
üçüncü sütun boş değildir: her sınır, kaldırdığı sorunun yerine yenisini koydu.

Bir şey de eksik kaldı. Bu tablodaki arıza kiplerinin çoğu **sessizdir**: sessizce geri alınan
sınır, sessizce ayrışan istemciler, sessizce yutulan olay, sessizce yanlış okunan alan.
Sistem artık birden çok süreçte koşuyor ve her sınır yeni bir arıza kipi doğurdu; ama bir
isteğin **nerede yavaşladığını ya da nerede kaybolduğunu görmenin** aracı hiç kurulmadı. Her
ders kendi ölçümünü kendi yazdığı sayaçla yaptı; koşan sistemin kendisi bu sayaçları taşımıyor.
Sonraki kurs, **M16/K07 Gözlemlenebilirlik ve Güvenilirlik**, tam olarak bu eksiği ele alır.

