---
title: 'Alan Odaklı Tasarım'
source: 'https://academia.sh/tr/kurslar/alan-odakli-tasarim'
language: tr
updated: '2026-08-17T04:42:39+00:00'
license: 'CC BY-SA 4.0'
lessons: 19
outcomes:
    - 'Alan dilini modelin merkezine koyar'
    - 'Varlık nesnesi, değer nesnesi ve toplama sınırlarını doğru çizer'
    - 'Sınırlı bağlamlar arasında ilişki haritası kurar'
---

# Alan Odaklı Tasarım

Bir kod tabanının yapısının alanın kendi ayrımlarından türetilmesi: alan dilinin modelin merkezine konması, kimlikle ve değerle tanımlanan nesnelerin ayrılması, tutarlılık sınırlarının toplama kökleriyle çizilmesi, modelin geçerli olduğu alanın sınırlı bağlamlarla belirlenmesi ve bağlamlar arası ilişkilerin haritalanması; her modelleme kararı alanla hizasının ölçülmesiyle sınanır.

## Alan Modeli

Karmaşıklığın alanla hizalanması: kod ile konuşma dilinin tek sözlükte birleşmesi, kimlikle tanımlanan nesnelerin değerle tanımlanan değişmez nesnelerden ayrılması, tutarlılık sınırının toplama ve kökle çizilmesi, nesneye ait olmayan davranışın alan servislerine verilmesi, yaratım ve erişimin alan modeline uydurulması, anlamlı iş olaylarının açık modeli ve davranışsız veri sınıflarının bedeli.

### Alan Odaklı Tasarımın Gerekçesi

Source: https://academia.sh/tr/kurslar/alan-odakli-tasarim/alan-odakli-tasarimin-gerekcesi

Modelin alanla hizasının bir ölçü hâline getirilmesi: alan uzmanının tek cümlesindeki on iki adın iki modelde kaçının birebir bulunduğunun sayılması, alan dilinde yazılmış üç değişiklik isteğinin her modelde kaç satıra indiğinin ölçülmesi ve iki modelin aynı ücreti verdiğinin gösterilmesiyle hizanın doğruluktan ayrı bir nitelik olduğunun ortaya konması.

Tasarım Kalıpları kursu, bir kalıbın ancak bir ölçüyü iyileştirdiği ölçüde kalıp olduğunu
göstererek kapandı. Otuz dersin hepsi çözümün **biçimi** üzerineydi: kural nereye yazılır,
nesne nasıl yaratılır, sınır nereye çizilir. Hiçbiri kuralın **ne olduğunu** sormadı.
Gönderi, tarife, bölge, rota ve indirim sözcüklerinin ne anlama geldiği, hangilerinin bir
arada değiştiği, aynı sözcüğün iki takımda iki farklı şeyi adlandırıp adlandırmadığı o
kursun dışında kaldı.

Bu ders o soruyu alır. **Alan odaklı tasarım** (domain-driven design), bir kod tabanının
yapısını alanın kendi ayrımlarından türetmeyi öneren bir yaklaşımdır; buradaki alan,
yazılımın hakkında olduğu iş dünyasıdır. Yaklaşımın iddiası bir estetik iddia değildir,
dolayısıyla bir ölçüsü olmalıdır. Kurs boyunca ölçülen şey **modelin alanla hizasıdır**:
alanı konuşan insanın kullandığı ayrımların kodda birebir karşılığı var mı, yok mu.

#### Alan Uzmanının Cümlesi

Ölçünün dayanağı, alanı işi gereği bilen kişinin — **alan uzmanının** (domain expert) —
kuralı söylerken kullandığı sözcüklerdir. Gönderi ücretlendirme kitaplığında bu kişi
ücretlendirme uzmanıdır ve kuralı tek cümlede söylüyor:

> Bir gönderinin ücreti, ağırlık kademesinin taban ücretiyle bölge katsayısının
> çarpımıdır; hacim ağırlıktan büyükse hacim esas alınır, indirim oranlarının toplamı
> tavanı aşamaz, net ücret asgari ücretin altına inemez.

Cümlede on iki ad geçiyor: gönderi, ağırlık, hacim, kademe, taban ücret, bölge, katsayı,
posta kodu, indirim, oran, tavan, asgari ücret. Bu on iki ad ölçünün sabit listesidir.

#### Aynı Kuralın İki Modeli

Kitaplığın iki sürümü var. İkisi de aynı kuralı uyguluyor; ayrım yalnız adlardadır.

```sh
mkdir -p teknik alan
```

```js
// teknik/hesap.mjs — ayni kural, teknik adlarla
const T1 = [[1, 3900], [5, 6400], [Infinity, 11800]];
const T2 = { "34": 1, "06": 1.35, "65": 1.8 };
const LIM = 0.4;
const MIN = 3990;
const DIV = 5000;

export function process(d) {
  const v = Math.max(d.w, d.vol / DIV);
  const base = T1.find(([u]) => v <= u)[1];
  const f = T2[d.z.slice(0, 2)] ?? 1.8;
  const r = Math.min(d.ds.reduce((t, x) => t + x.r, 0), LIM);
  return Math.max(Math.round(base * f * (1 - r)), MIN);
}
```

Dosya kısa, dallanması az ve doğru çalışıyor. Temiz Kod kursunun ölçüleriyle bakıldığında
kusuru yalnız adlandırmadır. Alan odaklı tasarımın söylediği şey bunun bir adlandırma
kusuru olmadığıdır: `T1` ile `T2` arasındaki ayrımın alanda bir karşılığı vardır ve o ayrım
kodda kayıptır.

```js
// alan/ucret.mjs — ayni kural, alan adlariyla
const KADEMELER = [[1, 3900], [5, 6400], [Infinity, 11800]];
const BOLGE_KATSAYISI = { "34": 1, "06": 1.35, "65": 1.8 };
const INDIRIM_TAVANI = 0.4;
const ASGARI_UCRET = 3990;
const HACIM_BOLENI = 5000;

const ucretliAgirlik = (gonderi) => Math.max(gonderi.agirlik, gonderi.hacim / HACIM_BOLENI);
const tabanUcret = (agirlik) => KADEMELER.find(([ustAgirlik]) => agirlik <= ustAgirlik)[1];
const bolgeKatsayisi = (postaKodu) => BOLGE_KATSAYISI[postaKodu.slice(0, 2)] ?? 1.8;
const indirimOrani = (indirimler) =>
  Math.min(indirimler.reduce((t, i) => t + i.oran, 0), INDIRIM_TAVANI);

export function netUcret(gonderi) {
  const ham = tabanUcret(ucretliAgirlik(gonderi)) * bolgeKatsayisi(gonderi.postaKodu);
  return Math.max(Math.round(ham * (1 - indirimOrani(gonderi.indirimler))), ASGARI_UCRET);
}
```

#### Hizanın Ölçülmesi

İki ölçü alınır. Birincisi **ad hizası**: uzmanın cümlesindeki on iki addan kaçı kaynakta
bir tanımlayıcının içinde birebir bulunuyor. İkincisi **aday satır sayısı**: alan dilinde
gelen bir değişiklik isteğinin sözcükleri kaç satırda birlikte geçiyor. İkinci ölçü, isteği
alan kişinin dosyada kaç satıra bakması gerektiğini verir.

```js
// hiza-olc.mjs — uzman cumlesinin adlari kodda var mi, istek kac satirda gorunuyor
import { readFileSync } from "node:fs";
import { process as teknikUcret } from "./teknik/hesap.mjs";
import { netUcret } from "./alan/ucret.mjs";

const ADLAR = ["gonderi", "agirlik", "hacim", "kademe", "tabanucret", "bolge",
  "katsayi", "postakodu", "indirim", "oran", "tavan", "asgariucret"];
const ISTEKLER = {
  "indirim tavani inecek": ["indirim", "tavan"],
  "asgari ucret cikacak": ["asgari", "ucret"],
  "hacim boleni degisecek": ["hacim", "bolen"],
};
const MODEL = { teknik: "teknik/hesap.mjs", alan: "alan/ucret.mjs" };

for (const [model, dosya] of Object.entries(MODEL)) {
  const metin = readFileSync(dosya, "utf8");
  const ham = metin.match(/[A-Za-z_][A-Za-z0-9_]*/g) ?? [];
  const kimlik = ham.map((s) => s.toLowerCase().replaceAll("_", ""));
  const bulunan = ADLAR.filter((ad) => kimlik.some((k) => k.includes(ad)));
  const satir = metin.split("\n").filter((s) => s.trim() !== "" && !s.startsWith("//"));
  console.log(`${model}: alan adi ${bulunan.length}/${ADLAR.length}, satir ${satir.length}`);
  console.log(`  eksik ad: ${ADLAR.filter((a) => !bulunan.includes(a)).join(" ") || "yok"}`);
  for (const [istek, sozcuk] of Object.entries(ISTEKLER)) {
    const aday = satir.filter((s) => sozcuk.every((w) => s.toLowerCase().includes(w)));
    console.log(`  "${istek}" -> aday satir ${aday.length}/${satir.length}`);
  }
}

const GONDERILER = [
  { agirlik: 0.6, hacim: 2000, postaKodu: "34710", indirimler: [] },
  { agirlik: 3, hacim: 24000, postaKodu: "06800", indirimler: [{ ad: "sozlesme", oran: 0.15 }] },
  { agirlik: 12, hacim: 40000, postaKodu: "65100", indirimler: [{ ad: "hacim", oran: 0.5 }] },
  { agirlik: 0.4, hacim: 9000, postaKodu: "81600", indirimler: [{ ad: "kampanya", oran: 0.1 }] },
];
const cevir = (g) => ({ w: g.agirlik, vol: g.hacim, z: g.postaKodu,
  ds: g.indirimler.map((i) => ({ r: i.oran })) });

let ayrilan = 0;
for (const g of GONDERILER) if (teknikUcret(cevir(g)) !== netUcret(g)) ayrilan += 1;
console.log(`ayrilan sonuc = ${ayrilan} / ${GONDERILER.length}`);
```

```sh
node hiza-olc.mjs
```

```
teknik: alan adi 0/12, satir 12
  eksik ad: gonderi agirlik hacim kademe tabanucret bolge katsayi postakodu indirim oran tavan asgariucret
  "indirim tavani inecek" -> aday satir 0/12
  "asgari ucret cikacak" -> aday satir 0/12
  "hacim boleni degisecek" -> aday satir 0/12
alan: alan adi 12/12, satir 14
  eksik ad: yok
  "indirim tavani inecek" -> aday satir 2/14
  "asgari ucret cikacak" -> aday satir 2/14
  "hacim boleni degisecek" -> aday satir 2/14
ayrilan sonuc = 0 / 4
```

İlk sayı hizanın kendisidir: uzmanın on iki adından teknik sürümde 0'ı, alan sürümünde
12'si kodda bulunuyor. İkinci grup bunun bedelini veriyor. Üç değişiklik isteğinin üçünde
de teknik sürümde aday satır 0 çıkıyor; aday yoksa isteği alan kişinin on iki satırın
tamamını okuması, her adın neyi tuttuğunu çıkarması ve ancak sonra doğru satırı bulması
gerekir. Alan sürümünde aynı üç istek 14 satırın 2'sine iniyor.

#### Hiza Doğruluk Değildir

Çıktının son satırı ölçünün ne olmadığını söylüyor: dört gönderinin dördünde iki sürüm
aynı ücreti veriyor. Teknik sürüm yanlış değildir, yavaş da değildir. Hizasızlığın bedeli
çalışma zamanında değil, **değişiklik zamanında** ödenir.

Bedelin ödendiği yer ölçüm betiğinde de görünüyor: `cevir` işlevi. İki sürümü aynı
gönderilerle çağırabilmek için `agirlik` alanının `w`, `hacim` alanının `vol`, `postaKodu`
alanının `z` olduğunu bilen bir eşleme yazmak gerekti. Bu eşleme kodda dört satırdır ama
kodda olmayan bir yerde de tutulur: isteği alan kişinin kafasında. Alan odaklı tasarımın
temel önerisi bu eşlemeyi ortadan kaldırmaktır — çeviri yapılmasın diye alanla kodun aynı
sözcükleri kullanması.

Ayrım bir adlandırma alışkanlığından fazlasıdır çünkü kayıp yalnız adlarda değil
**ayrımlarda**dır. Teknik sürümde `LIM` ile `MIN` iki sayıdır; alanda biri bir oran tavanı,
öteki bir para tutarıdır ve ikisi ayrı kararlarla, ayrı sıklıkta değişir. Tasarım İlkeleri
kursundaki politika–ayrıntı ayrımı bu iki sayının aynı dosyada durmasını değişim sıklığıyla
sorgulamıştı; alan odaklı tasarım aynı soruyu bir adım önce sorar: bu ikisi alanda aynı tür
şey mi?

#### Karmaşıklık Nerede

Yaklaşımın bir sınırı vardır ve kendi gerekçesinden çıkar. Alanla hizalanmanın kazancı,
alanın kendisinin karmaşık olmasına bağlıdır. Kitaplığın ücretlendirme tarafında kademe,
bölge, hacim, tavan ve asgari tutar gibi birbirinden bağımsız ayrımlar var; uzmanın cümlesi
uzundur ve sık değişir. Bu yüzden on iki adı taşımak kazanç üretir.

Karmaşıklığın alanda değil teknik tarafta olduğu işlerde hesap tersine döner. Aldığı
baytları başka bir yere yazan bir bileşende alan sözlüğü iki sözcükten oluşur; oraya bir
alan katmanı kurmak, ölçülecek bir ad hizası olmadığı için bedelini karşılamaz. Ayrım
ölçütü kod tabanının boyutu da değildir — uzmanın kuralı söylemek için kaç ayrı ada ihtiyaç
duyduğudur.

İkinci sınır, alan uzmanına erişimin kendisidir. Ad hizası ölçüsünün girdisi uzmanın
cümlesidir; o cümle alınamıyorsa ölçünün sabit listesi de yoktur. Bu durumda kodda görünen
adlar alanın değil, kodu yazan kişinin varsayımlarının adları olur.

#### Özet

- Alan odaklı tasarım, kod tabanının yapısını alanın kendi ayrımlarından türetmeyi önerir;
  kurs boyunca ölçülen nitelik modelin alanla hizasıdır.
- Ölçünün girdisi alan uzmanının kuralı söylediği cümledir; ücretlendirme kuralının cümlesi
  on iki ad taşıyor.
- Teknik adlarla yazılmış sürümde bu adların 0'ı, alan adlarıyla yazılmış sürümde 12'si
  kaynakta birebir bulundu.
- Alan dilinde gelen üç değişiklik isteği teknik sürümde 0 aday satıra, alan sürümünde
  14 satırın 2'sine indi; aday yoksa dosyanın tamamı okunmak zorundadır.
- Dört gönderinin dördünde iki sürüm aynı ücreti verdi: hiza bir doğruluk niteliği değil,
  değişiklik zamanında ödenen bir maliyet kalemidir.
- Kazanç alanın karmaşıklığına bağlıdır; uzmanın kuralı iki adla söylediği işlerde ölçülecek
  bir hiza yoktur.

#### Sonraki Adım

Bu derste hiza tek dosyada ölçüldü ve uzmanın on iki adı bir kez yazıldı. Gerçek bir kod
tabanında aynı kavram birden çok yerde adlandırılır: veritabanı sütunu bir ad kullanır,
uygulama katmanı ikincisini, dışa açılan arayüz üçüncüsünü. O zaman soru adların alandan
gelip gelmediği değil, **kaç tanesinin** aynı şeyi adlandırdığı olur. Sonraki ders kavram
başına eş ad sayısını ve iki ad arasında çeviri yapan nokta sayısını sayar, iki eş adın aynı
kurala ayrı ayrı tabi tutulmasının ürettiği tutarsızlığı ölçer ve tek sözlüğün bu sayıları
nereye indirdiğini gösterir.

### Her Yerde Aynı Dil

Source: https://academia.sh/tr/kurslar/alan-odakli-tasarim/her-yerde-ayni-dil

Kod ile konuşma dilinin tek sözlükte birleşmesi: beş alan kavramına düşen ad sayısının iki sürümde sayılması, aynı kavramın iki adı arasında çeviri yapan nokta ve birim çevrimi sayısının ölçülmesi, aynı kuralın kaç gövdede yazıldığının ve tavanı kaç gövdenin uyguladığının çıkarılması, otuz gönderide teklif ile ücretin ayrılma sayısının ölçülmesi.

Önceki ders hizayı tek dosyada ölçtü: uzmanın on iki adı bir kez yazıldı ve on ikisi de
kodda bulundu. Gerçek bir kod tabanında aynı kavram birden çok yerde adlandırılır. Girdiyi
alan modül bir ad kullanır, ücret hesaplayan modül ikincisini, teklif üreten modül
üçüncüsünü. O zaman soru adların alandan gelip gelmediği değil, **kaç tanesinin** aynı şeyi
adlandırdığıdır.

Bu dersin konusu **her yerde aynı dil** (ubiquitous language): bir alan kavramının, alanın
konuşulduğu her yerde ve kodun her katmanında tek bir adla anılması. "Her yerde" sözü bir
üslup önerisi değil, ölçülebilir bir kısıttır — kavram başına ad sayısı bir olacaktır.

#### Beş Kavram, On Üç Ad

Kitaplığın ücretlendirme tarafında beş kavram var: gönderi, ağırlık, bölge, indirim, asgari
ücret. Aşağıdaki sürüm üç dosyaya yayılmış ve her dosya kendi adlarını seçmiş.

```sh
mkdir -p dagnik birlesik
```

```js
// dagnik/giris.mjs — kaydin girisi: kargo
export const kargoKaydi = (agirlikGram, zonKodu, iskontolar) =>
  ({ agirlikGram, zonKodu, iskontolar });
```

```js
// dagnik/ucretlendirme.mjs — paket adiyla calisir, tavani uygular
const KADEME = [[1, 3900], [5, 6400], [Infinity, 11800]];
const KATSAYI = { "34": 1, "06": 1.35, "65": 1.8 };
const TABAN_FIYAT = 3990;
const TAVAN = 0.4;

export function ucret(kargo) {
  const paket = {
    agirlik: kargo.agirlikGram / 1000,
    bolge: kargo.zonKodu,
    indirimler: kargo.iskontolar,
  };
  const ham = KADEME.find(([u]) => paket.agirlik <= u)[1] * (KATSAYI[paket.bolge] ?? 1.8);
  const oran = Math.min(paket.indirimler.reduce((t, i) => t + i.oran, 0), TAVAN);
  return Math.max(Math.round(ham * (1 - oran)), TABAN_FIYAT);
}
```

```js
// dagnik/teklif.mjs — gonderi adiyla calisir, tavani bilmiyor
const KADEME = [[1, 3900], [5, 6400], [Infinity, 11800]];
const KATSAYI = { "34": 1, "06": 1.35, "65": 1.8 };
const ASGARI_UCRET = 3990;

export function teklif(kargo) {
  const gonderi = {
    kg: kargo.agirlikGram / 1000,
    bolgeKodu: kargo.zonKodu,
    iskontolar: kargo.iskontolar,
  };
  const ham = KADEME.find(([u]) => gonderi.kg <= u)[1] * (KATSAYI[gonderi.bolgeKodu] ?? 1.8);
  const oran = gonderi.iskontolar.reduce((t, i) => t + i.oran, 0);
  return Math.max(Math.round(ham * (1 - oran)), ASGARI_UCRET);
}
```

Üç dosyanın her biri kendi içinde tutarlıdır ve her adı alandan gelmiştir: `kargo`, `paket`,
`gonderi` sözcüklerinin üçü de kitaplığın konuşulduğu odada duyulur. Önceki dersin ad hizası
ölçüsü bu sürümde yüksek çıkar. Sorun tek tek adlarda değil, aynı kavramın birden çok adı
olmasındadır.

#### Tek Sözlük

Birleşik sürüm her kavram için bir ad seçer ve o adı girişten hesaba kadar taşır. Ağırlık
kilogram cinsinden girer, dolayısıyla dosya içinde bir birim çevrimi kalmaz.

```js
// birlesik/giris.mjs — kaydin girisi: gonderi, agirlik kilogram cinsinden
export const gonderi = (agirlik, bolge, indirimler) => ({ agirlik, bolge, indirimler });
```

```js
// birlesik/ucretlendirme.mjs — tek ad kumesi, tek kural govdesi
const KADEMELER = [[1, 3900], [5, 6400], [Infinity, 11800]];
const BOLGE_KATSAYISI = { "34": 1, "06": 1.35, "65": 1.8 };
const ASGARI_UCRET = 3990;
const INDIRIM_TAVANI = 0.4;

const tabanUcret = (agirlik) => KADEMELER.find(([ust]) => agirlik <= ust)[1];
const indirimOrani = (indirimler) =>
  Math.min(indirimler.reduce((t, i) => t + i.oran, 0), INDIRIM_TAVANI);

function netUcret(gonderi) {
  const ham = tabanUcret(gonderi.agirlik) * (BOLGE_KATSAYISI[gonderi.bolge] ?? 1.8);
  return Math.max(Math.round(ham * (1 - indirimOrani(gonderi.indirimler))), ASGARI_UCRET);
}

export const ucret = (gonderi) => netUcret(gonderi);
export const teklif = (gonderi) => netUcret(gonderi);
```

Alanda teklif ile fatura ücretinin aynı kurala tabi olduğu söyleniyor; birleşik sürümde bu
cümlenin karşılığı iki dışa açılan adın aynı gövdeyi çağırmasıdır.

#### Ölçüm

Dört ölçü alınır: kavram başına ad sayısı, aynı kavramın iki adı arasında çeviri yapan nokta
sayısı, kuralın kaç gövdede yazıldığı ve tavanı kaç gövdenin uyguladığı. Sonuna otuz
gönderide teklif ile ücretin ayrılma sayısı eklenir.

```js
// dil-olc.mjs — kavram basina ad, ceviri noktasi, kural govdesi ve iki yolun ayrilmasi
import { readFileSync } from "node:fs";
import { ucret as dUcret } from "./dagnik/ucretlendirme.mjs";
import { teklif as dTeklif } from "./dagnik/teklif.mjs";
import { kargoKaydi } from "./dagnik/giris.mjs";
import { ucret as bUcret, teklif as bTeklif } from "./birlesik/ucretlendirme.mjs";
import { gonderi as bGonderi } from "./birlesik/giris.mjs";

const KAVRAM = {
  gonderi: ["kargo", "paket", "gonderi"],
  agirlik: ["agirlikGram", "kg", "agirlik"],
  bolge: ["zonKodu", "bolgeKodu", "bolge"],
  indirim: ["iskontolar", "indirimler"],
  asgariUcret: ["TABAN_FIYAT", "ASGARI_UCRET"],
};
const SURUM = {
  dagnik: ["dagnik/giris.mjs", "dagnik/ucretlendirme.mjs", "dagnik/teklif.mjs"],
  birlesik: ["birlesik/giris.mjs", "birlesik/ucretlendirme.mjs"],
};
const kavramiBul = (ad) => Object.keys(KAVRAM).find((k) => KAVRAM[k].includes(ad)) ?? null;

for (const [surum, dosyalar] of Object.entries(SURUM)) {
  const metin = dosyalar.map((d) => readFileSync(d, "utf8")).join("\n");
  const jeton = new Set(metin.match(/[A-Za-z_][A-Za-z0-9_]*/g) ?? []);
  const dokum = [];
  let toplamAd = 0;
  for (const [kavram, adlar] of Object.entries(KAVRAM)) {
    const varOlan = adlar.filter((a) => jeton.has(a));
    toplamAd += varOlan.length;
    dokum.push(`${kavram}=${varOlan.length}`);
  }
  const tek = metin.replace(/\s+/g, " ");
  const ceviri = [...tek.matchAll(/(\w+):\s*\w+\.(\w+)/g)].filter(
    ([, sol, sag]) => sol !== sag && kavramiBul(sol) && kavramiBul(sol) === kavramiBul(sag));
  const birim = [...tek.matchAll(/\/ 1000/g)].length;
  const govdeli = dosyalar.map((d) => readFileSync(d, "utf8"))
    .filter((m) => /Math\.max\(Math\.round/.test(m));
  const tavanli = govdeli.filter((m) => /TAVAN/.test(m)).length;
  console.log(`${surum}: dosya ${dosyalar.length}, 5 kavrama ${toplamAd} ad`);
  console.log(`  ad dagilimi: ${dokum.join("  ")}`);
  console.log(`  ceviri noktasi ${ceviri.length}, birim cevrimi ${birim}`);
  console.log(`  kural govdesi ${govdeli.length}, tavani uygulayan govde ${tavanli}`);
}

const ORNEK = [];
for (let i = 0; i < 30; i += 1) {
  const hacimOrani = [0.1, 0.2, 0.3][i % 3] + (i % 5) * 0.05;
  ORNEK.push([600 + i * 400, ["34", "06", "65"][i % 3],
    [{ ad: "sozlesme", oran: 0.2 }, { ad: "hacim", oran: Math.round(hacimOrani * 100) / 100 }]]);
}
const ayrilan = (a, b) => ORNEK.filter((o) => a(o) !== b(o)).length;

console.log(`dagnik   ucret != teklif : ${ayrilan(
  (o) => dUcret(kargoKaydi(...o)), (o) => dTeklif(kargoKaydi(...o)))} / ${ORNEK.length}`);
console.log(`birlesik ucret != teklif : ${ayrilan(
  (o) => bUcret(bGonderi(o[0] / 1000, o[1], o[2])),
  (o) => bTeklif(bGonderi(o[0] / 1000, o[1], o[2])))} / ${ORNEK.length}`);
```

```sh
node dil-olc.mjs
```

```
dagnik: dosya 3, 5 kavrama 13 ad
  ad dagilimi: gonderi=3  agirlik=3  bolge=3  indirim=2  asgariUcret=2
  ceviri noktasi 5, birim cevrimi 2
  kural govdesi 2, tavani uygulayan govde 1
birlesik: dosya 2, 5 kavrama 5 ad
  ad dagilimi: gonderi=1  agirlik=1  bolge=1  indirim=1  asgariUcret=1
  ceviri noktasi 0, birim cevrimi 0
  kural govdesi 1, tavani uygulayan govde 1
dagnik   ucret != teklif : 19 / 30
birlesik ucret != teklif : 0 / 30
```

Beş kavram dağınık sürümde on üç ad taşıyor, birleşik sürümde beş. Aradaki sekiz fazla adın
bedeli ikinci satırda: aynı kavramın iki adı arasında beş çeviri noktası ve iki birim
çevrimi. Çeviri noktası, bir dosyanın öteki dosyanın sözlüğünü bilmek zorunda olduğu
yerdir; her biri bir eşleme satırı olarak yazılır ve bir ad değiştiğinde tek tek gözden
geçirilir.

#### Ayrılan Sonuçların Kaynağı

Son iki satır hizasızlığın bedelini davranış cinsinden veriyor: otuz gönderinin
on dokuzunda teklif ile ücret farklı çıkıyor, oysa alan uzmanı ikisinin aynı olduğunu
söylüyor. Ayrılmanın doğrudan yapısal nedeni dördüncü satırdadır: dağınık sürümde iki kural
gövdesi var ve tavanı yalnız biri uyguluyor.

Buradaki asıl gözlem, iki gövdenin *aynı kural olduğunun kod üzerinden görünmemesidir*.
`paket.indirimler` ile `gonderi.iskontolar` iki ayrı ad olduğu için tavanı arayan bir okuma
ikinci gövdede aranacak sözcüğü bulamaz. Adlar ayrıldığında kuralların ayrılması bir dikkat
kusuru olmaktan çıkar, yapının izin verdiği bir sonuç hâline gelir. Birleşik sürümde aynı
ayrılma yazılamaz: tek gövde vardır, iki dışa açılan ad onu çağırır ve ayrılan sonuç 0'a
iner.

#### Sözlüğün Bakımı

Tek sözlük bir kereye mahsus bir seçim değildir; alan uzmanının dili değiştikçe kodun da
değişmesi gerekir. Bunun iki yönü vardır. Birincisi, kodda bir ad değiştiğinde alanda
karşılığının da değişmiş olması: `iskonto` sözcüğü artık kullanılmıyorsa kaynakta da
kalmamalıdır. İkincisi ve daha önemlisi, alanda **yeni bir ayrım** doğduğunda kodda yeni bir
ad açılmasıdır. Sözleşmeli müşterinin indirimi ile kampanya indirimi ayrı kurallara tabi
olmaya başladığı anda, ikisini `indirim` adı altında toplamak tek sözlük olmaktan çıkar —
alanda iki kavram olan şeyi kodda bir kavramla anmak, aynı hizasızlığın ters yöndeki
biçimidir.

Bu yüzden ölçü çift taraflıdır: kavram başına ad sayısı bir olacak, ad başına kavram sayısı
da bir olacaktır. İkinci kısmın ihlali dersin sonunda görünmeye başlar; aynı `gonderi`
sözcüğünün iki farklı şeyi adlandırdığı durum bu konunun ölçtüğü şeyin dışındadır.

#### Ne Zaman Karşılığını Vermez

Tek sözlüğün bedeli yeniden adlandırma işidir ve bu bedel dokunulan dosya sayısıyla ölçülür.
Yukarıdaki üç dosyada sekiz fazla adı gidermek küçük bir iştir; sözlüğün yüz dosyaya yayıldığı
bir kod tabanında aynı iş bir dizi mekanik değişiklik gerektirir ve Yazılım Kalitesi ve Test
kursunda kurulan davranış koruması olmadan yapılamaz.

İkinci durum ölçünün girdisiyle ilgilidir. Ad sayısı ancak kavram listesi sabitse
sayılabilir. Alanın kendisi henüz oturmamışsa — hangi ayrımların kalıcı olduğu bilinmiyorsa —
adlar da oturmaz ve erken yapılan bir birleştirme, alanda gerçekten iki olan şeyi tek ada
sıkıştırabilir. Bu durumda ölçü sıfıra iner ama hiza bozulur.

#### Özet

- Her yerde aynı dil, bir alan kavramının kodun her yerinde tek adla anılmasıdır; ölçüsü
  kavram başına ad sayısıdır.
- Dağınık sürümde beş kavram on üç ad taşıyordu; birleşik sürümde beş ad kaldı.
- Fazla adların bedeli sayıldı: aynı kavramın iki adı arasında 5 çeviri noktası ve 2 birim
  çevrimi, birleşik sürümde 0 ve 0.
- Dağınık sürümde kural iki gövdeye bölünmüştü ve tavanı yalnız biri uyguluyordu; otuz
  gönderinin 19'unda teklif ile ücret ayrıldı, birleşik sürümde 0'ında.
- Ölçü çift taraflıdır: kavram başına bir ad, ad başına bir kavram. İkincisinin ihlali
  alanda iki olan şeyi kodda bir adla anmaktır.

#### Sonraki Adım

Birleşik sürümde gönderi üç alan taşıyan düz bir kayıttı ve iki gönderi ancak alanları
eşitse aynı sayılabilirdi. Alanda bu doğru değildir: aynı gönderinin ağırlığı tartım
düzeltmesiyle değişir ve gönderi hâlâ aynı gönderidir; iki ayrı gönderinin bütün alanları
tesadüfen eşit olabilir ve ikisi hâlâ iki gönderidir. Sonraki ders bu ayrımı ölçüye çevirir:
aynı kimliğin kaç ayrı örnekle temsil edilebildiğini ve alanları eşitleyince kaç ayrı
gönderinin tek örneğe çöktüğünü sayar, kimliği açıkça taşıyan bir modelle karşılaştırır.

### Varlıklar

Source: https://academia.sh/tr/kurslar/alan-odakli-tasarim/varliklar

Kimlikle tanımlanan nesnelerin seçim ölçütü: on iki gönderinin değer eşitliğiyle kaç ayrı örneğe indiğinin sayılması, fatura okumasının kaçının doğru çıktığının ölçülmesi, tartım düzeltmesinden sonra aynı kimliği gösteren anahtar sayısının çıkarılması ve iki eşitlik tanımının alanın iki sorusuna verdiği yanıtların karşılaştırılması.

Önceki dersin birleşik sürümünde gönderi üç alan taşıyan düz bir kayıttı ve iki gönderi
ancak alanları eşitse aynı sayılabiliyordu. Alanda bu doğru değildir. Aynı gönderinin
ağırlığı tartım düzeltmesiyle değişir ve gönderi hâlâ aynı gönderidir; iki ayrı gönderinin
bütün alanları eşit olabilir ve ikisi hâlâ iki gönderidir.

Bu ayrımı taşıyan nesneye **varlık nesnesi** (entity) denir: alanları değişse bile aynı
kalan, dolayısıyla eşitliği alanlarıyla değil bir **kimlikle** tanımlanan nesne. Tanım
Nesneye Dayalı Programlamaya Giriş kursunda verilmişti; buradaki iş tanım değil **seçim
ölçütüdür** — hangi alan kavramına kimlik verilir, verilmediğinde ne kaybedilir.

#### İki Soru

Ölçüt iki soruya indirgenir. Birincisi: bu şey, alanları değiştikten sonra da aynı şey
midir? İkincisi: bütün alanları eşit olan iki tanesi ayrı ayrı izlenmek zorunda mıdır?

Gönderi ücretlendirme kitaplığında ikisinin de yanıtı evettir. Bir müşteri aynı gün aynı
bölgeye aynı ağırlıkta beş koli gönderir; beşine beş ayrı fatura kesilir. Aynı koli
tartıldığında beyan ağırlığı düzeltilir; fatura o koliye kesilmeye devam eder.

#### İki Model

Birinci sürüm gönderiyi alanlarıyla tanımlar. Eşitlik, alanların birleşiminden üretilen bir
anahtardır.

```sh
mkdir -p degerle kimlikle
```

```js
// degerle/gonderi.mjs — kimliksiz gonderi: esitlik alanlarin esitligidir
export const gonderi = (musteri, agirlik, bolge) => ({ musteri, agirlik, bolge });
export const anahtar = (g) => `${g.musteri}|${g.agirlik}|${g.bolge}`;
export const esitMi = (a, b) => anahtar(a) === anahtar(b);
export const agirligiDuzelt = (g, yeniAgirlik) => ({ ...g, agirlik: yeniAgirlik });
```

İkinci sürüm gönderiye bir numara verir. Eşitlik yalnız o numaraya bakar; ağırlık düzeltmesi
nesneyi değiştirir ama kimliğine dokunmaz.

```js
// kimlikle/gonderi.mjs — varlik nesnesi: esitlik gonderi numarasinin esitligidir
export class Gonderi {
  constructor(gonderiNo, musteri, agirlik, bolge) {
    this.gonderiNo = gonderiNo;
    this.musteri = musteri;
    this.agirlik = agirlik;
    this.bolge = bolge;
  }
  anahtar() { return this.gonderiNo; }
  aynisiMi(o) { return o instanceof Gonderi && o.gonderiNo === this.gonderiNo; }
  agirligiDuzelt(yeniAgirlik) { this.agirlik = yeniAgirlik; return this; }
}
```

#### Ölçüm

Ölçünün girdisi on iki gönderi ve her birine kesilmiş bir faturadır. Beş gönderi aynı
müşteriye, aynı ağırlığa ve aynı bölgeye aittir; iki tanesi başka bir müşteride, iki tanesi
üçüncü müşterinin ağır kolilerinde tekrarlanır.

```js
// kayitlar.mjs — ucretlendirme baglaminda 12 gonderi ve her birine kesilen fatura
export const KAYITLAR = [
  ["G-1041", "ARC", 2.4, "34", "F-9001"],
  ["G-1042", "ARC", 2.4, "34", "F-9002"],
  ["G-1043", "ARC", 2.4, "34", "F-9003"],
  ["G-1044", "ARC", 2.4, "34", "F-9004"],
  ["G-1045", "ARC", 2.4, "34", "F-9005"],
  ["G-1046", "ARC", 5, "34", "F-9006"],
  ["G-1047", "BKM", 0.8, "06", "F-9007"],
  ["G-1048", "BKM", 0.8, "06", "F-9008"],
  ["G-1049", "BKM", 12, "65", "F-9009"],
  ["G-1050", "CTS", 0.8, "06", "F-9010"],
  ["G-1051", "CTS", 18, "65", "F-9011"],
  ["G-1052", "CTS", 18, "65", "F-9012"],
];
```

Her sürümde fatura tablosu, gönderinin o sürümdeki anahtarıyla kurulur. Sonra ilk gönderinin
ağırlığı 2,4'ten 3,1 kilograma düzeltilir ve tablo yeniden okunur.

```js
// kimlik-olc.mjs — 12 gonderi kac ornekle temsil ediliyor, fatura okumasi kaci dogru
import { KAYITLAR } from "./kayitlar.mjs";
import * as deger from "./degerle/gonderi.mjs";
import { Gonderi } from "./kimlikle/gonderi.mjs";

function olc(ad, kur, anahtar, duzelt) {
  const nesneler = KAYITLAR.map(([no, m, a, b]) => kur(no, m, a, b));
  const tablo = new Map();
  nesneler.forEach((n, i) => tablo.set(anahtar(n), KAYITLAR[i][4]));
  const dogru = nesneler.filter((n, i) => tablo.get(anahtar(n)) === KAYITLAR[i][4]).length;
  console.log(`${ad}: 12 gonderi -> ${tablo.size} ayri ornek`);
  console.log(`  fatura okumasi dogru ${dogru}/12`);

  const eski = anahtar(nesneler[0]);
  const yeni = duzelt(nesneler[0], 3.1);
  tablo.set(anahtar(yeni), tablo.get(eski));
  nesneler[0] = yeni;
  const dogru2 = nesneler.filter((n, i) => tablo.get(anahtar(n)) === KAYITLAR[i][4]).length;
  const isaret = [...tablo.keys()].filter((k) => k === eski || k === anahtar(yeni)).length;
  console.log(`  tartim duzeltmesi sonrasi: tablo girdisi ${tablo.size}, ` +
    `G-1041'i gosteren anahtar ${isaret}`);
  console.log(`  duzeltme sonrasi fatura okumasi dogru ${dogru2}/12`);
}

olc("degerle", (no, m, a, b) => deger.gonderi(m, a, b), deger.anahtar,
  (g, a) => deger.agirligiDuzelt(g, a));
olc("kimlikle", (no, m, a, b) => new Gonderi(no, m, a, b), (g) => g.anahtar(),
  (g, a) => g.agirligiDuzelt(a));

const [ilk, ikinci] = [deger.gonderi("ARC", 2.4, "34"), deger.gonderi("ARC", 2.4, "34")];
const [v1, v2] = [new Gonderi("G-1041", "ARC", 2.4, "34"), new Gonderi("G-1042", "ARC", 2.4, "34")];
console.log(`iki ayri gonderi esit mi -> degerle ${deger.esitMi(ilk, ikinci)}, ` +
  `kimlikle ${v1.aynisiMi(v2)}`);
const v1b = new Gonderi("G-1041", "ARC", 3.1, "34");
console.log(`duzeltilmis gonderi ayni mi -> degerle ` +
  `${deger.esitMi(ilk, deger.agirligiDuzelt(ilk, 3.1))}, kimlikle ${v1.aynisiMi(v1b)}`);
```

```sh
node kimlik-olc.mjs
```

```
degerle: 12 gonderi -> 6 ayri ornek
  fatura okumasi dogru 6/12
  tartim duzeltmesi sonrasi: tablo girdisi 7, G-1041'i gosteren anahtar 2
  duzeltme sonrasi fatura okumasi dogru 6/12
kimlikle: 12 gonderi -> 12 ayri ornek
  fatura okumasi dogru 12/12
  tartim duzeltmesi sonrasi: tablo girdisi 12, G-1041'i gosteren anahtar 1
  duzeltme sonrasi fatura okumasi dogru 12/12
iki ayri gonderi esit mi -> degerle true, kimlikle false
duzeltilmis gonderi ayni mi -> degerle false, kimlikle true
```

İlk sayı kaybın büyüklüğünü veriyor: değer eşitliğiyle on iki gönderi altı örneğe iniyor.
Altı gönderi bir başkasının anahtarına çöktüğü için fatura okumasının yarısı yanlış çıkıyor
— altı gönderi kendisine kesilmeyen faturayı gösteriyor.

İkinci sayı ters yönde bozulmayı gösteriyor. Ağırlık düzeltmesi değer sürümünde yeni bir
anahtar üretiyor; eski anahtar silinemez, çünkü ona bağlı başka gönderiler var. Sonuçta tablo
yedi girdiye çıkıyor ve tek bir gönderi iki anahtarla gösterilir hâle geliyor. Kimlik
sürümünde aynı düzeltme tablo boyunu değiştirmiyor: anahtar `G-1041` kalıyor, on iki okumanın
on ikisi doğru.

Son iki satır ölçütü tek cümleye indiriyor. Alanın birinci sorusu "bu iki gönderi aynı mı"
sorusudur ve yanıtı hayırdır; değer eşitliği evet der. İkinci soru "düzeltilmiş gönderi aynı
gönderi mi" sorusudur ve yanıtı evettir; değer eşitliği hayır der. İki tanımdan biri alanın
iki sorusuna da yanlış, öteki ikisine de doğru yanıt veriyor.

#### Kimlik Nereden Gelir

Kimliğin taşıdığı tek şart, nesnenin yaşamı boyunca değişmemesi ve başka hiçbir nesnede
tekrarlanmamasıdır. Alanda hazır bir kimlik varsa o kullanılır: gönderi numarası, sözleşme
numarası, vergi kimlik numarası. Yoksa bir tane üretilir ve üretim kuralı alanın değil
uygulamanın kararıdır.

Alandan gelen bir alan kimlik gibi kullanıldığında ölçü bozulur. Ücretlendirme bağlamında
müşteri ile bölgenin birleşimi çoğu zaman tek bir gönderiyi verir; "çoğu zaman" bir kimlik
ölçütü değildir ve yukarıdaki ölçüm o varsayımın bedelini gösteriyor. Aynı sebeple ağırlık,
posta kodu ve beyan değeri gibi düzeltilebilen alanlar kimliğin parçası olamaz.

Kimliğin ikinci sonucu tekilliktir: aynı kimliğe sahip iki örnek bellekte aynı anda
bulunuyorsa hangisinin doğru olduğu belirsizleşir. Bu sorun Tasarım Kalıpları kursundaki
Kimlik Haritası dersinde ele alınmıştı; varlık nesnesi kararı o kalıbın önkoşuludur, çünkü
tekilliği koruyacak bir kimlik olmadan haritanın anahtarı da yoktur.

#### Ne Zaman Kimlik Gerekmez

Ölçütün iki sorusu da hayırsa nesne varlık nesnesi değildir. Kitaplıkta bunun açık bir örneği
bölge katsayısıdır: 1,35 katsayısı ile bir başka 1,35 katsayısı arasında ayrım yoktur ve
katsayının bir yaşam öyküsü de yoktur. Aynı şey ağırlık, para tutarı ve indirim oranı için de
geçerlidir.

Bu kavramlara kimlik verilmesi ölçüyü ters yönde bozar. Bir katsayıya numara verildiğinde
eşitlik karşılaştırması kimliğe bakar ve alanda eşit olan iki değer kodda eşit çıkmaz.
Kimlik, taşıdığı yerde kazanç üretir; taşımadığı yerde her karşılaştırmaya bir kural ekler.

#### Özet

- Varlık nesnesi, alanları değiştiği hâlde aynı kalan ve eşitliği bir kimlikle tanımlanan
  nesnedir; seçim ölçütü iki sorudur — alanları değişince aynı mı kalıyor, alanları eşit olan
  ikisi ayrı ayrı izleniyor mu.
- Değer eşitliğiyle on iki gönderi altı örneğe indi; fatura okumasının 6'sı yanlış çıktı.
- Tartım düzeltmesi değer sürümünde tabloyu yedi girdiye çıkardı ve tek gönderiyi iki
  anahtarla gösterdi; kimlik sürümünde tablo on iki girdide kaldı, on iki okuma doğru çıktı.
- Değer eşitliği alanın iki sorusuna da yanlış, kimlik eşitliği ikisine de doğru yanıt verdi.
- Kimlik yaşam boyunca değişmeyen ve tekrarlanmayan bir ad olmalıdır; düzeltilebilen alanlar
  kimliğin parçası olamaz.
- İki sorunun ikisi de hayırsa kimlik gerekmez; katsayı, ağırlık ve oran gibi kavramlara
  kimlik vermek her karşılaştırmaya gereksiz bir kural ekler.

#### Sonraki Adım

Bu dersin son bölümü kimlik gerektirmeyen kavramları saydı ama onları hâlâ çıplak sayı ve
katar olarak tuttu: ağırlık bir `number`, bölge bir `string`. O zaman ağırlıkla hacmi
toplamak, kilogramla gramı karşılaştırmak ve kuruşla lirayı çarpmak kodun izin verdiği
işlemler olur. Sonraki ders bu kavramları kendi tipleri hâline getirir, değerle
karşılaştırmanın kaç yerde elle yazıldığını sayar ve birim karışmasının kaç durumda sessizce
yanlış tutar ürettiğini ölçer.

### Değer Nesneleri

Source: https://academia.sh/tr/kurslar/alan-odakli-tasarim/deger-nesneleri

Değerle tanımlanan değişmez nesnelerin seçim ölçütü: gram cinsinden gelen on iki girdinin çıplak sayı modelinde kaç yanlış tutar ürettiğinin sayılması, eşitlik kuralının kaç satırda elle yazıldığının ölçülmesi, paylaşılan bir ölçünün yerinde düzeltilmesiyle kaç gönderinin bozulduğunun çıkarılması ve iki modelin aynı gruplamayı verdiğinin gösterilmesi.

Önceki dersin son bölümü kimlik gerektirmeyen kavramları saydı ama onları hâlâ çıplak sayı
olarak tuttu: ağırlık bir sayıydı, bölge bir katardı. O zaman kilogramla gramı aynı
parametreye geçirmek, ikisini toplamak ve karşılaştırmak kodun izin verdiği işlemler olur.

Kimliği olmayan, eşitliği taşıdığı değerle tanımlanan ve bir kez kurulduktan sonra
değişmeyen nesneye **değer nesnesi** (value object) denir. Tanım Nesneye Dayalı
Programlamaya Giriş kursunda verilmişti. Buradaki iş yine seçim ölçütüdür ve üç soruya
ayrılır: kimlik gerekiyor mu, eşitlik neye göre kurulacak, değişmezlik neyi ucuzlatıyor.

#### Ölçüt

Önceki dersin iki sorusu bir kavram için de hayır yanıtı veriyorsa o kavram varlık nesnesi
değildir: iki tanesi arasında ayrım yoktur ve bir yaşam öyküsü taşımaz. Ağırlık böyledir. 2,4
kilogram ile bir başka 2,4 kilogram aynı şeydir; ağırlığın "düzeltilmesi" ağırlığın değişmesi
değil, gönderinin başka bir ağırlığa sahip olmasıdır.

İkinci soru eşitliğin neye göre kurulacağıdır ve yanıtı ölçünün birimini de içine alır. 2400
gram ile 2,4 kilogram eşittir; 2400 ile 2,4 eşit değildir. Aradaki fark, birimin nerede
tutulduğudur.

#### Çıplak Sayı Modeli

Birinci sürümde birim yalnız parametre adında yazılıdır.

```sh
mkdir -p ciplak deger
```

```js
// ciplak/ucret.mjs — agirlik ciplak sayi, birim yalnizca parametre adinda
const KADEMELER = [[1, 3900], [5, 6400], [Infinity, 11800]];
const BOLGE_KATSAYISI = { "34": 1, "06": 1.35, "65": 1.8 };
const ASGARI_UCRET = 3990;

export function netUcret(agirlikKg, bolge, oran) {
  const taban = KADEMELER.find(([ust]) => agirlikKg <= ust)[1];
  const ham = taban * (BOLGE_KATSAYISI[bolge] ?? 1.8);
  return Math.max(Math.round(ham * (1 - oran)), ASGARI_UCRET);
}
```

```js
// ciplak/mutabakat.mjs — esitlik kurali her cagri yerinde yeniden yazilir
export const tartimUyuyorMu = (beyanKg, tartimKg) => Math.abs(beyanKg - tartimKg) < 0.001;
export const ayniKademeMi = (aKg, bKg) => Math.abs(aKg - bKg) < 0.001;
export const agirlikTopla = (aKg, bKg) => aKg + bKg;
```

```js
// ciplak/rapor.mjs — agirliga gore gruplama; esitlik kurali burada da yazili
export function agirlikGruplari(agirliklarKg) {
  const gruplar = [];
  for (const a of agirliklarKg) {
    const grup = gruplar.find((g) => Math.abs(g[0] - a) < 0.001);
    if (grup) grup.push(a);
    else gruplar.push([a]);
  }
  return gruplar;
}
```

Kayan noktalı sayıların karşılaştırılması bir hoşgörü payı gerektirir ve o pay üç dosyada üç
kez yazılmış durumda. Payın büyüklüğü bir alan kararıdır — kaç grama kadar iki ağırlık aynı
sayılır — ama kodda alan kararı gibi görünmüyor.

#### Değer Nesnesi Modeli

İkinci sürümde ağırlık kendi tipini alır. Birim yapıcıda çözülür, iç gösterim gram olur,
nesne dondurulur.

```js
// deger/agirlik.mjs — degistirilemez deger nesnesi: birim yapinin icinde
const GRAM = { kg: 1000, g: 1 };

export class Agirlik {
  constructor(miktar, birim) {
    if (!(birim in GRAM)) throw new RangeError(`bilinmeyen agirlik birimi: ${birim}`);
    this.gram = Math.round(miktar * GRAM[birim]);
    Object.freeze(this);
  }
  static kg(miktar) { return new Agirlik(miktar, "kg"); }
  static g(miktar) { return new Agirlik(miktar, "g"); }
  kilogram() { return this.gram / 1000; }
  esitMi(o) { return o instanceof Agirlik && o.gram === this.gram; }
  arti(o) { return new Agirlik(this.gram + o.gram, "g"); }
}
```

Hoşgörü payı ortadan kalktı çünkü iç gösterim tam sayıdır: yuvarlama bir kez yapıcıda
yapılıyor, karşılaştırma tam sayı eşitliği oluyor. `arti` yeni bir nesne döndürüyor, çağıranı
değiştirmiyor.

```js
// deger/ucret.mjs — agirlik yerine Agirlik bekler, ciplak sayiyi reddeder
import { Agirlik } from "./agirlik.mjs";

const KADEMELER = [[1, 3900], [5, 6400], [Infinity, 11800]];
const BOLGE_KATSAYISI = { "34": 1, "06": 1.35, "65": 1.8 };
const ASGARI_UCRET = 3990;

export function netUcret(agirlik, bolge, oran) {
  if (!(agirlik instanceof Agirlik)) throw new TypeError("agirlik bir Agirlik olmali");
  const taban = KADEMELER.find(([ust]) => agirlik.kilogram() <= ust)[1];
  const ham = taban * (BOLGE_KATSAYISI[bolge] ?? 1.8);
  return Math.max(Math.round(ham * (1 - oran)), ASGARI_UCRET);
}
```

```js
// deger/mutabakat.mjs — esitlik kurali deger nesnesinin icinde, cagri yeri onu cagirir
export const tartimUyuyorMu = (beyan, tartim) => beyan.esitMi(tartim);
export const ayniKademeMi = (a, b) => a.esitMi(b);
export const agirlikTopla = (a, b) => a.arti(b);
```

```js
// deger/rapor.mjs — ayni gruplama; esitlik deger nesnesinin kendi yontemi
export function agirlikGruplari(agirliklar) {
  const gruplar = [];
  for (const a of agirliklar) {
    const grup = gruplar.find((g) => g[0].esitMi(a));
    if (grup) grup.push(a);
    else gruplar.push([a]);
  }
  return gruplar;
}
```

#### Ölçüm

Üç ölçü alınır. Yirmi dört girdinin yarısı gram cinsinden gelir; kaç tanesinde sessizce
yanlış tutar üretildiği sayılır. Eşitlik kuralının kaç satırda yazıldığı taranır. Beş gönderi
aynı ölçüyü paylaşırken biri düzeltildiğinde kaç gönderinin bozulduğu ölçülür.

```js
// deger-olc.mjs — birim karismasi, esitlik kuralinin yeri ve paylasilan ornegin bozulmasi
import { readFileSync } from "node:fs";
import { netUcret as ciplakUcret } from "./ciplak/ucret.mjs";
import { netUcret as degerUcret } from "./deger/ucret.mjs";
import { Agirlik } from "./deger/agirlik.mjs";
import { agirlikGruplari as ciplakGrup } from "./ciplak/rapor.mjs";
import { agirlikGruplari as degerGrup } from "./deger/rapor.mjs";

const BOLGELER = ["34", "06", "65"];
const GIRDILER = [];
for (let i = 0; i < 24; i += 1) {
  const kg = [0.4, 0.9, 2.4, 3.1, 7, 12][i % 6];
  GIRDILER.push({ kaynak: i % 2 === 0 ? "kg" : "g", kg, bolge: BOLGELER[i % 3] });
}
// dogru tutar: kural, gercek kilogram degerine uygulanmis hali
const dogruUcret = (g) => ciplakUcret(g.kg, g.bolge, 0);

let ciplakYanlis = 0;
for (const g of GIRDILER) {
  const sayi = g.kaynak === "kg" ? g.kg : g.kg * 1000;
  if (ciplakUcret(sayi, g.bolge, 0) !== dogruUcret(g)) ciplakYanlis += 1;
}
let degerYanlis = 0;
let degerHata = 0;
for (const g of GIRDILER) {
  const sayi = g.kaynak === "kg" ? g.kg : g.kg * 1000;
  if (degerUcret(new Agirlik(sayi, g.kaynak), g.bolge, 0) !== dogruUcret(g)) degerYanlis += 1;
  try { degerUcret(sayi, g.bolge, 0); } catch { degerHata += 1; }
}
console.log(`24 girdinin 12'si gram cinsinden geliyor`);
console.log(`  ciplak: sessizce yanlis ucret ${ciplakYanlis}/24, firlatilan hata 0/24`);
console.log(`  deger : sessizce yanlis ucret ${degerYanlis}/24, ` +
  `ciplak sayi gecirildiginde hata ${degerHata}/24`);

const KURAL = /Math\.abs\(|\.gram ===/;
for (const [ad, dosyalar] of Object.entries({
  ciplak: ["ciplak/ucret.mjs", "ciplak/mutabakat.mjs", "ciplak/rapor.mjs"],
  deger: ["deger/ucret.mjs", "deger/mutabakat.mjs", "deger/rapor.mjs", "deger/agirlik.mjs"],
})) {
  const satir = dosyalar.flatMap((d) => readFileSync(d, "utf8").split("\n"));
  console.log(`${ad}: esitlik kurali yazan satir ${satir.filter((s) => KURAL.test(s)).length}`);
}

const OLCUM = [0.4, 2.4, 0.4, 7, 2.4, 12, 7, 0.9];
const cGrup = ciplakGrup(OLCUM).map((g) => g.length).join(",");
const dGrup = degerGrup(OLCUM.map((k) => Agirlik.kg(k))).map((g) => g.length).join(",");
console.log(`8 olcumun gruplanmasi -> ciplak [${cGrup}], deger [${dGrup}], ` +
  `ayni mi ${cGrup === dGrup}`);

const paylasilan = { miktarKg: 2.4 };
const gonderiler = [0, 1, 2, 3, 4].map(() => ({ agirlik: paylasilan }));
paylasilan.miktarKg = 3.1;
const bozulan = gonderiler.slice(1).filter((g) => g.agirlik.miktarKg !== 2.4).length;

const ilkAgirlik = Agirlik.kg(2.4);
const gonderilerD = [0, 1, 2, 3, 4].map(() => ({ agirlik: ilkAgirlik }));
gonderilerD[0].agirlik = Agirlik.kg(3.1);
const bozulanD = gonderilerD.slice(1).filter((g) => !g.agirlik.esitMi(Agirlik.kg(2.4))).length;
let yerindeHata = 0;
try { ilkAgirlik.gram = 3100; } catch { yerindeHata = 1; }

console.log(`5 gonderi ayni agirlik ornegini paylasiyor, biri duzeltiliyor`);
console.log(`  degistirilebilir kayit: bozulan gonderi ${bozulan}/4`);
console.log(`  deger nesnesi         : bozulan gonderi ${bozulanD}/4, ` +
  `yerinde degistirme denemesi hata ${yerindeHata}`);
```

```sh
node deger-olc.mjs
```

```
24 girdinin 12'si gram cinsinden geliyor
  ciplak: sessizce yanlis ucret 8/24, firlatilan hata 0/24
  deger : sessizce yanlis ucret 0/24, ciplak sayi gecirildiginde hata 24/24
ciplak: esitlik kurali yazan satir 3
deger: esitlik kurali yazan satir 1
8 olcumun gruplanmasi -> ciplak [2,2,2,1,1], deger [2,2,2,1,1], ayni mi true
5 gonderi ayni agirlik ornegini paylasiyor, biri duzeltiliyor
  degistirilebilir kayit: bozulan gonderi 4/4
  deger nesnesi         : bozulan gonderi 0/4, yerinde degistirme denemesi hata 1
```

Birinci ölçü birim sorusunu yanıtlıyor. Gram cinsinden gelen on iki girdinin sekizinde çıplak
sayı modeli yanlış tutar üretiyor ve bunu sessizce yapıyor: hata sayısı sıfırdır, çağıran taraf
hiçbir uyarı almaz. Kalan dört girdide tutar tesadüfen doğru çıkıyor, çünkü 12 kilogramlık
gönderiler gram olarak da en üst kademeye düşüyor; tesadüf bir güvence değildir. Değer nesnesi
modelinde yanlış tutar sayısı sıfır. Aynı modelde çıplak bir sayı geçirildiğinde yirmi dört
çağrının yirmi dördü hata fırlatıyor — sessiz yanlış, gürültülü hataya dönüşmüş durumda.

İkinci ölçü eşitlik sorusunu yanıtlıyor. Hoşgörü payı çıplak modelde üç satırda yazılı; değer
nesnesi modelinde bir satırda, `esitMi` yönteminin içinde. Payın alan kararı olduğu ikinci
durumda görünür hâle geliyor: değişmesi gerekirse dokunulacak yer birdir.

Üçüncü ölçü değişmezliğin ne ucuzlattığını gösteriyor. Değiştirilebilir kayıt beş gönderi
tarafından paylaşıldığında bir düzeltme dördünü bozuyor. Değer nesnesi modelinde aynı
düzeltme yeni bir nesne üretiyor, dolayısıyla bozulan gönderi sayısı sıfır. Dondurulmuş
nesnede yerinde değiştirme denemesi hata veriyor. Kazanç bir savunma kopyası yazmak
zorunda kalmamaktır: paylaşım bedelsiz olur.

Gruplama satırı yine hizanın doğruluktan ayrı olduğunu söylüyor: sekiz ölçüm iki modelde de
aynı beş gruba dağılıyor.

#### Değer Nesnesi Neyi Taşıyabilir

Değer nesnesinin taşıdığı asıl kazanç kendi kurallarını taşıyabilmesidir. Ağırlık negatif
olamaz, bölge kodu iki basamaklıdır, indirim oranı sıfırla bir arasındadır. Bu kurallar
çıplak sayı modelinde çağrı yerlerine dağılır; değer nesnesinde yapıcıya girer ve bir kez
yazılır. Bunun sonucu, kuralı bozan bir değerin **hiç var olamamasıdır** — sonraki dersin
ölçtüğü şey tam olarak bu tür ulaşılabilirliktir.

Kitaplıkta aynı işlemi gören ikinci aday para tutarıdır: kuruş ile lira karışması ağırlıkla
aynı hatayı üretir ve çözümü de aynıdır — tutarı kuruş cinsinden tam sayı olarak tutan,
eşitliği ve toplamayı kendi üzerinde taşıyan bir tip.

#### Ne Zaman Karşılığını Vermez

Bedel iki kalemdir: her kavram için bir tip ve her erişim için bir yöntem çağrısı. Ölçüm
dosyalarında bu bedel görünüyor — değer nesnesi sürümü dört dosya tutuyor, çıplak sürüm üç.

Kavramın tek bir yerde okunup bir daha kullanılmadığı durumlarda bu bedel karşılıksız kalır.
Bir sınırdan geçen ham kayıtta ağırlık bir sayıdır; onu tipe çevirmenin kazancı ancak o değer
kitaplığın içinde dolaşmaya başladığında doğar. Aynı biçimde birimi tek olan ve hiç
karşılaştırılmayan bir ölçü — kütüğe yazılan bir sıra numarası gibi — tip açmayı hak etmez.

#### Özet

- Değer nesnesi kimliği olmayan, eşitliği taşıdığı değerle kurulan ve kurulduktan sonra
  değişmeyen nesnedir; ölçütü üç sorudur: kimlik, eşitlik, değişmezlik.
- Gram cinsinden gelen on iki girdinin 8'inde çıplak sayı modeli sessizce yanlış tutar
  üretti; değer nesnesi modelinde yanlış tutar 0, çıplak sayı geçirildiğinde hata 24/24.
- Eşitliğin hoşgörü payı çıplak modelde 3 satırda, değer nesnesi modelinde 1 satırda yazılı;
  alan kararı ikinci durumda tek noktada durur.
- Beş gönderi aynı ölçüyü paylaşırken bir düzeltme değiştirilebilir kayıtta 4 gönderiyi
  bozdu, değer nesnesinde 0; dondurulmuş nesnede yerinde değiştirme hata verdi.
- Sekiz ölçüm iki modelde de aynı gruplara dağıldı: kazanç doğrulukta değil, yanlışın
  yazılamaz hâle gelmesindedir.
- Bedel her kavram için bir tip ve her erişim için bir çağrıdır; tek yerde okunup bırakılan
  ham değerlerde bu bedel karşılıksızdır.

#### Sonraki Adım

Buraya kadar gönderi tek başına ele alındı. Alanda tek başına değildir: indirimleri,
sözleşmesi ve beyan kalemleri vardır ve aralarında bir kural durur — indirim oranlarının
toplamı tavanı aşamaz. Bu kural tek bir nesneye ait değildir, gönderi ile indirimlerinin
bütününe aittir. Sonraki ders o bütünün sınırını çizer: kuralı bozan bir durumun kodla kaç
ayrı çağrı yolundan üretilebildiğini sayar, sınırı dışa açan ve yalnız kökten geçiren iki
modeli karşılaştırır ve bir işlemde kaç ayrı bütünün yazıldığını ölçer.

### Toplamlar ve Kök

Source: https://academia.sh/tr/kurslar/alan-odakli-tasarim/toplamlar-ve-kok

Tutarlılık sınırının çizilmesi: indirim tavanı değişmezinin kaç ayrı çağrı yolundan bozulabildiğinin beş yol üzerinde sayılması, kökten geçen erişimde bu sayının sıfıra inmesi, bir işlemde dokunulan toplama sayısının ölçülmesi ve toplamalar arası bağın kimlik atfına çevrilmesinin bedeli.

Buraya kadar gönderi tek başına ele alındı. Alanda tek başına değildir: indirimleri,
sözleşmesi ve beyan kalemleri vardır ve aralarında bir kural durur — indirim oranlarının
toplamı tavanı aşamaz. Bu kural gönderiye de indirimlere de tek başına ait değildir;
gönderiyle indirimlerinin bütününe aittir.

Böyle bir bütüne **toplama** (aggregate), o bütüne dışarıdan erişimin geçtiği tek nesneye
**toplama kökü** (aggregate root) denir. Toplama kavramı Tasarım Kalıpları kursundaki Depo ve
İş Birimi dersinde erişim ve yazma birimi olarak kurulmuştu. Bu dersin sorusu farklıdır:
sınır **tutarlılık** ölçütüne göre nereye çizilir — hangi değişmez sınırın içindedir ve sınır
korunuyor mu.

#### Değişmez Kimin İçinde

Ölçüt tek cümlededir: bir değişmez, doğruluğunu denetlemek için gerekli bütün verinin
bulunduğu en küçük sınırın içine konur. İndirim tavanı için gereken veri bir gönderinin
indirim listesidir; başka gönderiye, başka sözleşmeye bakmak gerekmez. Sınır bu yüzden
gönderi ile indirimlerini kapsar, sözleşmeyi kapsamaz.

Sözleşmenin kendi değişmezi vardır: taahhüt edilen hacim aşılamaz. O kuralın girdisi
sözleşmeye bağlı bütün gönderilerin sayısıdır, dolayısıyla ikinci bir sınır doğar. İki sınır
arasındaki bağ nesne başvurusu değil **kimlik atfıdır**: gönderi sözleşme numarasını taşır,
sözleşme nesnesini taşımaz.

#### Sınırı Dışa Açan Model

Birinci sürümde indirim listesi doğrudan erişilebilir ve tavan kuralı yalnız bir yardımcı
işlevin içindedir.

```sh
mkdir -p acik kok
```

```js
// acik/gonderi.mjs — indirimler disa acik; tavan kurali yalniz yardimci islevde
export const INDIRIM_TAVANI = 0.4;

export const gonderi = (gonderiNo, sozlesmeNo) =>
  ({ gonderiNo, sozlesmeNo, indirimler: [] });
export const toplamOran = (g) => g.indirimler.reduce((t, i) => t + i.oran, 0);

export function indirimEkle(g, indirim) {
  if (toplamOran(g) + indirim.oran > INDIRIM_TAVANI) throw new RangeError("tavan asildi");
  g.indirimler.push(indirim);
  return g;
}
```

```js
// acik/senaryo.mjs — sozlesmeli indirim: iki toplama ayni islemde yazilir
import { indirimEkle } from "./gonderi.mjs";

export function sozlesmeliIndirimUygula(g, sozlesme, oran, yaz) {
  indirimEkle(g, { ad: "sozlesme", oran });
  sozlesme.kullanilanHacim += 1;
  yaz("gonderi", g.gonderiNo);
  yaz("sozlesme", sozlesme.sozlesmeNo);
}
```

#### Kökten Geçen Model

İkinci sürümde indirim listesi özel bir alandır. Dışa verilen liste bir kopyadır ve ögeleri
dondurulmuştur; ekleme yalnız kökün yönteminden geçer.

```js
// kok/gonderi.mjs — toplama koku: indirimlere yalniz kokten ulasilir
const INDIRIM_TAVANI = 0.4;

export class Gonderi {
  #indirimler = [];
  constructor(gonderiNo, sozlesmeNo) {
    this.gonderiNo = gonderiNo;
    this.sozlesmeNo = sozlesmeNo;
  }
  toplamOran() { return this.#indirimler.reduce((t, i) => t + i.oran, 0); }
  indirimler() { return this.#indirimler.map((i) => Object.freeze({ ...i })); }
  indirimEkle(indirim) {
    if (this.toplamOran() + indirim.oran > INDIRIM_TAVANI) throw new RangeError("tavan asildi");
    this.#indirimler.push(Object.freeze({ ...indirim }));
    return this;
  }
}
```

```js
// kok/senaryo.mjs — her islem tek toplama yazar; sozlesmeye kimlikle atif yapilir
export function sozlesmeliIndirimUygula(gonderi, oran, yaz) {
  gonderi.indirimEkle({ ad: "sozlesme", oran });
  yaz("gonderi", gonderi.gonderiNo);
}

export function sozlesmeKullanimiIsle(sozlesme, yaz) {
  sozlesme.kullanilanHacim += 1;
  yaz("sozlesme", sozlesme.sozlesmeNo);
}
```

#### Ölçüm

İki ölçü alınır. Birincisi **ulaşılabilirlik**: 0,30 oranlı bir indirimi olan gönderide tavanı
aşan bir durum beş ayrı çağrı yolundan kaçında üretilebiliyor. İkincisi **işlem başına
toplama**: bir senaryo kaç ayrı toplamayı yazıyor.

```js
// sinir-olc.mjs — degismez kac yoldan bozulabiliyor, bir islemde kac toplama yaziliyor
import * as acik from "./acik/gonderi.mjs";
import { Gonderi } from "./kok/gonderi.mjs";
import { sozlesmeliIndirimUygula as acikSenaryo } from "./acik/senaryo.mjs";
import * as kokSenaryo from "./kok/senaryo.mjs";

const TAVAN = 0.4;
const EK = { ad: "hacim", oran: 0.5 };
const YENI = [{ ad: "hacim", oran: 0.9 }];

const ACIK_YOLLAR = {
  "alan yontemi": (g) => acik.indirimEkle(g, EK),
  "diziye ekleme": (g) => { g.indirimler.push({ ...EK }); },
  "ogenin orani": (g) => { g.indirimler[0].oran = 0.5; },
  "dizinin degistirilmesi": (g) => { g.indirimler = YENI.map((i) => ({ ...i })); },
  "toplu atama": (g) => { Object.assign(g, { indirimler: YENI.map((i) => ({ ...i })) }); },
};
const KOK_YOLLAR = {
  "alan yontemi": (g) => g.indirimEkle(EK),
  "diziye ekleme": (g) => { g.indirimler().push({ ...EK }); },
  "ogenin orani": (g) => { g.indirimler()[0].oran = 0.5; },
  "dizinin degistirilmesi": (g) => { g.indirimler = YENI.map((i) => ({ ...i })); },
  "toplu atama": (g) => { Object.assign(g, { indirimler: YENI.map((i) => ({ ...i })) }); },
};

function tara(ad, kur, oran, yollar) {
  let ihlal = 0;
  let hata = 0;
  for (const [yol, dene] of Object.entries(yollar)) {
    const g = kur();
    try { dene(g); } catch { hata += 1; }
    const bozuk = oran(g) > TAVAN;
    if (bozuk) ihlal += 1;
    console.log(`  ${yol.padEnd(24)} toplam oran ${oran(g).toFixed(2)}  ` +
      `${bozuk ? "IHLAL" : "korundu"}`);
  }
  console.log(`${ad}: 5 yoldan ${ihlal} tanesi degismezi bozdu, ${hata} tanesi hata verdi`);
}

console.log("acik model");
tara("acik", () => acik.indirimEkle(acik.gonderi("G-2001", "S-77"), { ad: "sozlesme", oran: 0.3 }),
  acik.toplamOran, ACIK_YOLLAR);
console.log("kok modeli");
tara("kok", () => new Gonderi("G-2001", "S-77").indirimEkle({ ad: "sozlesme", oran: 0.3 }),
  (g) => g.toplamOran(), KOK_YOLLAR);

function islemOlc(ad, kos) {
  const yazilan = [];
  kos((tur) => yazilan.push(tur));
  const tekil = new Set(yazilan);
  console.log(`${ad}: yazma cagrisi ${yazilan.length}, dokunulan toplama ${tekil.size} ` +
    `[${[...tekil].join(" ")}]`);
}

islemOlc("acik islem", (yaz) => {
  const g = acik.gonderi("G-2002", "S-77");
  acikSenaryo(g, { sozlesmeNo: "S-77", kullanilanHacim: 4 }, 0.25, yaz);
});
islemOlc("kok islemi", (yaz) => {
  const g = new Gonderi("G-2002", "S-77");
  kokSenaryo.sozlesmeliIndirimUygula(g, 0.25, yaz);
});
islemOlc("kok ikinci islemi", (yaz) => {
  kokSenaryo.sozlesmeKullanimiIsle({ sozlesmeNo: "S-77", kullanilanHacim: 4 }, yaz);
});
```

```sh
node sinir-olc.mjs
```

```
acik model
  alan yontemi             toplam oran 0.30  korundu
  diziye ekleme            toplam oran 0.80  IHLAL
  ogenin orani             toplam oran 0.50  IHLAL
  dizinin degistirilmesi   toplam oran 0.90  IHLAL
  toplu atama              toplam oran 0.90  IHLAL
acik: 5 yoldan 4 tanesi degismezi bozdu, 1 tanesi hata verdi
kok modeli
  alan yontemi             toplam oran 0.30  korundu
  diziye ekleme            toplam oran 0.30  korundu
  ogenin orani             toplam oran 0.30  korundu
  dizinin degistirilmesi   toplam oran 0.30  korundu
  toplu atama              toplam oran 0.30  korundu
kok: 5 yoldan 0 tanesi degismezi bozdu, 2 tanesi hata verdi
acik islem: yazma cagrisi 2, dokunulan toplama 2 [gonderi sozlesme]
kok islemi: yazma cagrisi 1, dokunulan toplama 1 [gonderi]
kok ikinci islemi: yazma cagrisi 1, dokunulan toplama 1 [sozlesme]
```

Açık modelde beş yolun dördü tavanı aşan bir gönderi üretiyor. Kuralın yazılı olduğu tek yol —
yardımcı işlev — hata veriyor; kalan dördü listeye doğrudan dokunuyor ve kuralı hiç görmüyor.
Kök modelinde aynı beş denemenin sıfırı değişmezi bozuyor: iki deneme hata veriyor, biri kopya
üzerinde çalıştığı için etkisiz kalıyor, ikisi kökün özel alanına erişemiyor. Değişmezin
korunması bir dikkat sorusu olmaktan çıkıp yapının verdiği bir güvence oluyor.

Son üç satır sınırın genişliğini veriyor. Açık modelde tek bir işlem iki toplamayı yazıyor:
gönderi ile sözleşme birlikte kesinleşiyor. Kök modelinde aynı iş iki işleme bölünüyor ve her
biri tek toplamaya dokunuyor.

#### Sınırın Bedeli

İşlem başına tek toplama kuralının bedeli açıktır: sözleşmenin kullanılan hacmi, gönderiye
indirim eklendiği anda güncellenmiş olmaz. İki toplama arasındaki tutarlılık **anlık
değildir**; ikinci işlem çalışana kadar sözleşme eski sayıyı taşır.

Bu, işlem sınırı seçiminden ayrı bir karardır. İşlem sınırının nereye çizileceği ve iki
yazmanın birlikte kesinleşmesi Veri Erişim Katmanı ve İş Mantığı kursundaki İşlem Sınırları
dersinde ölçülmüştü. Tutarlılık sınırı ondan önce gelir: hangi kuralın **anlık** korunması
gerektiğini söyler. İndirim tavanı anlık korunmalıdır, çünkü tavanı aşan bir gönderi bir an
için bile var olmamalıdır. Sözleşme hacminin sayımı anlık korunmak zorunda değildir, çünkü
alan bunu bir mutabakat sorusu olarak görür.

Ölçü bu yüzden iki yönlüdür. Sınır genişletilirse işlem başına toplama sayısı düşer ama
kilitlenen veri büyür; daraltılırsa yazma tek toplamaya iner ama anlık korunmayan kural sayısı
artar. Seçim, hangi kuralın bir an için bile bozulamayacağı sorusunun yanıtıdır.

#### Kökün Kuralları

Ölçümdeki güvenceyi üreten üç kısıt vardır. Sınırın içindeki nesnelere dışarıdan başvuru
verilmez; verilecekse kopya verilir. Sınır dışındaki bir toplamaya kimlikle atıf yapılır,
nesneyle değil. Sınırın içini değiştiren her işlem kökün bir yönteminden geçer.

Üçüncü kısıt yukarıdaki ölçümde doğrudan görünüyor: `indirimler()` bir kopya döndürdüğü için
"diziye ekleme" yolu etkisiz kalıyor, ögeleri dondurulmuş olduğu için "ögenin oranı" yolu hata
veriyor. Kopya olmasa iki yol da değişmezi bozardı.

#### Ne Zaman Uygulanmaz

Kökten geçen erişimin bedeli, sınırın içine bakmak isteyen her okumanın kökten geçmesi ve her
okumada kopya üretilmesidir. Değişmezi olmayan bir bütünde bu bedel karşılıksızdır: yalnız
birlikte okunan, hiçbir kuralı paylaşmayan nesneler için sınır çizmek bir dolaylılık
basamağından fazlasını üretmez.

İkinci durum sınırın büyüklüğüyle ilgilidir. Bir toplama, içindeki nesne sayısı büyüdükçe
pahalanır: her yazma bütünü yükler, her okuma kopya üretir. Sözleşmeye bağlı bütün gönderileri
tek toplamaya koymak indirim tavanını da sözleşme hacmini de anlık korur, karşılığında aynı
sözleşmedeki iki gönderinin aynı anda işlenmesini engeller. Sınırın doğru yeri, anlık
korunması gereken kural kümesinin en küçük kapsayıcısıdır.

#### Özet

- Toplama, bir değişmezi paylaşan nesnelerin bütünüdür; toplama kökü o bütüne dışarıdan
  erişimin geçtiği tek nesnedir.
- Sınır ölçütü tutarlılıktır: bir değişmez, denetimi için gereken bütün verinin bulunduğu en
  küçük sınırın içine konur. İndirim tavanı gönderi ile indirimlerini kapsar, sözleşmeyi
  kapsamaz.
- Sınırı dışa açan modelde beş çağrı yolunun 4'ü tavanı aşan bir gönderi üretti; kökten geçen
  modelde 0'ı üretti.
- Açık modelde bir işlem 2 toplamaya dokundu; kök modelinde aynı iş iki işleme bölündü ve her
  biri 1 toplamaya dokundu.
- Bedel anlık olmayan tutarlılıktır: sözleşmenin hacim sayımı, gönderi yazıldığı anda
  güncellenmiş olmaz.
- Güvenceyi üç kısıt üretir — içeriye başvuru verilmez, sınır dışına kimlikle atıf yapılır,
  değişiklik kökten geçer.

#### Sonraki Adım

Kökün yöntemleri kendi sınırının içindeki veriyle karar veriyor. Kitaplıkta bir de bu kalıba
uymayan bir davranış var: bir gönderiye uygulanacak en uygun tarifenin seçilmesi. Bu karar
gönderiyi, tarife kataloğunu ve müşterinin sözleşmesini birlikte gerektirir; üçünden hiçbiri
ötekilerin sahibi değildir. Kararı gönderiye yüklemek, gönderinin bilmek zorunda olduğu dış
ad sayısını artırır. Sonraki ders bu sayıyı ölçer, davranışın nesneye mi yoksa ayrı bir alan
servisine mi ait olduğunu ortak çalışan sayısıyla belirler ve alan servisini uygulama
servisinden ayıran ölçüyü kurar.

### Alan Servisleri

Source: https://academia.sh/tr/kurslar/alan-odakli-tasarim/alan-servisleri

Nesneye ait olmayan davranışın yerleştirilmesi: en uygun tarife seçiminin gönderiye yüklendiği ve ayrı bir alan servisine verildiği iki modelde gönderi modülünün ithal ettiği modül sayısının, bir gönderiden erişilebilen nesne sayısının ve serileştirme uzunluğunun ölçülmesi, alan servisini uygulama servisinden ayıran kalıcılık izinin sayılması.

Toplama kökünün yöntemleri kendi sınırının içindeki veriyle karar veriyor. Kitaplıkta bu
kalıba uymayan bir davranış var: bir gönderiye uygulanacak en uygun tarifenin seçilmesi. Alan
uzmanı kuralı şöyle söylüyor — uygulanabilir tarifeler arasından en düşük net ücreti veren
seçilir, sözleşme gerektiren tarifeler yalnız o tarifeye yetkili sözleşmesi olan müşteriye
uygulanır.

Karar üç şeyi birlikte gerektiriyor: gönderi, tarife kataloğu ve sözleşme. Üçünden hiçbiri
ötekilerin sahibi değildir. Kendi verisiyle karar veremeyen, birden çok alan nesnesini
birlikte gerektiren ve hiçbirine ait olmayan davranışın konulduğu yere **alan servisi**
(domain service) denir.

#### Ölçüt: Ortak Çalışan Sayısı

Bir davranışın nereye ait olduğu iki soruyla belirlenir. Kararı vermek için kaç alan nesnesi
gerekiyor? Bu nesnelerden biri ötekilerin sahibi mi?

Gönderinin hacimli sayılıp sayılmadığı kararı yalnız gönderinin ağırlığını gerektirir; ortak
çalışan sayısı sıfırdır ve karar gönderinin yöntemidir. Tarife seçimi üç nesne gerektirir ve
sahip yoktur; karar bir alan servisine aittir. Tavan kuralı iki nesne gerektirir — gönderi ve
indirimleri — ama biri ötekinin sahibidir, dolayısıyla önceki dersteki gibi kökün yöntemi
olarak kalır.

#### Davranışı Nesneye Yükleyen Model

İki sürüm de aynı katalogu ve aynı yetki kuralını kullanır.

```sh
mkdir -p ortak yuklu servis
```

```js
// ortak/tarife-katalogu.mjs — tarife katalogu ve bir tarifenin ucret hesabi
export const TARIFELER = [
  { ad: "standart", kademe: [[1, 3900], [5, 6400], [Infinity, 11800]], asgari: 3990, sozlesmeli: false },
  { ad: "yogun-hacim", kademe: [[1, 4200], [5, 5900], [Infinity, 9800]], asgari: 4500, sozlesmeli: true },
  { ad: "bolgesel", kademe: [[1, 3600], [5, 6900], [Infinity, 13200]], asgari: 3600, sozlesmeli: false },
];
const KATSAYI = { "34": 1, "06": 1.35, "65": 1.8 };

export function tarifeUcreti(agirlik, bolge, tarife) {
  const taban = tarife.kademe.find(([ust]) => agirlik <= ust)[1];
  return Math.max(Math.round(taban * (KATSAYI[bolge] ?? 1.8)), tarife.asgari);
}
```

```js
// ortak/sozlesme-kurallari.mjs — sozlesme gerektiren tarifeye yetki kurali
export const yetkiliMi = (sozlesme, tarife) =>
  !tarife.sozlesmeli || (sozlesme?.yetkiliTarifeler ?? []).includes(tarife.ad);
```

Birinci sürüm seçimi gönderinin yöntemi yapar. Yöntem katalogu ve yetki kuralını kendi
modülünde ithal eder, sözleşmeyi de gönderinin içinde tutar.

```js
// yuklu/gonderi.mjs — secim gonderinin yontemi; katalog ve sozlesme gonderinin icinde
import { TARIFELER, tarifeUcreti } from "../ortak/tarife-katalogu.mjs";
import { yetkiliMi } from "../ortak/sozlesme-kurallari.mjs";

export class Gonderi {
  constructor(gonderiNo, agirlik, bolge, sozlesme) {
    this.gonderiNo = gonderiNo;
    this.agirlik = agirlik;
    this.bolge = bolge;
    this.sozlesme = sozlesme;
  }
  enUygunTarife() {
    const uygun = TARIFELER.filter((t) => yetkiliMi(this.sozlesme, t));
    return uygun.reduce((en, t) =>
      tarifeUcreti(this.agirlik, this.bolge, t) < tarifeUcreti(this.agirlik, this.bolge, en)
        ? t : en);
  }
  netUcret() { return tarifeUcreti(this.agirlik, this.bolge, this.enUygunTarife()); }
}
```

Bu sürüm önceki dersin kuralını da bozuyor: gönderi, kendi tutarlılık sınırının dışındaki bir
toplamayı — sözleşmeyi — kimlikle değil nesneyle taşıyor.

#### Davranışı Servise Veren Model

İkinci sürümde gönderi yalnız kendi verisini tutar, sözleşmeye numarasıyla atıf yapar. Seçim
ayrı bir modülde, üç parametreli bir işlev olur.

```js
// servis/gonderi.mjs — gonderi yalniz kendi verisini tasir, sozlesmeye kimlikle atif yapar
export class Gonderi {
  constructor(gonderiNo, agirlik, bolge, sozlesmeNo) {
    this.gonderiNo = gonderiNo;
    this.agirlik = agirlik;
    this.bolge = bolge;
    this.sozlesmeNo = sozlesmeNo;
  }
  hacimliMi() { return this.agirlik >= 5; }
}
```

```js
// servis/tarife-secimi.mjs — alan servisi: ucu de parametre, hicbirinin sahibi degil
import { tarifeUcreti } from "../ortak/tarife-katalogu.mjs";
import { yetkiliMi } from "../ortak/sozlesme-kurallari.mjs";

export function enUygunTarife(gonderi, tarifeler, sozlesme) {
  const uygun = tarifeler.filter((t) => yetkiliMi(sozlesme, t));
  if (uygun.length === 0) throw new RangeError("uygulanabilir tarife yok");
  return uygun.reduce((en, t) =>
    tarifeUcreti(gonderi.agirlik, gonderi.bolge, t)
      < tarifeUcreti(gonderi.agirlik, gonderi.bolge, en) ? t : en);
}

export const netUcret = (gonderi, tarifeler, sozlesme) =>
  tarifeUcreti(gonderi.agirlik, gonderi.bolge, enUygunTarife(gonderi, tarifeler, sozlesme));
```

Alan servisi ile uygulama servisini ayıran çizgi, aynı işi yapan üçüncü dosyada görünür.
Uygulama katmanının sorumluluğu Veri Erişim Katmanı ve İş Mantığı kursundaki Katman
Sorumlulukları dersinde, servis katmanı kalıbı da Tasarım Kalıpları kursunda kurulmuştu;
buradaki soru yalnız hangi bilginin hangi dosyada durduğudur.

```js
// servis/fiyatla.mjs — uygulama servisi: depoyu, saati ve kaydetmeyi burasi bilir
import { netUcret } from "./tarife-secimi.mjs";

export function fiyatla(gonderiNo, depo, saat) {
  const gonderi = depo.gonderiBul(gonderiNo);
  const sozlesme = depo.sozlesmeBul(gonderi.sozlesmeNo);
  const tutar = netUcret(gonderi, depo.tarifeler(), sozlesme);
  depo.kaydet({ gonderiNo, tutar, an: saat() });
  return tutar;
}
```

#### Ölçüm

Dört ölçü alınır: gönderi modülünün ithal ettiği modül sayısı, bir gönderi örneğinden
erişilebilen nesne sayısı, serileştirme uzunluğu ve iki dosyanın kalıcılık izi. Sona altı
gönderide iki modelin aynı tarifeyi ve aynı tutarı verip vermediği eklenir.

```js
// servis-olc.mjs — gonderinin bildigi dis ad, nesne grafigi, kalicilik izi ve esdegerlik
import { readFileSync } from "node:fs";
import { TARIFELER } from "./ortak/tarife-katalogu.mjs";
import { Gonderi as YukluGonderi } from "./yuklu/gonderi.mjs";
import { Gonderi as ServisGonderi } from "./servis/gonderi.mjs";
import { enUygunTarife, netUcret } from "./servis/tarife-secimi.mjs";

const SOZLESMELER = {
  "S-77": { sozlesmeNo: "S-77", yetkiliTarifeler: ["yogun-hacim"] },
  "S-90": { sozlesmeNo: "S-90", yetkiliTarifeler: [] },
};
const GIRDILER = [
  ["G-3001", 0.4, "34", "S-77"], ["G-3002", 3, "06", "S-77"],
  ["G-3003", 12, "65", "S-77"], ["G-3004", 0.9, "34", "S-90"],
  ["G-3005", 7, "06", "S-90"], ["G-3006", 18, "65", "S-90"],
];

const ithal = (d) => (readFileSync(d, "utf8").match(/^import /gm) ?? []).length;
console.log(`gonderi modulunun ithal ettigi modul: yuklu ${ithal("yuklu/gonderi.mjs")}, ` +
  `servis ${ithal("servis/gonderi.mjs")}`);

function erisilebilir(kok) {
  const gorulen = new Set();
  const kuyruk = [kok];
  while (kuyruk.length > 0) {
    const n = kuyruk.pop();
    if (n === null || typeof n !== "object" || gorulen.has(n)) continue;
    gorulen.add(n);
    for (const v of Object.values(n)) kuyruk.push(v);
  }
  return gorulen.size;
}

const y = new YukluGonderi("G-3001", 0.4, "34", SOZLESMELER["S-77"]);
const s = new ServisGonderi("G-3001", 0.4, "34", "S-77");
console.log(`bir gonderiden erisilebilen nesne: yuklu ${erisilebilir(y)}, ` +
  `servis ${erisilebilir(s)}`);
console.log(`serilestirme uzunlugu: yuklu ${JSON.stringify(y).length}, ` +
  `servis ${JSON.stringify(s).length}`);

const IZ = /depo|kaydet|saat/;
for (const d of ["servis/tarife-secimi.mjs", "servis/fiyatla.mjs"]) {
  const satir = readFileSync(d, "utf8").split("\n").filter((l) => IZ.test(l));
  console.log(`${d}: kalicilik izi tasiyan satir ${satir.length}`);
}

let ayrilan = 0;
for (const [no, agirlik, bolge, sozlesmeNo] of GIRDILER) {
  const yg = new YukluGonderi(no, agirlik, bolge, SOZLESMELER[sozlesmeNo]);
  const sg = new ServisGonderi(no, agirlik, bolge, sozlesmeNo);
  const sozlesme = SOZLESMELER[sozlesmeNo];
  const ayni = yg.enUygunTarife().ad === enUygunTarife(sg, TARIFELER, sozlesme).ad
    && yg.netUcret() === netUcret(sg, TARIFELER, sozlesme);
  if (!ayni) ayrilan += 1;
  console.log(`  ${no} ${String(agirlik).padStart(4)} kg ${bolge} ${sozlesmeNo} -> ` +
    `${yg.enUygunTarife().ad.padEnd(12)} ${String(yg.netUcret()).padStart(6)}`);
}
console.log(`ayrilan sonuc = ${ayrilan} / ${GIRDILER.length}`);
```

```sh
node servis-olc.mjs
```

```
gonderi modulunun ithal ettigi modul: yuklu 2, servis 0
bir gonderiden erisilebilen nesne: yuklu 3, servis 1
serilestirme uzunlugu: yuklu 117, servis 69
servis/tarife-secimi.mjs: kalicilik izi tasiyan satir 0
servis/fiyatla.mjs: kalicilik izi tasiyan satir 6
  G-3001  0.4 kg 34 S-77 -> bolgesel       3600
  G-3002    3 kg 06 S-77 -> yogun-hacim    7965
  G-3003   12 kg 65 S-77 -> yogun-hacim   17640
  G-3004  0.9 kg 34 S-90 -> bolgesel       3600
  G-3005    7 kg 06 S-90 -> standart      15930
  G-3006   18 kg 65 S-90 -> standart      21240
ayrilan sonuc = 0 / 6
```

İlk satır davranışı nesneye yüklemenin dolaysız bedelini veriyor: gönderi modülü iki dış
modülü ithal etmek zorunda. Bu, gönderiyi kullanan her yerin dolaylı olarak tarife kataloğunu
ve sözleşme kurallarını da yüklemesi anlamına gelir.

İkinci ve üçüncü satır bedelin nesne düzeyindeki karşılığıdır. Yüklü modelde bir gönderiden
üç nesneye erişilebiliyor — gönderinin kendisi, sözleşme ve sözleşmenin yetki listesi —
servis modelinde bire. Serileştirme uzunluğu 117'den 69'a iniyor; gönderi tek başına
taşındığında sözleşme onunla birlikte gitmiyor.

Dördüncü ölçü alan servisiyle uygulama servisinin çizgisini veriyor. Tarife seçimi dosyasında
depo, kaydetme ve saat sözcüklerini taşıyan satır yok; aynı sözcükler uygulama servisinde altı
satırda geçiyor. Alan servisi kararı verir, kararın nereden okunup nereye yazıldığını
bilmez.

Son satır ölçünün ne olmadığını yine söylüyor: altı gönderinin altısında iki model aynı
tarifeyi seçiyor ve aynı tutarı veriyor.

#### Alan Servisi Ne Değildir

Alan servisi bir kural taşıyıcısıdır, bir iş akışı yürütücüsü değildir. Ayrımın ölçüsü
yukarıdaki dördüncü satırdır: alan servisinde kalıcılık izi sıfır olmalıdır. Bir servis
depodan okuyor, işlem açıyor, saate bakıyor ya da bildirim gönderiyorsa uygulama katmanına
aittir.

İkinci ayrım durumla ilgilidir. Alan servisi kendi durumunu tutmaz; girdisi parametreleriyle
gelir, çıktısı dönüş değeridir. Durum tutan bir "servis", adı ne olursa olsun sınırı belirsiz
bir varlık nesnesidir ve önceki iki dersin ölçüleri onun için geçerlidir.

#### Ne Zaman Alan Servisi Açılmaz

Servisin bedeli bir dosya, bir dışa açılan ad ve her çağrıda geçirilen fazladan
parametrelerdir. Ortak çalışan sayısı bir olan davranışlar için bu bedel karşılıksızdır:
gönderinin hacimli sayılıp sayılmadığı kararı servise taşınırsa çağrı yeri gönderiyi
parametre olarak geçirmek zorunda kalır ve kazanç sıfır olur.

İkinci karşı durum, servis adının bir kavramı örtmesidir. Üç nesne gerektiren bir kararın
aslında dördüncü bir alan kavramı olduğu görülürse — tarife seçiminin bir "fiyatlandırma
politikası" olduğu gibi — o kavram kendi tipini alır ve servis yeniden bir yöntem hâline
gelir. "Servis" eki taşıyan her modül, alan diline karşılık gelmeyen bir kavramı saklıyor
olabilir; ölçü, o modülün adının alan uzmanının cümlesinde geçip geçmediğidir.

#### Özet

- Alan servisi, birden çok alan nesnesini birlikte gerektiren ve hiçbirine ait olmayan
  davranışın konulduğu yerdir; ölçüt ortak çalışan sayısı ve sahiplik sorusudur.
- Tarife seçimi gönderiye yüklendiğinde gönderi modülü 2 dış modül ithal etti; servise
  verildiğinde 0.
- Bir gönderiden erişilebilen nesne 3'ten 1'e, serileştirme uzunluğu 117'den 69'a indi;
  gönderi artık sözleşmeyi nesneyle değil numarasıyla taşıyor.
- Alan servisiyle uygulama servisini ayıran ölçü kalıcılık izidir: tarife seçiminde 0 satır,
  uygulama servisinde 6 satır.
- Altı gönderinin altısında iki model aynı tarifeyi ve aynı tutarı verdi; kazanç doğrulukta
  değil bağımlılık dağılımındadır.
- Ortak çalışan sayısı bir olan davranış servise taşınmaz; servis adı bir alan kavramını
  örtüyorsa o kavram kendi tipini alır.

#### Sonraki Adım

Uygulama servisi gönderiyi bir depodan okudu ve tarifeleri ondan istedi. O deponun arayüzünde
hangi sözcüklerin yazılı olduğu bu derste sorulmadı: `gonderiBul` mu yoksa sütun adları ve
karşılaştırma işleçleri taşıyan bir sorgu nesnesi mi? Aynı soru yaratım için de geçerlidir —
gönderi doğrudan yapıcıyla mı kuruluyor, yoksa değişmezini kuruluş anında güvenceye alan bir
fabrikadan mı geçiyor? Sonraki ders depo arayüzündeki alan dışı ad sayısını ve bir sütun adı
değiştiğinde dokunulan çağrı yeri sayısını ölçer, geçersiz bir gönderinin kaç yoldan
yaratılabildiğini sayar.

### Fabrikalar ve Depolar

Source: https://academia.sh/tr/kurslar/alan-odakli-tasarim/fabrikalar-ve-depolar

Yaratım ve erişimin alan modeline uydurulması: geçersiz bir gönderinin dört yaratım yolundan kaçında üretilebildiğinin sayılması, fabrikadan geçen modelde bu sayının sıfıra inmesi, depo arayüzü sorgu diliyle yazıldığında çağrı yerine sızan sütun adı, işleç ve eşik sayısının ölçülmesi ve iki deponun aynı sonucu verdiğinin gösterilmesi.

Önceki dersin uygulama servisi gönderiyi bir depodan okudu ve tarifeleri ondan istedi. O
deponun arayüzünde hangi sözcüklerin yazılı olduğu sorulmadı. Aynı soru yaratım için de
geçerlidir: gönderi doğrudan yapıcıyla mı kuruluyor, yoksa değişmezini kuruluş anında
güvenceye alan bir yerden mi geçiyor?

**Fabrika** (factory) ve **depo** (repository) kalıpları Tasarım Kalıpları kursunda kuruldu;
biri yaratım sorumluluğunun nereye konduğunu, öteki toplamaya erişimin nereden geçtiğini
söylüyordu. Bu ders kalıpları yeniden anlatmaz. Tek soruyu sorar: ikisi de **alan diliyle mi**
yazılmış? Fabrika için bu, değişmezin kuruluş anında sağlanması demektir; depo için, arayüzde
alan sözcüklerinin bulunması ve sütun adlarının bulunmaması demektir.

#### Geçerlilik Kuralı

İki model de aynı kurala tabi tutulur: gönderi numarası boş olmayan bir katardır, ağırlık
pozitif bir tam sayıdır, bölge kodu iki basamaklıdır.

```sh
mkdir -p ortak sorgu alan
```

```js
// ortak/kural.mjs — iki modelin de tabi oldugu gecerlilik kurali
export const gecerliMi = (g) =>
  typeof g.gonderiNo === "string" && g.gonderiNo.length > 0
  && Number.isInteger(g.agirlikGram) && g.agirlikGram > 0
  && /^\d{2}$/.test(g.bolgeKodu);
```

#### Yaratım: Kural Nerede Uygulanıyor

Birinci sürümde yapıcı dışa açıktır, alanlar yazılabilir ve kural hiçbir yerde uygulanmaz.

```js
// sorgu/gonderi.mjs — yapici disa acik, alanlar yazilabilir, kural kurulus aninda yok
export class Gonderi {
  constructor(gonderiNo, agirlikGram, bolgeKodu, sozlesmeNo = null) {
    this.gonderiNo = gonderiNo;
    this.agirlikGram = agirlikGram;
    this.bolgeKodu = bolgeKodu;
    this.sozlesmeNo = sozlesmeNo;
  }
}

export const kayittanKur = (satir) =>
  new Gonderi(satir.gonderi_no, satir.agirlik_gram, satir.bolge_kodu, satir.sozlesme_no);
```

İkinci sürümde yapıcıya yalnız fabrikanın bildiği bir belirteçle girilir. İki fabrika yöntemi
vardır — biri alan çağrıları için, biri ham kayıttan kurmak için — ve ikisi de aynı kuralı
uygular. Nesne kuruluşta dondurulur.

```js
// alan/gonderi.mjs — fabrika: gecerlilik kurulus aninda saglanir, nesne dondurulur
import { gecerliMi } from "../ortak/kural.mjs";

const FABRIKA = Symbol("fabrika");

export class Gonderi {
  constructor(anahtar, gonderiNo, agirlikGram, bolgeKodu, sozlesmeNo) {
    if (anahtar !== FABRIKA) throw new TypeError("Gonderi yalnizca fabrikadan kurulur");
    this.gonderiNo = gonderiNo;
    this.agirlikGram = agirlikGram;
    this.bolgeKodu = bolgeKodu;
    this.sozlesmeNo = sozlesmeNo;
    Object.freeze(this);
  }
  static olustur(gonderiNo, agirlikGram, bolgeKodu, sozlesmeNo = null) {
    const aday = { gonderiNo, agirlikGram, bolgeKodu, sozlesmeNo };
    if (!gecerliMi(aday)) throw new RangeError(`gecersiz gonderi: ${gonderiNo}`);
    return new Gonderi(FABRIKA, gonderiNo, agirlikGram, bolgeKodu, sozlesmeNo);
  }
  static kayittanKur(satir) {
    return Gonderi.olustur(satir.gonderi_no, satir.agirlik_gram, satir.bolge_kodu,
      satir.sozlesme_no);
  }
}
```

Ölçü, önceki dersteki ulaşılabilirlik ölçüsünün yaratım tarafındaki karşılığıdır: geçersiz bir
gönderi dört yoldan kaçında üretilebiliyor?

```js
// fabrika-olc.mjs — gecersiz bir gonderi kac yoldan yaratilabiliyor
import { gecerliMi } from "./ortak/kural.mjs";
import { Gonderi as SorguGonderi, kayittanKur } from "./sorgu/gonderi.mjs";
import { Gonderi as AlanGonderi } from "./alan/gonderi.mjs";

const BOZUK_SATIR = { gonderi_no: "G-4099", agirlik_gram: -50, bolge_kodu: "3",
  sozlesme_no: null };

const SORGU_YOLLARI = {
  "yapicidan dogrudan": () => new SorguGonderi("", -3, "3"),
  "alanin sonradan atanmasi": () => {
    const g = new SorguGonderi("G-4098", 2400, "34");
    g.agirlikGram = -3;
    return g;
  },
  "toplu atama": () => Object.assign(new SorguGonderi("G-4098", 2400, "34"),
    { bolgeKodu: "abc" }),
  "ham kayittan kurma": () => kayittanKur(BOZUK_SATIR),
};
const ALAN_YOLLARI = {
  "yapicidan dogrudan": () => new AlanGonderi("", -3, "3"),
  "alanin sonradan atanmasi": () => {
    const g = AlanGonderi.olustur("G-4098", 2400, "34");
    g.agirlikGram = -3;
    return g;
  },
  "toplu atama": () => Object.assign(AlanGonderi.olustur("G-4098", 2400, "34"),
    { bolgeKodu: "abc" }),
  "ham kayittan kurma": () => AlanGonderi.kayittanKur(BOZUK_SATIR),
};

for (const [ad, yollar] of Object.entries({ sorgu: SORGU_YOLLARI, alan: ALAN_YOLLARI })) {
  let gecersiz = 0;
  let hata = 0;
  for (const [yol, dene] of Object.entries(yollar)) {
    let sonuc;
    try {
      const g = dene();
      sonuc = gecerliMi(g) ? "gecerli" : "GECERSIZ NESNE";
      if (sonuc !== "gecerli") gecersiz += 1;
    } catch {
      sonuc = "hata";
      hata += 1;
    }
    console.log(`  ${ad.padEnd(6)} ${yol.padEnd(26)} ${sonuc}`);
  }
  console.log(`${ad}: 4 yoldan ${gecersiz} tanesi gecersiz nesne uretti, ${hata} tanesi hata verdi`);
}
```

```sh
node fabrika-olc.mjs
```

```
  sorgu  yapicidan dogrudan         GECERSIZ NESNE
  sorgu  alanin sonradan atanmasi   GECERSIZ NESNE
  sorgu  toplu atama                GECERSIZ NESNE
  sorgu  ham kayittan kurma         GECERSIZ NESNE
sorgu: 4 yoldan 4 tanesi gecersiz nesne uretti, 0 tanesi hata verdi
  alan   yapicidan dogrudan         hata
  alan   alanin sonradan atanmasi   hata
  alan   toplu atama                hata
  alan   ham kayittan kurma         hata
alan: 4 yoldan 0 tanesi gecersiz nesne uretti, 4 tanesi hata verdi
```

Dört yolun dördü birinci modelde geçersiz bir gönderi üretiyor ve hiçbiri uyarı vermiyor.
Fabrikadan geçen modelde dördü de hata veriyor. Kazanç kuralın kaç kez yazıldığı değildir —
kural iki modelde de bir kez yazılabilirdi — kuralı **atlayan bir yolun kalmamasıdır**.

#### Erişim: Depo Hangi Dili Konuşuyor

Kalıcılık tarafından gelen ham satırlar sütun adlarını taşır.

```js
// ortak/satirlar.mjs — kalicilik tarafindan gelen ham satirlar
export const SATIRLAR = [
  { gonderi_no: "G-4001", agirlik_gram: 2400, bolge_kodu: "34", sozlesme_no: "S-77" },
  { gonderi_no: "G-4002", agirlik_gram: 7200, bolge_kodu: "06", sozlesme_no: null },
  { gonderi_no: "G-4003", agirlik_gram: 400, bolge_kodu: "65", sozlesme_no: "S-77" },
  { gonderi_no: "G-4004", agirlik_gram: 12000, bolge_kodu: "34", sozlesme_no: null },
  { gonderi_no: "G-4005", agirlik_gram: 5600, bolge_kodu: "06", sozlesme_no: "S-77" },
];
```

Birinci depo genel bir sorgu arayüzü verir: koşulu çağıran taraf kurar ve ham satır alır.

```js
// sorgu/depo.mjs — sorgu diliyle yazilmis depo: kosulu cagiran taraf kurar
const KARS = {
  "=": (a, b) => a === b,
  ">": (a, b) => a > b,
  "<": (a, b) => a < b,
};

export const depo = (satirlar) => ({
  bul: (kosul) => satirlar.filter((s) => kosul.every(([alan, isl, d]) => KARS[isl](s[alan], d))),
});
```

```js
// sorgu/cagrilar.mjs — sutun adlari, isleciler ve esikler cagri yerinde yazili
export const agirGonderiler = (d) => d.bul([["agirlik_gram", ">", 5000]]);
export const bolgedekiler = (d, bolge) => d.bul([["bolge_kodu", "=", bolge]]);
export const sozlesmesizAgirlar = (d) =>
  d.bul([["agirlik_gram", ">", 5000], ["sozlesme_no", "=", null]]);
export const bolgeAgirliklari = (d, bolge) =>
  d.bul([["bolge_kodu", "=", bolge]]).map((s) => s.agirlik_gram);
export const hafifSozlesmeliler = (d) =>
  d.bul([["agirlik_gram", "<", 1000], ["sozlesme_no", "=", "S-77"]]);
```

İkinci depo alan sözcüklerini yöntem adı olarak verir, ham satırı fabrikadan geçirir ve alan
nesnesi döndürür. "Ağır" ile "hafif" eşiklerinin ne olduğu da burada durur.

```js
// alan/depo.mjs — alan diliyle yazilmis depo: sutun adi, esik ve islec burada kalir
import { Gonderi } from "./gonderi.mjs";

const AGIR_SINIR_GRAM = 5000;
const HAFIF_SINIR_GRAM = 1000;
const kur = (satirlar) => satirlar.map((s) => Gonderi.kayittanKur(s));

export const depo = (satirlar) => ({
  agirGonderiler: () => kur(satirlar.filter((s) => s.agirlik_gram > AGIR_SINIR_GRAM)),
  bolgedekiler: (bolge) => kur(satirlar.filter((s) => s.bolge_kodu === bolge)),
  sozlesmesizAgirlar: () =>
    kur(satirlar.filter((s) => s.agirlik_gram > AGIR_SINIR_GRAM && s.sozlesme_no === null)),
  hafifSozlesmeliler: (sozlesmeNo) => kur(satirlar.filter((s) =>
    s.agirlik_gram < HAFIF_SINIR_GRAM && s.sozlesme_no === sozlesmeNo)),
});
```

```js
// alan/cagrilar.mjs — cagri yerinde yalniz alan sozcukleri var
export const agirGonderiler = (d) => d.agirGonderiler();
export const bolgedekiler = (d, bolge) => d.bolgedekiler(bolge);
export const sozlesmesizAgirlar = (d) => d.sozlesmesizAgirlar();
export const bolgeAgirliklari = (d, bolge) => d.bolgedekiler(bolge).map((g) => g.agirlikGram);
export const hafifSozlesmeliler = (d) => d.hafifSozlesmeliler("S-77");
```

```js
// depo-olc.mjs — sutun adi ve islec cagri yerine siziyor mu, iki depo ayni sonucu veriyor mu
import { readFileSync } from "node:fs";
import { SATIRLAR } from "./ortak/satirlar.mjs";
import { depo as sorguDepo } from "./sorgu/depo.mjs";
import { depo as alanDepo } from "./alan/depo.mjs";
import * as sorguCagri from "./sorgu/cagrilar.mjs";
import * as alanCagri from "./alan/cagrilar.mjs";

const SUTUN = /agirlik_gram|bolge_kodu|sozlesme_no/g;
const ISLEC = /"[=<>]"/g;
const ESIK = /5000|1000/g;
const MODEL = {
  sorgu: { cagri: "sorgu/cagrilar.mjs", sinir: ["sorgu/depo.mjs"] },
  alan: { cagri: "alan/cagrilar.mjs", sinir: ["alan/depo.mjs", "alan/gonderi.mjs"] },
};

for (const [ad, { cagri, sinir }] of Object.entries(MODEL)) {
  const metin = readFileSync(cagri, "utf8");
  const say = (r) => (metin.match(r) ?? []).length;
  const sinirda = sinir.map((d) => (readFileSync(d, "utf8").match(SUTUN) ?? []).length)
    .reduce((a, b) => a + b, 0);
  console.log(`${ad}: cagri yerinde sutun adi ${say(SUTUN)}, islec ${say(ISLEC)}, ` +
    `esik ${say(ESIK)}`);
  console.log(`  sinir dosyalarinda sutun adi ${sinirda} [${sinir.join(" ")}]`);
}

const s = sorguDepo(SATIRLAR);
const a = alanDepo(SATIRLAR);
const ad = (liste) => liste.map((x) => x.gonderi_no ?? x.gonderiNo ?? x).join(",");
let ayrilan = 0;
for (const [islem, x, y] of [
  ["agirGonderiler", sorguCagri.agirGonderiler(s), alanCagri.agirGonderiler(a)],
  ["bolgedekiler", sorguCagri.bolgedekiler(s, "34"), alanCagri.bolgedekiler(a, "34")],
  ["sozlesmesizAgirlar", sorguCagri.sozlesmesizAgirlar(s), alanCagri.sozlesmesizAgirlar(a)],
  ["bolgeAgirliklari", sorguCagri.bolgeAgirliklari(s, "06"), alanCagri.bolgeAgirliklari(a, "06")],
  ["hafifSozlesmeliler", sorguCagri.hafifSozlesmeliler(s), alanCagri.hafifSozlesmeliler(a)],
]) {
  if (ad(x) !== ad(y)) ayrilan += 1;
  console.log(`  ${islem.padEnd(20)} ${ad(x)}`);
}
console.log(`ayrilan sonuc = ${ayrilan} / 5`);
```

```sh
node depo-olc.mjs
```

```
sorgu: cagri yerinde sutun adi 8, islec 7, esik 3
  sinir dosyalarinda sutun adi 0 [sorgu/depo.mjs]
alan: cagri yerinde sutun adi 0, islec 0, esik 0
  sinir dosyalarinda sutun adi 9 [alan/depo.mjs alan/gonderi.mjs]
  agirGonderiler       G-4002,G-4004,G-4005
  bolgedekiler         G-4001,G-4004
  sozlesmesizAgirlar   G-4002,G-4004
  bolgeAgirliklari     7200,5600
  hafifSozlesmeliler   G-4003
ayrilan sonuc = 0 / 5
```

Sorgu diliyle yazılmış depoda çağrı yeri sekiz sütun adı, yedi karşılaştırma işleci ve üç eşik
sayısı taşıyor. Alan diliyle yazılmış depoda üçü de sıfır. Sütun adları yok olmadı; dokuz
geçişle iki sınır dosyasına toplandı.

Toplanmanın karşılığı üç ayrı karar cinsindedir. Sütun adı bir kalıcılık kararıdır; işleç bir
sorgu kararıdır; "ağır" eşiği bir **alan** kararıdır. Sorgu modelinde üçü de aynı satırda,
uygulama katmanının içinde duruyor. Eşiğin 5000 gramdan 4000 grama inmesi orada iki çağrı
yerine dokunmayı gerektirir ve o iki yer alan sözlüğünde "ağır" diye bir kavram bulunmadığı
için aranamaz. Alan modelinde aynı değişiklik `AGIR_SINIR_GRAM` sabitine dokunur.

Son satır ölçünün ne olmadığını yine söylüyor: beş sorgunun beşinde iki depo aynı gönderileri
döndürüyor.

#### Deponun Alan Diliyle Yazılmasının Sınırı

Alan diliyle yazılmış deponun bedeli yöntem sayısıdır. Her yeni alan sorusu yeni bir yöntem
açar ve arayüz büyür; genel sorgu arayüzünde arayüz tek yöntemde kalır, büyüyen şey çağrı
yerlerindeki koşullardır.

Ölçü bu yüzden soruların **tekrarına** bakar. Aynı soru birden çok yerden soruluyorsa
yöntemleşmesi kazanç üretir. Yalnız bir yerden sorulan, bir daha sorulmayacak sorularda —
bir kereye mahsus bir mutabakat raporu gibi — genel arayüz daha ucuzdur; sorgu nesneleri ve
belirtimler kalıbı Veri Erişim Katmanı ve İş Mantığı kursunda tam bu durum için kurulmuştu.

İkinci sınır fabrikadadır. Fabrikanın kazancı, atlanacak yol bırakmamasından gelir; bu ise
yapıcıya erişimi kapatmayı ve nesneyi dondurmayı gerektirir. Değişmezi olmayan bir nesnede
bu bedelin karşılığı yoktur: dondurmak her güncellemede yeni nesne üretmek demektir ve kuralı
olmayan bir nesnede korunacak bir şey de yoktur.

#### Özet

- Fabrika ve depo bu derste kalıp olarak değil, alan diline uyup uymadıklarıyla ele alındı.
- Yapıcının dışa açık olduğu modelde geçersiz bir gönderi dört yaratım yolunun 4'ünde üretildi
  ve hiçbiri uyarı vermedi; fabrikadan geçen modelde 0'ında üretildi, dördü hata verdi.
- Fabrikanın kazancı kuralın kaç kez yazıldığı değil, kuralı atlayan yolun kalmamasıdır.
- Sorgu diliyle yazılmış deponun çağrı yeri 8 sütun adı, 7 işleç ve 3 eşik taşıyordu; alan
  diliyle yazılmış deponun çağrı yerinde üçü de 0, sütun adları iki sınır dosyasında toplandı.
- Sorgu modelinde kalıcılık, sorgu ve alan kararları aynı satırda karışıyor; "ağır" eşiği bir
  alan kararı olduğu hâlde alan sözlüğünde adı bulunmuyor.
- Beş sorgunun beşinde iki depo aynı sonucu verdi; alan diliyle yazılmış deponun bedeli
  arayüzün soru sayısıyla büyümesidir.

#### Sonraki Adım

Buraya kadarki bütün ölçüler modelin **durumu** üzerineydi: hangi ad hangi kavramı gösteriyor,
hangi değişmez hangi sınırın içinde, nesne hangi yolla kuruluyor. Alanda bir de olup bitenler
var: bir gönderi yeniden fiyatlandırıldı, sözleşme indirimi kaldırıldı, tarife değiştiği için
tutar yeniden hesaplandı. Bu olguların kodda bir adı olabilir ya da yalnız "kayıt güncellendi"
diye geçebilirler. Sonraki ders bir olay kütüğü üzerinde alan uzmanının sorularını yanıtlamayı
dener: olay adları alan dilinden geldiğinde kaç soru tek süzmeyle yanıtlanıyor, geldiği yerde
kaç olayın alanları karşılaştırılmak zorunda kalıyor.

### Alan Olayları

Source: https://academia.sh/tr/kurslar/alan-odakli-tasarim/alan-olaylari

Anlamlı iş olaylarının açık modeli: aynı altmış olgudan üretilen iki kütükte alan uzmanının üç sorusunun kaçının tek süzmeyle doğru yanıtlandığının ölçülmesi, yanıt için kaç olayın alanlarının karşılaştırılmak zorunda kaldığının sayılması, olay adlarının alan sözlüğünde bulunma oranının ve kütük uzunluğunun karşılaştırılması.

Buraya kadarki bütün ölçüler modelin durumu üzerineydi: hangi ad hangi kavramı gösteriyor,
hangi değişmez hangi sınırın içinde, nesne hangi yolla kuruluyor. Alanda bir de olup bitenler
var — bir gönderi fiyatlandı, sözleşme indirimi kaldırıldı, tarife değiştiği için tutar
yeniden hesaplandı. Bu olguların kodda kendi adları olabilir ya da hepsi "kayıt güncellendi"
diye geçebilir.

Alanda olup biten, geçmiş zamanda anlatılan ve alan dilinde bir adı olan olguya **alan olayı**
(domain event) denir. Olayın yayımlanması, dinleyicilere ulaştırılması ve işlemle birlikte
kesinleşmesi Veri Erişim Katmanı ve İş Mantığı kursundaki Alan Olayları dersinde kurulmuş ve
ölçülmüştü. Bu ders o tarafa girmez. Tek soruyu sorar: olayın **adı** nereden geliyor?

#### Ölçünün Kurulumu

Ölçü için bir günün ücretlendirme geçmişi altmış olgu olarak yazılır. Her olgunun bir türü,
bir gönderi numarası ve öncesi–sonrası görüntüsü vardır. İki kütük de bu tek kaynaktan
üretilir; aradaki fark yalnız neyin saklandığıdır.

```sh
mkdir -p genel alan
```

```js
// olgular.mjs — bir gunun ucretlendirme gecmisi: 60 olgu, iki kutugun de kaynagi
const OLGU = ["gonderi-fiyatlandi", "sozlesme-indirimi-kaldirildi", "hacim-indirimi-eklendi",
  "tartim-duzeltmesi-uygulandi", "tarife-degistigi-icin-yeniden-fiyatlandi",
  "asgari-ucret-uygulandi"];
export const ASGARI_UCRET = 3990;

export const OLGULAR = [];
for (let i = 0; i < 60; i += 1) {
  const olgu = OLGU[i % 6];
  const gonderiNo = `G-5${101 + (i % 20)}`;
  const t = 4200 + (i % 5) * 900;
  let oncesi;
  let sonrasi;
  if (olgu === "gonderi-fiyatlandi") {
    oncesi = { tutar: 0, tarife: null, indirimler: [] };
    sonrasi = { tutar: t, tarife: "standart", indirimler: [] };
  } else if (olgu === "sozlesme-indirimi-kaldirildi") {
    oncesi = { tutar: t, tarife: "standart", indirimler: ["sozlesme", "hacim"] };
    sonrasi = { tutar: t + 600, tarife: "standart", indirimler: ["hacim"] };
  } else if (olgu === "hacim-indirimi-eklendi") {
    oncesi = { tutar: t, tarife: "standart", indirimler: ["sozlesme"] };
    sonrasi = { tutar: t - 400, tarife: "standart", indirimler: ["sozlesme", "hacim"] };
  } else if (olgu === "tartim-duzeltmesi-uygulandi") {
    oncesi = { tutar: t, tarife: "standart", indirimler: ["hacim"] };
    sonrasi = { tutar: i % 4 === 3 ? ASGARI_UCRET : t + 250, tarife: "standart",
      indirimler: ["hacim"] };
  } else if (olgu === "tarife-degistigi-icin-yeniden-fiyatlandi") {
    oncesi = { tutar: t, tarife: "standart", indirimler: ["hacim"] };
    sonrasi = { tutar: t - 700, tarife: "bolgesel", indirimler: ["hacim"] };
  } else {
    oncesi = { tutar: t, tarife: "bolgesel", indirimler: ["hacim"] };
    sonrasi = { tutar: ASGARI_UCRET, tarife: "bolgesel", indirimler: ["hacim"] };
  }
  OLGULAR.push({ sira: i + 1, olgu, gonderiNo, oncesi, sonrasi });
}
```

Birinci kütük olayın adını iki genel addan birine indirir ve olup biteni önce–sonra
görüntüsüyle anlatır. Bu, kalıcılık tarafından bakıldığında en doğal seçimdir: her yazma bir
güncellemedir.

```js
// genel/kutuk.mjs — her olgu ayni iki addan birini alir, yuk oncesi/sonrasi goruntusudur
import { OLGULAR } from "../olgular.mjs";

export const KUTUK = OLGULAR.map((o) => ({
  tur: o.oncesi.tarife === null ? "GonderiOlusturuldu" : "GonderiGuncellendi",
  gonderiNo: o.gonderiNo,
  oncesi: o.oncesi,
  sonrasi: o.sonrasi,
}));
```

İkinci kütük olayın adını alan dilinden alır. Görüntüye ihtiyaç kalmaz: ad neyin olduğunu
söyler, yük yalnız o adın gerektirdiği kadardır.

```js
// alan/kutuk.mjs — olayin adi alan dilinden gelir, yuk o adin gerektirdigi kadardir
import { OLGULAR } from "../olgular.mjs";

const AD = {
  "gonderi-fiyatlandi": "GonderiFiyatlandi",
  "sozlesme-indirimi-kaldirildi": "SozlesmeIndirimiKaldirildi",
  "hacim-indirimi-eklendi": "HacimIndirimiEklendi",
  "tartim-duzeltmesi-uygulandi": "TartimDuzeltmesiUygulandi",
  "tarife-degistigi-icin-yeniden-fiyatlandi": "TarifeDegistigiIcinYenidenFiyatlandi",
  "asgari-ucret-uygulandi": "AsgariUcretUygulandi",
};

export const KUTUK = OLGULAR.map((o) => ({
  tur: AD[o.olgu],
  gonderiNo: o.gonderiNo,
  tutar: o.sonrasi.tutar,
}));
```

#### Ölçüm

Alan uzmanı kütüğe üç soru soruyor: kaç gönderi sözleşme indirimini sonradan kaybetti, tarife
değiştiği için kaç kez yeniden fiyatlama yapıldı, kaç kez asgari ücret devreye girdi. Her soru
iki kütükte de tek bir süzmeyle yanıtlanmaya çalışılır ve yanıt gerçek sayıyla karşılaştırılır.

```js
// olay-olc.mjs — uzmanin uc sorusu iki kutukte kac dogru yanit ve kac karsilastirma istiyor
import { OLGULAR, ASGARI_UCRET } from "./olgular.mjs";
import { KUTUK as GENEL } from "./genel/kutuk.mjs";
import { KUTUK as ALAN } from "./alan/kutuk.mjs";

const gercek = (olgu) => OLGULAR.filter((o) => o.olgu === olgu).length;
const ind = (g) => g?.indirimler ?? [];

const SORULAR = [
  ["sozlesme indirimini kaybeden gonderi", "sozlesme-indirimi-kaldirildi",
    (k) => k.filter((e) => ind(e.oncesi).includes("sozlesme")
      && !ind(e.sonrasi).includes("sozlesme")).length,
    (k) => k.filter((e) => e.tur === "SozlesmeIndirimiKaldirildi").length],
  ["tarife degistigi icin yeniden fiyatlama", "tarife-degistigi-icin-yeniden-fiyatlandi",
    (k) => k.filter((e) => e.oncesi.tarife !== e.sonrasi.tarife).length,
    (k) => k.filter((e) => e.tur === "TarifeDegistigiIcinYenidenFiyatlandi").length],
  ["asgari ucretin devreye girmesi", "asgari-ucret-uygulandi",
    (k) => k.filter((e) => e.sonrasi.tutar === ASGARI_UCRET).length,
    (k) => k.filter((e) => e.tur === "AsgariUcretUygulandi").length],
];

let genelDogru = 0;
let alanDogru = 0;
for (const [soru, olgu, genelYanit, alanYanit] of SORULAR) {
  const d = gercek(olgu);
  const g = genelYanit(GENEL);
  const a = alanYanit(ALAN);
  if (g === d) genelDogru += 1;
  if (a === d) alanDogru += 1;
  console.log(`${soru}`);
  console.log(`  gercek ${d}  genel kutuk ${g} ${g === d ? "dogru" : "YANLIS"}  ` +
    `alan kutugu ${a} ${a === d ? "dogru" : "YANLIS"}`);
}
console.log(`tek suzmeyle dogru yanit: genel ${genelDogru}/3, alan ${alanDogru}/3`);

const goruntulu = (k) => k.filter((e) => "oncesi" in e).length;
console.log(`3 soru icin alan karsilastirilan olay: genel ${goruntulu(GENEL) * 3}, ` +
  `alan ${goruntulu(ALAN) * 3}`);

// SOZLUK: alan uzmaninin cumlelerinde gecen sozcukler
const SOZLUK = ["gonderi", "tarife", "bolge", "agirlik", "hacim", "indirim", "sozlesme",
  "tartim", "duzeltme", "asgari", "ucret", "fiyatlandi", "eklendi", "kaldirildi",
  "uygulandi", "yeniden", "degis", "icin"];
const parcala = (t) => t.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase().split(" ");
for (const [ad, k] of Object.entries({ genel: GENEL, alan: ALAN })) {
  const turler = [...new Set(k.map((e) => e.tur))];
  const alanli = turler.filter((t) =>
    parcala(t).every((p) => SOZLUK.some((s) => p.startsWith(s))));
  console.log(`${ad}: olay turu ${turler.length}, adinin tamami alan sozlugunde olan ` +
    `${alanli.length}/${turler.length}`);
  console.log(`  ${turler.join(" ")}`);
}
console.log(`kutuk uzunlugu: genel ${JSON.stringify(GENEL).length}, ` +
  `alan ${JSON.stringify(ALAN).length}`);
```

```sh
node olay-olc.mjs
```

```
sozlesme indirimini kaybeden gonderi
  gercek 10  genel kutuk 10 dogru  alan kutugu 10 dogru
tarife degistigi icin yeniden fiyatlama
  gercek 10  genel kutuk 20 YANLIS  alan kutugu 10 dogru
asgari ucretin devreye girmesi
  gercek 10  genel kutuk 15 YANLIS  alan kutugu 10 dogru
tek suzmeyle dogru yanit: genel 1/3, alan 3/3
3 soru icin alan karsilastirilan olay: genel 180, alan 0
genel: olay turu 2, adinin tamami alan sozlugunde olan 0/2
  GonderiOlusturuldu GonderiGuncellendi
alan: olay turu 6, adinin tamami alan sozlugunde olan 6/6
  GonderiFiyatlandi SozlesmeIndirimiKaldirildi HacimIndirimiEklendi TartimDuzeltmesiUygulandi TarifeDegistigiIcinYenidenFiyatlandi AsgariUcretUygulandi
kutuk uzunlugu: genel 11121, alan 4141
```

Üç sorunun yalnız birinde genel kütük doğru sayıyı veriyor. İkinci soruda yirmi diyor, oysa
tarife değişikliği yüzünden yapılan yeniden fiyatlama on kez olmuş; fark, ilk fiyatlamanın da
tarife alanını boştan doluya çevirmesinden geliyor. Üçüncü soruda on beş diyor, oysa asgari
ücret on kez devreye girmiş; beş olayda tutar tartım düzeltmesi sonucu asgari ücretle eşitlenmiş
ama kural uygulanmamış.

Bu iki yanlış, sorgunun düzeltilmesiyle giderilebilir: ilk fiyatlamayı dışarıda bırakan bir
koşul, asgari ücret eşitliğini başka bir alanla doğrulayan bir koşul yazılabilir. Ölçünün
gösterdiği şey bu düzeltmelerin **nereye** yazıldığıdır. Her düzeltme, kütükte bulunmayan bir
alan bilgisini sorgunun içine gömer ve kütükten sınanamaz. Alan kütüğünde aynı üç soru tür
adına bakan tek bir süzmeyle yanıtlanıyor, üçü de doğru çıkıyor.

İkinci satır bedeli işlem cinsinden veriyor: üç soruyu yanıtlamak için genel kütükte yüz seksen
olayın alanları karşılaştırılıyor, alan kütüğünde hiçbiri. Üçüncü ölçü adların kaynağını
gösteriyor: genel kütükteki iki türün adı alan sözlüğünde yok — "oluşturuldu" ve "güncellendi"
uzmanın kullandığı sözcükler değil, kalıcılık işlemlerinin adları. Alan kütüğündeki altı türün
altısı alan sözlüğünden geliyor.

Son satır bir yan kazançtır: adın taşıdığı bilgi, görüntünün taşıdığı bilgiden kısadır. Kütük
uzunluğu 11121 karakterden 4141 karaktere iniyor.

#### Olayın Adı Ne Söyler

Alan olayının adı üç şeyi birlikte taşır: neyin olduğu, hangi kavramda olduğu ve olmuş bitmiş
olduğu. `SozlesmeIndirimiKaldirildi` adı bunların üçünü de veriyor. Ad geçmiş zamandadır çünkü
olay bir istek değildir; olmasını istemek başka bir kavramdır ve başka bir adı vardır.

Adın alan diline ait olmasının ölçülebilir sonucu, uzmanın kütüğü doğrulayabilmesidir. Alan
kütüğündeki altı tür adı uzmanın önüne konduğunda uzman eksik olanı söyleyebilir: "iade
ücretinin hesaplanması da bir olgudur, listede yok." Genel kütükteki iki ad önüne konduğunda
söyleyecek bir şeyi olmaz, çünkü o adlar onun dilinde değildir.

Olayın yükü de aynı ölçütten geçer. `TarifeDegistigiIcinYenidenFiyatlandi` olayının yükünde
yeni tutarın bulunması alanla ilgilidir; kaydın veritabanı satır kimliğinin bulunması değildir.
Kütüğe yük eklendiğinde sorulan soru, o alanın uzmanın cümlesinde geçip geçmediğidir.

#### Ne Zaman Genel Ad Yeterlidir

Alan olaylarının bedeli tür sayısıdır. Altı tür, altı ad, altı süzme koşulu ve zaman içinde
altı ayrı sürüm demektir; genel kütükte iki ad vardır ve yeni bir olgu türü hiçbir şey
eklemez.

Bu bedelin karşılıksız kaldığı durum, kütüğe alan sorusu sorulmadığı durumdur. Yalnız
"bu kayda en son ne zaman dokunuldu" sorusu için tutulan bir iz kaydında önce–sonra görüntüsü
yeterlidir ve tür ayrımı kazanç üretmez. Ayrım ölçütü kütüğün boyutu da değildir — kütüğe
sorulan alan sorusunun sayısıdır.

İkinci durum, olgunun alanda bir adı olmamasıdır. Uzmanın "bu bir şey değil, kaydın
düzeltilmesi" dediği değişiklikler için alan olayı adı uydurmak, sözlüğe alanda karşılığı
olmayan bir kavram eklemek olur; ikinci dersin ölçüsü bunu ad başına kavram sayısı olarak
sayar.

#### Özet

- Alan olayı, alanda olup biten ve alan dilinde bir adı olan olgudur; bu ders yalnız adın
  nereden geldiğini ölçtü, yayım ve ulaştırma tarafına girmedi.
- Aynı altmış olgudan üretilen iki kütükte uzmanın üç sorusundan genel kütük 1'ini, alan
  kütüğü 3'ünü tek süzmeyle doğru yanıtladı.
- Genel kütüğün iki yanlış yanıtı düzeltilebilir, ama her düzeltme kütükte bulunmayan bir alan
  bilgisini sorgunun içine gömer ve kütükten sınanamaz hâle getirir.
- Üç soruyu yanıtlamak genel kütükte 180 olayın alanlarının karşılaştırılmasını gerektirdi,
  alan kütüğünde 0.
- Genel kütükteki 2 tür adının 0'ı, alan kütüğündeki 6 tür adının 6'sı alan sözlüğünden
  geliyor; kütük uzunluğu 11121'den 4141 karaktere indi.
- Bedel tür sayısıdır; kütüğe alan sorusu sorulmuyorsa ya da olgunun alanda bir adı yoksa
  genel ad yeterlidir.

#### Sonraki Adım

Bu konudaki dersler kimliği, değeri, sınırı, davranışın yerini, yaratımı ve olayı tek tek
ölçtü. Hepsinin ortak varsayımı, alan nesnesinin bir davranışı olduğuydu. Yaygın bir düzen bu
varsayımı kaldırır: alan sınıfları yalnız alan taşır, bütün kurallar servislere yazılır ve
sınıflar bir veri biçiminden fazlası olmaz. Sonraki ders bu düzeni alan modeli bağlamında
ölçer: aynı kuralın kaç serviste tekrarlandığını, bir kuralın metni değiştiğinde dokunulan
dosya sayısını ve uzmanın cümlesindeki fiillerin kaçının alan nesnesinde bir yöntem olarak
bulunduğunu sayar.

### Kansız Model Eleştirisi

Source: https://academia.sh/tr/kurslar/alan-odakli-tasarim/kansiz-model-elestirisi

Davranışsız veri sınıflarının alan modeli bağlamında ölçülmesi: beş kuralın kaç ayrı serviste yazıldığının ve beş kural için kaç dosya düzenlemesi gerektiğinin sayılması, aynı kuralı yazan servislerin 36 gönderide kaç kez ayrı sonuç verdiğinin sayılması ve alan uzmanının fiillerinden kaçının gönderiye sorulabildiğinin karşılaştırılması.

Bu konudaki dersler kimliği, değeri, tutarlılık sınırını, davranışın yerini, yaratımı ve olayı
tek tek ölçtü. Hepsinin ortak varsayımı, alan nesnesinin bir davranışı olduğuydu. Yaygın bir
düzen bu varsayımı kaldırır: alan sınıfları yalnız alan taşır, bütün kurallar servislere
yazılır ve sınıf bir veri biçiminden fazlası olmaz.

Alan taşıyıp kural taşımayan bu düzene **anemik model** (anemic model) denir; dersin
başlığındaki "kansız model" aynı düzenin katalogdaki adıdır ve gövdede tek biçim olarak
anemik model kullanılır. Düzenin nesne sorumluluğu açısından eleştirisi Programlama
Paradigmaları kursundaki Nesne Sorumluluğu dersinde yapıldı ve burada tekrarlanmaz. Bu ders
aynı düzeni alan modeli bağlamında ölçer: bir kural kaç ayrı serviste yazılıdır, beş kural kaç
dosya düzenlemesi ister, aynı kuralı yazan servisler aynı gönderide aynı sonuca varır mı,
uzmanın cümlesindeki fiillerin kaçı alan nesnesine sorulabilir.

#### Beş Kural ve Üç Servis

Ücretlendirme uzmanının bir cümlesi beş kural taşıyor: ücrete esas ağırlık gerçek ağırlıkla
hacimden türetilen ağırlığın büyüğüdür, sözleşmesi olan müşteride sözleşme indirimi
uygulanır, sözleşme ve hacim indirimlerinin toplamı indirim tavanını aşamaz, ücret asgari
ücretin altına inmez, beyan değeri olan gönderiye sigorta primi eklenir.

Kitaplıkta bu kuralları kullanan üç servis var: fatura satırı, dönem sonu mutabakatı ve toplu
fiyatlama listesi. Ölçüm verisi otuz altı gönderi kaydıdır; on ikisinde sözleşme numarası boş
olduğu hâlde bir indirim oranı yazılıdır ve ikinci kuralın gözlenebilmesi buna bağlıdır.

```sh
mkdir -p anemik alan
```

```js
// veri.mjs — ucretlendirme baglaminin tarife tablosu ve 36 gonderi kaydi
export const TARIFE = {
  standart: { yakin: 1800, orta: 2400, uzak: 3200 },
  agir: { yakin: 1400, orta: 1900, uzak: 2600 },
};

export const KAYITLAR = [];
let i = 0;
for (const [agirlikGram, hacimDesimetreKup] of [[700, 2], [2400, 9], [6000, 18], [18000, 60]])
  for (const [sozlesmeNo, sozlesmeIndirimi] of [[null, 0.22], ["S-77", 0.22], ["S-12", 0.3]])
    for (const hacimIndirimi of [0, 0.12, 0.2]) {
      i += 1;
      KAYITLAR.push({
        gonderiNo: `G-6${100 + i}`, agirlikGram, hacimDesimetreKup,
        ucretBolgesi: ["yakin", "orta", "uzak"][i % 3],
        tarifeKodu: agirlikGram >= 10000 ? "agir" : "standart",
        beyanDegeriKurus: i % 3 === 0 ? 0 : 60000 * (1 + (i % 4)),
        sozlesmeNo, sozlesmeIndirimi, hacimIndirimi,
      });
    }
```

#### Anemik Düzen

Birinci düzende gönderi kaydı olduğu gibi taşınır; hiçbir kural onunla birlikte durmaz.

```js
// anemik/gonderi.mjs — yalniz alan tasir; hicbir kural burada degil
export const gonderi = (k) => ({ ...k });
```

Fatura servisi beş kuralın beşini de kendi içinde yazar.

```js
// anemik/fatura.mjs — bes kuralin besini de kendisi yazar
export const faturaSatiri = (g, tarife) => {
  const esasGram = Math.max(g.agirlikGram, g.hacimDesimetreKup * 200);
  const sozlesme = g.sozlesmeNo === null ? 0 : g.sozlesmeIndirimi;
  const indirim = Math.min(0.4, sozlesme + g.hacimIndirimi);
  const ham = Math.round((esasGram / 1000) * tarife[g.tarifeKodu][g.ucretBolgesi] * (1 - indirim));
  const ucret = Math.max(3990, ham);
  const prim = Math.round(g.beyanDegeriKurus * 0.002);
  return { esasGram, sozlesme, indirim, asgari: ucret > ham, prim, tutar: ucret + prim };
};
```

Mutabakat servisi aynı beş kuralı yazar, tavanı yalnız sözleşme indirimine uygular.

```js
// anemik/mutabakat.mjs — ayni bes kural; tavan yalniz sozlesme indirimine uygulanmis
export const mutabakatSatiri = (g, tarife) => {
  const esasGram = Math.max(g.agirlikGram, g.hacimDesimetreKup * 200);
  const sozlesme = g.sozlesmeNo === null ? 0 : g.sozlesmeIndirimi;
  const indirim = Math.min(0.4, sozlesme) + g.hacimIndirimi;
  const ham = Math.round((esasGram / 1000) * tarife[g.tarifeKodu][g.ucretBolgesi] * (1 - indirim));
  const ucret = Math.max(3990, ham);
  const prim = Math.round(g.beyanDegeriKurus * 0.002);
  return { esasGram, sozlesme, indirim, asgari: ucret > ham, prim, tutar: ucret + prim };
};
```

Toplu fiyatlama servisi sigorta primini hesaplamaz ve indirim oranını kayıttan olduğu gibi
alır; sözleşme koşulu bu dosyada yok.

```js
// anemik/toplu-fiyatlama.mjs — ayni kurallar bir kez daha; sozlesme kosulu yazilmamis
export const listeFiyati = (g, tarife) => {
  const esasGram = Math.max(g.agirlikGram, g.hacimDesimetreKup * 200);
  const sozlesme = g.sozlesmeIndirimi;
  const indirim = Math.min(0.4, sozlesme + g.hacimIndirimi);
  const ham = Math.round((esasGram / 1000) * tarife[g.tarifeKodu][g.ucretBolgesi] * (1 - indirim));
  const ucret = Math.max(3990, ham);
  return { esasGram, sozlesme, indirim, asgari: ucret > ham,
    kilogramFiyati: Math.round(ucret / (esasGram / 1000)) };
};
```

#### Davranışı Taşıyan Model

İkinci düzende beş kural gönderinin kendisindedir; alanlar kapalıdır, dışa yalnız kural
sonuçları açılır.

```js
// alan/gonderi.mjs — bes kural gonderide; disa yalniz kural sonuclari acilir
const HACIM_KATSAYISI = 200;
const INDIRIM_TAVANI = 0.4;
const ASGARI_UCRET_KURUS = 3990;
const SIGORTA_ORANI = 0.002;

export const gonderi = (k) => {
  const esasAgirlikGram = () => Math.max(k.agirlikGram, k.hacimDesimetreKup * HACIM_KATSAYISI);
  const sozlesmeIndirimOrani = () => (k.sozlesmeNo === null ? 0 : k.sozlesmeIndirimi);
  const uygulanacakIndirimOrani = () =>
    Math.min(INDIRIM_TAVANI, sozlesmeIndirimOrani() + k.hacimIndirimi);
  const ham = (tarife) => Math.round((esasAgirlikGram() / 1000)
    * tarife[k.tarifeKodu][k.ucretBolgesi] * (1 - uygulanacakIndirimOrani()));
  const ucretKurus = (tarife) => Math.max(ASGARI_UCRET_KURUS, ham(tarife));
  const sigortaPrimiKurus = () => Math.round(k.beyanDegeriKurus * SIGORTA_ORANI);
  return Object.freeze({
    gonderiNo: k.gonderiNo,
    esasAgirlikGram, sozlesmeIndirimOrani, uygulanacakIndirimOrani, ucretKurus,
    sigortaPrimiKurus,
    asgariUcretUygulandiMi: (tarife) => ucretKurus(tarife) > ham(tarife),
    tutarKurus: (tarife) => ucretKurus(tarife) + sigortaPrimiKurus(),
  });
};
```

Aynı üç servis artık hesaplamaz, sorar.

```js
// alan/fatura.mjs — kural yazmaz, gonderiye sorar
export const faturaSatiri = (g, tarife) => ({
  esasGram: g.esasAgirlikGram(), sozlesme: g.sozlesmeIndirimOrani(),
  indirim: g.uygulanacakIndirimOrani(), asgari: g.asgariUcretUygulandiMi(tarife),
  prim: g.sigortaPrimiKurus(), tutar: g.tutarKurus(tarife),
});
```

```js
// alan/mutabakat.mjs — tavani kendisi yorumlamaz, uygulanacak orani alir
export const mutabakatSatiri = (g, tarife) => ({
  esasGram: g.esasAgirlikGram(), sozlesme: g.sozlesmeIndirimOrani(),
  indirim: g.uygulanacakIndirimOrani(), asgari: g.asgariUcretUygulandiMi(tarife),
  prim: g.sigortaPrimiKurus(), tutar: g.tutarKurus(tarife),
});
```

```js
// alan/toplu-fiyatlama.mjs — kilogram fiyati kendi isi, kurallar gonderinin
export const listeFiyati = (g, tarife) => ({
  esasGram: g.esasAgirlikGram(), sozlesme: g.sozlesmeIndirimOrani(),
  indirim: g.uygulanacakIndirimOrani(), asgari: g.asgariUcretUygulandiMi(tarife),
  kilogramFiyati: Math.round(g.ucretKurus(tarife) / (g.esasAgirlikGram() / 1000)),
});
```

#### Kural Kaç Dosyada Yazılı

Birinci ölçü her kuralı iki dizinde arar ve kaç dosyada göründüğünü sayar. Kalıplar kuralı hem
sayı değeriyle hem adlandırılmış sabitle bulur; adı değişen bir sabit taramadan kaçmaz. Aynı
betik uzmanın beş fiilinden kaçının gönderiye tek çağrıyla sorulabildiğini de sayar.

```js
// kural-olc.mjs — kural kac dosyada yazili, bes kural kac duzenleme ister, fiil nereye sorulur
import { readdirSync, readFileSync } from "node:fs";
import { KAYITLAR } from "./veri.mjs";
import { gonderi } from "./anemik/gonderi.mjs";
import { gonderi as alanGonderi } from "./alan/gonderi.mjs";

const KURAL = [
  ["ucrete esas agirlik", /hacimDesimetreKup \* (?:200|HACIM_KATSAYISI)/],
  ["sozlesme indirimi", /sozlesmeNo === null \? 0/],
  ["indirim tavani", /Math\.min\((?:0\.4|INDIRIM_TAVANI)/],
  ["asgari ucret", /Math\.max\((?:3990|ASGARI_UCRET_KURUS)/],
  ["sigorta primi", /\* (?:0\.002|SIGORTA_ORANI)/],
];

for (const dizin of ["anemik", "alan"]) {
  const dosyalar = readdirSync(dizin).sort().map((d) => [d, readFileSync(`${dizin}/${d}`, "utf8")]);
  let toplam = 0;
  console.log(`${dizin} (${dosyalar.length} dosya)`);
  for (const [ad, kalip] of KURAL) {
    const yerler = dosyalar.filter(([, icerik]) => kalip.test(icerik)).map(([d]) => d);
    toplam += yerler.length;
    console.log(`  ${ad.padEnd(20)} yazili dosya ${yerler.length}  ${yerler.join(" ")}`);
  }
  console.log(`  5 kural icin toplam dosya duzenlemesi = ${toplam}`);
}

const FIIL = [
  ["ucrete esas agirligi bulur", "esasAgirlikGram"],
  ["sozlesme indirimini uygular", "sozlesmeIndirimOrani"],
  ["indirim tavanini asmaz", "uygulanacakIndirimOrani"],
  ["asgari ucretin altina inmez", "ucretKurus"],
  ["sigorta primini ekler", "sigortaPrimiKurus"],
];
for (const [ad, nesne] of [["anemik", gonderi(KAYITLAR[0])], ["alan", alanGonderi(KAYITLAR[0])]]) {
  const sorulabilen = FIIL.filter(([, yontem]) => typeof nesne[yontem] === "function");
  console.log(`${ad}: uzmanin ${FIIL.length} fiilinden ${sorulabilen.length}'i gonderiye sorulabiliyor`);
}
```

```sh
node kural-olc.mjs
```

```
anemik (4 dosya)
  ucrete esas agirlik  yazili dosya 3  fatura.mjs mutabakat.mjs toplu-fiyatlama.mjs
  sozlesme indirimi    yazili dosya 2  fatura.mjs mutabakat.mjs
  indirim tavani       yazili dosya 3  fatura.mjs mutabakat.mjs toplu-fiyatlama.mjs
  asgari ucret         yazili dosya 3  fatura.mjs mutabakat.mjs toplu-fiyatlama.mjs
  sigorta primi        yazili dosya 2  fatura.mjs mutabakat.mjs
  5 kural icin toplam dosya duzenlemesi = 13
alan (4 dosya)
  ucrete esas agirlik  yazili dosya 1  gonderi.mjs
  sozlesme indirimi    yazili dosya 1  gonderi.mjs
  indirim tavani       yazili dosya 1  gonderi.mjs
  asgari ucret         yazili dosya 1  gonderi.mjs
  sigorta primi        yazili dosya 1  gonderi.mjs
  5 kural icin toplam dosya duzenlemesi = 5
anemik: uzmanin 5 fiilinden 0'i gonderiye sorulabiliyor
alan: uzmanin 5 fiilinden 5'i gonderiye sorulabiliyor
```

Anemik düzende beş kural on üç dosya düzenlemesiyle yazılmış, alan modelinde beşle. Bir kuralın
metni değiştiğinde anemik düzende iki ya da üç dosya, alan modelinde bir dosya düzenlenir.

Sözleşme indirimi satırındaki iki, bir kazanç değildir. Toplu fiyatlama servisi de indirim
uyguluyor, dolayısıyla sözleşme koşulunu uygulamak zorundadır; gereken sayı on üç değil on
dörttür. Kuralın kaç yerde yazıldığını sayan bir ölçü, bir yerde hiç yazılmamış olmasını
yinelemenin azlığından ayırt edemez: eksik yer bir yokluk olarak değil, düşük bir sayı olarak
görünür. Bunu ancak sonucu karşılaştıran bir ölçü ayırt eder.

Son iki satır aynı soruyu dil tarafından sorar. Uzmanın beş fiili anemik gönderiye
sorulamıyor, çünkü o nesnenin hiçbir yöntemi yoktur. Fiillerin karşılığı servislerin içindeki
üç satırdır ve o satırların alan sözlüğünde bir adı yoktur.

#### Servisler Ayrıştığında

İkinci ölçü sonuca bakar. Her kuralın bir gözlenebilir çıktısı vardır: esas ağırlık, uygulanan
sözleşme oranı, uygulanacak indirim oranı, asgari ücretin devreye girip girmediği ve sigorta
primi. Her gönderi için o kuralı yazan servislerin ürettiği değerler karşılaştırılır ve birden
çok değer çıkan gönderiler sayılır.

```js
// ayrisma-olc.mjs — ayni kurali yazan servisler 36 gonderide kac kez ayri sonuc veriyor
import { TARIFE, KAYITLAR } from "./veri.mjs";
import { gonderi } from "./anemik/gonderi.mjs";
import { faturaSatiri as aFatura } from "./anemik/fatura.mjs";
import { mutabakatSatiri as aMutabakat } from "./anemik/mutabakat.mjs";
import { listeFiyati as aListe } from "./anemik/toplu-fiyatlama.mjs";
import { gonderi as alanGonderi } from "./alan/gonderi.mjs";
import { faturaSatiri as sFatura } from "./alan/fatura.mjs";
import { mutabakatSatiri as sMutabakat } from "./alan/mutabakat.mjs";
import { listeFiyati as sListe } from "./alan/toplu-fiyatlama.mjs";

const KURAL = [["ucrete esas agirlik", "esasGram"], ["sozlesme indirimi", "sozlesme"],
  ["indirim tavani", "indirim"], ["asgari ucret", "asgari"], ["sigorta primi", "prim"]];
const N = KAYITLAR.length;

const olc = (ad, servisler, kur) => {
  const sonuc = KAYITLAR.map((k) => servisler.map((f) => f(kur(k), TARIFE)));
  const ayrisanGonderi = new Set();
  console.log(ad);
  for (const [kuralAd, alan] of KURAL) {
    const yazan = sonuc[0].filter((r) => alan in r).length;
    let ayrisan = 0;
    KAYITLAR.forEach((k, i) => {
      const d = new Set(sonuc[i].filter((r) => alan in r).map((r) => String(r[alan])));
      if (d.size > 1) { ayrisan += 1; ayrisanGonderi.add(k.gonderiNo); }
    });
    console.log(`  ${kuralAd.padEnd(20)} yazan servis ${yazan}  ayrisan gonderi ${ayrisan}/${N}`);
  }
  console.log(`  en az bir kuralda ayrisan gonderi = ${ayrisanGonderi.size}/${N}`);
  return sonuc;
};

const A = olc("anemik", [aFatura, aMutabakat, aListe], (k) => gonderi(k));
olc("alan", [sFatura, sMutabakat, sListe], (k) => alanGonderi(k));

const ayri = KAYITLAR.map((_, i) => i).filter((i) => A[i][0].indirim !== A[i][1].indirim);
console.log(`fatura ile mutabakatin indirim orani ayrilan gonderi = ${ayri.length}/${N}, ilk ucu:`);
for (const i of ayri.slice(0, 3)) {
  console.log(`  ${KAYITLAR[i].gonderiNo}  fatura ${A[i][0].indirim.toFixed(2)} / ${A[i][0].tutar}` +
    `   mutabakat ${A[i][1].indirim.toFixed(2)} / ${A[i][1].tutar}`);
}
const gizli = ayri.filter((i) => A[i][0].tutar === A[i][1].tutar).length;
console.log(`indirim orani ayrildigi halde tutari ayni cikan gonderi = ${gizli}/${ayri.length}`);

let fark = 0;
for (const k of KAYITLAR) {
  if (aFatura(gonderi(k), TARIFE).tutar !== sFatura(alanGonderi(k), TARIFE).tutar) fark += 1;
}
console.log(`fatura tutari iki modelde ayrilan gonderi = ${fark}/${N}`);
```

```sh
node ayrisma-olc.mjs
```

```
anemik
  ucrete esas agirlik  yazan servis 3  ayrisan gonderi 0/36
  sozlesme indirimi    yazan servis 3  ayrisan gonderi 12/36
  indirim tavani       yazan servis 3  ayrisan gonderi 24/36
  asgari ucret         yazan servis 3  ayrisan gonderi 0/36
  sigorta primi        yazan servis 2  ayrisan gonderi 0/36
  en az bir kuralda ayrisan gonderi = 24/36
alan
  ucrete esas agirlik  yazan servis 3  ayrisan gonderi 0/36
  sozlesme indirimi    yazan servis 3  ayrisan gonderi 0/36
  indirim tavani       yazan servis 3  ayrisan gonderi 0/36
  asgari ucret         yazan servis 3  ayrisan gonderi 0/36
  sigorta primi        yazan servis 2  ayrisan gonderi 0/36
  en az bir kuralda ayrisan gonderi = 0/36
fatura ile mutabakatin indirim orani ayrilan gonderi = 12/36, ilk ucu:
  G-6106  fatura 0.40 / 3990   mutabakat 0.42 / 3990
  G-6108  fatura 0.40 / 4110   mutabakat 0.42 / 4110
  G-6109  fatura 0.40 / 3990   mutabakat 0.50 / 3990
indirim orani ayrildigi halde tutari ayni cikan gonderi = 5/12
fatura tutari iki modelde ayrilan gonderi = 0/36
```

Otuz altı gönderinin yirmi dördünde üç servis en az bir kuralda ayrı sonuç veriyor. İki kaynak
var. Sözleşme indirimi satırındaki on iki, taramada eksik görünen yerin bedelidir: sözleşme
numarası boş olan on iki gönderide toplu fiyatlama yirmi iki puanlık sözleşme indirimini
uygularken öteki iki servis onu sıfır sayıyor. İndirim tavanı satırındaki yirmi dört bu on ikiyi de
içeriyor — sözleşme oranı ayrıldığında ona bağlı olan tavan da ayrılıyor — kalan on iki gönderi
mutabakatın tavanı yalnız sözleşme indirimine uygulamasından geliyor.

Örnek satırlar ayrışmanın niçin fark edilmediğini gösteriyor. `G-6106` gönderisinde fatura
yüzde kırk indirim uyguluyor, mutabakat yüzde kırk iki, iki tutar da 3990 çıkıyor. Ayrılan on
iki gönderinin beşinde tutar aynı, çünkü asgari ücret kuralı iki hesabı aynı tabana oturtuyor.
Ayrışma kalkmadı, görünmez oldu: fark yalnız yedi gönderide dışa vuruyor.

Alan modelinde beş kuralın beşinde ayrışan gönderi sayısı sıfır; üç servis aynı yönteme
soruyor ve ayrışacak ikinci bir yazım yoktur. Son satır ölçünün ne olmadığını söylüyor: doğru
yazılmış anemik servis ile alan modeli aynı fatura tutarını veriyor. Kazanç hesabın değişmesi
değildir.

#### Kuralı Ortak Bir Modüle Çıkarmak

Anemik düzenin bilinen düzeltmesi kuralları ortak bir yardımcı modüle çıkarmaktır. Bu, birinci
ölçüyü alan modelininkine eşitler: her kural bir dosyada yazılı olur. İkinci ölçüde ise
yardımcı modül tek başına bir şey vaat etmez. Yardımcı işlev alanları parametre olarak alır,
yani hangi değerin verileceğine çağıran servis karar verir; toplu fiyatlamanın ayrışması tam
bu karardan doğdu — kuralın metnini yanlış yazmadı, kurala yanlış oranı verdi. Ölçünün ayırt
ettiği şey buradan çıkar: kuralın kaç dosyada yazıldığı değil, girdisine kimin karar verdiği.
Alan nesnesine sorulduğunda girdi nesnenin kendi alanıdır ve servisin verecek bir şeyi yoktur.

#### Anemik Sınıfın Yerinde Olduğu Yer

Alan taşıyan davranışsız kaydın yerinde olduğu durum Nesne Sorumluluğu dersinde ölçülmüştü:
bir sınırı geçen kayıtların işi biçimi korumaktır, kural yorumlamak değil. Bu ders o sınırın
üzerine alan modeline özgü iki ayrım ekler.

Birincisi kuralın kime ait olduğudur. Beş kuralın beşi de tek bir gönderinin alanlarıyla
yanıtlanabiliyordu; bu yüzden gönderiye taşındılar. İki toplamayı birlikte gerektiren bir
kural — bir sözleşmenin dönem hacminin indirimi etkilemesi gibi — gönderiye taşınmaz, Alan
Servisleri dersinde kurulan alan servisine gider. Anemik modelin karşıtı "bütün kurallar
nesnenin içinde" değildir.

İkincisi bedeldir: gönderi beş yöntem kazandı ve tarifeyi parametre olarak almak zorunda
kaldı. Ölçüt kural sayısındadır — yorumlanmayan alanı taşıyan kayıt davranışsız kalabilir,
yorumlanan alanın kuralı onunla birlikte durmalıdır.

#### Özet

- Anemik model, alan taşıyıp kural taşımayan düzendir; bu ders onu alan modeli bağlamında
  ölçtü, nesne sorumluluğu tartışmasına girmedi.
- Beş kural anemik düzende 13 dosya düzenlemesiyle yazılıydı, alan modelinde 5'le; bir kuralın
  değişmesi anemik düzende 2–3 dosyaya, alan modelinde 1 dosyaya dokunuyor.
- Sözleşme indirimi satırındaki 2 kazanç değil eksiklikti: gereken 14'tü, taramada eksik yer
  düşük bir sayı olarak göründü.
- Aynı kuralı yazan 3 servis 36 gönderinin 24'ünde en az bir kuralda ayrı sonuç verdi; alan
  modelinde beş kuralın beşinde ayrışan gönderi 0 çıktı.
- Fatura ile mutabakatın indirim oranı 12 gönderide ayrıldı, bunların 5'inde asgari ücret
  kuralı tutarları eşitlediği için ayrışma dışa vurmadı.
- Uzmanın 5 fiilinden 0'ı anemik gönderiye, 5'i alan gönderisine tek çağrıyla sorulabiliyordu;
  iki modelin fatura tutarı 36 gönderide 0 kez ayrıldı.

#### Sonraki Adım

Bu konu boyunca tek bir modelin iç düzeni kuruldu: kimlik hangi nesnede, hangi değişmez hangi
sınırın içinde, davranış nereye ait, nesne hangi yoldan yaratılır, olayın adı nereden gelir.
Her karar modelin alanla hizasıyla ölçüldü. Sorulmayan tek soru şudur: bu, **hangi alanın**
modeli? Gönderi ücretlendirme ve yönlendirme kitaplığında `gonderi` sözcüğü iki takımın
ağzında iki farklı nesneyi adlandırıyor — ücretlendirme için ağırlık, hacim, bölge ve beyan
değeri taşıyan fiyatlanabilir bir birim, teslimat operasyonu için rotası ve durum zaman
damgaları olan fiziksel bir paket. Sözcük aynı olduğu için tek bir sınıf yazılır ve o sınıf
iki işi birden yapmaya zorlanır. Sonraki konu bu zorlamanın bedelini iki sayıyla ölçer:
sınıfın taşıdığı alan sayısı ve hiçbir kullanımda birlikte geçmeyen alan çiftlerinin sayısı.

## Bağlamlar

Bir modelin geçerli olduğu alanın sınırının çizilmesi ve bağlamlar arası ilişkilerin haritalanması: aynı sözcüğün iki bağlamda iki farklı nesneyi adlandırması, bağlam haritasındaki ilişki türleri, dış modelin sızmasının yozlaşma önleyici katmanla engellenmesi, paylaşılan çekirdek ile uyum sağlayan işbirliği kalıpları ve çekirdek, destekleyici ve genel alt alanların ayrılması.

### Sınırlı Bağlam

Source: https://academia.sh/tr/kurslar/alan-odakli-tasarim/sinirli-baglam

Bir modelin geçerli olduğu alanın sınırı: aynı sözcüğün iki anlamı tek sınıfta birleştirildiğinde sınıfın taşıdığı alan sayısının ve hiçbir kullanımda birlikte geçmeyen alan çiftlerinin sayılması, iki bağlamın değişmezlerinin tek sınıfta kaç nesneyi reddettiğinin ölçülmesi ve sınırın çizilmesinin ücreti değiştirmediğinin gösterilmesi.

Alan Modeli konusu anemik model eleştirisiyle kapandı. O konu boyunca tek bir modelin iç
düzeni kuruldu — kimlik hangi nesnede, değişmez hangi sınırın içinde, davranış nereye ait —
ve her karar modelin alanla hizasıyla ölçüldü. Sorulmayan tek soru şuydu: bu, **hangi
alanın** modeli?

Soru boş değil. Gönderi ücretlendirme ve yönlendirme kitaplığında `gonderi` sözcüğü iki
takımın ağzında iki farklı nesneyi adlandırıyor. Ücretlendirme takımı için gönderi; ağırlık,
hacim, bölge ve beyan değeri taşıyan fiyatlanabilir bir birimdir. Teslimat operasyonu takımı
için gönderi; rotası, aktarma noktaları ve durum zaman damgaları olan fiziksel bir pakettir.
Sözcük aynı olduğu için tek bir sınıf yazılır ve o sınıf iki işi birden yapmaya zorlanır. Bu
ders o zorlamanın bedelini iki sayıyla ölçer: sınıfın taşıdığı alan sayısı ve hiçbir
kullanımda birlikte geçmeyen alan çiftlerinin sayısı.

#### Sınırlı Bağlam Bir Dil Sınırıdır

Bir modelin ve o modelin dilinin geçerli olduğu alana **sınırlı bağlam** (bounded context)
denir. Sınırın içinde her ad tek bir şeyi gösterir; dışında aynı ad başka bir şeyi
gösterebilir ve bu bir tutarsızlık değildir.

Tasarım İlkeleri kursundaki modül, bileşen ve politika–ayrıntı sınırları, sınırın nereye
çizileceğini değişim sıklığı ve birlikte değişme ile yanıtlıyordu; sınırlı bağlam ise bir
**dil sınırıdır** ve bir adın anlamının değiştiği yerden geçer, birlikte paketlenen dosya
kümesinden değil. İki bağlam aynı bileşende de durabilir; ayrımı yapan şey paketleme değil,
sözlüktür.

Her Yerde Aynı Dil dersinde kurulan kural burada bir koşul kazanır: dilin her yerde aynı
olması, bir bağlamın içinde her yerde aynı olması anlamına gelir. İki takımın sözlüğü
karşılaştırıldığında ortaklık sanıldığından azdır.

| Sözcük | Ücretlendirme bağlamı | Teslimat operasyonu bağlamı |
|---|---|---|
| `gonderi` | ağırlık, hacim, bölge ve beyan değeri taşıyan fiyatlanabilir birim | rota, aktarma sayısı ve durum geçmişi taşıyan fiziksel paket |
| `bolge` | ücret katsayısını veren tarife bölgesi | karşılığı yok; yerine dağıtım merkezi kodu var |
| `tarife` | kademe ve katsayı tablosu | karşılığı yok |
| `rota` | karşılığı yok | çıkış, aktarma ve dağıtım noktalarının dizisi |
| `teslimat` | karşılığı yok | durum ve zaman damgası kaydı |

Beş sözcükten yalnız biri iki bağlamda da geçiyor ve o bir sözcük iki farklı nesneyi
adlandırıyor. Aynı alanın iki düzenlenişi bu tablodan çıkar: sözcüğü tek sınıfta birleştiren
düzen ve sınırı sözcüğün anlamının değiştiği yerden geçiren düzen.

#### Tek Sınıfta Birleştirilmiş Düzen

Birinci düzende iki sözlük tek sınıfta toplanıyor. Sınıf her iki tarafın alanlarını, her iki
tarafın davranışını ve her iki tarafın değişmezlerini taşıyor. Değişmezler `dogrula`
işlevinde toplanmış; ücretlendirme tarafından beşi `U`, teslimat tarafından beşi `T`
önekiyle işaretli.

```sh
mkdir -p birlesik ucretlendirme teslimat
```

```js
// birlesik/gonderi.mjs — iki baglamin alanlarini ve degismezlerini tek sinifta tasiyan gonderi
const BOLGELER = ["yakin", "orta", "uzak"];
const DURUMLAR = ["kabul", "aktarmada", "dagitimda", "teslim-edildi"];
const BOS = {
  gonderiNo: null, agirlikGram: null, hacimDesimetreKup: null, ucretBolgesi: null,
  beyanDegeriKurus: null, tarifeKodu: null, sozlesmeNo: null, indirimOrani: null,
  tasiyiciKodu: null, rotaNoktalari: null, aktarmaSayisi: null, sonDurum: null,
  durumGecmisi: null, teslimAlanAd: null, teslimZamani: null,
};

export class Gonderi {
  constructor(v) { Object.assign(this, BOS, v); }
  ucret(tarife) {
    const esasGram = Math.max(this.agirlikGram, this.hacimDesimetreKup * 200);
    const birim = tarife[this.tarifeKodu][this.ucretBolgesi];
    return Math.round((esasGram / 1000) * birim * (1 - this.indirimOrani));
  }
  teslimIsaretle(ad, zaman) {
    this.teslimAlanAd = ad; this.teslimZamani = zaman; this.sonDurum = "teslim-edildi";
    this.durumGecmisi = [...this.durumGecmisi, ["teslim-edildi", zaman]];
  }
  dogrula() {
    const ihlal = [];
    if (!(Number.isInteger(this.agirlikGram) && this.agirlikGram > 0)) ihlal.push("U1-agirlik");
    if (!(this.hacimDesimetreKup > 0)) ihlal.push("U2-hacim");
    if (!BOLGELER.includes(this.ucretBolgesi)) ihlal.push("U3-bolge");
    if (!Number.isInteger(this.beyanDegeriKurus)) ihlal.push("U4-beyan");
    if (!(this.indirimOrani >= 0 && (this.indirimOrani === 0 || this.sozlesmeNo !== null))) ihlal.push("U5-indirim");
    if (!(this.rotaNoktalari?.length >= 2)) ihlal.push("T1-rota");
    if (this.aktarmaSayisi !== (this.rotaNoktalari?.length ?? 0) - 2) ihlal.push("T2-aktarma");
    if (!DURUMLAR.includes(this.sonDurum)) ihlal.push("T3-durum");
    if (this.durumGecmisi?.at(-1)?.[0] !== this.sonDurum) ihlal.push("T4-gecmis");
    if (this.sonDurum === "teslim-edildi" && this.teslimAlanAd === null) ihlal.push("T5-teslim");
    return ihlal;
  }
}
```

Sınıfın kurucusundaki `BOS` nesnesi dikkate değer: her alanın varsayılanı `null`. Bu bir
gerçekleştirim ayrıntısı değil, birleştirmenin zorunlu sonucudur. Ücretlendirme kaydında rota
yoktur, teslimat kaydında beyan değeri yoktur; tek sınıf ikisini de kabul edecekse her alanı
yokluğa açmak zorundadır.

#### Sınırın Çizildiği Düzen

İkinci düzende sınır sözcüğün anlamının değiştiği yerden geçiyor. İki dizin, iki `Gonderi`
sınıfı, iki değişmez kümesi. Aynı adın iki dosyada bulunması bir yineleme değildir; iki
farklı nesnenin iki farklı bağlamdaki adıdır.

```js
// ucretlendirme/gonderi.mjs — ucretlendirme baglaminda gonderi: fiyatlanabilir birim
const BOLGELER = ["yakin", "orta", "uzak"];

export class Gonderi {
  constructor(v) {
    Object.assign(this, { sozlesmeNo: null, indirimOrani: 0 }, v);
  }
  ucret(tarife) {
    const esasGram = Math.max(this.agirlikGram, this.hacimDesimetreKup * 200);
    const birim = tarife[this.tarifeKodu][this.ucretBolgesi];
    return Math.round((esasGram / 1000) * birim * (1 - this.indirimOrani));
  }
  dogrula() {
    const ihlal = [];
    if (!(Number.isInteger(this.agirlikGram) && this.agirlikGram > 0)) ihlal.push("U1-agirlik");
    if (!(this.hacimDesimetreKup > 0)) ihlal.push("U2-hacim");
    if (!BOLGELER.includes(this.ucretBolgesi)) ihlal.push("U3-bolge");
    if (!Number.isInteger(this.beyanDegeriKurus)) ihlal.push("U4-beyan");
    if (!(this.indirimOrani >= 0 && (this.indirimOrani === 0 || this.sozlesmeNo !== null))) ihlal.push("U5-indirim");
    return ihlal;
  }
}
```

```js
// teslimat/gonderi.mjs — teslimat operasyonu baglaminda gonderi: fiziksel paket
const DURUMLAR = ["kabul", "aktarmada", "dagitimda", "teslim-edildi"];

export class Gonderi {
  constructor(v) {
    Object.assign(this, { teslimAlanAd: null, teslimZamani: null }, v);
  }
  teslimIsaretle(ad, zaman) {
    this.teslimAlanAd = ad; this.teslimZamani = zaman; this.sonDurum = "teslim-edildi";
    this.durumGecmisi = [...this.durumGecmisi, ["teslim-edildi", zaman]];
  }
  dogrula() {
    const ihlal = [];
    if (!(this.rotaNoktalari?.length >= 2)) ihlal.push("T1-rota");
    if (this.aktarmaSayisi !== (this.rotaNoktalari?.length ?? 0) - 2) ihlal.push("T2-aktarma");
    if (!DURUMLAR.includes(this.sonDurum)) ihlal.push("T3-durum");
    if (this.durumGecmisi?.at(-1)?.[0] !== this.sonDurum) ihlal.push("T4-gecmis");
    if (this.sonDurum === "teslim-edildi" && this.teslimAlanAd === null) ihlal.push("T5-teslim");
    return ihlal;
  }
}
```

İki sınıfın karşılaştırılabilmesi için ölçüm verisi gerekiyor: iki takımın kendi
kayıtlarından aynı üç gönderi ve ücretlendirmenin tarife tablosu.

```js
// veri.mjs — iki takimin kendi kayitlarindan ayni uc gonderi ve ortak tarife
export const TARIFE = {
  standart: { yakin: 1800, orta: 2400, uzak: 3200 },
  agir: { yakin: 1400, orta: 1900, uzak: 2600 },
};

export const UCRET_SATIRLARI = [
  { gonderiNo: "G-1041", agirlikGram: 2400, hacimDesimetreKup: 9, ucretBolgesi: "orta",
    beyanDegeriKurus: 180000, tarifeKodu: "standart", sozlesmeNo: "S-77", indirimOrani: 0.12 },
  { gonderiNo: "G-1042", agirlikGram: 700, hacimDesimetreKup: 2, ucretBolgesi: "yakin",
    beyanDegeriKurus: 0, tarifeKodu: "standart", sozlesmeNo: null, indirimOrani: 0 },
  { gonderiNo: "G-1043", agirlikGram: 18000, hacimDesimetreKup: 60, ucretBolgesi: "uzak",
    beyanDegeriKurus: 950000, tarifeKodu: "agir", sozlesmeNo: "S-12", indirimOrani: 0.2 },
];

export const TESLIMAT_SATIRLARI = [
  { gonderiNo: "G-1041", tasiyiciKodu: "T-A", rotaNoktalari: ["34-mrk", "06-akt", "06-dgt"],
    aktarmaSayisi: 1, sonDurum: "dagitimda",
    durumGecmisi: [["kabul", 1], ["aktarmada", 2], ["dagitimda", 3]] },
  { gonderiNo: "G-1042", tasiyiciKodu: "T-B", rotaNoktalari: ["34-mrk", "34-dgt"],
    aktarmaSayisi: 0, sonDurum: "kabul", durumGecmisi: [["kabul", 1]] },
  { gonderiNo: "G-1043", tasiyiciKodu: "T-A", rotaNoktalari: ["34-mrk", "35-akt", "65-akt", "65-dgt"],
    aktarmaSayisi: 2, sonDurum: "aktarmada", durumGecmisi: [["kabul", 1], ["aktarmada", 2]] },
];
```

#### Alanların ve Alan Çiftlerinin Sayılması

Birinci ölçü sınıfın taşıdığı alan sayısıdır. İkinci ölçü daha ayırt edicidir: iki alan
hiçbir kullanımda birlikte okunmuyorsa aynı sınıfta durmalarının gerekçesi yoktur. Aşağıdaki
betik altı kullanım senaryosunu ve her senaryonun okuduğu alanları taşıyor, sonra her alan
çiftinin en az bir senaryoda birlikte geçip geçmediğini sayıyor.

```js
// alan-say.mjs — alan sayisi, kullanim basina okunmayan alan ve birlikte hic gecmeyen alan cifti
import { Gonderi as Birlesik } from "./birlesik/gonderi.mjs";
import { Gonderi as UcretGonderi } from "./ucretlendirme/gonderi.mjs";
import { Gonderi as TeslimatGonderi } from "./teslimat/gonderi.mjs";
import { UCRET_SATIRLARI, TESLIMAT_SATIRLARI } from "./veri.mjs";

const KULLANIM = [
  ["ucretHesapla", ["gonderiNo", "agirlikGram", "hacimDesimetreKup", "ucretBolgesi", "tarifeKodu"]],
  ["indirimUygula", ["gonderiNo", "tarifeKodu", "sozlesmeNo", "indirimOrani"]],
  ["sigortaPrimi", ["gonderiNo", "beyanDegeriKurus", "ucretBolgesi"]],
  ["rotaAta", ["gonderiNo", "tasiyiciKodu", "rotaNoktalari", "aktarmaSayisi"]],
  ["aktarmaKaydet", ["gonderiNo", "rotaNoktalari", "aktarmaSayisi", "sonDurum", "durumGecmisi"]],
  ["teslimIsaretle", ["gonderiNo", "sonDurum", "durumGecmisi", "teslimAlanAd", "teslimZamani"]],
];

const alanlari = (Sinif, satir) => Object.keys(new Sinif(satir));

function olc(ad, alanlar, senaryolar) {
  let cift = 0, birlikte = 0;
  for (let i = 0; i < alanlar.length; i += 1) {
    for (let j = i + 1; j < alanlar.length; j += 1) {
      cift += 1;
      if (senaryolar.some(([, o]) => o.includes(alanlar[i]) && o.includes(alanlar[j]))) birlikte += 1;
    }
  }
  console.log(`${ad}: ${alanlar.length} alan, ${cift} alan cifti, ${senaryolar.length} senaryo`);
  for (const [senaryo, okunan] of senaryolar) {
    const icerde = okunan.filter((a) => alanlar.includes(a)).length;
    console.log(`  ${senaryo.padEnd(15)} okunan ${String(icerde).padStart(2)}, okunmayan ${String(alanlar.length - icerde).padStart(2)}`);
  }
  console.log(`  hic birlikte gecmeyen cift = ${cift - birlikte} / ${cift}`);
  return alanlar;
}

const hepsi = olc("birlesik", alanlari(Birlesik, {}), KULLANIM);
const ucret = alanlari(UcretGonderi, UCRET_SATIRLARI[0]);
const teslimat = alanlari(TeslimatGonderi, TESLIMAT_SATIRLARI[0]);
const yalnizUcret = ucret.filter((a) => !teslimat.includes(a));
const yalnizTeslimat = teslimat.filter((a) => !ucret.includes(a));
console.log(`  bunun ${yalnizUcret.length * yalnizTeslimat.length}'i baglam disi cift (${yalnizUcret.length} x ${yalnizTeslimat.length})`);
olc("ucretlendirme", ucret, KULLANIM.filter(([, o]) => o.every((a) => ucret.includes(a))));
olc("teslimat", teslimat, KULLANIM.filter(([, o]) => o.every((a) => teslimat.includes(a))));
console.log(`iki baglamda ortak alan = ${hepsi.filter((a) => ucret.includes(a) && teslimat.includes(a)).join(", ")}`);
```

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

```
birlesik: 15 alan, 105 alan cifti, 6 senaryo
  ucretHesapla    okunan  5, okunmayan 10
  indirimUygula   okunan  4, okunmayan 11
  sigortaPrimi    okunan  3, okunmayan 12
  rotaAta         okunan  4, okunmayan 11
  aktarmaKaydet   okunan  5, okunmayan 10
  teslimIsaretle  okunan  5, okunmayan 10
  hic birlikte gecmeyen cift = 68 / 105
  bunun 49'i baglam disi cift (7 x 7)
ucretlendirme: 8 alan, 28 alan cifti, 3 senaryo
  ucretHesapla    okunan  5, okunmayan  3
  indirimUygula   okunan  4, okunmayan  4
  sigortaPrimi    okunan  3, okunmayan  5
  hic birlikte gecmeyen cift = 11 / 28
teslimat: 8 alan, 28 alan cifti, 3 senaryo
  rotaAta         okunan  4, okunmayan  4
  aktarmaKaydet   okunan  5, okunmayan  3
  teslimIsaretle  okunan  5, okunmayan  3
  hic birlikte gecmeyen cift = 8 / 28
iki baglamda ortak alan = gonderiNo
```

Birleşik sınıf 15 alan taşıyor ve altı senaryonun hiçbiri beşten fazlasını okumuyor: her
kullanımda en az on alan ilgisiz duruyor. Asıl sayı son satırdadır. 105 alan çiftinin 68'i
hiçbir senaryoda birlikte geçmiyor ve bunların 49'u bağlam dışı çift, yani biri
ücretlendirmeye, öteki teslimata ait alan çiftleri. Sınır çizildiğinde çift sayısı 105'ten
56'ya, hiç birlikte geçmeyen çift sayısı 68'den 19'a iniyor; 49 bağlam dışı çift tamamen
ortadan kalkıyor. İki bağlamın ortak tek alanı `gonderiNo`, yani kimliğin kendisi.

#### Değişmezlerin Sayılması

Alan sayımı yapının gevşekliğini gösterir; değişmez sayımı daha keskin bir şey gösterir.
Birleşik sınıfın `dogrula` işlevi on değişmez taşıyor ve bu on değişmezin hepsi her nesne
için çalışıyor. Ücretlendirme kaydının rotası olmadığı, teslimat kaydının beyan değeri
olmadığı için sonuç baştan bellidir, ama sayılması gerekir.

```js
// ihlal-say.mjs — on degismezin alti gonderide kac ihlal verdigi ve ucretin degisip degismedigi
import { Gonderi as Birlesik } from "./birlesik/gonderi.mjs";
import { Gonderi as UcretGonderi } from "./ucretlendirme/gonderi.mjs";
import { Gonderi as TeslimatGonderi } from "./teslimat/gonderi.mjs";
import { TARIFE, UCRET_SATIRLARI, TESLIMAT_SATIRLARI } from "./veri.mjs";

const birlesikNesneler = [...UCRET_SATIRLARI, ...TESLIMAT_SATIRLARI].map((s) => new Birlesik(s));
let toplam = 0, gecen = 0;
console.log("birlesik sinif (10 degismez)");
for (const g of birlesikNesneler) {
  const ihlal = g.dogrula();
  toplam += ihlal.length;
  if (ihlal.length === 0) gecen += 1;
  console.log(`  ${g.gonderiNo}  ihlal ${ihlal.length}  ${ihlal.join(" ") || "-"}`);
}
console.log(`  gecen nesne = ${gecen} / ${birlesikNesneler.length}, toplam ihlal = ${toplam}`);

const ayrilmis = [...UCRET_SATIRLARI.map((s) => new UcretGonderi(s)),
                  ...TESLIMAT_SATIRLARI.map((s) => new TeslimatGonderi(s))];
let t2 = 0, g2 = 0;
console.log("ayrilmis siniflar (5 + 5 degismez)");
for (const g of ayrilmis) {
  const ihlal = g.dogrula();
  t2 += ihlal.length;
  if (ihlal.length === 0) g2 += 1;
}
console.log(`  gecen nesne = ${g2} / ${ayrilmis.length}, toplam ihlal = ${t2}`);

let ayrilan = 0;
for (const s of UCRET_SATIRLARI) {
  const a = new Birlesik(s).ucret(TARIFE), b = new UcretGonderi(s).ucret(TARIFE);
  if (a !== b) ayrilan += 1;
  console.log(`  ${s.gonderiNo}  birlesik ${a}  ayrilmis ${b}`);
}
console.log(`ayrilan ucret = ${ayrilan} / ${UCRET_SATIRLARI.length}`);
```

```sh
node ihlal-say.mjs
```

```
birlesik sinif (10 degismez)
  G-1041  ihlal 4  T1-rota T2-aktarma T3-durum T4-gecmis
  G-1042  ihlal 4  T1-rota T2-aktarma T3-durum T4-gecmis
  G-1043  ihlal 4  T1-rota T2-aktarma T3-durum T4-gecmis
  G-1041  ihlal 5  U1-agirlik U2-hacim U3-bolge U4-beyan U5-indirim
  G-1042  ihlal 5  U1-agirlik U2-hacim U3-bolge U4-beyan U5-indirim
  G-1043  ihlal 5  U1-agirlik U2-hacim U3-bolge U4-beyan U5-indirim
  gecen nesne = 0 / 6, toplam ihlal = 27
ayrilmis siniflar (5 + 5 degismez)
  gecen nesne = 6 / 6, toplam ihlal = 0
  G-1041  birlesik 5069  ayrilmis 5069
  G-1042  birlesik 1260  ayrilmis 1260
  G-1043  birlesik 37440  ayrilmis 37440
ayrilan ucret = 0 / 3
```

Altı gerçek gönderinin hiçbiri birleşik sınıfın değişmezlerini geçmiyor: 0/6, toplam 27
ihlal. Aynı altı gönderi, aynı on değişmez iki sınıfa dağıtıldığında 6/6 geçiyor ve ihlal
sayısı sıfır. Değişmezlerin metni değişmedi, yalnız uygulandıkları alan değişti.

Son üç satır ayrımın davranışı bozmadığını gösteriyor: üç gönderinin ücreti iki düzende de
aynı. Sınır çizmek modeli iki parçaya böler, hesabı değiştirmez.

#### Koşullu Değişmezin Gizlediği Şey

Birleşik sınıfı kurtarmanın bilinen bir yolu vardır: değişmezleri koşullu yazmak. Rota
denetimi yalnız rota alanı doluysa çalışsın, ağırlık denetimi yalnız ağırlık doluysa
çalışsın. Bu, ihlal sayısını sıfıra indirir ama bir şeyi kaybeder: koşul, hangi bağlamın
nesnesiyle çalışıldığını sınıfın içine bir veri olarak geri koyar. Değişmez artık "gönderinin
rotası en az iki noktadır" demez, "eğer bu nesne teslimat tarafından geldiyse rotası en az
iki noktadır" der. İlki alan uzmanının cümlesidir, ikincisi değildir.

Sınırın nereden geçeceğini bulmanın yordamı buradan çıkar: bir sınıfın değişmezlerinden
hangilerinin ancak koşul altında yazılabildiğine bakılır. Koşul bir bağlam ayrımını
gizliyorsa, sınır o koşulun yerinden geçer.

#### Özet

- Sınırlı bağlam, bir modelin ve dilinin geçerli olduğu alandır; bileşen sınırı gibi
  birlikte paketlenen dosyalardan değil, bir adın anlamının değiştiği yerden geçer.
- İki bağlamın sözlüğünde beş sözcükten yalnız biri ortaktı ve o sözcük iki farklı nesneyi
  adlandırıyordu; iki sınıfın ortak tek alanı `gonderiNo` çıktı.
- Birleşik sınıf 15 alan taşıdı, altı senaryonun hiçbiri beşten fazlasını okumadı; 105 alan
  çiftinin 68'i hiçbir senaryoda birlikte geçmedi ve bunların 49'u bağlam dışı çiftti.
- Sınır çizildiğinde alan çifti 105'ten 56'ya, hiç birlikte geçmeyen çift 68'den 19'a indi;
  bağlam dışı 49 çift ortadan kalktı.
- On değişmez tek sınıfta altı gerçek gönderinin altısını da reddetti (0/6, 27 ihlal); aynı
  değişmezler iki sınıfa dağıtıldığında 6/6 geçti ve üç gönderinin ücreti değişmedi.
- Değişmezi koşullu yazmak ihlali sıfırlar ama bağlam ayrımını sınıfın içine veri olarak
  geri koyar; koşulun yeri sınırın yerini gösterir.

#### Sonraki Adım

Sınır çizildi ve iki bağlam kendi sözlüğüne kavuştu. Ne var ki iki bağlam birbirinden habersiz
çalışamaz: ücretlendirme bir gönderinin fiyatını hesaplarken teslimat o gönderiyi yola
çıkarır, teslim edildiğinde ücretlendirmenin bunu bilmesi gerekir. Sınır bir duvar değil, bir
geçittir ve geçidin kaç tane olduğu, hangi yöne baktığı, kimin kimin sözlüğüne uyduğu
tasarım kararıdır. Sonraki ders bağlamlar arası ilişkileri bir çizge olarak yazar, ilişki
türlerini yön ve bağımlılık cinsinden ayırt eder, sonra iki haritada aynı ölçüyü koşturur:
bir bağlam değiştiğinde kaç bağlamda dosya düzenlenir ve bu sayı ilişkiler adlandırıldığında
nereye iner.

### Bağlam Haritası

Source: https://academia.sh/tr/kurslar/alan-odakli-tasarim/baglam-haritasi

Bağlamlar arası ilişki türleri: yukarı akış ile aşağı akış yönünün ayrılması, haritanın gerçek ithallerden çıkarılması, karşılıklı bağımlı bağlam çifti ile bir bağlam değiştiğinde etkilenen bağlam sayısının iki haritada sayılması ve yukarı akışın adlarının kaç çekirdek dosyada göründüğünün ölçülmesi.

Sınır çizildi ve iki bağlam kendi sözlüğüne kavuştu. Ne var ki bağlamlar birbirinden habersiz
çalışamaz: ücretlendirme indirim oranını sözleşme bağlamından alır, teslimat servis seviyesini
yine oradan alır, sözleşme özeti ise ikisinin sonucunu bir arada gösterir. Sınır bir duvar
değil bir geçittir; geçidin kaç tane olduğu, hangi yöne baktığı ve kimin kimin sözlüğüne
uyduğu tasarım kararıdır.

Bu kararların yazıya geçirilmiş hâline **bağlam haritası** (context map) denir. Harita bir
kutu-ok çizimi değildir; her okun bir türü, bir yönü ve bir bedeli vardır. Bu ders haritayı
kodun gerçek ithallerinden çıkarır ve iki haritayı aynı üç ölçüyle karşılaştırır: karşılıklı
bağımlı bağlam çifti sayısı, bir bağlam değiştiğinde etkilenen bağlam sayısı ve yukarı akışın
adlarının göründüğü çekirdek dosya sayısı.

#### Yukarı Akış ve Aşağı Akış

İki bağlam arasındaki okun yönü, hangisinin ötekinin değişimine uymak zorunda olduğunu
söyler. Modeli başkasının uyduğu tarafa **yukarı akış** (upstream), uymak zorunda olan tarafa
**aşağı akış** (downstream) denir. Yön alan bilgisinden değil, karar hakkından çıkar:
sözleşme bağlamı indirim oranının ne anlama geldiğine karar veriyorsa yukarı akıştır,
ücretlendirme o karara uyuyorsa aşağı akıştır.

Bu, Tasarım İlkeleri kursundaki bağımlılık yönünden farklı bir sorudur. Orada ok, kararlılığı
yüksek olan tarafa bakardı; burada ok, alan kararını veren tarafa bakar. İkisi çakışabilir ama
zorunlu değildir: bir bağlam teknik olarak kararsız, alan olarak belirleyici olabilir.

#### İlişki Türleri

Aynı iki bağlam arasında birden çok ilişki kurulabilir; hangisinin kurulduğu haritada adıyla
yazılır.

| İlişki | Yön | Ne söyler |
|---|---|---|
| **ortaklık** (partnership) | simetrik | İki takım birlikte başarır veya birlikte başarısız olur; sürümler birlikte planlanır |
| **müşteri–tedarikçi** (customer–supplier) | yukarı → aşağı | Aşağı akışın ihtiyaçları yukarı akışın planına girer; yukarı akış söz verir |
| **açık sunucu servisi** (open host service) | yukarı akış yayımlar | Yukarı akış, birden çok tüketici için tanımlı bir erişim yüzeyi sunar |
| **yayımlanmış dil** (published language) | yukarı akış yayımlar | Yüzeyde geçen adlar ve tipler ayrıca belgelenir; iç model değişse de yayımlanan dil durur |
| **ayrı yollar** (separate ways) | yok | İki bağlam bütünleştirilmez; ihtiyaç varsa dışarıda karşılanır |
| **adsız ilişki** | belirsiz | Bağ vardır ama yönü ve türü yazılmamıştır; her taraf ötekinin modelini okur |

Bu listenin dışında iki işbirliği kalıbı ile dış modele karşı bir savunma kalıbı daha vardır;
üçü kendi derslerinde ele alınır. Bu dersin ölçtüğü şey türlerin kendisi değil, adsız ilişkiyi
adlandırmanın sayısal karşılığıdır.

#### Üç Bağlam, İki Düzen

Yukarı akış bağlamı sözleşme ve müşteridir. Kendi sözlüğünde beş ad taşıyor.

```sh
mkdir -p sozlesme adsiz adli
```

```js
// sozlesme/sozlesme.mjs — yukari akis baglami: sozlesme ve musteri kayitlari
const SOZLESMELER = [
  { sozlesmeNo: "S-77", musteriNo: "M-4", tarifeIndirimYuzdesi: 12, servisSeviyeKodu: "SS2", pesinOdemeliMi: true },
  { sozlesmeNo: "S-12", musteriNo: "M-9", tarifeIndirimYuzdesi: 20, servisSeviyeKodu: "SS1", pesinOdemeliMi: false },
];

export const sozlesmeBul = (no) => SOZLESMELER.find((s) => s.sozlesmeNo === no) ?? null;
export const sozlesmeNolari = () => SOZLESMELER.map((s) => s.sozlesmeNo);
```

Birinci düzende ilişkiler adsız. İki aşağı akış bağlamı sözleşmenin kendi adlarını doğrudan
okuyor; rapor ise sözleşme bağlamının içinde duruyor ve iki aşağı akış bağlamını geri
çağırıyor.

```js
// adsiz/ucret.mjs — ucretlendirme, sozlesme baglaminin adlarini dogrudan okuyor
import { sozlesmeBul } from "../sozlesme/sozlesme.mjs";

export function netUcret(brutKurus, sozlesmeNo) {
  const s = sozlesmeBul(sozlesmeNo);
  if (s === null) return brutKurus;
  const indirimli = Math.round(brutKurus * (1 - s.tarifeIndirimYuzdesi / 100));
  return s.pesinOdemeliMi ? Math.round(indirimli * 0.98) : indirimli;
}
```

```js
// adsiz/teslimat.mjs — teslimat, sozlesme baglaminin adlarini dogrudan okuyor
import { sozlesmeBul } from "../sozlesme/sozlesme.mjs";

const PENCERE = { SS1: 24, SS2: 48, SS3: 72 };

export function teslimPenceresiSaat(sozlesmeNo) {
  const s = sozlesmeBul(sozlesmeNo);
  return s === null ? 72 : PENCERE[s.servisSeviyeKodu];
}
```

```js
// adsiz/rapor.mjs — sozlesme baglaminda duran rapor, iki asagi akis baglamini geri cagiriyor
import { sozlesmeBul, sozlesmeNolari } from "../sozlesme/sozlesme.mjs";
import { netUcret } from "./ucret.mjs";
import { teslimPenceresiSaat } from "./teslimat.mjs";

export const sozlesmeOzeti = (brutKurus) => sozlesmeNolari().map((no) => ({
  odeyen: sozlesmeBul(no).musteriNo,
  net: netUcret(brutKurus, no),
  pencere: teslimPenceresiSaat(no),
}));
```

İkinci düzende her aşağı akış bağlamı yukarı akışın adlarını kendi diline çeviren bir sınır
dosyası taşıyor; çekirdek dosyalar yalnız kendi dillerini konuşuyor. Rapor da yerini
değiştiriyor: ücretlendirme bağlamının bir raporu olduğu kabul edilip oraya taşınıyor ve
teslimattan yalnız yayımlanmış pencere işlevini kullanıyor.

```js
// adli/ucretlendirme-ceviri.mjs — yukari akis adlarini ucretlendirmenin diline cevirir
import { sozlesmeBul, sozlesmeNolari } from "../sozlesme/sozlesme.mjs";

export const sozlesmeListesi = () => sozlesmeNolari();

export function ucretKosullari(sozlesmeNo) {
  const s = sozlesmeBul(sozlesmeNo);
  if (s === null) return { odeyen: null, indirimOrani: 0, pesinIndirimi: 0 };
  return { odeyen: s.musteriNo, indirimOrani: s.tarifeIndirimYuzdesi / 100,
           pesinIndirimi: s.pesinOdemeliMi ? 0.02 : 0 };
}
```

```js
// adli/ucret.mjs — ucretlendirme cekirdegi: yalniz kendi dilini konusur
import { ucretKosullari } from "./ucretlendirme-ceviri.mjs";

export function netUcret(brutKurus, sozlesmeNo) {
  const { indirimOrani, pesinIndirimi } = ucretKosullari(sozlesmeNo);
  return Math.round(Math.round(brutKurus * (1 - indirimOrani)) * (1 - pesinIndirimi));
}
```

```js
// adli/teslimat-ceviri.mjs — yukari akis adlarini teslimatin diline cevirir
import { sozlesmeBul } from "../sozlesme/sozlesme.mjs";

const PENCERE = { SS1: 24, SS2: 48, SS3: 72 };

export function teslimKosullari(sozlesmeNo) {
  const s = sozlesmeBul(sozlesmeNo);
  return { pencereSaat: s === null ? 72 : PENCERE[s.servisSeviyeKodu] };
}
```

```js
// adli/teslimat.mjs — teslimat cekirdegi: yalniz kendi dilini konusur
import { teslimKosullari } from "./teslimat-ceviri.mjs";

export const teslimPenceresiSaat = (sozlesmeNo) => teslimKosullari(sozlesmeNo).pencereSaat;
```

```js
// adli/rapor.mjs — ucretlendirme baglaminda rapor: kendi dili ve teslimatin yayimladigi pencere
import { sozlesmeListesi, ucretKosullari } from "./ucretlendirme-ceviri.mjs";
import { netUcret } from "./ucret.mjs";
import { teslimPenceresiSaat } from "./teslimat.mjs";

export const sozlesmeOzeti = (brutKurus) => sozlesmeListesi().map((no) => ({
  odeyen: ucretKosullari(no).odeyen,
  net: netUcret(brutKurus, no),
  pencere: teslimPenceresiSaat(no),
}));
```

Çevirinin yaptığı iş adların değiştirilmesinden fazlasıdır. `tarifeIndirimYuzdesi` yüzde
cinsinden bir tam sayıdır, ücretlendirme ise oran ister; `pesinOdemeliMi` bir ödeme koşuludur,
ücretlendirme ondan bir indirim oranı türetir; `musteriNo` sözleşme bağlamının kimliğidir,
ücretlendirme aynı kişiyi `odeyen` diye adlandırır. Sınır dosyası bu üç dönüşümün durduğu
yerdir.

#### Haritanın Koddan Çıkarılması

Harita elle yazıldığında koddan sapabilir. Aşağıdaki betik her dosyanın ithallerini okuyup
bağlam düzeyinde yönlü kenarları çıkarıyor, sonra üç ölçüyü hesaplıyor: karşılıklı bağımlı
bağlam çifti, bir bağlam değiştiğinde geçişli olarak etkilenen bağlam sayısı ve yukarı akış
sözlüğünden ad geçen dosyaların kaçının çekirdek dosya olduğu.

```js
// harita-cikar.mjs — gercek ithallerden baglam haritasini cikarir, yayilmayi ve sizmayi olcer
import { readFileSync } from "node:fs";
import { dirname, join, normalize } from "node:path";

const BAGLAM = {
  "sozlesme/sozlesme.mjs": "sozlesme",
  "adsiz/rapor.mjs": "sozlesme", "adsiz/ucret.mjs": "ucretlendirme", "adsiz/teslimat.mjs": "teslimat",
  "adli/ucretlendirme-ceviri.mjs": "ucretlendirme", "adli/ucret.mjs": "ucretlendirme",
  "adli/rapor.mjs": "ucretlendirme",
  "adli/teslimat-ceviri.mjs": "teslimat", "adli/teslimat.mjs": "teslimat",
};
const SOZLUK = ["tarifeIndirimYuzdesi", "servisSeviyeKodu", "pesinOdemeliMi", "musteriNo"];
const SINIR = (d) => d.includes("-ceviri.mjs");

function kenarlar(dosyalar) {
  const kume = new Set();
  for (const d of dosyalar) {
    for (const [, yol] of readFileSync(d, "utf8").matchAll(/from "([^"]+)"/g)) {
      const a = BAGLAM[d], b = BAGLAM[normalize(join(dirname(d), yol))];
      if (a !== b) kume.add(`${a}->${b}`);
    }
  }
  return [...kume].sort();
}

function etkilenen(kenar, hedef) {
  const yigit = [hedef], gorulen = new Set();
  while (yigit.length > 0) {
    const b = yigit.pop();
    for (const k of kenar) {
      const [a, v] = k.split("->");
      if (v === b && a !== hedef && !gorulen.has(a)) { gorulen.add(a); yigit.push(a); }
    }
  }
  return gorulen;
}

for (const [ad, dosyalar] of [
  ["adsiz", ["sozlesme/sozlesme.mjs", "adsiz/ucret.mjs", "adsiz/teslimat.mjs", "adsiz/rapor.mjs"]],
  ["adli", ["sozlesme/sozlesme.mjs", "adli/ucretlendirme-ceviri.mjs", "adli/ucret.mjs",
            "adli/rapor.mjs", "adli/teslimat-ceviri.mjs", "adli/teslimat.mjs"]],
]) {
  const kenar = kenarlar(dosyalar);
  const karsilikli = kenar.filter((k) => kenar.includes(k.split("->").reverse().join("->"))).length / 2;
  console.log(`${ad}: ${dosyalar.length} dosya, ${kenar.length} yonlu kenar, ${karsilikli} karsilikli baglam cifti`);
  for (const k of kenar) console.log(`  ${k}`);
  let toplam = 0;
  for (const b of ["sozlesme", "ucretlendirme", "teslimat"]) {
    const e = etkilenen(kenar, b);
    toplam += e.size;
    const liste = e.size > 0 ? ` (${[...e].sort().join(", ")})` : "";
    console.log(`  ${b.padEnd(14)} degisirse etkilenen baglam = ${e.size}${liste}`);
  }
  console.log(`  toplam yayilma = ${toplam}`);
  const sizan = dosyalar.filter((d) => BAGLAM[d] !== "sozlesme"
    && SOZLUK.some((n) => new RegExp(`\\b${n}\\b`).test(readFileSync(d, "utf8"))));
  const cekirdek = dosyalar.filter((d) => BAGLAM[d] !== "sozlesme" && !SINIR(d));
  console.log(`  yukari akis adi gecen dosya = ${sizan.length} (${sizan.join(", ")})`);
  console.log(`  bunlardan cekirdek dosya = ${sizan.filter((d) => !SINIR(d)).length} / ${cekirdek.length}`);
}
```

```sh
node harita-cikar.mjs
```

```
adsiz: 4 dosya, 4 yonlu kenar, 2 karsilikli baglam cifti
  sozlesme->teslimat
  sozlesme->ucretlendirme
  teslimat->sozlesme
  ucretlendirme->sozlesme
  sozlesme       degisirse etkilenen baglam = 2 (teslimat, ucretlendirme)
  ucretlendirme  degisirse etkilenen baglam = 2 (sozlesme, teslimat)
  teslimat       degisirse etkilenen baglam = 2 (sozlesme, ucretlendirme)
  toplam yayilma = 6
  yukari akis adi gecen dosya = 2 (adsiz/ucret.mjs, adsiz/teslimat.mjs)
  bunlardan cekirdek dosya = 2 / 2
adli: 6 dosya, 3 yonlu kenar, 0 karsilikli baglam cifti
  teslimat->sozlesme
  ucretlendirme->sozlesme
  ucretlendirme->teslimat
  sozlesme       degisirse etkilenen baglam = 2 (teslimat, ucretlendirme)
  ucretlendirme  degisirse etkilenen baglam = 0
  teslimat       degisirse etkilenen baglam = 1 (ucretlendirme)
  toplam yayilma = 3
  yukari akis adi gecen dosya = 2 (adli/ucretlendirme-ceviri.mjs, adli/teslimat-ceviri.mjs)
  bunlardan cekirdek dosya = 0 / 3
```

Adsız düzende üç bağlam arasında dört yönlü kenar var ve bunların ikisi karşılıklı çift: hem
sözleşme ücretlendirmeye bağımlı hem ücretlendirme sözleşmeye. Karşılıklılığı yaratan tek şey
raporun yeridir. Rapor sözleşme bağlamının içinde durduğu için o bağlam, kendisine bağımlı iki
bağlamı geri çağırmak zorunda kalıyor. Sonuç, üç bağlamın hepsinin birbirine bağlanması:
hangisi değişirse öteki ikisi etkileniyor, toplam yayılma 6.

Adlı düzende kenar sayısı üçe, karşılıklı çift sayısı sıfıra iniyor. Yukarı akıştaki sözleşme
bağlamı değiştiğinde iki bağlam hâlâ etkilenir — bu kaçınılmazdır, indirim oranını gerçekten o
bağlam tanımlıyor. Değişen şey ötekiler: ücretlendirme değişirse hiçbir bağlam etkilenmiyor,
teslimat değişirse bir bağlam etkileniyor. Toplam yayılma 6'dan 3'e düştü.

Son iki satır sızmayı gösteriyor. Yukarı akışın dört adı iki düzende de iki dosyada geçiyor;
sayı aynı. Fark, o iki dosyanın ne olduğudur. Adsız düzende ikisi de aşağı akış bağlamlarının
çekirdek dosyaları (2/2); adlı düzende ikisi de sınır dosyası ve çekirdek dosyaların hiçbirinde
yukarı akıştan tek ad geçmiyor (0/3). Sözleşme bağlamı `tarifeIndirimYuzdesi` yerine
`indirimOrani` demeye karar verirse iki düzende de iki dosya düzenlenir, ama adsız düzende
düzenlenen dosyalar iş kuralının kendisini taşıyan dosyalardır.

#### Davranış Korundu mu

Haritanın değişmesi çıktının değişmesi değildir; sınanır.

```js
// kosum.mjs — iki duzen ayni raporu ve ayni teslim penceresini veriyor mu
import { sozlesmeOzeti as adsizOzet } from "./adsiz/rapor.mjs";
import { teslimPenceresiSaat as adsizPencere } from "./adsiz/teslimat.mjs";
import { sozlesmeOzeti as adliOzet } from "./adli/rapor.mjs";
import { teslimPenceresiSaat as adliPencere } from "./adli/teslimat.mjs";

const a = JSON.stringify(adsizOzet(10000)), b = JSON.stringify(adliOzet(10000));
console.log(`adsiz ${a}`);
console.log(`adli  ${b}`);
console.log(`rapor ayni mi = ${a === b}`);
let ayrilan = 0;
for (const no of ["S-77", "S-12", "S-99"]) {
  if (adsizPencere(no) !== adliPencere(no)) ayrilan += 1;
}
console.log(`ayrilan pencere = ${ayrilan} / 3`);
```

```sh
node kosum.mjs
```

```
adsiz [{"odeyen":"M-4","net":8624,"pencere":48},{"odeyen":"M-9","net":8000,"pencere":24}]
adli  [{"odeyen":"M-4","net":8624,"pencere":48},{"odeyen":"M-9","net":8000,"pencere":24}]
rapor ayni mi = true
ayrilan pencere = 0 / 3
```

Rapor birebir aynı, üç sözleşme numarasının teslim penceresi de aynı. Tanımsız sözleşme
numarası `S-99` dâhil. Harita yeniden çizildi, alan davranışı yerinde kaldı.

#### Haritanın Bedeli

Adlı düzen iki dosya daha büyük ve her okumada bir dolaylılık katmanı fazla taşıyor. Bedelin
ikinci parçası daha az görünür: sınır dosyası yalnız çevirdiği kadarını geçirir. Sözleşme
bağlamı yeni bir alan yayımladığında aşağı akış onu doğrudan kullanamaz, önce çeviriye
eklenmesi gerekir. Bu, gecikme demektir; karşılığında iş kuralı dosyalarının yukarı akışın
sözlüğünü hiç tanımaması gelir.

Haritanın kendisinin de bir bedeli var: yazılmayan harita yoktur, yalnız görülmeyen harita
vardır. Adsız düzenin de bir haritası çıktı, dört kenarlı ve iki karşılıklı çiftli. Fark,
birinin yazılmış olmasıdır.

#### Özet

- Bağlam haritası, bağlamlar arası ilişkilerin türünü ve yönünü yazıya geçirir; ok, alan
  kararını veren tarafa bakar ve bu, bağımlılık yönündeki kararlılık ölçütünden ayrı bir
  sorudur.
- İlişki türleri adlandırılır: ortaklık, müşteri–tedarikçi, açık sunucu servisi, yayımlanmış
  dil, ayrı yollar; adsız ilişkide bağ vardır ama yönü yazılmamıştır.
- Harita kodun gerçek ithallerinden çıkarıldığında adsız düzende 4 yönlü kenar ve 2 karşılıklı
  bağlam çifti göründü; karşılıklılığı yaratan şey raporun yukarı akış bağlamının içinde
  durmasıydı.
- İlişkiler adlandırıldığında kenar sayısı 3'e, karşılıklı çift sayısı 0'a indi; toplam yayılma
  6'dan 3'e düştü ve ücretlendirme değişirse etkilenen bağlam sayısı 2'den 0'a indi.
- Yukarı akışın dört adı iki düzende de iki dosyada geçti, ama adsız düzende bu dosyalar
  çekirdek dosyalardı (2/2), adlı düzende hepsi sınır dosyasıydı (çekirdekte 0/3).
- Rapor ve üç teslim penceresi iki düzende birebir aynı çıktı; bedel iki fazla dosya ve
  çevirinin yalnız eşlediği kadarını geçirmesidir.

#### Sonraki Adım

Bu dersteki çeviri dosyaları kolay bir durumda çalıştı: yukarı akıştaki sözleşme bağlamı aynı
kod tabanının parçası, adları düzenli, tipleri belli. Kitaplığın gerçek dünyada konuştuğu
yukarı akışların bir kısmı böyle değil — taşıyıcı sağlayıcılarının kendi modelleri, kendi durum
kümeleri ve kendi kimlik biçimleri var; hiçbiri kitaplığın alan diline uymak zorunda değil ve
değiştiklerinde kimseye danışmıyor. Böyle bir yukarı akışın kavramları içeri sızarsa iç model
dışın diliyle konuşmaya başlar: teslimat bağlamının durumları taşıyıcının durum kodlarına
dönüşür. Sonraki ders dış modelin adlarının ve tiplerinin kaç dosyada göründüğünü sayar,
araya bir savunma katmanı koyduğunda bu sayının nereye indiğini ölçer ve katmanın bedelini
dosya ile satır cinsinden çıkarır.

### Yozlaşma Önleyici Katman

Source: https://academia.sh/tr/kurslar/alan-odakli-tasarim/yozlasma-onleyici-katman

Dış modelin sızmasının engellenmesi: iki taşıyıcı sağlayıcısının ad, durum kümesi ve birimlerinin kaç dosyada göründüğünün sayılması, iş kuralının tanıdığı dış durum değeri sayısının ölçülmesi, çeviri katmanı eklendiğinde çekirdek dosyalardaki sızmanın sıfıra inmesi ve katmanın dosya ile satır cinsinden bedelinin çıkarılması.

Bir önceki dersin çeviri dosyaları elverişli bir durumda çalıştı: yukarı akıştaki sözleşme
bağlamı aynı kod tabanının parçasıydı, adları düzenliydi, tipleri belliydi. Kitaplığın
konuştuğu yukarı akışların bir kısmı böyle değil. Taşıyıcı sağlayıcılarının kendi modelleri,
kendi durum kümeleri, kendi kimlik biçimleri ve kendi ağırlık birimleri var; hiçbiri
kitaplığın alan diline uymak zorunda değil ve değiştiklerinde kimseye danışmıyorlar.

Bu dersin sorusu arayüz sorusu değil. İki sağlayıcının çağrı biçimlerinin farklı olması,
Tasarım İlkeleri ve Tasarım Kalıpları kurslarında ölçülmüş bir sorundur ve çözümü
bağdaştırıcıdır. Buradaki sorun bir düzey içeride: sağlayıcının **kavramları** içeri sızarsa iç
model dışın diliyle konuşmaya başlar. Teslimat bağlamının durumları taşıyıcının hareket
kodlarına dönüşür, gönderinin ağırlığı taşıyıcının birimine bağlanır, "kayıt bulunamadı" gibi
bir aktarım hatası alanın bir durumu gibi davranır. Bu ders o sızmayı sayar.

#### Uyumsuzluğun İki Düzeyi

Bağdaştırıcı, iki tarafın **imzalarını** uzlaştırır: çağrının adı, parametre sırası, dönüş
biçimi. Uzlaştırdığı şey biçimdir, kavram değildir. Bir bağdaştırıcı iki sağlayıcıyı tek bir
işlev adı altında toplayabilir ve yine de her ikisinin durum kodlarını olduğu gibi yukarıya
verebilir.

**Yozlaşma önleyici katman** (anti-corruption layer) ise iki tarafın **modellerini**
uzlaştırır: dışın durum kümesi içinkine, dışın birimi içinkine, dışın hata biçimi içinkine
çevrilir ve bu çeviri tek bir yerde durur. Adı bunu söylüyor: korunan şey iç modelin
kendisidir. Ölçüsü de buradan çıkar — dışın adları ve değerleri iç modelin dosyalarında kaç
kez görünüyor?

İki sağlayıcı, kitaplığın dışında duran iki dış sistem olarak yazılıyor. Kendi sözlükleri
kasten birbirine benzemiyor.

```sh
mkdir -p saglayici sizan korumali
```

```js
// saglayici/tasiyici-a.mjs — dis sistem A: kendi hareket kodlari, epok zamani, libre agirligi
const KAYITLAR = {
  "AA-1041": { hareketKodu: "H25", sonHareketEpok: 1700000000, agirlikLibre: 5.3, hataKodu: 0 },
  "AA-1042": { hareketKodu: "H30", sonHareketEpok: 1700050000, agirlikLibre: 1.5, hataKodu: 0 },
};

export function sevkDurumu(sevkKimligi) {
  const k = KAYITLAR[sevkKimligi];
  return k === undefined
    ? { sevkKimligi, hareketKodu: "H99", sonHareketEpok: 0, agirlikLibre: 0, hataKodu: 404 }
    : { sevkKimligi, ...k };
}
```

```js
// saglayici/tasiyici-b.mjs — dis sistem B: kendi durum etiketleri, ISO zamani, ons agirligi
const KAYITLAR = {
  "BB/2210": { durumEtiketi: "tamamlandi", sonHareketZamani: "2023-11-14T22:13:20Z", agirlikOns: 84 },
  "BB/2211": { durumEtiketi: "yolda", sonHareketZamani: "2023-11-15T20:00:00Z", agirlikOns: 12 },
};

export function takip(referans) {
  const k = KAYITLAR[referans];
  return k === undefined
    ? { referans, durumEtiketi: "kayit-yok", sonHareketZamani: null, agirlikOns: 0 }
    : { referans, ...k };
}
```

Farklar sayılabilir: A durumu altı kodla, B beş etiketle veriyor; A zamanı epok saniyesi, B
ISO metni olarak veriyor; A ağırlığı libre, B ons cinsinden veriyor; A bulunamayan kaydı hata
koduyla, B bir durum etiketiyle bildiriyor. Alan tarafındaki iş kuralı ise tek bir soru
soruyor: bu gönderi gecikmeli mi?

#### Sızan Düzen

Birinci düzende iş kuralı iki sağlayıcının sözlüğünü doğrudan tanıyor.

```js
// sizan/gecikme.mjs — is kurali iki saglayicinin durum sozlugunu ve birimlerini dogrudan taniyor
import { sevkDurumu } from "../saglayici/tasiyici-a.mjs";
import { takip } from "../saglayici/tasiyici-b.mjs";

export function gecikmeliMi(tasiyici, referans, pencereSaat, simdiEpok) {
  if (tasiyici === "A") {
    const y = sevkDurumu(referans);
    if (y.hataKodu !== 0 || y.hareketKodu === "H99") return null;
    if (y.hareketKodu === "H30" || y.hareketKodu === "H40") return false;
    return (simdiEpok - y.sonHareketEpok) / 3600 > pencereSaat;
  }
  const y = takip(referans);
  if (y.durumEtiketi === "kayit-yok") return null;
  if (y.durumEtiketi === "tamamlandi" || y.durumEtiketi === "iade") return false;
  return (simdiEpok - Math.floor(Date.parse(y.sonHareketZamani) / 1000)) / 3600 > pencereSaat;
}
```

```js
// sizan/rapor.mjs — rapor da iki saglayicinin birim ve durum sozluguyle yaziliyor
import { sevkDurumu } from "../saglayici/tasiyici-a.mjs";
import { takip } from "../saglayici/tasiyici-b.mjs";
import { gecikmeliMi } from "./gecikme.mjs";

export function ozet(kayitlar, pencereSaat, simdiEpok) {
  let gecikmeli = 0, bilinmeyen = 0, gram = 0;
  for (const [tasiyici, referans] of kayitlar) {
    const g = gecikmeliMi(tasiyici, referans, pencereSaat, simdiEpok);
    if (g === null) bilinmeyen += 1; else if (g) gecikmeli += 1;
    gram += tasiyici === "A"
      ? Math.round(sevkDurumu(referans).agirlikLibre * 453.59237)
      : Math.round(takip(referans).agirlikOns * 28.349523125);
  }
  return { gecikmeli, bilinmeyen, gram };
}
```

Dosyalar çalışıyor ve kısa. Sorun, kuralın söylediği cümlededir. "Teslim edilmemiş bir gönderi
son hareketinden bu yana servis penceresini aştıysa gecikmelidir" cümlesi alan uzmanının
cümlesidir; dosyada duran cümle ise "hareket kodu H30 veya H40 değilse ve durum etiketi
tamamlandı ya da iade değilse" biçimindedir. İkisi aynı şeyi söylüyor olabilir; ama ikincisi
sağlayıcı sözleşmesi değiştiğinde yanlış olur ve yanlış olduğu yer iş kuralı dosyasıdır.

#### Korumalı Düzen

İkinci düzende her sağlayıcı için bir çeviri dosyası var. Çeviri dört işi birden yapıyor:
durum kümesini eşliyor, zamanı tek biçime getiriyor, ağırlığı grama çeviriyor ve bulunamayan
kaydı iç modelin `bilinmiyor` durumuna indiriyor.

```js
// korumali/tasiyici-a-ceviri.mjs — A saglayicisinin modelini ic modele cevirir
import { sevkDurumu } from "../saglayici/tasiyici-a.mjs";

const DURUM = { H10: "kabul", H20: "aktarmada", H25: "dagitimda", H30: "teslim-edildi", H40: "iade-edildi" };

export function gonderiGetir(referans) {
  const y = sevkDurumu(referans);
  if (y.hataKodu !== 0) return { gonderiNo: referans, sonDurum: "bilinmiyor", sonHareketAni: 0, agirlikGram: 0 };
  return {
    gonderiNo: referans,
    sonDurum: DURUM[y.hareketKodu] ?? "bilinmiyor",
    sonHareketAni: y.sonHareketEpok,
    agirlikGram: Math.round(y.agirlikLibre * 453.59237),
  };
}
```

```js
// korumali/tasiyici-b-ceviri.mjs — B saglayicisinin modelini ayni ic modele cevirir
import { takip } from "../saglayici/tasiyici-b.mjs";

const DURUM = { alindi: "kabul", yolda: "aktarmada", "dagitima-verildi": "dagitimda",
                tamamlandi: "teslim-edildi", iade: "iade-edildi" };

export function gonderiGetir(referans) {
  const y = takip(referans);
  const sonDurum = DURUM[y.durumEtiketi] ?? "bilinmiyor";
  return {
    gonderiNo: referans,
    sonDurum,
    sonHareketAni: sonDurum === "bilinmiyor" ? 0 : Math.floor(Date.parse(y.sonHareketZamani) / 1000),
    agirlikGram: Math.round(y.agirlikOns * 28.349523125),
  };
}
```

```js
// korumali/gecikme.mjs — is kurali yalniz ic modelin durum kumesini taniyor
const KAPANMIS = ["teslim-edildi", "iade-edildi"];

export function gecikmeliMi(gonderi, pencereSaat, simdiEpok) {
  if (gonderi.sonDurum === "bilinmiyor") return null;
  if (KAPANMIS.includes(gonderi.sonDurum)) return false;
  return (simdiEpok - gonderi.sonHareketAni) / 3600 > pencereSaat;
}
```

```js
// korumali/rapor.mjs — rapor da yalniz ic modeli okur
import { gecikmeliMi } from "./gecikme.mjs";

export function ozet(gonderiler, pencereSaat, simdiEpok) {
  let gecikmeli = 0, bilinmeyen = 0, gram = 0;
  for (const g of gonderiler) {
    const s = gecikmeliMi(g, pencereSaat, simdiEpok);
    if (s === null) bilinmeyen += 1; else if (s) gecikmeli += 1;
    gram += g.agirlikGram;
  }
  return { gecikmeli, bilinmeyen, gram };
}
```

Kural dosyası artık hangi sağlayıcının konuştuğunu bilmiyor. Ağırlık alanının adı da
değişti: iç modelin zaman alanı `sonHareketAni`, A sağlayıcısının `sonHareketEpok` alanı
değil. Aynı sayıyı taşıyan iki adın ayrı tutulması gereksiz görünür; ama adın dışarıdan
alınması, o adın dışarıyla birlikte değişmesi demektir.

#### Sızmanın Sayılması

Ölçü üç sayıdan oluşuyor: dış adların ve dış durum değerlerinin geçtiği dosya sayısı, bunların
kaçının çekirdek dosya olduğu ve iş kuralının tanıdığı dış durum değeri sayısı.

```js
// sizma-say.mjs — dis modelin adlarinin ve durum degerlerinin kac dosyada gorundugunu sayar
import { readFileSync } from "node:fs";

const DIS_ADLAR = ["hareketKodu", "sonHareketEpok", "agirlikLibre", "hataKodu",
                   "durumEtiketi", "sonHareketZamani", "agirlikOns"];
const DIS_DEGERLER = ["H10", "H20", "H25", "H30", "H40", "H99",
                      "alindi", "yolda", "dagitima-verildi", "tamamlandi", "iade", "kayit-yok"];
const IC_DEGERLER = ["kabul", "aktarmada", "dagitimda", "teslim-edildi", "iade-edildi", "bilinmiyor"];

const gecen = (metin, adlar) => adlar.filter((a) => new RegExp(`(?<![\\w-])${a}(?![\\w-])`).test(metin));

for (const [duzen, dosyalar, kural] of [
  ["sizan", ["sizan/gecikme.mjs", "sizan/rapor.mjs"], "sizan/gecikme.mjs"],
  ["korumali", ["korumali/tasiyici-a-ceviri.mjs", "korumali/tasiyici-b-ceviri.mjs",
                "korumali/gecikme.mjs", "korumali/rapor.mjs"], "korumali/gecikme.mjs"],
]) {
  const satir = dosyalar.reduce((t, d) => t + readFileSync(d, "utf8").trimEnd().split("\n").length, 0);
  console.log(`${duzen}/ (${dosyalar.length} dosya, ${satir} satir)`);
  let sizan = 0, cekirdekSizan = 0, cekirdek = 0;
  for (const d of dosyalar) {
    const metin = readFileSync(d, "utf8");
    const ad = gecen(metin, DIS_ADLAR), dd = gecen(metin, DIS_DEGERLER), id = gecen(metin, IC_DEGERLER);
    const sinir = d.includes("-ceviri.mjs");
    if (!sinir) cekirdek += 1;
    if (ad.length > 0 || dd.length > 0) { sizan += 1; if (!sinir) cekirdekSizan += 1; }
    console.log(`  ${d.split("/")[1].padEnd(22)} dis ad ${ad.length}  dis deger ${String(dd.length).padStart(2)}  ic deger ${id.length}`);
  }
  console.log(`  dis ad veya deger gecen dosya = ${sizan} / ${dosyalar.length}, cekirdek dosyada = ${cekirdekSizan} / ${cekirdek}`);
  const k = readFileSync(kural, "utf8");
  console.log(`  is kuralinin tanidigi durum degeri: dis ${gecen(k, DIS_DEGERLER).length}, ic ${gecen(k, IC_DEGERLER).length}`);
}
```

```sh
node sizma-say.mjs
```

```
sizan/ (2 dosya, 32 satir)
  gecikme.mjs            dis ad 5  dis deger  6  ic deger 0
  rapor.mjs              dis ad 2  dis deger  0  ic deger 0
  dis ad veya deger gecen dosya = 2 / 2, cekirdek dosyada = 2 / 2
  is kuralinin tanidigi durum degeri: dis 6, ic 0
korumali/ (4 dosya, 51 satir)
  tasiyici-a-ceviri.mjs  dis ad 4  dis deger  5  ic deger 6
  tasiyici-b-ceviri.mjs  dis ad 3  dis deger  5  ic deger 6
  gecikme.mjs            dis ad 0  dis deger  0  ic deger 3
  rapor.mjs              dis ad 0  dis deger  0  ic deger 0
  dis ad veya deger gecen dosya = 2 / 4, cekirdek dosyada = 0 / 2
  is kuralinin tanidigi durum degeri: dis 0, ic 3
```

Sızan düzende iki dosyanın ikisi de dışın sözlüğünü tanıyor ve ikisi de çekirdek dosya:
2/2. Korumalı düzende dışın sözlüğü hâlâ iki dosyada geçiyor — sayı aynı — ama ikisi de sınır
dosyası; çekirdekte 0/2. Sızmanın toplam miktarı azalmıyor, yeri değişiyor. Yozlaşma önleyici
katmanın iddiası bu kadardır: dışın modeli bir yerde görünmek zorundadır, o yer iş kuralı
olmamalıdır.

İkinci satır çifti daha keskin. Sızan düzende iş kuralı altı dış durum değeri tanıyor ve
hiçbir iç durum değeri tanımıyor; iç modelin durum kümesi kodda hiç yok, iki sağlayıcının
kümelerinin birleşimi olarak dolaylı biçimde var. Korumalı düzende kural sıfır dış değer, üç
iç değer tanıyor. Alan kavramı olarak "durum" ilk kez tek bir kümedir.

#### Davranış ve Bedel

Çevirinin doğru olduğu iddia edilmez, sınanır. Altı kayıt iki düzende de aynı kararı ve aynı
özeti vermelidir.

```js
// kosum.mjs — iki duzen ayni gecikme kararini ve ayni ozeti veriyor mu
import { gecikmeliMi as sizanKarar } from "./sizan/gecikme.mjs";
import { ozet as sizanOzet } from "./sizan/rapor.mjs";
import { gonderiGetir as aCeviri } from "./korumali/tasiyici-a-ceviri.mjs";
import { gonderiGetir as bCeviri } from "./korumali/tasiyici-b-ceviri.mjs";
import { gecikmeliMi as korumaliKarar } from "./korumali/gecikme.mjs";
import { ozet as korumaliOzet } from "./korumali/rapor.mjs";

const KAYITLAR = [["A", "AA-1041"], ["A", "AA-1042"], ["A", "AA-9999"],
                  ["B", "BB/2210"], ["B", "BB/2211"], ["B", "BB/9999"]];
const PENCERE = 24, SIMDI = 1700100000;

const icModel = KAYITLAR.map(([t, r]) => (t === "A" ? aCeviri(r) : bCeviri(r)));
let ayrilan = 0;
for (let i = 0; i < KAYITLAR.length; i += 1) {
  const a = sizanKarar(KAYITLAR[i][0], KAYITLAR[i][1], PENCERE, SIMDI);
  const b = korumaliKarar(icModel[i], PENCERE, SIMDI);
  if (a !== b) ayrilan += 1;
  console.log(`${KAYITLAR[i][1].padEnd(9)} ${String(icModel[i].sonDurum).padEnd(13)} sizan ${String(a).padEnd(5)} korumali ${b}`);
}
console.log(`ayrilan karar = ${ayrilan} / ${KAYITLAR.length}`);
console.log(`sizan ozet    = ${JSON.stringify(sizanOzet(KAYITLAR, PENCERE, SIMDI))}`);
console.log(`korumali ozet = ${JSON.stringify(korumaliOzet(icModel, PENCERE, SIMDI))}`);
```

```sh
node kosum.mjs
```

```
AA-1041   dagitimda     sizan true  korumali true
AA-1042   teslim-edildi sizan false korumali false
AA-9999   bilinmiyor    sizan null  korumali null
BB/2210   teslim-edildi sizan false korumali false
BB/2211   aktarmada     sizan false korumali false
BB/9999   bilinmiyor    sizan null  korumali null
ayrilan karar = 0 / 6
sizan ozet    = {"gecikmeli":1,"bilinmeyen":2,"gram":5805}
korumali ozet = {"gecikmeli":1,"bilinmeyen":2,"gram":5805}
```

Altı kayıtta karar aynı, özetin üç alanı da aynı. Bulunamayan iki kayıt ve iki farklı ağırlık
birimi dâhil, çeviri sayısal bir fark üretmedi.

Bedel iki dosya ve on dokuz satırdır: 32 satırlık iki dosya yerine 51 satırlık dört dosya.
İkinci bedel satırla ölçülmez: çeviri yalnız eşlediği kadarını geçirir. A sağlayıcısının
yalnız kendisinde bulunan bir kavramı — örneğin gümrük beklemesi — iç modelin durum kümesinde
karşılığı olmadığı sürece iş kuralına ulaşamaz. Bu bir kayıp gibi görünür, oysa kararın
verildiği yeri belirler: yeni bir alan kavramı gerekip gerekmediğine iç model karar verir,
sağlayıcı karar vermez.

Katmanın gerekçesi de buradan çıkar ve her dış sistem için geçerli değildir. Sözleşmesi
kitaplığın denetiminde olan, modeli alanla aynı ayrımları taşıyan bir yukarı akış için katman
yalnız bir dolaylılıktır. Gerekçe, dışın modelinin farklı ayrımlar yapması ve kitaplığa
danışmadan değişmesidir.

#### Özet

- Bağdaştırıcı imzaları uzlaştırır, yozlaşma önleyici katman modelleri uzlaştırır; korunan şey
  iç modelin kendi kavramlarıdır.
- İki sağlayıcı durumu altı kod ile beş etiketle, zamanı epok ile ISO metniyle, ağırlığı libre
  ile ons cinsinden veriyordu; bulunamayan kaydı biri hata koduyla, öteki bir durum etiketiyle
  bildiriyordu.
- Sızan düzende dış sözlük iki dosyanın ikisinde de geçti ve ikisi de çekirdek dosyaydı (2/2);
  korumalı düzende yine iki dosyada geçti ama ikisi de sınır dosyasıydı, çekirdekte 0/2.
- İş kuralı sızan düzende altı dış durum değeri tanıyordu ve hiçbir iç değer tanımıyordu;
  korumalı düzende sıfır dış, üç iç değer tanıdı — durum ilk kez tek bir küme oldu.
- Altı kayıtta karar ve özetin üç alanı iki düzende de aynı çıktı; katmanın bedeli 2 dosya ve
  19 satır, ayrıca çevirinin yalnız eşlediği kavramı geçirmesidir.
- Katmanın gerekçesi dışın modelinin farklı ayrımlar yapması ve kitaplığa danışmadan
  değişmesidir; sözleşmesi kitaplığın denetimindeki bir yukarı akış için katman yalnız bir
  dolaylılıktır.

#### Sonraki Adım

Buraya kadarki iki savunma da aynı varsayıma dayanıyordu: karşı taraf yabancıdır, arada bir
çeviri durur. İki bağlamın iki ayrı takıma değil aynı takıma ait olduğu ya da aşağı akış
takımının yukarı akışı değiştirme gücünün hiç bulunmadığı durumlarda bu varsayım tutmaz.
Birinci durumda takımlar bir parçayı ortak tutmayı seçebilir; ikinci durumda aşağı akış
çeviriden vazgeçip yukarı akışın modelini olduğu gibi benimseyebilir. İkisi de ölçülebilir
kararlardır. Sonraki ders bu iki işbirliği kalıbını kurar, ortak tutulan parça büyüdüğünde iki
takımın birlikte düzenlemek zorunda kaldığı dosya sayısını sayar ve çeviriden vazgeçmenin
bedelini aynı ölçekte gösterir.

### Paylaşılan Çekirdek ve Uyum Sağlayan

Source: https://academia.sh/tr/kurslar/alan-odakli-tasarim/paylasilan-cekirdek-ve-uyum-saglayan

İki işbirliği kalıbı: paylaşılan çekirdek büyüdüğünde ortak karar gerektiren değişiklik oranının ve iki takımın birlikte düzenlediği dosya sayısının sayılması, tek bağlamın kullandığı çekirdek dosyalarının ölçülmesi, uyum sağlayan düzenin dosya ve satır kazancının aşağı akışın kendi sözlüğünde bıraktığı ad sayısıyla karşılaştırılması.

Önceki iki ders aynı varsayıma dayanıyordu: karşı taraf yabancıdır, arada bir çeviri durur.
Bu varsayım iki durumda tutmaz. Birincisi, iki bağlam aynı takımın ya da birlikte plan yapan
iki takımın elindedir; ikisi arasında çeviri tutmak, aynı kararı iki yerde vermek anlamına
gelir. İkincisi, aşağı akış takımının yukarı akışı etkileme gücü hiç yoktur; çeviri yazsa da
yazmasa da yukarı akış istediği gibi değişir.

Birinci durumun cevabı ortak tutulan bir parçadır: **paylaşılan çekirdek** (shared kernel).
İkinci durumun cevabı çeviriden vazgeçmektir: **uyum sağlayan** (conformist). İkisi de bedeli
ölçülebilir kararlardır. Bu ders çekirdek büyüdüğünde iki takımın birlikte düzenlemek zorunda
kaldığı dosya sayısını sayar, sonra uyum sağlayanın kazancını aşağı akışın kendi sözlüğünde
bıraktığı ad sayısıyla karşılaştırır.

#### Paylaşılan Çekirdeğin Tanımı

Paylaşılan çekirdek, iki bağlamın **ortak sahiplendiği** bir model parçasıdır. Ortaklık kod
paylaşımından fazlasını gerektirir: çekirdekteki bir değişiklik iki takımın da onayını ister,
iki tarafın testleri birlikte koşar, sürüm birlikte planlanır. Bu yüzden çekirdek küçük
tutulduğunda işe yarar bir anlaşmadır, büyüdüğünde iki bağlamı tek bağlama geri döndürür.

Ölçü açık: bir değişikliğin çekirdeğe dokunup dokunmaması, o değişikliğin ortak karar
gerektirip gerektirmemesidir. Aşağıdaki günlük ölçüm gönderi ücretlendirme ve yönlendirme
kitaplığının on altı model değişikliğini, her birinin dokunduğu alan kavramıyla taşıyor.

```js
// degisiklik-gunlugu.mjs — on alti degisiklik ve her birinin dokundugu alan kavrami
export const GUNLUK = [
  ["2025-01-14", "olcu-birimi"], ["2025-01-28", "tarife-kademesi"],
  ["2025-02-11", "durum-kumesi"], ["2025-02-25", "tarife-kademesi"],
  ["2025-03-11", "esas-agirlik-kurali"], ["2025-03-25", "indirim-kurali"],
  ["2025-04-08", "tarife-kademesi"], ["2025-04-22", "durum-kumesi"],
  ["2025-05-06", "arac-secimi"], ["2025-05-20", "olcu-birimi"],
  ["2025-06-03", "tarife-kademesi"], ["2025-06-17", "esas-agirlik-kurali"],
  ["2025-07-01", "indirim-kurali"], ["2025-07-15", "durum-kumesi"],
  ["2025-07-29", "sozlesme-esigi"], ["2025-08-12", "aktarma-kurali"],
];
```

Aynı sekiz kavram iki düzende farklı dosyalara yerleşiyor. Küçük çekirdekte ortak tutulan tek
şey ölçüdür: gram ile desimetreküpün tanımı ve hacim ağırlığının esas ağırlığa çevrilme kuralı.
Büyük çekirdekte tarife kademesi ile durum kümesi de çekirdeğe alınmış — ikisi de tek bir
bağlamın kavramı olduğu hâlde.

```js
// ortak-say.mjs — paylasilan cekirdek buyudugunde ortak karar gerektiren degisiklik ve dosya sayisi
import { GUNLUK } from "./degisiklik-gunlugu.mjs";

const KUCUK = {
  "olcu-birimi": ["cekirdek/olcu.mjs", ["ucretlendirme", "teslimat"]],
  "esas-agirlik-kurali": ["cekirdek/olcu.mjs", ["ucretlendirme", "teslimat"]],
  "tarife-kademesi": ["ucretlendirme/tarife.mjs", ["ucretlendirme"]],
  "indirim-kurali": ["ucretlendirme/indirim.mjs", ["ucretlendirme"]],
  "sozlesme-esigi": ["ucretlendirme/indirim.mjs", ["ucretlendirme"]],
  "durum-kumesi": ["teslimat/durum.mjs", ["teslimat"]],
  "arac-secimi": ["teslimat/arac.mjs", ["teslimat"]],
  "aktarma-kurali": ["teslimat/rota.mjs", ["teslimat"]],
};

const BUYUK = {
  ...KUCUK,
  "tarife-kademesi": ["cekirdek/tarife.mjs", ["ucretlendirme"]],
  "durum-kumesi": ["cekirdek/durum.mjs", ["teslimat"]],
};

for (const [ad, esleme] of [["kucuk cekirdek", KUCUK], ["buyuk cekirdek", BUYUK]]) {
  const cekirdek = new Map();
  for (const [dosya, kullanan] of Object.values(esleme)) {
    if (dosya.startsWith("cekirdek/")) cekirdek.set(dosya, kullanan);
  }
  const ortakDegisiklik = GUNLUK.filter(([, k]) => esleme[k][0].startsWith("cekirdek/"));
  const dokunulan = new Set(ortakDegisiklik.map(([, k]) => esleme[k][0]));
  const tekKullanan = [...cekirdek].filter(([, k]) => k.length === 1);
  console.log(`${ad}: ${cekirdek.size} cekirdek dosyasi`);
  for (const [dosya, kullanan] of [...cekirdek].sort()) {
    console.log(`  ${dosya.padEnd(20)} kullanan baglam: ${kullanan.join(", ")}`);
  }
  console.log(`  ortak karar gerektiren degisiklik = ${ortakDegisiklik.length} / ${GUNLUK.length}`);
  console.log(`  iki takimin birlikte duzenledigi dosya = ${dokunulan.size}`);
  console.log(`  tek baglamin kullandigi cekirdek dosyasi = ${tekKullanan.length} / ${cekirdek.size}`);
  for (const b of ["ucretlendirme", "teslimat"]) {
    const yalniz = GUNLUK.filter(([, k]) => !esleme[k][0].startsWith("cekirdek/") && esleme[k][1].includes(b));
    console.log(`  ${b.padEnd(14)} tek basina yapabildigi degisiklik = ${yalniz.length}`);
  }
}
```

```sh
node ortak-say.mjs
```

```
kucuk cekirdek: 1 cekirdek dosyasi
  cekirdek/olcu.mjs    kullanan baglam: ucretlendirme, teslimat
  ortak karar gerektiren degisiklik = 4 / 16
  iki takimin birlikte duzenledigi dosya = 1
  tek baglamin kullandigi cekirdek dosyasi = 0 / 1
  ucretlendirme  tek basina yapabildigi degisiklik = 7
  teslimat       tek basina yapabildigi degisiklik = 5
buyuk cekirdek: 3 cekirdek dosyasi
  cekirdek/durum.mjs   kullanan baglam: teslimat
  cekirdek/olcu.mjs    kullanan baglam: ucretlendirme, teslimat
  cekirdek/tarife.mjs  kullanan baglam: ucretlendirme
  ortak karar gerektiren degisiklik = 11 / 16
  iki takimin birlikte duzenledigi dosya = 3
  tek baglamin kullandigi cekirdek dosyasi = 2 / 3
  ucretlendirme  tek basina yapabildigi degisiklik = 3
  teslimat       tek basina yapabildigi degisiklik = 2
```

Küçük çekirdekte on altı değişikliğin dördü ortak karar gerektiriyor; on ikisi bir takımın
kendi başına yapabildiği değişiklik. Çekirdek bir dosyadan üç dosyaya çıktığında ortak karar
gerektiren değişiklik dörtten on bire yükseliyor, iki takımın kendi başına yapabildiği
değişiklik on ikiden beşe iniyor. Ortaklık oranı yüzde yirmi beşten yüzde altmış dokuza çıkmış
durumda.

Son satırlardan biri kararın yanlış tarafını gösteriyor: büyük çekirdekteki üç dosyanın ikisini
yalnız tek bağlam kullanıyor. `cekirdek/tarife.mjs` yalnız ücretlendirmenin, `cekirdek/durum.mjs`
yalnız teslimatın kavramı; ikisi de ortak sahiplikte durduğu için sahibi olmayan takımın onayını
bekliyor. Çekirdeğin ölçütü buradan çıkar: bir dosya çekirdekte ancak iki bağlam da onu
gerçekten kullanıyorsa durur.

#### Çekirdeğe Neyin Girdiği

Küçük çekirdekteki iki kavram tesadüfen orada değil. Ölçü birimi ve hacim ağırlığının esas
ağırlığa çevrilme kuralı iki bağlamda **aynı** şeyi anlatır: ücretlendirme onunla fiyat
hesaplar, teslimat onunla araç seçer, ama tanım tektir ve iki tarafın da aynı sayıya varması
gerekir. İki tanım tutmak burada bir kazanç değil, bir tutarsızlık kaynağıdır.

Tarife kademesi ile durum kümesi böyle değildir. Kademelerin nerede olduğu ücretlendirmenin
kararıdır; teslimat onu bilmek zorunda değildir. Durum kümesinin hangi değerleri taşıdığı
teslimatın kararıdır; ücretlendirme için gönderi ya fiyatlanmıştır ya değildir. Bu iki kavramın
çekirdeğe alınması bir modelleme kararı değil, dosyayı ortada bırakma kolaylığıdır.

Ölçüt bir cümleyle yazılabilir: çekirdeğe yalnız iki bağlamın da **aynı anlamla** kullandığı ve
ayrı tanımlandığında tutarsızlık üretecek kavramlar girer.

#### Uyum Sağlayan

İkinci kalıp bambaşka bir durumdan çıkar. Aşağı akış takımının yukarı akışın modelini
değiştirme gücü yoktur; yukarı akış kendi kararlarını kendi verir, aşağı akışın ihtiyaçları
planına girmez. Bu durumda aşağı akışın iki seçeneği vardır: çeviri yazmak ya da yukarı akışın
modelini olduğu gibi benimsemek. İkincisi **uyum sağlayan** kalıbıdır.

Yukarı akış ücretlendirme bağlamıdır ve tarife satırını kendi adlarıyla verir.

```sh
mkdir -p yukari cevirili uyumlu
```

```js
// yukari/tarife.mjs — yukari akis: ucretlendirme baglaminin tarife modeli
const KADEMELER = [
  { ustEsasGram: 1000, kademeUcretiKurus: 4990 },
  { ustEsasGram: 5000, kademeUcretiKurus: 8490 },
  { ustEsasGram: 30000, kademeUcretiKurus: 24990 },
];
const BOLGE_CARPANI = { yakin: 1, orta: 1.35, uzak: 1.8 };

export function tarifeSatiri(esasGram, ucretBolgesi) {
  const k = KADEMELER.find((x) => esasGram <= x.ustEsasGram);
  return k === undefined ? null
    : { kademeUcretiKurus: k.kademeUcretiKurus, bolgeCarpani: BOLGE_CARPANI[ucretBolgesi] ?? 1.8 };
}
```

Aşağı akış teslimat bağlamıdır ve araç seçimi için gönderinin taşıma değerine ihtiyaç duyar.
Uyum sağlayan düzende tek dosya yazıyor ve yukarı akışın adlarını doğrudan kullanıyor.

```js
// uyumlu/arac.mjs — asagi akis, yukari akisin modelini oldugu gibi benimsiyor
import { tarifeSatiri } from "../yukari/tarife.mjs";

export function aracSec(esasGram, ucretBolgesi) {
  const t = tarifeSatiri(esasGram, ucretBolgesi);
  if (t === null) return "elden-teslim";
  const tutar = t.kademeUcretiKurus * t.bolgeCarpani;
  if (tutar >= 30000) return "kapali-kasa";
  return esasGram > 5000 ? "kamyonet" : "motosiklet";
}
```

Çevirili düzende aynı iş iki dosyaya bölünüyor; sınır dosyası yukarı akışın adlarını teslimatın
diline çeviriyor.

```js
// cevirili/tasima-ceviri.mjs — yukari akisin adlarini teslimatin diline cevirir
import { tarifeSatiri } from "../yukari/tarife.mjs";

export function tasimaKosullari(esasGram, ucretBolgesi) {
  const t = tarifeSatiri(esasGram, ucretBolgesi);
  if (t === null) return { fiyatlanabilir: false, degerKurus: 0, yukGram: esasGram };
  return { fiyatlanabilir: true, degerKurus: t.kademeUcretiKurus * t.bolgeCarpani, yukGram: esasGram };
}
```

```js
// cevirili/arac.mjs — asagi akis cekirdegi: yalniz teslimatin kendi dilini konusur
import { tasimaKosullari } from "./tasima-ceviri.mjs";

export function aracSec(yukGram, bolge) {
  const k = tasimaKosullari(yukGram, bolge);
  if (!k.fiyatlanabilir) return "elden-teslim";
  if (k.degerKurus >= 30000) return "kapali-kasa";
  return k.yukGram > 5000 ? "kamyonet" : "motosiklet";
}
```

#### Uyumun Kazancı ve Bedeli

Ölçü üç sayıdan oluşuyor: dosya ile satır sayısı, yukarı akışın adlarının aşağı akış
çekirdeğinde geçip geçmediği ve aşağı akışın kendi sözlüğünde kaç ad kaldığı.

```js
// uyum-say.mjs — uyum saglayan ile cevirili duzenin dosya, satir ve yukari akis adi sayimi
import { readFileSync } from "node:fs";
import { aracSec as uyumluSec } from "./uyumlu/arac.mjs";
import { aracSec as cevriliSec } from "./cevirili/arac.mjs";

const YUKARI_ADLAR = ["tarifeSatiri", "kademeUcretiKurus", "bolgeCarpani", "ucretBolgesi", "esasGram"];
const ASAGI_ADLAR = ["fiyatlanabilir", "degerKurus", "yukGram", "bolge"];
const gecen = (m, adlar) => adlar.filter((a) => new RegExp(`\\b${a}\\b`).test(m));

for (const [ad, dosyalar] of [
  ["uyum saglayan", ["uyumlu/arac.mjs"]],
  ["cevirili", ["cevirili/tasima-ceviri.mjs", "cevirili/arac.mjs"]],
]) {
  const satir = dosyalar.reduce((t, d) => t + readFileSync(d, "utf8").trimEnd().split("\n").length, 0);
  console.log(`${ad}: ${dosyalar.length} dosya, ${satir} satir`);
  let cekirdek = 0, cekirdekSizan = 0;
  for (const d of dosyalar) {
    const metin = readFileSync(d, "utf8");
    const g = gecen(metin, YUKARI_ADLAR), y = gecen(metin, ASAGI_ADLAR);
    const sinir = d.includes("-ceviri.mjs");
    if (!sinir) { cekirdek += 1; if (g.length > 0) cekirdekSizan += 1; }
    console.log(`  ${d.padEnd(28)} yukari akis adi ${g.length}, kendi adi ${y.length}`);
  }
  console.log(`  yukari akis adi gecen cekirdek dosyasi = ${cekirdekSizan} / ${cekirdek}`);
}

const ORNEKLER = [[800, "orta"], [4000, "uzak"], [20000, "yakin"], [20000, "uzak"], [50000, "orta"]];
let ayrilan = 0;
for (const [gram, bolge] of ORNEKLER) {
  const a = uyumluSec(gram, bolge), b = cevriliSec(gram, bolge);
  if (a !== b) ayrilan += 1;
  console.log(`${String(gram).padStart(5)} g  ${bolge.padEnd(5)} uyumlu ${a.padEnd(13)} cevirili ${b}`);
}
console.log(`ayrilan arac = ${ayrilan} / ${ORNEKLER.length}`);
```

```sh
node uyum-say.mjs
```

```
uyum saglayan: 1 dosya, 10 satir
  uyumlu/arac.mjs              yukari akis adi 5, kendi adi 0
  yukari akis adi gecen cekirdek dosyasi = 1 / 1
cevirili: 2 dosya, 17 satir
  cevirili/tasima-ceviri.mjs   yukari akis adi 5, kendi adi 3
  cevirili/arac.mjs            yukari akis adi 0, kendi adi 4
  yukari akis adi gecen cekirdek dosyasi = 0 / 1
  800 g  orta  uyumlu motosiklet    cevirili motosiklet
 4000 g  uzak  uyumlu motosiklet    cevirili motosiklet
20000 g  yakin uyumlu kamyonet      cevirili kamyonet
20000 g  uzak  uyumlu kapali-kasa   cevirili kapali-kasa
50000 g  orta  uyumlu elden-teslim  cevirili elden-teslim
ayrilan arac = 0 / 5
```

Kazanç somut: bir dosya ve yedi satır az. Bedel de somut. Uyum sağlayan düzende aşağı akışın tek
çekirdek dosyası yukarı akışın beş adını taşıyor ve kendi sözlüğünden hiçbir ad taşımıyor
(kendi adı 0). Çevirili düzende çekirdek dosya yukarı akıştan tek ad taşımıyor, kendi
sözlüğünden dört ad taşıyor. Beş örnekte seçilen araç iki düzende de aynı.

Bu, uyum sağlayanı bir hata olarak okumak için yeterli değildir. Aşağı akış yukarı akışın
modelini gerçekten uygun buluyorsa, çeviri yalnız iki adı birbirine eşleyen bir dosyadır ve
bedeli karşılıksızdır. Kalıp bir seçim olarak yazılır: aşağı akış, kendi modelini kurma hakkından
sözleşme uyumu karşılığında vazgeçer. Yanlış olan, bu vazgeçmenin farkında olmadan yapılmasıdır
— o zaman aşağı akışın kendi sözlüğü hiç kurulmaz ve yukarı akış her değiştiğinde iş kuralı
dosyası düzenlenir.

#### İki Kalıbın Ortak Noktası

Paylaşılan çekirdek ile uyum sağlayan aynı şeyden vazgeçer: çeviriden. Farkı, vazgeçmenin
karşılığında ne alındığıdır. Paylaşılan çekirdekte iki taraf da karar hakkının bir kısmını
verir ve karşılığında tek bir tanım alır; ölçüsü ortak karar gerektiren değişiklik oranıdır.
Uyum sağlayanda yalnız bir taraf verir ve karşılığında dosya ile satır alır; ölçüsü aşağı akışın
kendi sözlüğünde kalan ad sayısıdır. İkisi de bir bedelle alınır, ikisi de sayılabilir.

#### Özet

- Paylaşılan çekirdek, iki bağlamın ortak sahiplendiği model parçasıdır; çekirdeğe dokunan her
  değişiklik iki takımın kararını gerektirir.
- On altı değişiklikte çekirdek bir dosyadan üç dosyaya çıktığında ortak karar gerektiren
  değişiklik 4/16'dan 11/16'ya yükseldi; takımların tek başına yapabildiği değişiklik 12'den 5'e
  indi.
- Büyük çekirdekteki üç dosyanın ikisini yalnız tek bağlam kullanıyordu; çekirdeğe yalnız iki
  bağlamın da aynı anlamla kullandığı ve ayrı tanımlandığında tutarsızlık üretecek kavramlar
  girer.
- Uyum sağlayan, aşağı akışın çeviriden vazgeçip yukarı akışın modelini olduğu gibi
  benimsemesidir; kazancı 1 dosya ve 7 satır oldu.
- Bedeli aynı ölçekte göründü: uyum sağlayan düzende aşağı akış çekirdeği yukarı akıştan 5 ad
  taşıdı ve kendi sözlüğünden 0 ad taşıdı; çevirili düzende bu sayılar 0 ve 4 oldu. Beş örnekte
  seçilen araç iki düzende de aynıydı.
- İki kalıp da çeviriden vazgeçer; paylaşılan çekirdek karşılığında tek tanım alır, uyum sağlayan
  karşılığında dosya ve satır alır.

#### Sonraki Adım

Buraya kadar bağlamların sınırı çizildi, aralarındaki ilişkiler adlandırıldı ve üç işbirliği
biçiminin bedeli sayıldı. Bütün bu kararlar bir soruyu yanıtsız bıraktı: hangi bağlama ne kadar
tasarım emeği verilecek? Ölçü birimi tanımı ile ücret kuralı aynı özenle mi modellenir, taşıyıcı
sağlayıcısının durum çevirisi ile indirim kuralı aynı sınıfa mı girer? Alan kendi içinde eşit
değildir: bir kısmı kurumun kazandığı işin kendisidir, bir kısmı onu ayakta tutar, bir kısmı ise
her kurumda aynıdır. Sonraki ders bu üçlü ayrımı kurar, değişiklik günlüğünden hangi parçanın
gerçekten ne kadar değiştiğini çıkarır ve emeğin dağılımını değişimin dağılımıyla karşılaştırıp
yanlış yere harcanan emeği sayar.

### Alt Alanlar

Source: https://academia.sh/tr/kurslar/alan-odakli-tasarim/alt-alanlar

Çekirdek, destekleyici ve genel alt alanların ayrılması: alt alan ile alan katmanının farkının kurulması, alt alan başına satır ve değişmez sayısından emek payının çıkarılması, değişiklik günlüğünden değişim payının hesaplanması ve genel alt alan inceltildiğinde emek ile değişim dağılımının hizalanmasının ölçülmesi.

Önceki dört ders bağlamların sınırını çizdi, aralarındaki ilişkileri adlandırdı ve üç
işbirliği biçiminin bedelini saydı. Bu kararların hepsi bir soruyu yanıtsız bıraktı: hangi
parçaya ne kadar tasarım emeği verilecek? Ölçü biriminin tanımı ile ücret kuralı aynı özenle mi
modellenir? Posta kodu biçim denetimi ile sözleşmeli indirim kuralı aynı incelikle mi
yazılır?

Alan kendi içinde eşit değildir. Bir kısmı kurumun kazandığı işin kendisidir, bir kısmı onu
ayakta tutar, bir kısmı ise her kurumda aynıdır. Bu üç kısma **alt alan** (subdomain) denir ve
ayrım bir sınıflandırma egzersizi değil, emek dağıtma kararıdır. Bu ders emeği satır ve değişmez
sayısıyla, ihtiyacı değişiklik günlüğüyle ölçer ve ikisinin oranını çıkarır.

#### Alt Alan, Alan Katmanı ve Sınırlı Bağlam

Üç kavram birbirine benzer adlar taşıyor; ayrımı baştan yapmak gerekiyor.

**Alan katmanı** (Veri Erişim Katmanı ve İş Mantığı kursunda kurulmuştu) kodun düzenlenişindeki
bir yerdir: iş
kurallarının durduğu, sunum ve altyapıya bakmayan katman. **Alt alan** kodda değil, işin
kendisindedir: kurumun yaptığı işin bir bölümü. Bir alt alanın kodu genellikle birkaç katmana
dağılır — ücretlendirmenin kuralı alan katmanında, tarife tablosunun okunması altyapı
katmanındadır — ve bir alan katmanı birden çok alt alanın modelini barındırabilir.

**Sınırlı bağlam** ise çözüm tarafındadır: bir modelin geçerli olduğu alan. Alt alan problemin
bölümü, sınırlı bağlam çözümün bölümüdür. İkisinin birebir eşlenmesi iyi bir hedeftir ama
zorunlu değildir; bir bağlam iki alt alanın modelini taşıyabilir, bir alt alan iki bağlama
yayılabilir. Bu dersin ölçtüğü şey bağlam sınırı değil, alt alan başına harcanan emektir.

#### Üç Tür

**Çekirdek alt alan** (core subdomain), kurumun rakiplerinden ayrıldığı yerdir. Ücretlendirme
kitaplığında ücret kuralı ile sözleşmeli indirim kuralı budur: kurum işi bu iki kuralla kazanır,
ikisi de kurumun kendi kararıdır ve dışarıda karşılığı yoktur.

**Destekleyici alt alan** (supporting subdomain), iş için gerekli ama fark yaratmayan yerdir.
Servis seviyesinden teslim penceresinin türetilmesi böyledir: bulunmazsa iş yürümez, ama kimse
onun yüzünden bu kurumu seçmez.

**Genel alt alan** (generic subdomain), her kurumda aynı olan yerdir. Posta kodu biçiminin
doğrulanması ve kuruş aritmetiği böyledir. Ayırt edici ölçüt şudur: bir başkasının çözümü
olduğu gibi alınabiliyorsa o parça genel alt alandır.

Ayrımın kararı emek dağıtmaktır: çekirdek alt alana en fazla modelleme emeği verilir, genel alt
alana en az. Bu bir tercih değil, ölçülebilir bir hizadır — ve pratikte sık ters döner.

#### Kalın Modellenmiş Genel Alt Alan

Aşağıdaki iki dosya, genel alt alanın özenle modellenmiş hâlini gösteriyor: posta kodu bir değer
nesnesi olarak kurulmuş, beş değişmez taşıyor, eşitlik karşılaştırması ve metne çevirme
yöntemleri var. Para da öyle: dört değişmez, para birimi alanı, aritmetik yöntemler.

```sh
mkdir -p alan genel-kalin genel-ince
```

```js
// genel-kalin/posta-kodu.mjs — genel alt alan, kalin modellenmis: posta kodu degeri
const IL_ONEKLERI = new Set(["01", "06", "34", "35", "65", "81"]);

export class PostaKodu {
  #metin;
  constructor(metin) {
    const ihlal = PostaKodu.dogrula(metin);
    if (ihlal.length > 0) throw new RangeError(`posta kodu gecersiz: ${ihlal.join(",")}`);
    this.#metin = String(metin).trim();
  }
  static dogrula(metin) {
    const ihlal = [];
    const d = typeof metin === "string" ? metin.trim() : "";
    if (d.length === 0) ihlal.push("G1-bos");
    if (d.length !== 5) ihlal.push("G2-uzunluk");
    if (/\D/.test(d)) ihlal.push("G3-rakam");
    if (d.startsWith("00")) ihlal.push("G4-sifir-onek");
    if (d.length === 5 && !/\D/.test(d) && !IL_ONEKLERI.has(d.slice(0, 2))) ihlal.push("G5-il");
    return ihlal;
  }
  get onEk() { return this.#metin.slice(0, 2); }
  toString() { return this.#metin; }
  esitMi(oteki) { return oteki instanceof PostaKodu && oteki.toString() === this.#metin; }
}

export const onEk = (metin) => (PostaKodu.dogrula(metin).length > 0 ? null : new PostaKodu(metin).onEk);
```

```js
// genel-kalin/para-birimi.mjs — genel alt alan, kalin modellenmis: para degeri
export class Para {
  #kurus; #birim;
  constructor(kurus, birim = "TRY") {
    const ihlal = Para.dogrula(kurus, birim);
    if (ihlal.length > 0) throw new RangeError(`para gecersiz: ${ihlal.join(",")}`);
    this.#kurus = kurus; this.#birim = birim;
  }
  static dogrula(kurus, birim) {
    const ihlal = [];
    if (!Number.isInteger(kurus)) ihlal.push("G1-tam-sayi");
    if (kurus < 0) ihlal.push("G2-negatif");
    if (kurus > 1e12) ihlal.push("G3-tasma");
    if (birim !== "TRY") ihlal.push("G4-birim");
    return ihlal;
  }
  carp(oran) { return new Para(Math.round(this.#kurus * oran), this.#birim); }
  yuvarla(adim) { return new Para(Math.round(this.#kurus / adim) * adim, this.#birim); }
  get kurus() { return this.#kurus; }
}

export const carp = (kurus, oran) => new Para(kurus).carp(oran).kurus;
export const yuvarla = (kurus, adim) => new Para(kurus).yuvarla(adim).kurus;
```

İki dosyanın da kötü yazılmış olduğunu söylemek yanlış olur. İkisi de düzgün değer
nesneleridir, değişmezleri açıktır, kimlik yerine değerle karşılaştırılırlar. Sorun kalitede
değil, yerdedir.

#### Çekirdek ve Destekleyici

Çekirdek alt alanın iki dosyası genel alt alanı dışarıdan alıyor: `genel` adlı bir nesne
üzerinden ön ek çıkarma, çarpma ve yuvarlama işlemlerini çağırıyorlar. Bu, iki genel sürümün
aynı kuralla sınanabilmesini sağlıyor.

```js
// alan/ucret-kurali.mjs — cekirdek alt alan: kurumun kazandigi isin kurali
const KADEMELER = [
  { ustEsasGram: 1000, ucretKurus: 4990 },
  { ustEsasGram: 5000, ucretKurus: 8490 },
  { ustEsasGram: 30000, ucretKurus: 24990 },
];
const BOLGE_CARPANI = { "01": 1.35, "06": 1.35, "34": 1, "35": 1.35, "65": 1.8, "81": 1.8 };
const ASGARI_KURUS = 3990;

export function dogrula(gonderi) {
  const ihlal = [];
  if (!(Number.isInteger(gonderi.esasGram) && gonderi.esasGram > 0)) ihlal.push("C1-esas-agirlik");
  if (gonderi.esasGram > 30000) ihlal.push("C2-kademe-disi");
  if (typeof gonderi.postaKodu !== "string") ihlal.push("C3-adres");
  return ihlal;
}

export function ucretHesapla(gonderi, genel) {
  if (dogrula(gonderi).length > 0) return null;
  const onEk = genel.onEk(gonderi.postaKodu);
  if (onEk === null) return null;
  const kademe = KADEMELER.find((k) => gonderi.esasGram <= k.ustEsasGram);
  const ham = genel.carp(kademe.ucretKurus, BOLGE_CARPANI[onEk]);
  return Math.max(genel.yuvarla(ham, 50), ASGARI_KURUS);
}
```

```js
// alan/indirim-kurali.mjs — cekirdek alt alan: sozlesmeli indirim kurali
const HACIM_KADEMELERI = [
  { aylikGonderi: 500, oran: 0.05 },
  { aylikGonderi: 2000, oran: 0.12 },
  { aylikGonderi: Infinity, oran: 0.2 },
];
const UST_SINIR = 0.3;

export function dogrula(sozlesme) {
  const ihlal = [];
  if (!Number.isInteger(sozlesme.aylikGonderi) || sozlesme.aylikGonderi < 0) ihlal.push("C1-hacim");
  if (!(sozlesme.pazarlikOrani >= 0 && sozlesme.pazarlikOrani <= UST_SINIR)) ihlal.push("C2-pazarlik");
  if (sozlesme.pazarlikOrani > 0 && sozlesme.sozlesmeNo === null) ihlal.push("C3-sozlesmesiz-pazarlik");
  return ihlal;
}

export function indirimliUcret(brutKurus, sozlesme, genel) {
  if (dogrula(sozlesme).length > 0) return null;
  const kademe = HACIM_KADEMELERI.find((k) => sozlesme.aylikGonderi <= k.aylikGonderi);
  const oran = Math.min(kademe.oran + sozlesme.pazarlikOrani, UST_SINIR);
  return genel.carp(brutKurus, 1 - oran);
}
```

```js
// alan/teslim-penceresi.mjs — destekleyici alt alan: servis seviyesinden teslim penceresi
const PENCERE_SAAT = { SS1: 24, SS2: 48, SS3: 72 };

export function dogrula(servisSeviyeKodu) {
  const ihlal = [];
  if (typeof servisSeviyeKodu !== "string") ihlal.push("D1-tip");
  if (PENCERE_SAAT[servisSeviyeKodu] === undefined) ihlal.push("D2-taninmayan-seviye");
  return ihlal;
}

export const pencereSaat = (servisSeviyeKodu) =>
  (dogrula(servisSeviyeKodu).length > 0 ? 72 : PENCERE_SAAT[servisSeviyeKodu]);
```

#### Genel Alt Alanın İnceltilmesi

İnce sürüm genel alt alandan yalnız çekirdeğin gerçekten ihtiyaç duyduğu kadarını bırakıyor: ön
ek çıkarma ve iki aritmetik işlem. Değer nesnesi, eşitlik karşılaştırması ve para birimi alanı
yok; çünkü kitaplık bunları hiç kullanmıyordu.

```js
// genel-ince/posta-kodu.mjs — genel alt alan, ince: yalniz alanin ihtiyaci kadar
const IL_ONEKLERI = new Set(["01", "06", "34", "35", "65", "81"]);

export function onEk(metin) {
  const d = typeof metin === "string" ? metin.trim() : "";
  if (!/^\d{5}$/.test(d) || d.startsWith("00") || !IL_ONEKLERI.has(d.slice(0, 2))) return null;
  return d.slice(0, 2);
}
```

```js
// genel-ince/para-birimi.mjs — genel alt alan, ince: yalniz alanin ihtiyaci kadar
const gecerli = (kurus) => Number.isInteger(kurus) && kurus >= 0 && kurus <= 1e12;

export const carp = (kurus, oran) => {
  if (!gecerli(kurus)) throw new RangeError("para gecersiz");
  return Math.round(kurus * oran);
};
export const yuvarla = (kurus, adim) => Math.round(kurus / adim) * adim;
```

#### Emeğin ve Değişimin Dağılımı

Emek iki göstergeyle ölçülüyor: alt alan başına satır sayısı ve tanımlanmış değişmez sayısı.
İhtiyaç, yirmi model değişikliğinin alt alanlara dağılımıyla ölçülüyor. Üçüncü sayı ikisinin
oranıdır ve biri kesin bir eşiktir: emek payı değişim payına eşitse oran 1'dir.

```js
// emek-say.mjs — alt alan basina emek payi, degisim payi ve ikisinin orani
import { readFileSync } from "node:fs";

const ALT_ALAN = {
  "alan/ucret-kurali.mjs": "cekirdek", "alan/indirim-kurali.mjs": "cekirdek",
  "alan/teslim-penceresi.mjs": "destekleyici",
  "genel-kalin/posta-kodu.mjs": "genel", "genel-kalin/para-birimi.mjs": "genel",
  "genel-ince/posta-kodu.mjs": "genel", "genel-ince/para-birimi.mjs": "genel",
};

const GUNLUK = [
  ["ucret-kurali", 6], ["indirim-kurali", 5], ["teslim-penceresi", 4],
  ["posta-kodu", 3], ["para-birimi", 2],
];
const KAVRAM_ALT_ALAN = { "ucret-kurali": "cekirdek", "indirim-kurali": "cekirdek",
  "teslim-penceresi": "destekleyici", "posta-kodu": "genel", "para-birimi": "genel" };
const TOPLAM_DEGISIKLIK = GUNLUK.reduce((t, [, n]) => t + n, 0);

const TURLER = ["cekirdek", "destekleyici", "genel"];
const yuzde = (a, b) => `${((a / b) * 100).toFixed(1).padStart(5)}%`;

for (const [ad, dosyalar] of [
  ["kalin genel", ["alan/ucret-kurali.mjs", "alan/indirim-kurali.mjs", "alan/teslim-penceresi.mjs",
                   "genel-kalin/posta-kodu.mjs", "genel-kalin/para-birimi.mjs"]],
  ["ince genel", ["alan/ucret-kurali.mjs", "alan/indirim-kurali.mjs", "alan/teslim-penceresi.mjs",
                  "genel-ince/posta-kodu.mjs", "genel-ince/para-birimi.mjs"]],
]) {
  const satir = {}, degismez = {};
  for (const t of TURLER) { satir[t] = 0; degismez[t] = 0; }
  for (const d of dosyalar) {
    const metin = readFileSync(d, "utf8");
    const t = ALT_ALAN[d];
    satir[t] += metin.trimEnd().split("\n").length;
    degismez[t] += (metin.match(/ihlal\.push/g) ?? []).length;
  }
  const toplamSatir = TURLER.reduce((s, t) => s + satir[t], 0);
  const toplamDegismez = TURLER.reduce((s, t) => s + degismez[t], 0);
  console.log(`${ad}: ${toplamSatir} satir, ${toplamDegismez} degismez, ${TOPLAM_DEGISIKLIK} degisiklik`);
  for (const t of TURLER) {
    const dgs = GUNLUK.filter(([k]) => KAVRAM_ALT_ALAN[k] === t).reduce((s, [, n]) => s + n, 0);
    const emekPayi = satir[t] / toplamSatir, degisimPayi = dgs / TOPLAM_DEGISIKLIK;
    console.log(`  ${t.padEnd(12)} satir ${String(satir[t]).padStart(3)} (${yuzde(satir[t], toplamSatir)})`
      + `  degismez ${String(degismez[t]).padStart(2)} (${yuzde(degismez[t], toplamDegismez)})`
      + `  degisim ${String(dgs).padStart(2)} (${yuzde(dgs, TOPLAM_DEGISIKLIK)})`
      + `  emek/degisim ${(emekPayi / degisimPayi).toFixed(2)}`);
  }
}
```

```sh
node emek-say.mjs
```

```
kalin genel: 108 satir, 17 degismez, 20 degisiklik
  cekirdek     satir  47 ( 43.5%)  degismez  6 ( 35.3%)  degisim 11 ( 55.0%)  emek/degisim 0.79
  destekleyici satir  12 ( 11.1%)  degismez  2 ( 11.8%)  degisim  4 ( 20.0%)  emek/degisim 0.56
  genel        satir  49 ( 45.4%)  degismez  9 ( 52.9%)  degisim  5 ( 25.0%)  emek/degisim 1.81
ince genel: 75 satir, 8 degismez, 20 degisiklik
  cekirdek     satir  47 ( 62.7%)  degismez  6 ( 75.0%)  degisim 11 ( 55.0%)  emek/degisim 1.14
  destekleyici satir  12 ( 16.0%)  degismez  2 ( 25.0%)  degisim  4 ( 20.0%)  emek/degisim 0.80
  genel        satir  16 ( 21.3%)  degismez  0 (  0.0%)  degisim  5 ( 25.0%)  emek/degisim 0.85
```

Kalın düzenin ilk satırı hizasızlığı veriyor: kodun yüzde 45,4'ü genel alt alanda, oysa
değişikliklerin yalnız yüzde 25'i oraya dokunuyor. Çekirdek alt alan tersinden aynı durumda:
değişikliklerin yüzde 55'ini alıyor, kodun yüzde 43,5'ünü tutuyor. Oranlar 1,81 ve 0,79;
aradaki fark iki kattan fazla.

Değişmez sayısı daha keskin bir hizasızlık gösteriyor. Genel alt alan dokuz değişmez tanımlamış,
çekirdek alt alan altı. Kurumun işi kazandığı iki kural için altı değişmez yazılırken, her
kurumda aynı olan posta kodu ve para için dokuz değişmez yazılmış durumda.

İnce düzende üç oran 1,14 – 0,80 – 0,85 aralığına giriyor; genel alt alanın satır payı yüzde
45,4'ten yüzde 21,3'e düşüyor. Genel alt alanın tanımladığı alan değişmezi kalmıyor: ince
sürümdeki iki denetim birer koruma cümlesidir, alanın kuralı değil. Toplam kod 108 satırdan 75
satıra iniyor ve boşalan emek çekirdeğe kalıyor.

#### Çekirdek Değişmedi mi

İnceltmenin çekirdeği bozmadığı iddia edilmez, sınanır. Aynı çekirdek kuralları iki genel sürümle
de koşuyor.

```js
// kosum.mjs — genel alt alan incelestirilince cekirdegin sonuclari degisti mi
import { ucretHesapla } from "./alan/ucret-kurali.mjs";
import { indirimliUcret } from "./alan/indirim-kurali.mjs";
import * as kalinPosta from "./genel-kalin/posta-kodu.mjs";
import * as kalinPara from "./genel-kalin/para-birimi.mjs";
import * as incePosta from "./genel-ince/posta-kodu.mjs";
import * as incePara from "./genel-ince/para-birimi.mjs";

const KALIN = { onEk: kalinPosta.onEk, carp: kalinPara.carp, yuvarla: kalinPara.yuvarla };
const INCE = { onEk: incePosta.onEk, carp: incePara.carp, yuvarla: incePara.yuvarla };

const GONDERILER = [
  { esasGram: 800, postaKodu: "34710" }, { esasGram: 4000, postaKodu: "06800" },
  { esasGram: 20000, postaKodu: "65100" }, { esasGram: 20000, postaKodu: "99999" },
  { esasGram: 0, postaKodu: "34710" }, { esasGram: 40000, postaKodu: "34710" },
];
const SOZLESMELER = [
  { aylikGonderi: 1500, pazarlikOrani: 0.05, sozlesmeNo: "S-7" },
  { aylikGonderi: 100, pazarlikOrani: 0, sozlesmeNo: null },
  { aylikGonderi: 100, pazarlikOrani: 0.1, sozlesmeNo: null },
];

let ayrilan = 0;
for (const g of GONDERILER) {
  const a = ucretHesapla(g, KALIN), b = ucretHesapla(g, INCE);
  if (a !== b) ayrilan += 1;
  console.log(`${String(g.esasGram).padStart(5)} g ${g.postaKodu}  kalin ${String(a).padStart(5)}  ince ${String(b).padStart(5)}`);
}
for (const s of SOZLESMELER) {
  const a = indirimliUcret(45000, s, KALIN), b = indirimliUcret(45000, s, INCE);
  if (a !== b) ayrilan += 1;
  console.log(`hacim ${String(s.aylikGonderi).padStart(4)} pazarlik ${s.pazarlikOrani}  kalin ${String(a).padStart(5)}  ince ${String(b).padStart(5)}`);
}
console.log(`ayrilan sonuc = ${ayrilan} / ${GONDERILER.length + SOZLESMELER.length}`);
```

```sh
node kosum.mjs
```

```
  800 g 34710  kalin  5000  ince  5000
 4000 g 06800  kalin 11450  ince 11450
20000 g 65100  kalin 45000  ince 45000
20000 g 99999  kalin  null  ince  null
    0 g 34710  kalin  null  ince  null
40000 g 34710  kalin  null  ince  null
hacim 1500 pazarlik 0.05  kalin 37350  ince 37350
hacim  100 pazarlik 0  kalin 42750  ince 42750
hacim  100 pazarlik 0.1  kalin  null  ince  null
ayrilan sonuc = 0 / 9
```

Dokuz durumda da sonuç aynı: geçerli üç ücret, tanınmayan il ön eki, sıfır ağırlık, kademe dışı
ağırlık, iki indirim hesabı ve sözleşmesiz pazarlık denemesi dâhil. Kalın sürümün beş
değişmezinin dördü hiçbir çağrıda karşılığını vermedi, çünkü çekirdek onları hiç
sınamıyordu.

#### Ayrımın Sınırı

Alt alan türü sabit bir etiket değildir. Bir kurum için genel olan, başka bir kurum için çekirdek
olabilir; adres çözümleme, teslimat işini adres kalitesiyle kazanan bir kurumda genel alt alan
değildir. Ayrım, alanın kendisinden değil kurumun rekabet ettiği yerden okunur ve zamanla
değişir.

İkinci sınır ölçünün kendisindedir. Satır sayısı emeğin kaba bir göstergesidir; bir kuralın
zorluğu satır sayısıyla artmaz. Bu yüzden ölçü tek başına bir karar vermez, bir soru üretir:
neden en çok değişen parça en az modelleme taşıyor?

#### Özet

- Alt alan, işin bir bölümüdür; alan katmanı kodun düzenlenişindeki bir yer, sınırlı bağlam ise
  bir modelin geçerlilik alanıdır. Bir alt alanın kodu birkaç katmana dağılır.
- Üç tür ayırt edilir: çekirdek alt alan kurumun işi kazandığı yer, destekleyici alt alan gerekli
  ama fark yaratmayan yer, genel alt alan başkasının çözümü olduğu gibi alınabilen yerdir.
- Kalın düzende kodun yüzde 45,4'ü genel alt alandaydı ama değişikliklerin yüzde 25'i oraya
  dokunuyordu; çekirdek alt alan değişikliklerin yüzde 55'ini alıp kodun yüzde 43,5'ini
  tutuyordu. Oranlar 1,81 ile 0,79.
- Değişmez sayımı hizasızlığı doğruladı: genel alt alan dokuz, çekirdek alt alan altı değişmez
  tanımlamıştı.
- Genel alt alan inceltildiğinde üç oran 1,14 – 0,80 – 0,85 aralığına girdi, toplam kod 108
  satırdan 75 satıra indi ve dokuz durumda çekirdeğin sonuçları değişmedi.
- Alt alan türü kuruma göredir ve zamanla değişir; satır sayısı emeğin kaba göstergesidir, ölçü
  karar vermez, soru üretir.

#### Sonraki Adım

Bu konu modelin **nerede** geçerli olduğunu kurdu: sınır bir adın anlamının değiştiği yerden
geçiyor, bağlamlar arası ilişkiler yön ve türle adlandırılıyor, dış modelin sızması bir katmanla
durduruluyor, alt alan ayrımı hangi parçaya ne kadar emek verileceğini söylüyor. Açıkta kalan
soru bir bağlamın **içindedir**: o bağlamdaki model bir uygulamanın içine nasıl yerleşir? Bir
istek nereden girer, hangi sırayla hangi nesneye iner, işlemin sınırını kim çizer, sonuç kime
döner? Buraya kadarki bütün örneklerde bu düzen örtük kaldı; kurallar doğrudan çağrıldı. Sonraki
konu uygulama katmanının sorumluluğunu tanımlar ve ilk ders kullanım senaryolarını bu katmanın
birimi olarak alır: alan modelini çağıran, işlemi başlatıp bitiren, ama iş kuralını kendisi
taşımayan ince bir kabuk.

## Uygulama Mimarisine Bağlanma

Alan modelinin bir uygulamanın içine yerleştirilmesi: uygulama katmanının sorumluluğunun kullanım senaryolarıyla tanımlanması, alanın dışa bağlanma noktalarının bağdaştırıcılarla ayrılması, yazma ve okuma modellerinin ayrılmasının bedeli ve kazancı, durum yerine olay dizisinin saklanması ve modelin gerçekleştirimle birlikte evrimi.

### Kullanım Senaryoları

Source: https://academia.sh/tr/kurslar/alan-odakli-tasarim/kullanim-senaryolari

Uygulama katmanının sorumluluğunun kullanım senaryosuyla tanımlanması: aynı senaryonun kararları uygulama katmanında taşıyan ve kararları toplama köküne indiren iki sürümünde karar noktalarının sayılması, alan uzmanının beş cümlesinin kodda kaç yere yazıldığının ölçülmesi ve iki sürümün davranış eşitliğinin sınanması.

Bağlamlar konusu modelin **nerede** geçerli olduğunu kurdu: sınır bir adın anlamının değiştiği
yerden geçiyor, bağlamlar arası ilişkiler yön ve türle adlandırılıyor, dış modelin sızması bir
katmanla durduruluyor, alt alan ayrımı hangi parçaya ne kadar emek verileceğini söylüyor. Açıkta
kalan soru bir bağlamın **içindedir**: o bağlamdaki model bir uygulamanın içine nasıl yerleşir?
Bir istek nereden girer, hangi sırayla hangi nesneye iner, işlemin sınırını kim çizer, sonuç
kime döner?

Bu konu o düzeni kurar ve ilk ders **kullanım senaryosunu** uygulama katmanının birimi olarak
alır: alan modelini çağıran, işlemi başlatıp bitiren, ama iş kuralını kendisi taşımayan ince
bir kabuk. Buraya kadarki örneklerde kurallar doğrudan çağrılmıştı; şimdi çağıranın ne olduğu
sorulacak. Bu bölümün through-line'ı teslimat operasyonu bağlamıdır: `gonderi` burada rotası ve
durum damgaları olan fiziksel pakettir, `rota` aktarma noktalarının sırasıdır, `teslimat` o
paketin operasyon boyunca izlenen durumudur. Ele alınacak senaryo tek bir cümledir: bir aktarma
noktasına varış kaydet.

#### Senaryonun Adımı ile Kararı

**Kullanım senaryosu** (use case) uygulamanın dışarıya açtığı tek bir işlemdir ve iki farklı
şeyi bir arada barındırabilir: adımların **sırası** ile o adımların içindeki **kararlar**.
Sıra teknik bir düzenlemedir — deponun çağrılması, işlem sınırının açılması, sonucun yazılması,
dışarıya dönen sözleşmenin üretilmesi. Karar ise alanın kendi cümlesidir.

Uygulama sınırının tek yerde tanımlanması Tasarım Kalıpları kursundaki servis katmanı
kalıbında, katmanların sorumlulukları ve aralarındaki bağımlılık yönü ise Veri Erişim Katmanı
ve İş Mantığı kursunda kurulmuş ve ölçülmüştü. Burada yeni olan şey katmanın tanımı değil, tek
bir soru: senaryonun içindeki kararlar hangi dosyada duruyor?

Alan uzmanının varış kaydı hakkında söylediği beş cümle şunlardır: kapanmış bir teslimata
varış işlenmez; varış ancak rotada bulunan bir noktaya işlenir; aynı nokta iki kez geçilmiş
sayılmaz; varış rota sırasına uygun olmalıdır; rotanın son noktasına varan gönderi dağıtıma
çıkar. Bu beş cümlenin kodda kaç yere yazıldığı, bu dersin hiza ölçüsüdür.

İki sürüm de aynı kalıcılık düzenini kullanır.

```js
// depo.mjs — teslimat kayitlarini tutan bellek ici depo; iki duzen de bunu kullanir
const TABLO = new Map();

export const teslimatDeposu = {
  bul: (id) => structuredClone(TABLO.get(id) ?? null),
  yaz: (kayit) => { TABLO.set(kayit.id, structuredClone(kayit)); },
};

export function tohum(id) {
  TABLO.set(id, { id, durum: "kabul-edildi", rota: ["34", "06", "35"], gecilen: [] });
}
```

#### Kararlar Uygulama Katmanında

İlk sürümde teslimat kaydı davranışsız bir veri yığınıdır; beş cümlenin hepsi senaryonun
gövdesine yazılmıştır.

```sh
mkdir -p kararlar-yukarida/uygulama
```

```js
// kararlar-yukarida/uygulama/varis-kaydet.mjs — kullanim senaryosu hem sirayi hem kararlari tasiyor
import { teslimatDeposu } from "../../depo.mjs";

export function varisKaydet(id, nokta) {
  const t = teslimatDeposu.bul(id);
  if (t === null) throw new RangeError(`teslimat yok: ${id}`);
  if (t.durum === "teslim-edildi") throw new Error("kapali teslimata varis islenemez");
  if (!t.rota.includes(nokta)) throw new Error(`nokta rotada yok: ${nokta}`);
  if (t.gecilen.includes(nokta)) throw new Error(`nokta zaten gecildi: ${nokta}`);
  const sira = t.rota.indexOf(nokta);
  if (sira !== t.gecilen.length) throw new Error("rota sirasi bozuk");
  t.gecilen.push(nokta);
  t.durum = sira === t.rota.length - 1 ? "dagitimda" : "aktarmada";
  teslimatDeposu.yaz(t);
  return { kimlik: t.id, durum: t.durum, gecilen: t.gecilen.length };
}
```

Uygulamanın ikinci istemcisi aktarma merkezinin gün sonunda gönderdiği okuma listesini toplu
işler. Kararlar senaryonun gövdesinde durduğu için ikinci istemci onları yeniden yazar.

```js
// kararlar-yukarida/uygulama/toplu-varis.mjs — ikinci istemci ayni kararlari yeniden yaziyor
import { teslimatDeposu } from "../../depo.mjs";

export function topluVaris(girdiler) {
  return girdiler.map(({ id, nokta }) => {
    try {
      const t = teslimatDeposu.bul(id);
      if (t === null) throw new RangeError(`teslimat yok: ${id}`);
      if (t.durum === "teslim-edildi") throw new Error("kapali teslimata varis islenemez");
      if (!t.rota.includes(nokta)) throw new Error(`nokta rotada yok: ${nokta}`);
      if (t.gecilen.includes(nokta)) throw new Error(`nokta zaten gecildi: ${nokta}`);
      const sira = t.rota.indexOf(nokta);
      if (sira !== t.gecilen.length) throw new Error("rota sirasi bozuk");
      t.gecilen.push(nokta);
      t.durum = sira === t.rota.length - 1 ? "dagitimda" : "aktarmada";
      teslimatDeposu.yaz(t);
      return { kimlik: t.id, durum: t.durum, gecilen: t.gecilen.length };
    } catch (hata) { return { kimlik: id, hata: hata.message }; }
  });
}
```

Toplu betik tekil senaryonun gövdesini bir eşlemenin içine almış hâlidir. Tekrarlanan şey
satır değil, alanın beş cümlesidir.

#### Kararlar Alan Modelinde

İkinci sürümde beş cümle **toplama kökünün** içine taşınır. Teslimat bir varlık nesnesidir:
kimliği vardır, durumu zamanla değişir ve geçilen noktalar dizisi onun değişmezini taşır.

```sh
mkdir -p kararlar-alanda/alan kararlar-alanda/uygulama
```

```js
// kararlar-alanda/alan/teslimat.mjs — ayni bes karar, toplama kokunun icinde
export class Teslimat {
  #id; #rota; #gecilen; #durum;

  constructor(kayit) {
    this.#id = kayit.id; this.#rota = [...kayit.rota];
    this.#gecilen = [...kayit.gecilen]; this.#durum = kayit.durum;
  }

  varisKaydet(nokta) {
    if (this.#durum === "teslim-edildi") throw new Error("kapali teslimata varis islenemez");
    if (!this.#rota.includes(nokta)) throw new Error(`nokta rotada yok: ${nokta}`);
    if (this.#gecilen.includes(nokta)) throw new Error(`nokta zaten gecildi: ${nokta}`);
    const sira = this.#rota.indexOf(nokta);
    if (sira !== this.#gecilen.length) throw new Error("rota sirasi bozuk");
    this.#gecilen.push(nokta);
    this.#durum = sira === this.#rota.length - 1 ? "dagitimda" : "aktarmada";
  }

  kayda() { return { id: this.#id, durum: this.#durum, rota: [...this.#rota], gecilen: [...this.#gecilen] }; }
  ozet() { return { kimlik: this.#id, durum: this.#durum, gecilen: this.#gecilen.length }; }
}
```

Senaryoda dört adım kalır: kaydı oku, toplamaya söyle, sonucu yaz, sözleşmeyi döndür.

```js
// kararlar-alanda/uygulama/varis-kaydet.mjs — kullanim senaryosu yalniz sirayi tasiyor
import { teslimatDeposu } from "../../depo.mjs";
import { Teslimat } from "../alan/teslimat.mjs";

export function varisKaydet(id, nokta) {
  const kayit = teslimatDeposu.bul(id);
  if (kayit === null) throw new RangeError(`teslimat yok: ${id}`);
  const teslimat = new Teslimat(kayit);
  teslimat.varisKaydet(nokta);
  teslimatDeposu.yaz(teslimat.kayda());
  return teslimat.ozet();
}
```

```js
// kararlar-alanda/uygulama/toplu-varis.mjs — ikinci istemci yalniz dongu ekliyor
import { varisKaydet } from "./varis-kaydet.mjs";

export function topluVaris(girdiler) {
  return girdiler.map(({ id, nokta }) => {
    try { return varisKaydet(id, nokta); }
    catch (hata) { return { kimlik: id, hata: hata.message }; }
  });
}
```

İkinci istemcinin tekilden gerçek farkı ortaya çıktı: bir döngü ve hata biçimi. Beş cümleye
dair hiçbir şey bilmiyor.

#### Ölçüm

Ölçüm üç sayı çıkarır. Birincisi dosya başına karar noktası: dallanma belirteçlerinin
(`if`, üçlü işleç, `catch`) sayısı. İkincisi kural başına yazım yeri: beş cümlenin her birinin
kaç dosyada göründüğü. Üçüncüsü koşumdan gelir: iki sürüm aynı işlem dizisinde aynı sonucu
veriyor mu?

```js
// karar-say.mjs — karar noktasi, kuralin yazildigi dosya sayisi ve iki duzenin davranis esitligi
import { readFileSync } from "node:fs";
import { teslimatDeposu, tohum } from "./depo.mjs";
import { topluVaris as yukariToplu } from "./kararlar-yukarida/uygulama/toplu-varis.mjs";
import { topluVaris as alanToplu } from "./kararlar-alanda/uygulama/toplu-varis.mjs";

const DAL = /\bif \(|\bcatch \(| \? /g;
const KURAL = {
  "kapali-teslimat": /=== "teslim-edildi"/,
  "nokta-rotada": /rota\.includes\(nokta\)/,
  "nokta-tekrari": /gecilen\.includes\(nokta\)/,
  "rota-sirasi": /sira !== /,
  "son-nokta": /"dagitimda"/,
};
const YUKARIDA = ["kararlar-yukarida/uygulama/varis-kaydet.mjs", "kararlar-yukarida/uygulama/toplu-varis.mjs"];
const ALANDA = ["kararlar-alanda/uygulama/varis-kaydet.mjs", "kararlar-alanda/uygulama/toplu-varis.mjs",
  "kararlar-alanda/alan/teslimat.mjs"];

function tara(ad, dosyalar) {
  console.log(ad);
  const metinler = dosyalar.map((d) => [d, readFileSync(d, "utf8")]);
  let uygulamaKarari = 0, alanKarari = 0;
  for (const [d, m] of metinler) {
    const karar = (m.match(DAL) ?? []).length;
    if (d.includes("/uygulama/")) uygulamaKarari += karar; else alanKarari += karar;
    console.log(`  ${d.padEnd(46)} karar noktasi = ${karar}`);
  }
  console.log(`  uygulama katmani karari = ${uygulamaKarari}, alan modulu karari = ${alanKarari}`);
  let yer = 0;
  for (const [k, kalip] of Object.entries(KURAL)) {
    const nerede = metinler.filter(([, m]) => kalip.test(m)).map(([d]) => d.split("/").at(-1));
    yer += nerede.length;
    console.log(`  kural ${k.padEnd(16)} ${nerede.length} yerde (${nerede.join(", ")})`);
  }
  console.log(`  bes kuralin toplam yazim yeri = ${yer}`);
}

tara("kararlar uygulama katmaninda", YUKARIDA);
tara("kararlar alan modelinde", ALANDA);

const ISLEMLER = (id) => [
  { id, nokta: "34" }, { id, nokta: "34" }, { id, nokta: "35" }, { id, nokta: "99" },
  { id, nokta: "06" }, { id, nokta: "35" }, { id: "T-YOK", nokta: "34" },
];
tohum("T-A"); tohum("T-B");
const a = yukariToplu(ISLEMLER("T-A"));
const b = alanToplu(ISLEMLER("T-B"));
const sadeles = (r, id) => JSON.stringify(r).replaceAll(id, "T");
let ayrilan = 0;
for (let i = 0; i < a.length; i += 1) {
  const x = sadeles(a[i], "T-A"), y = sadeles(b[i], "T-B");
  if (x !== y) ayrilan += 1;
  console.log(`${String(i).padStart(2)} ${x}`);
}
console.log(`ayrilan sonuc = ${ayrilan} / ${a.length}`);
console.log(`son kayit = ${JSON.stringify(teslimatDeposu.bul("T-B"))}`);
```

```sh
node karar-say.mjs
```

```
kararlar uygulama katmaninda
  kararlar-yukarida/uygulama/varis-kaydet.mjs    karar noktasi = 6
  kararlar-yukarida/uygulama/toplu-varis.mjs     karar noktasi = 7
  uygulama katmani karari = 13, alan modulu karari = 0
  kural kapali-teslimat  2 yerde (varis-kaydet.mjs, toplu-varis.mjs)
  kural nokta-rotada     2 yerde (varis-kaydet.mjs, toplu-varis.mjs)
  kural nokta-tekrari    2 yerde (varis-kaydet.mjs, toplu-varis.mjs)
  kural rota-sirasi      2 yerde (varis-kaydet.mjs, toplu-varis.mjs)
  kural son-nokta        2 yerde (varis-kaydet.mjs, toplu-varis.mjs)
  bes kuralin toplam yazim yeri = 10
kararlar alan modelinde
  kararlar-alanda/uygulama/varis-kaydet.mjs      karar noktasi = 1
  kararlar-alanda/uygulama/toplu-varis.mjs       karar noktasi = 1
  kararlar-alanda/alan/teslimat.mjs              karar noktasi = 5
  uygulama katmani karari = 2, alan modulu karari = 5
  kural kapali-teslimat  1 yerde (teslimat.mjs)
  kural nokta-rotada     1 yerde (teslimat.mjs)
  kural nokta-tekrari    1 yerde (teslimat.mjs)
  kural rota-sirasi      1 yerde (teslimat.mjs)
  kural son-nokta        1 yerde (teslimat.mjs)
  bes kuralin toplam yazim yeri = 5
 0 {"kimlik":"T","durum":"aktarmada","gecilen":1}
 1 {"kimlik":"T","hata":"nokta zaten gecildi: 34"}
 2 {"kimlik":"T","hata":"rota sirasi bozuk"}
 3 {"kimlik":"T","hata":"nokta rotada yok: 99"}
 4 {"kimlik":"T","durum":"aktarmada","gecilen":2}
 5 {"kimlik":"T","durum":"dagitimda","gecilen":3}
 6 {"kimlik":"T-YOK","hata":"teslimat yok: T-YOK"}
ayrilan sonuc = 0 / 7
son kayit = {"id":"T-B","durum":"dagitimda","rota":["34","06","35"],"gecilen":["34","06","35"]}
```

#### Sayıların Okunması

Uygulama katmanının karar noktası 13'ten 2'ye indi; alan modülünde 5 karar noktası oluştu.
Toplam 13'ten 7'ye düşmesinin nedeni tekrarın kalkmasıdır, kararların yok olması değil. Kalan 2
karar noktası dallanmanın kendisidir: biri kaydın bulunamamasını denetler, öteki senaryonun
ürettiği her hatayı — bulunamama dâhil — yakalar.

Beş cümlenin toplam yazım yeri 10'dan 5'e indi; oran 2,0'dan 1,0'a düştü. Bu oran dersin hiza
ölçüsüdür: alan uzmanının bir cümlesi kodda birden fazla yerde yaşıyorsa iki yerin birbirinden
sapması bir sürüm meselesidir, tasarım meselesi değildir. Oranın büyüme biçimi de bellidir.
Senaryoyu kendi içinde yürüten `n` istemci varken yazım yeri `5n`, kararlar toplama kökünde
iken sabit 5'tir; altıncı bir cümle geldiğinde ilk düzende `n` dosya, ikincisinde 1 dosya
düzenlenir.

Son yedi satır davranışın korunduğunu gösteriyor. Aynı işlem dizisi iki sürümde de aynı
sonucu üretti: üç geçerli varış, üç kural ihlali, bir bulunamayan kimlik. Ayrılan sonuç 0.
Son kayıt üç noktanın da geçildiğini ve durumun dağıtıma döndüğünü doğruluyor.

#### Uygulama Katmanında Kalan Kararlar

Ölçüyü "karar noktası sıfıra insin" biçiminde okumak yanlış olur. Uygulama katmanının kendine
ait, alan modeline ait olmayan kararları vardır ve bunlar aşağı indirilirse alan modeli
bilmemesi gereken şeyleri bilmeye başlar.

Kaydın bulunamaması bunlardan biridir: bu bir alan kuralı değil, bir arama sonucudur; teslimat
nesnesi henüz kurulmamışken verilir. İşlem sınırının nerede açılıp kapandığı, çağıranın bu
işlemi yapmaya yetkili olup olmadığı, birden çok toplamanın hangi sırada okunacağı, dışarıya
dönen sözleşmenin hangi alanları taşıdığı da uygulama katmanının kararlarıdır. Bunların ortak
özelliği alan uzmanının cümlesinde geçmemeleridir: uzman "kapanmış teslimata varış işlenmez"
der, "işlemi burada aç" demez.

Ayrım pratikte tek soruyla yapılır: bu karar alanın sözlüğünden mi geliyor, uygulamanın
gerçekleştirim düzeninden mi? Birinci durumda toplama köküne iner, ikinci durumda senaryoda
kalır.

#### Özet

- Kullanım senaryosu adım sırası ile kararı bir arada barındırabilir; sıra uygulamanın,
  karar alanın sözlüğüne ait olduğunda toplama kökünün işidir.
- Kararlar senaryoda iken uygulama katmanının karar noktası 13, alan modülünün 0'dı; kararlar
  toplama köküne indirildiğinde sayılar 2 ve 5 oldu.
- Alan uzmanının beş cümlesinin toplam yazım yeri 10'dan 5'e, cümle başına yer oranı 2,0'dan
  1,0'a indi; ilk düzende bu sayı istemci sayısıyla `5n` olarak büyür.
- Yedi işlemin yedisinde iki sürüm aynı sonucu üretti; taşıma davranışı değiştirmedi, kararın
  hangi dosyada durduğunu değiştirdi.
- Arama sonucu, işlem sınırı, yetki ve dışarıya dönen sözleşme uygulama katmanının kararlarıdır;
  ölçütü alan uzmanının cümlesinde geçip geçmemeleridir.

#### Sonraki Adım

Kararlar toplama köküne indi, ama senaryonun dört adımından biri hâlâ bir dış dünya adını
taşıyor: `teslimatDeposu`. Bu ad tek bir örnekte kalmayacak. Varış kaydı taşıyıcının kendi
nokta kodunu ister, bir varış numarası üretilmesini ister, aktarma merkezine duyuru
gönderilmesini ister.
Bu adların her biri alan modülüne doğrudan bir bağ olarak girerse alan modeli dışarıyı bilmeye
başlar; ithal kapanışı büyür ve modelin sınanması dış dünyanın hazır olmasına bağlanır. Sonraki
ders alanın dışa açıldığı her yeri açık bir bağlantı noktası olarak adlandırır, bağdaştırıcıları
dışarıda tutar, alan modülünden dış dünyaya giden doğrudan bağ sayısını ve bağdaştırıcı
değiştiğinde düzenlenen dosya sayısını iki düzende karşılaştırır.

### Bağlantı Noktaları ve Bağdaştırıcılar

Source: https://academia.sh/tr/kurslar/alan-odakli-tasarim/baglanti-noktalari-ve-bagdastiricilar

Altıgen mimarinin bir düzenleniş olarak sunulması: alanın dışa açıldığı yerlerin adlandırılmış bağlantı noktalarına dönüştürülmesi, alan modülünün ithal kapanışının ve dış dünyaya giden doğrudan bağ sayısının iki düzende sayılması, aynı alan dosyasının iki bağdaştırıcı takımıyla koşturulması ve karşılanmayan noktanın koşum anında bildirilmesi.

Önceki dersin senaryosu kararları toplama köküne indirdi, ama adımlarından biri hâlâ bir dış
dünya adı taşıyordu. Varış kaydının gerçek hâli tek bir depoyla da yetinmez: taşıyıcının kendi
nokta kodu okunur, bir varış numarası üretilir, aktarma merkezine duyuru gönderilir. Bu üç ad
alan modülünün içine doğrudan bir bağ olarak girerse alan modeli dışarıyı bilmeye başlar.

Bu dersin ölçtüğü şey o bağların sayısıdır. Ölçüyü kurmak için önce dışarının ne olduğunu
adlandırmak gerekir. **Bağlantı noktası** (port) alanın dışa açıldığı bir yerin adı ve
beklediği işlem kümesidir; **bağdaştırıcı** (adapter) o kümeyi karşılayan gerçekleştirimdir.
Aynı noktayı birden fazla bağdaştırıcı karşılayabilir. Bu ad, Bilgisayar Ağları'ndaki taşıma
katmanı bağlantı noktasıyla aynı sözcüktür ama akraba değildir: orada bir sayı, burada bir
işlem kümesi sözleşmesidir.

#### Yeni Olan Ne

Sınırın nereye çizileceği, kararlı kural ile değişken ayrıntının ayrımı ve bağımlılık okunun
yönü Tasarım İlkeleri kursundaki Politika ve Ayrıntı Ayrımı ile Sınırların Çizilmesi
derslerinde, katmanların sorumlulukları Veri Erişim Katmanı ve İş Mantığı kursundaki Katman
Sorumlulukları dersinde kurulmuş ve ölçülmüştü. Bağdaştırıcı kalıbının kendisi de Tasarım
Kalıpları kursunda ele alındı. Burada hiçbiri yeniden kurulmuyor.

Yeni olan tek şey bir **düzenlemedir**: aynı ilkeler, alanı merkeze koyup dışa açılan her yeri
adlandırılmış bir nokta hâline getirecek biçimde yerleştirildiğinde ortaya çıkan yerleşim
**altıgen mimari** (hexagonal architecture) adını alır. Altı sayısının bir anlamı yoktur;
şeklin yalnız iki yüzü olmadığını, yani girdi ve çıktı diye iki taraf bulunmadığını söylemek
için seçilmiştir. Nokta sayısı üç de olabilir, dokuz da.

#### Dışarısı

Üç dış modül, alanın konuşmak zorunda olduğu üç ayrı dünyayı temsil eder.

```sh
mkdir -p dis kapali/alan kapali/uygulama
```

```js
// dis/kayit-tablosu.mjs — teslimat kayitlarini tutan dis tablo
const TABLO = new Map();

export const kayitTablosu = {
  oku: (id) => structuredClone(TABLO.get(id) ?? null),
  yaz: (kayit) => { TABLO.set(kayit.id, structuredClone(kayit)); },
  tohum: (id) => { TABLO.set(id, { id, durum: "kabul-edildi", rota: ["34", "06", "35"], gecilen: [] }); },
};
```

```js
// dis/takip-servisi.mjs — tasiyicinin nokta kodlarini ve varis numarasini veren dis sistem
const KOD = { "34": "IST-A1", "06": "ANK-T2", "35": "IZM-D7" };
let sayac = 0;

export const takipServisi = {
  nokta: (n) => KOD[n] ?? "BILINMEYEN",
  numara: () => `V${String((sayac += 1)).padStart(4, "0")}`,
};
```

```js
// dis/bildirim-kanali.mjs — aktarma merkezine duyuru gonderen dis kanal
const GIDEN = [];

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

#### Bağı Doğrudan Kuran Düzen

İlk düzende toplama kökü üç modülü kendisi ithal eder. Kararlar yine onun içindedir; değişen
tek şey dışarıyla nasıl konuştuğudur.

```js
// kapali/alan/teslimat.mjs — toplama koku uc dis modulu dogrudan ithal ediyor
import { kayitTablosu } from "../../dis/kayit-tablosu.mjs";
import { takipServisi } from "../../dis/takip-servisi.mjs";
import { bildirimKanali } from "../../dis/bildirim-kanali.mjs";

export class Teslimat {
  #kayit;

  static yukle(id) {
    const kayit = kayitTablosu.oku(id);
    if (kayit === null) throw new RangeError(`teslimat yok: ${id}`);
    return new Teslimat(kayit);
  }

  constructor(kayit) { this.#kayit = kayit; }

  varisKaydet(nokta) {
    const k = this.#kayit;
    if (k.durum === "teslim-edildi") throw new Error("kapali teslimata varis islenemez");
    if (!k.rota.includes(nokta)) throw new Error(`nokta rotada yok: ${nokta}`);
    const sira = k.rota.indexOf(nokta);
    if (sira !== k.gecilen.length) throw new Error("rota sirasi bozuk");
    k.gecilen.push(nokta);
    k.durum = sira === k.rota.length - 1 ? "dagitimda" : "aktarmada";
    const numara = takipServisi.numara(), kod = takipServisi.nokta(nokta);
    kayitTablosu.yaz(k);
    bildirimKanali.gonder({ numara, id: k.id, kod, durum: k.durum });
    return { kimlik: k.id, numara, kod, durum: k.durum, gecilen: k.gecilen.length };
  }
}
```

```js
// kapali/uygulama/varis-kaydet.mjs — senaryonun disariya verecek bir seyi yok
import { Teslimat } from "../alan/teslimat.mjs";

export const varisKaydet = (id, nokta) => Teslimat.yukle(id).varisKaydet(nokta);
```

Senaryo bir satıra indi, çünkü verecek bir şeyi kalmadı. Bağların hepsi bir basamak aşağıda
kuruldu.

#### Noktaların Adlandırılması

İkinci düzende alan tarafı beklediği adları bir dosyada yazılı hâle getirir. Bu dosya alanın
içindedir: sözleşmeyi kararlı taraf yazar, karşılayan taraf ona uyar.

```sh
mkdir -p acik/alan acik/uygulama acik/bagdastiricilar
```

```js
// acik/alan/baglanti-noktalari.mjs — alanin disa acildigi uc nokta ve bekledigi bes ad
export const BAGLANTI_NOKTALARI = {
  kayitlar: ["oku", "yaz"],
  takip: ["noktaKodu", "varisNumarasi"],
  duyurular: ["gonder"],
};

export function cevreyiDogrula(cevre) {
  const eksik = [];
  for (const [nokta, adlar] of Object.entries(BAGLANTI_NOKTALARI)) {
    for (const ad of adlar) if (typeof cevre?.[nokta]?.[ad] !== "function") eksik.push(`${nokta}.${ad}`);
  }
  if (eksik.length > 0) throw new TypeError(`karsilanmayan baglanti noktasi: ${eksik.join(", ")}`);
}
```

```js
// acik/alan/teslimat.mjs — ayni bes karar; dis modul adi yok, cevre parametre olarak geliyor
import { cevreyiDogrula } from "./baglanti-noktalari.mjs";

export class Teslimat {
  #kayit; #cevre;

  static yukle(id, cevre) {
    cevreyiDogrula(cevre);
    const kayit = cevre.kayitlar.oku(id);
    if (kayit === null) throw new RangeError(`teslimat yok: ${id}`);
    return new Teslimat(kayit, cevre);
  }

  constructor(kayit, cevre) { this.#kayit = kayit; this.#cevre = cevre; }

  varisKaydet(nokta) {
    const k = this.#kayit, c = this.#cevre;
    if (k.durum === "teslim-edildi") throw new Error("kapali teslimata varis islenemez");
    if (!k.rota.includes(nokta)) throw new Error(`nokta rotada yok: ${nokta}`);
    const sira = k.rota.indexOf(nokta);
    if (sira !== k.gecilen.length) throw new Error("rota sirasi bozuk");
    k.gecilen.push(nokta);
    k.durum = sira === k.rota.length - 1 ? "dagitimda" : "aktarmada";
    const numara = c.takip.varisNumarasi(), kod = c.takip.noktaKodu(nokta);
    c.kayitlar.yaz(k);
    c.duyurular.gonder({ numara, id: k.id, kod, durum: k.durum });
    return { kimlik: k.id, numara, kod, durum: k.durum, gecilen: k.gecilen.length };
  }
}
```

```js
// acik/uygulama/varis-kaydet.mjs — cevreyi disaridan alip alan modeline veren senaryo
import { Teslimat } from "../alan/teslimat.mjs";

export const varisKaydet = (id, nokta, cevre) => Teslimat.yukle(id, cevre).varisKaydet(nokta);
```

#### İki Bağdaştırıcı Takımı

Aynı üç nokta iki ayrı biçimde karşılanır. Biri dış modüllere bağlanır, öbürü bellek içinde
kalır.

```js
// acik/bagdastiricilar/uretim.mjs — uc noktayi dis modullere baglayan bagdastiricilar
import { kayitTablosu } from "../../dis/kayit-tablosu.mjs";
import { takipServisi } from "../../dis/takip-servisi.mjs";
import { bildirimKanali } from "../../dis/bildirim-kanali.mjs";

export const uretimCevresi = {
  kayitlar: { oku: (id) => kayitTablosu.oku(id), yaz: (k) => kayitTablosu.yaz(k) },
  takip: { noktaKodu: (n) => takipServisi.nokta(n), varisNumarasi: () => takipServisi.numara() },
  duyurular: { gonder: (m) => bildirimKanali.gonder(m) },
};
```

```js
// acik/bagdastiricilar/sinama.mjs — ayni uc noktayi bellek icinde karsilayan bagdastiricilar
const TABLO = new Map();
let sayac = 0;
export const GIDEN = [];

export const sinamaCevresi = {
  kayitlar: {
    oku: (id) => structuredClone(TABLO.get(id) ?? null),
    yaz: (k) => { TABLO.set(k.id, structuredClone(k)); },
  },
  takip: { noktaKodu: (n) => `SINAMA-${n}`, varisNumarasi: () => `S${String((sayac += 1)).padStart(4, "0")}` },
  duyurular: { gonder: (m) => GIDEN.push(m) },
};

export const tohum = (id) => TABLO.set(id, { id, durum: "kabul-edildi", rota: ["34", "06", "35"], gecilen: [] });
```

#### Ölçüm

Ölçüm alan modülünün kökünden başlayıp ithal bağlarını yürür: kapanışa kaç dosya giriyor,
kaç bağ `dis/` dizinine iniyor. Ardından aynı alan dosyası iki bağdaştırıcı takımıyla
koşturulur ve karşılanmayan bir nokta ile ne olduğuna bakılır.

```js
// bag-say.mjs — alan modulunun ithal kapanisi, dogrudan bag sayisi ve iki bagdastirici takimiyla kosum
import { readFileSync } from "node:fs";
import { dirname, join, normalize } from "node:path";
import { kayitTablosu } from "./dis/kayit-tablosu.mjs";
import { bildirimKanali } from "./dis/bildirim-kanali.mjs";
import { BAGLANTI_NOKTALARI } from "./acik/alan/baglanti-noktalari.mjs";
import { varisKaydet as kapaliVaris } from "./kapali/uygulama/varis-kaydet.mjs";
import { varisKaydet as acikVaris } from "./acik/uygulama/varis-kaydet.mjs";
import { uretimCevresi } from "./acik/bagdastiricilar/uretim.mjs";
import { sinamaCevresi, tohum, GIDEN } from "./acik/bagdastiricilar/sinama.mjs";

const ITHAL = /^import[^"']*["']([^"']+)["']/gm;

function kapanis(kok) {
  const gorulen = new Set(), yigit = [kok], kenar = [];
  while (yigit.length > 0) {
    const dosya = yigit.pop();
    if (gorulen.has(dosya)) continue;
    gorulen.add(dosya);
    for (const [, yol] of readFileSync(dosya, "utf8").matchAll(ITHAL)) {
      if (yol.startsWith(".") === false) continue;
      const hedef = normalize(join(dirname(dosya), yol));
      kenar.push([dosya, hedef]);
      yigit.push(hedef);
    }
  }
  return { gorulen, kenar };
}

for (const [ad, kok] of [["kapali", "kapali/alan/teslimat.mjs"], ["acik", "acik/alan/teslimat.mjs"]]) {
  const { gorulen, kenar } = kapanis(kok);
  const disa = kenar.filter(([, hedef]) => hedef.startsWith("dis/"));
  console.log(`${ad} alan modulu: ithal kapanisi = ${gorulen.size}, dis dunyaya dogrudan bag = ${disa.length}`);
  for (const [k, h] of kenar) console.log(`  ${k} -> ${h}`);
}

const adSayisi = Object.values(BAGLANTI_NOKTALARI).flat().length;
console.log(`baglanti noktasi = ${Object.keys(BAGLANTI_NOKTALARI).length}, sozlesmedeki ad = ${adSayisi}`);

kayitTablosu.tohum("T-K"); kayitTablosu.tohum("T-U"); tohum("T-S");
const kosumlar = [
  ["kapali + dis moduller ", kapaliVaris("T-K", "34")],
  ["acik + uretim takimi  ", acikVaris("T-U", "34", uretimCevresi)],
  ["acik + sinama takimi  ", acikVaris("T-S", "34", sinamaCevresi)],
];
for (const [ad, s] of kosumlar) console.log(`${ad} ${JSON.stringify(s)}`);
const alan = kosumlar.map(([, s]) => `${s.durum}/${s.gecilen}`);
console.log(`alan alanlari esit mi = ${new Set(alan).size === 1}, ayri deger = ${new Set(alan).size}`);
console.log(`dis kanala giden duyuru = ${bildirimKanali.sayi()}, sinama kanalina giden = ${GIDEN.length}`);

try { acikVaris("T-S", "06", { kayitlar: sinamaCevresi.kayitlar }); }
catch (hata) { console.log(`eksik cevre: ${hata.message}`); }
```

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

```
kapali alan modulu: ithal kapanisi = 4, dis dunyaya dogrudan bag = 3
  kapali/alan/teslimat.mjs -> dis/kayit-tablosu.mjs
  kapali/alan/teslimat.mjs -> dis/takip-servisi.mjs
  kapali/alan/teslimat.mjs -> dis/bildirim-kanali.mjs
acik alan modulu: ithal kapanisi = 2, dis dunyaya dogrudan bag = 0
  acik/alan/teslimat.mjs -> acik/alan/baglanti-noktalari.mjs
baglanti noktasi = 3, sozlesmedeki ad = 5
kapali + dis moduller  {"kimlik":"T-K","numara":"V0001","kod":"IST-A1","durum":"aktarmada","gecilen":1}
acik + uretim takimi   {"kimlik":"T-U","numara":"V0002","kod":"IST-A1","durum":"aktarmada","gecilen":1}
acik + sinama takimi   {"kimlik":"T-S","numara":"S0001","kod":"SINAMA-34","durum":"aktarmada","gecilen":1}
alan alanlari esit mi = true, ayri deger = 1
dis kanala giden duyuru = 2, sinama kanalina giden = 1
eksik cevre: karsilanmayan baglanti noktasi: takip.noktaKodu, takip.varisNumarasi, duyurular.gonder
```

#### Sayıların Okunması

Alan modülünün ithal kapanışı 4'ten 2'ye, dış dünyaya giden doğrudan bağ sayısı 3'ten 0'a
indi. İkinci sayı belirleyici olanıdır: kapalı düzende toplama kökünü yüklemek üç dış modülü
de yüklemek anlamına gelir, açık düzende alan modülü yalnız kendi sözleşme dosyasını tanır.

Üç bağ, üç bağlantı noktasına ve beş ada dönüştü. Nokta sayısı bağ sayısına eşit çıktı; bu
rastlantı değildir, dönüşüm bağların adlandırılmasından oluşur. Beş ad ise dışarının alandan
gördüğü yüzeydir: karşı taraf yalnız bu beş işlemi sağlamak zorundadır, kendi içinde kaç
dosyası olduğu alanı ilgilendirmez.

Üç koşumun alan sonuçları eşit çıktı: aynı durum, aynı geçilen sayısı, ayrı değer sayısı 1.
Farklı olan tek şey bağdaştırıcıdan gelen alanlardır — varış numarası ve nokta kodu takıma
göre değişti, çünkü onları alan üretmiyor. Duyuru sayaçları da bunu doğruluyor: dış kanala 2
duyuru gitti, sınama kanalına 1. Aynı alan dosyası hem üretim takımıyla hem bellek içi takımla
koştu ve dosyaya tek satır dokunulmadı.

Son satır sözleşme dosyasının koşum anındaki karşılığıdır. Eksik bir çevre verildiğinde
karşılanmayan üç ad adıyla bildirildi. Kapalı düzende böyle bir hata iletisi olamaz, çünkü
karşılanacak bir sözleşme yazılı değildir; eksik olan şey bir ithal hatası olarak görünür.

#### Bedelin Sayılması

Düzen ücretsiz değildir. Açık düzen 1 dosya (sözleşme) ve bağdaştırıcı takımı başına 1 dosya
ekler; iki takımla toplam 3 dosya. Toplama kökünün her yaratımına bir parametre daha eklendi.
Nokta sayısı arttıkça bu parametre büyür: dokuz noktalı bir alanda çevre nesnesi dokuz alan
taşır ve her kullanım senaryosu onu baştan sona geçirir.

Kazanç iki koşula bağlıdır. Birincisi, bir noktanın gerçekten birden fazla bağdaştırıcısı
olmalıdır; tek bağdaştırıcılı bir nokta yalnız dolaylılık ekler. İkincisi, alan modülünün
kendi başına koşturulabilmesi bir işe yaramalıdır. Alanda karar yoksa — kararlar yine dışarıda
duruyorsa — bağlantı noktası boş bir sözleşmedir; ölçüm bunu doğrudan bağ sayısının 0 çıkması
ama karar noktası sayısının da 0 çıkması olarak gösterir.

#### Özet

- Bağlantı noktası alanın dışa açıldığı bir yerin adı ve beklediği işlem kümesidir;
  bağdaştırıcı o kümenin gerçekleştirimidir ve aynı nokta için birden fazla olabilir.
- Altıgen mimari yeni bir ilke getirmez; sınır çizimi, politika–ayrıntı ayrımı ve bağımlılık
  yönünü alanı merkeze alacak biçimde düzenler.
- Alan modülünün ithal kapanışı 4'ten 2'ye, dış dünyaya giden doğrudan bağ sayısı 3'ten 0'a
  indi; üç bağ üç noktaya ve beş ada dönüştü.
- Aynı alan dosyası iki bağdaştırıcı takımıyla koştu; üç koşumun alan sonuçları eşit çıktı,
  yalnız bağdaştırıcıdan gelen numara ve kod değişti.
- Bedel 3 dosya ve her yaratımda bir parametredir; kazanç bir noktanın birden fazla
  bağdaştırıcısı olduğunda ve alanda gerçekten karar bulunduğunda doğar.

#### Sonraki Adım

Buraya kadarki bütün ölçüler yazma yönündeydi: bir varış kaydının hangi kararlardan geçtiği,
hangi noktalardan dışarı çıktığı. Operasyon masasının asıl işi ise okumaktır — hangi
teslimatlar rotasının gerisinde, hangi aktarma merkezinde kaç paket bekliyor, hangi taşıyıcının
gecikme oranı yüksek. Bu soruların yanıtı toplama kökleri üzerinden aranırsa her soru için
onlarca nesne gezilir ve gezinti teslimat sayısıyla büyür. Sonraki ders aynı verinin okuma
için ayrı bir modelde tutulmasını ele alır: sorgu için gezilen nesne sayısını iki düzende
sayar, ayrı modelin kaç komut geriden geldiğini ölçer ve bunun karşılığında eklenen dosya ve
eşleme sayısını bedel olarak yazar.

### Komut ve Sorgu Sorumluluğu Ayrımı

Source: https://academia.sh/tr/kurslar/alan-odakli-tasarim/komut-ve-sorgu-sorumlulugu-ayrimi

Yazma ve okuma modellerinin ayrılması: operasyon panosunun toplama kökleri üzerinden ve ayrı bir okuma modeli üzerinden üretilmesi, üç veri boyunda gezilen nesne sayısının karşılaştırılması, okuma modelinin kaç komut geriden geldiğinin ve kaç yanıtın bayat çıktığının ölçülmesi, bedel olarak eklenen dosya, satır ve eşleme yazımı sayısı.

Buraya kadarki bütün ölçüler yazma yönündeydi: varış kaydının hangi kararlardan geçtiği ve
hangi bağlantı noktalarından dışarı çıktığı. Operasyon masasının asıl işi ise okumaktır: hangi
aktarma noktasında kaç paket bekliyor, bunların kaçı rotasının gerisinde kalmış. Bu soruların
yanıtı toplama kökleri üzerinden aranırsa her yanıt için bütün teslimatlar ve rotalarındaki
bütün ayaklar gezilir; gezinti teslimat sayısıyla büyür.

**Komut ve sorgu sorumluluğu ayrımı** (command query responsibility segregation) bu iki yönü
ayrı modellere verir. Yazma tarafı değişmezleri koruyacak biçimde kurulur — toplama kökleri,
tutarlılık sınırları, kararlar. Okuma tarafı ise sorunun biçimine göre kurulur: sorgunun
istediği satırlar hazır tutulur.

Bu ayrım, Programlama Temelleri kursunda tanıtılan **komut–sorgu ayrımından** farklı bir
şeydir: orada ölçüt bir yöntemin ya durumu değiştirmesi ya değer döndürmesiydi, yani tek bir
yöntemin imzasıydı. Burada ayrılan şey yöntem değil, **model**: aynı veriyi iki ayrı yapıda
tutmak.

#### Yazma Modeli

Yazma tarafı önceki derslerin toplama kökünü sürdürür. Her teslimatın rotası, geçtiği noktalar
ve o ana kadar geçmiş olması beklenen nokta sayısı vardır.

```js
// yazma-modeli.mjs — teslimat toplamalari ve tek komut: varis kaydi
const NOKTALAR = ["34", "06", "35", "01", "16"];
const TESLIMATLAR = new Map();

export function tohumla(sayi) {
  TESLIMATLAR.clear();
  let x = 7;
  for (let i = 1; i <= sayi; i += 1) {
    x = (x * 48271 + 11) % 2147483647;
    const uzunluk = 2 + (x % 4);
    const bas = x % NOKTALAR.length;
    const rota = Array.from({ length: uzunluk }, (_, j) => NOKTALAR[(bas + j) % NOKTALAR.length]);
    const ilerleme = x % uzunluk;
    TESLIMATLAR.set(`T${i}`, {
      id: `T${i}`, rota, gecilen: rota.slice(0, ilerleme), beklenen: (x % 2) + ilerleme,
    });
  }
  return TESLIMATLAR.size;
}

export const yazmaModeli = {
  tumu: () => [...TESLIMATLAR.values()].map((t) => structuredClone(t)),
  bul: (id) => structuredClone(TESLIMATLAR.get(id) ?? null),
};

export function varisKomutu(id) {
  const t = TESLIMATLAR.get(id);
  if (t === undefined || t.gecilen.length >= t.rota.length) return null;
  const once = structuredClone(t);
  t.gecilen.push(t.rota[t.gecilen.length]);
  return { once, sonra: structuredClone(t) };
}
```

Tohumlama sabit bir üreteçle yapılıyor; aynı teslimat sayısı her koşumda aynı rotaları verir.
Komut değişimin öncesini ve sonrasını birlikte döndürür, çünkü okuma tarafının hangi satırın
değiştiğini bilmesi gerekir.

#### Panonun Yazma Modelinden Üretilmesi

Pano sorgusu her teslimatı okur, rotasını ve geçilen noktalarını gezer, sıradaki noktayı
bulur ve o noktanın satırını arttırır.

```js
// pano-yazma.mjs — pano sorgusu yazma modeli uzerinden: her toplama ve her rota ayagi gezilir
export function panoYazmadan(yazmaModeli) {
  let gezilen = 0;
  const satir = new Map();
  for (const t of yazmaModeli.tumu()) {
    gezilen += 1;
    for (const _ of t.rota) gezilen += 1;
    for (const _ of t.gecilen) gezilen += 1;
    const siradaki = t.rota[t.gecilen.length];
    if (siradaki === undefined) continue;
    const s = satir.get(siradaki) ?? { nokta: siradaki, bekleyen: 0, geride: 0 };
    s.bekleyen += 1;
    if (t.gecilen.length < t.beklenen) s.geride += 1;
    satir.set(siradaki, s);
  }
  return { satirlar: [...satir.values()].sort((a, b) => a.nokta.localeCompare(b.nokta)), gezilen };
}
```

`gezilen` sayacı iddiayı ölçüye çeviriyor: her toplama kökü, her rota ayağı ve her geçilmiş
nokta bir gezinti sayılıyor.

#### Okuma Modeli

Okuma modeli beş satırdan oluşur — her aktarma noktası için bir satır. Satırlar bir kez
kurulur, sonra yalnız komutların ürettiği farklarla güncellenir. Uygulanmamış farklar bir
kuyrukta bekler; kuyruğun boyu modelin gecikmesidir.

```js
// okuma-modeli.mjs — pano satirlarini hazir tutan okuma modeli ve uygulanmamis guncelleme kuyrugu
const SATIR = new Map();
const BEKLEYEN = [];

export const okumaModeli = {
  kur(satirlar) {
    SATIR.clear(); BEKLEYEN.length = 0;
    for (const s of satirlar) SATIR.set(s.nokta, { ...s });
  },
  yayinla(delta) { BEKLEYEN.push(delta); },
  gecikme: () => BEKLEYEN.length,
  ilerlet(adet = BEKLEYEN.length) {
    const uygulanan = BEKLEYEN.splice(0, adet);
    for (const delta of uygulanan) {
      for (const [nokta, bekleyen, geride] of delta) {
        const s = SATIR.get(nokta) ?? { nokta, bekleyen: 0, geride: 0 };
        s.bekleyen += bekleyen; s.geride += geride;
        SATIR.set(nokta, s);
      }
    }
    return uygulanan.length;
  },
  pano() {
    let gezilen = 0;
    const satirlar = [];
    for (const s of SATIR.values()) { gezilen += 1; if (s.bekleyen > 0) satirlar.push({ ...s }); }
    return { satirlar: satirlar.sort((a, b) => a.nokta.localeCompare(b.nokta)), gezilen };
  },
};
```

İki modeli birbirine bağlayan tek parça eşlemedir: bir komutun hangi satırları hangi yönde
değiştirdiğini hesaplar.

```js
// izdusum.mjs — bir komutun degistirdigi pano satirlarini hesaplayan esleme
export function izdusum({ once, sonra }) {
  const eski = once.rota[once.gecilen.length], yeni = sonra.rota[sonra.gecilen.length];
  const delta = [];
  if (eski !== undefined) delta.push([eski, -1, once.gecilen.length < once.beklenen ? -1 : 0]);
  if (yeni !== undefined) delta.push([yeni, 1, sonra.gecilen.length < sonra.beklenen ? 1 : 0]);
  return delta;
}
```

#### Ölçüm

Ölçüm üç şeyi ayrı ayrı sayar. Birincisi gezinti: üç ayrı teslimat sayısında iki yolun gezdiği
nesne. İkincisi tazelik: yirmi dört komut boyunca okuma modelinin kaç komut geriden geldiği ve
kaç sorgunun yazma modelinden farklı yanıt verdiği. Üçüncüsü bedel: eklenen dosya, satır ve
eşleme yazımı sayısı.

```js
// pano-say.mjs — sorgu icin gezilen nesne, tazelik gecikmesi ve izdusum esleme sayisi
import { readFileSync } from "node:fs";
import { tohumla, yazmaModeli, varisKomutu } from "./yazma-modeli.mjs";
import { panoYazmadan } from "./pano-yazma.mjs";
import { okumaModeli } from "./okuma-modeli.mjs";
import { izdusum } from "./izdusum.mjs";

for (const n of [50, 200, 800]) {
  tohumla(n);
  const y = panoYazmadan(yazmaModeli);
  okumaModeli.kur(y.satirlar);
  const o = okumaModeli.pano();
  const esit = JSON.stringify(y.satirlar) === JSON.stringify(o.satirlar);
  console.log(`teslimat = ${String(n).padStart(3)}  yazma modelinden gezilen = ${String(y.gezilen).padStart(4)}` +
    `, okuma modelinden gezilen = ${o.gezilen}, satirlar esit mi = ${esit}`);
}

tohumla(200);
okumaModeli.kur(panoYazmadan(yazmaModeli).satirlar);
console.log(`baslangic panosu = ${JSON.stringify(okumaModeli.pano().satirlar)}`);

const KOMUTLAR = Array.from({ length: 24 }, (_, i) => `T${i * 7 + 3}`);
const ILERLETME_ARALIGI = 6;
let bayat = 0, enBuyukGecikme = 0, eslemeSayisi = 0, komutSayisi = 0;
for (const [sira, id] of KOMUTLAR.entries()) {
  const sonuc = varisKomutu(id);
  if (sonuc === null) continue;
  komutSayisi += 1;
  const delta = izdusum(sonuc);
  eslemeSayisi += delta.length;
  okumaModeli.yayinla(delta);
  if ((sira + 1) % ILERLETME_ARALIGI === 0) okumaModeli.ilerlet();
  enBuyukGecikme = Math.max(enBuyukGecikme, okumaModeli.gecikme());
  const yazma = JSON.stringify(panoYazmadan(yazmaModeli).satirlar);
  const okuma = JSON.stringify(okumaModeli.pano().satirlar);
  if (yazma !== okuma) bayat += 1;
}
console.log(`komut = ${komutSayisi}, izdusum esleme yazimi = ${eslemeSayisi}`);
console.log(`bayat yanit = ${bayat} / ${komutSayisi}, en buyuk gecikme = ${enBuyukGecikme} komut`);
okumaModeli.ilerlet();
const sonYazma = JSON.stringify(panoYazmadan(yazmaModeli).satirlar);
const sonOkuma = JSON.stringify(okumaModeli.pano().satirlar);
console.log(`kuyruk bosaltildiktan sonra esit mi = ${sonYazma === sonOkuma}, gecikme = ${okumaModeli.gecikme()}`);
console.log(`son pano = ${sonOkuma}`);

const satirSay = (d) => readFileSync(d, "utf8").split("\n").filter((s) => s.trim() !== "").length;
for (const [ad, dosyalar] of [["tek model", ["yazma-modeli.mjs", "pano-yazma.mjs"]],
  ["okuma modeli icin eklenen", ["okuma-modeli.mjs", "izdusum.mjs"]]]) {
  console.log(`${ad.padEnd(26)} ${dosyalar.length} dosya, ${dosyalar.reduce((s, d) => s + satirSay(d), 0)} satir`);
}
```

```sh
node pano-say.mjs
```

```
teslimat =  50  yazma modelinden gezilen =  309, okuma modelinden gezilen = 5, satirlar esit mi = true
teslimat = 200  yazma modelinden gezilen = 1220, okuma modelinden gezilen = 5, satirlar esit mi = true
teslimat = 800  yazma modelinden gezilen = 4621, okuma modelinden gezilen = 5, satirlar esit mi = true
baslangic panosu = [{"nokta":"01","bekleyen":45,"geride":29},{"nokta":"06","bekleyen":46,"geride":25},{"nokta":"16","bekleyen":32,"geride":19},{"nokta":"34","bekleyen":40,"geride":15},{"nokta":"35","bekleyen":37,"geride":24}]
komut = 24, izdusum esleme yazimi = 44
bayat yanit = 20 / 24, en buyuk gecikme = 5 komut
kuyruk bosaltildiktan sonra esit mi = true, gecikme = 0
son pano = [{"nokta":"01","bekleyen":43,"geride":27},{"nokta":"06","bekleyen":39,"geride":18},{"nokta":"16","bekleyen":29,"geride":17},{"nokta":"34","bekleyen":38,"geride":12},{"nokta":"35","bekleyen":47,"geride":23}]
tek model                  2 dosya, 46 satir
okuma modeli icin eklenen  2 dosya, 36 satir
```

#### Sayıların Okunması

Gezinti sayıları üç boyda da aynı şeyi söylüyor. Yazma modeli üzerinden pano üretmek 50
teslimatta 309, 200 teslimatta 1220, 800 teslimatta 4621 nesne geziyor; oran teslimat başına
yaklaşık 6'dır, çünkü her teslimatın kendisi, rota ayakları ve geçilmiş noktaları ayrı ayrı
okunuyor. Okuma modeli üzerinden aynı pano her boyda 5 nesne geziyor. Bu sayı teslimat
sayısına değil, **aktarma noktası sayısına** bağlıdır; veri büyürken sabit kalır. Üç boyda da
iki yolun ürettiği satırlar eşit çıktı, yani okuma modeli doğru bir izdüşümdür.

Tazelik sayıları bedeli gösteriyor. Yirmi dört komut boyunca kuyruk her altı komutta bir
boşaltıldı; okuma modeli en fazla 5 komut geriden geldi ve 24 sorgunun 20'si yazma modelinden
farklı yanıt verdi. Bu 20 yanıt yanlış değil, **eski**: bir aktarma noktasında bekleyen paket
sayısı en çok beş komut önceki hâliyle görünüyor. Kuyruk boşaltıldıktan sonra iki model yine
eşitlendi ve gecikme 0'a indi.

Bedel iki dosya ve 36 satır. Buna komut başına eşleme yazımı ekleniyor: 24 komut 44 satır
güncellemesi üretti, yani komut başına ortalama 1,8. Ayrım gezinti sayısını `6n`'den sabit
5'e indirirken yazma yolunu bu kadar ağırlaştırıyor.

#### Gecikmenin Alan Kararı Olması

Beş komutluk gecikme teknik bir ayar gibi görünür, ama kararı veren alan uzmanıdır. Aktarma
merkezinin panosunda beş komutluk gerilik sorun değildir: sayaç zaten dakikalar içinde
değişiyor. Aynı gecikme, bir paketin ikinci kez aynı noktaya kaydedilmesini engelleyen kararda
kabul edilemez. Bu yüzden ayrım "her sorgu okuma modelinden" biçiminde uygulanmaz; hangi
sorgunun ne kadar gerilik taşıyabileceği bağlam bağlamdır.

Okuma modeli tazeliğinin genel ölçütleri, geçersizleştirme ve tazelik penceresi Önbellekleme,
Kuyruklar ve Eşzamansız İşleme kursunda ele alındı; burada ölçülen tek şey ayrımın kendi
getirdiği geriliktir.

#### Ne Zaman Uygulanmaz

Kazanç iki koşula bağlıdır. Birincisi sorgunun gezinti sayısının veri boyuyla büyümesi:
tek bir teslimatı kimliğiyle okuyan bir sorgu yazma modelinde de 1 nesne geziyor, okuma modeli
ona hiçbir şey kazandırmaz. İkincisi sorgunun biçiminin kararlı olması; sorgu her ay
değişiyorsa eşleme de her ay değişir ve iki dosya sürekli açılır.

Üçüncü sınır tutarlılıktır. Bir alan kuralı sorgunun yanıtına dayanıyorsa — "bir noktada en
çok yüz paket bekleyebilir" gibi — o kural okuma modelinden okunamaz, çünkü okuma modeli
geriden gelir. Böyle bir kural yazma tarafındaki tutarlılık sınırının içinde kalmak zorundadır.

#### Özet

- Komut ve sorgu sorumluluğu ayrımı yazma ve okuma için iki ayrı model tutar; komut–sorgu
  ayrımı ise tek bir yöntemin ya durumu değiştirmesini ya değer döndürmesini söyler.
- Pano yazma modeli üzerinden 50, 200 ve 800 teslimatta 309, 1220 ve 4621 nesne gezdi; okuma
  modeli üzerinden üç boyda da 5 nesne gezdi ve satırlar eşit çıktı.
- Okuma modeli en fazla 5 komut geriden geldi; 24 sorgunun 20'si eski yanıt verdi, kuyruk
  boşaltılınca iki model eşitlendi.
- Bedel 2 dosya, 36 satır ve komut başına ortalama 1,8 eşleme yazımıdır; ayrım okuma yolunu
  ucuzlatırken yazma yolunu ağırlaştırır.
- Ne kadar geriliğin kabul edilebilir olduğu alan kararıdır; bir alan kuralı sorgunun yanıtına
  dayanıyorsa o kural yazma tarafındaki tutarlılık sınırında kalmalıdır.

#### Sonraki Adım

Okuma modeli komutların ürettiği farklardan kuruldu; farkların kendisi ise uygulandıktan sonra
atıldı. Bu, yazma tarafına bir soru sorulamaz hâle getiriyor: bir teslimatın adresi kaç kez
düzeltildi, hangi noktada kaç kez yeniden deneme yapıldı, dağıtıma çıkış ile teslim arasında
hangi sırayla ne oldu? Kayıt yalnız son durumu tuttuğu için bu soruların yanıtı modelde yok.
Sonraki ders durumu değil olay dizisini saklayan bir kalıcılık biçimini ele alır: durumun
olaylardan yeniden kurulup kurulabildiğini sınar, yeniden kurma için kaç olay okunduğunu sayar
ve anlık görüntü alındığında bu sayının nereye indiğini ölçer.

### Olay Kaynaklı Kalıcılık

Source: https://academia.sh/tr/kurslar/alan-odakli-tasarim/olay-kaynakli-kalicilik

Durum yerine olay dizisinin saklanması: dört sorunun durum tablosundan ve olay dizisinden yanıtlanabilirliğinin karşılaştırılması, durumun olaylardan yeniden kurulması için okunan olay sayısının sayılması, anlık görüntü aralığı değiştikçe okunan olay ile yazılan görüntü sayısının nasıl değiştiğinin ölçülmesi ve iki kalıcılığın bayt boyunun karşılaştırılması.

Önceki dersin okuma modeli komutların ürettiği farklardan kuruldu, farkların kendisi ise
uygulandıktan sonra atıldı. Yazma tarafında kalan tek şey son durumdu. Bu, sonradan sorulan
soruları yanıtsız bırakır: bir teslimatın adresi kaç kez düzeltildi, kaç aktarma noktasından
geçti, teslim kaç denemede oldu? Bu soruların hiçbiri kaydın şemasında yoktu, çünkü şema
izleme ekranı için tasarlanmıştı.

**Olay kaynaklı kalıcılık** (event sourcing) sıralamayı tersine çevirir: saklanan asıl şey
durum değil, durumu üreten olayların dizisidir. Durum bir türev hâline gelir — olaylar sırayla
katlanarak yeniden kurulur.

#### Olayın Kalıcılık Biçimi Olması

Olayların kuyruğa konması, tüketiciye ulaşması ve teslim garantileri Önbellekleme, Kuyruklar
ve Eşzamansız İşleme kursunda ele alındı. Bu dersin konusu o değil: burada olay dizisi bir
**kalıcılık biçimi** olarak ele alınıyor ve tek soru şudur — durum bu diziden yeniden
kurulabiliyor mu, kurulurken kaç olay okunuyor?

Olayların adları da bu kursun Alan Olayları dersinde kurulan ölçüte uyar: `aktarmaya-girdi`,
`adres-duzeltildi`, `teslim-denendi` alan uzmanının kullandığı sözcüklerdir, veritabanı
işlemlerinin adları değildir. Her olayın karşılığı bir geçiştir.

```js
// durum-kur.mjs — alan diline ait gecisler ve olay dizisinden durumun katlanarak kurulmasi
export const BOS = {
  durum: "yok", nokta: null, adres: null, aktarmaSayisi: 0, denemeSayisi: 0, adresDuzeltmesi: 0,
};

const GECIS = {
  "kabul-edildi": (d, o) => ({ ...d, durum: "kabul-edildi", adres: o.adres }),
  "aktarmaya-girdi": (d, o) => ({ ...d, durum: "aktarmada", nokta: o.nokta, aktarmaSayisi: d.aktarmaSayisi + 1 }),
  "aktarmadan-cikti": (d) => ({ ...d, durum: "yolda" }),
  "dagitima-cikildi": (d) => ({ ...d, durum: "dagitimda" }),
  "teslim-denendi": (d) => ({ ...d, denemeSayisi: d.denemeSayisi + 1 }),
  "adres-duzeltildi": (d, o) => ({ ...d, adres: o.adres, adresDuzeltmesi: d.adresDuzeltmesi + 1 }),
  "teslim-edildi": (d) => ({ ...d, durum: "teslim-edildi" }),
};

export function durumKur(baslangic, olaylar) {
  let d = baslangic;
  for (const o of olaylar) {
    const gecis = GECIS[o.tur];
    if (gecis === undefined) throw new RangeError(`bilinmeyen olay: ${o.tur}`);
    d = gecis(d, o);
  }
  return d;
}
```

Katlama işlevi saf fonksiyondur ve başlangıç durumunu parametre olarak alır. Bu ayrıntı anlık
görüntüyü olanaklı kılan şeydir: dizinin ortasındaki bir durumdan başlanabilir.

#### Durum Saklayan Kalıcılık

Karşılaştırılacak öbür kalıcılık yalnız son durumu tutar. Şema izleme ekranının sorularını
yanıtlayacak biçimde seçilmiştir: paket nerede, durumu ne, adresi ne.

```js
// durum-modeli.mjs — yalniz son durumu tutan kalicilik: izleme ekrani icin tasarlanmis semasi
const SATIR = new Map();

export const durumModeli = {
  uygula(id, olay) {
    const s = SATIR.get(id) ?? { id, durum: "yok", nokta: null, adres: null };
    if (olay.tur === "kabul-edildi") { s.durum = "kabul-edildi"; s.adres = olay.adres; }
    if (olay.tur === "aktarmaya-girdi") { s.durum = "aktarmada"; s.nokta = olay.nokta; }
    if (olay.tur === "aktarmadan-cikti") s.durum = "yolda";
    if (olay.tur === "dagitima-cikildi") s.durum = "dagitimda";
    if (olay.tur === "adres-duzeltildi") s.adres = olay.adres;
    if (olay.tur === "teslim-edildi") s.durum = "teslim-edildi";
    SATIR.set(id, s);
  },
  oku: (id) => structuredClone(SATIR.get(id) ?? null),
  boyut: () => JSON.stringify([...SATIR.values()]).length,
};
```

#### Olay Deposu ve Anlık Görüntü

Olay deposu yalnız ekleme kabul eder; yazılmış bir olay değiştirilmez. **Anlık görüntü**
(snapshot) belirli bir olay sayısında hesaplanmış durumu bir rafta tutar, böylece yeniden
kurma dizinin başından değil o noktadan başlar.

```js
// olay-deposu.mjs — eklemeli olay dizisi, anlik goruntu rafi ve yeniden kurma sayaci
import { BOS, durumKur } from "./durum-kur.mjs";

const DIZI = new Map();
const GORUNTU = new Map();

export const olayDeposu = {
  ekle(id, olay) {
    const dizi = DIZI.get(id) ?? [];
    dizi.push(olay);
    DIZI.set(id, dizi);
  },
  kimlikler: () => [...DIZI.keys()],
  olaySayisi: (id) => DIZI.get(id).length,
  toplamOlay: () => [...DIZI.values()].reduce((s, d) => s + d.length, 0),
  boyut: () => JSON.stringify([...DIZI.entries()]).length,

  goruntuleriHazirla(aralik) {
    GORUNTU.clear();
    if (aralik === 0) return 0;
    for (const [id, dizi] of DIZI) {
      for (let sinir = aralik; sinir <= dizi.length; sinir += aralik) {
        GORUNTU.set(`${id}:${sinir}`, durumKur(BOS, dizi.slice(0, sinir)));
      }
    }
    return GORUNTU.size;
  },

  yenidenKur(id, aralik) {
    const dizi = DIZI.get(id);
    const sinir = aralik === 0 ? 0 : Math.floor(dizi.length / aralik) * aralik;
    const goruntu = GORUNTU.get(`${id}:${sinir}`);
    const kalan = dizi.slice(sinir);
    return {
      durum: durumKur(goruntu ?? BOS, kalan),
      okunanOlay: kalan.length,
      okunanGoruntu: goruntu === undefined ? 0 : 1,
    };
  },
};
```

Gerçek bir sistemde görüntüler olay eklenirken alınır. Ölçümde aralık bir değişken olduğu için
`goruntuleriHazirla` hepsini bir kerede hesaplar ve kaç görüntü yazıldığını döndürür; bu sayı
bedelin karşılığıdır.

#### Aynı Geçmişin İki Kalıcılığa Yazılması

Karşılaştırmanın geçerli olması için iki kalıcılığın aynı olayları görmesi gerekir. Üretici
sabit bir sayı dizisiyle çalışır; her yirmi beşinci teslimat çok kez yönlendirilmiş sorunlu bir
gönderidir.

```js
// gecmis-uret.mjs — ayni olaylari iki kaliciliga da yazan sabit uretec
import { olayDeposu } from "./olay-deposu.mjs";
import { durumModeli } from "./durum-modeli.mjs";

const NOKTALAR = ["34", "06", "35", "01", "16"];

export function gecmisUret(teslimatSayisi) {
  let x = 13;
  const sonraki = () => (x = (x * 48271 + 11) % 2147483647);
  for (let i = 1; i <= teslimatSayisi; i += 1) {
    const id = `T${i}`;
    const yaz = (olay) => { olayDeposu.ekle(id, olay); durumModeli.uygula(id, olay); };
    yaz({ tur: "kabul-edildi", adres: `A${(sonraki() % 900) + 100}` });
    const aktarma = i % 25 === 0 ? 14 + (sonraki() % 8) : 1 + (sonraki() % 4);
    for (let a = 0; a < aktarma; a += 1) {
      yaz({ tur: "aktarmaya-girdi", nokta: NOKTALAR[sonraki() % NOKTALAR.length] });
      yaz({ tur: "aktarmadan-cikti" });
    }
    yaz({ tur: "dagitima-cikildi" });
    const deneme = 1 + (sonraki() % 3);
    for (let d = 0; d < deneme; d += 1) {
      yaz({ tur: "teslim-denendi" });
      if (d < deneme - 1 && sonraki() % 2 === 0) yaz({ tur: "adres-duzeltildi", adres: `A${(sonraki() % 900) + 100}` });
    }
    yaz({ tur: "teslim-edildi" });
  }
  return olayDeposu.toplamOlay();
}
```

#### Ölçüm

Ölçüm üç şeyi sayar. Birincisi yanıtlanabilirlik: dört sorunun kaçı hangi kalıcılıktan
çıkıyor. İkincisi yeniden kurma maliyeti: dört ayrı anlık görüntü aralığında okunan olay,
okunan görüntü ve yazılan görüntü sayıları. Üçüncüsü bayt boyu. Yeniden kurmanın süresi
donanıma ve koşum ortamına bağlı olduğu için ölçü olarak süre değil **okunan olay sayısı**
kullanılıyor; iş yükü doğrudan bu sayıyla artar.

```js
// olay-say.mjs — dort sorunun yanitlanabilirligi, yeniden kurma icin okunan olay ve anlik goruntu etkisi
import { olayDeposu } from "./olay-deposu.mjs";
import { durumModeli } from "./durum-modeli.mjs";
import { gecmisUret } from "./gecmis-uret.mjs";

const toplam = gecmisUret(400);
console.log(`teslimat = 400, olay = ${toplam}, olay dizisi boyutu = ${olayDeposu.boyut()} bayt` +
  `, durum tablosu boyutu = ${durumModeli.boyut()} bayt`);

const SORULAR = [
  ["su anki durum", (d) => d.durum, (s) => s.durum],
  ["adres duzeltmesi", (d) => d.adresDuzeltmesi, (s) => s.adresDuzeltmesi],
  ["aktarma sayisi", (d) => d.aktarmaSayisi, (s) => s.aktarmaSayisi],
  ["teslim denemesi", (d) => d.denemeSayisi, (s) => s.denemeSayisi],
];
const enUzun = olayDeposu.kimlikler().reduce((a, b) => (olayDeposu.olaySayisi(a) >= olayDeposu.olaySayisi(b) ? a : b));
console.log(`en uzun gecmis = ${enUzun}, olay = ${olayDeposu.olaySayisi(enUzun)}`);

const olaydan = olayDeposu.yenidenKur(enUzun, 0).durum;
const satirdan = durumModeli.oku(enUzun);
console.log(`${enUzun} icin dort soru:`);
let yanitlanan = 0;
for (const [ad, olayYolu, satirYolu] of SORULAR) {
  const o = olayYolu(olaydan), s = satirYolu(satirdan);
  if (s !== undefined) yanitlanan += 1;
  console.log(`  ${ad.padEnd(17)} olay dizisi = ${String(o).padEnd(13)} durum tablosu = ${s ?? "yanit yok"}`);
}
console.log(`durum tablosunun yanitladigi soru = ${yanitlanan} / 4, olay dizisinin = 4 / 4`);

const tam = JSON.stringify(olayDeposu.kimlikler().map((id) => olayDeposu.yenidenKur(id, 0).durum));
for (const aralik of [0, 20, 10, 5]) {
  const yazilan = olayDeposu.goruntuleriHazirla(aralik);
  let okunanOlay = 0, okunanGoruntu = 0;
  const durumlar = olayDeposu.kimlikler().map((id) => {
    const r = olayDeposu.yenidenKur(id, aralik);
    okunanOlay += r.okunanOlay; okunanGoruntu += r.okunanGoruntu;
    return r.durum;
  });
  const tek = olayDeposu.yenidenKur(enUzun, aralik).okunanOlay;
  console.log(`aralik = ${String(aralik).padStart(2)}  yazilan goruntu = ${String(yazilan).padStart(4)}` +
    `, okunan olay = ${String(okunanOlay).padStart(4)}, okunan goruntu = ${String(okunanGoruntu).padStart(3)}` +
    `, ${enUzun} icin okunan olay = ${String(tek).padStart(2)}, durum ayni mi = ${JSON.stringify(durumlar) === tam}`);
}
```

```sh
node olay-say.mjs
```

```
teslimat = 400, olay = 4699, olay dizisi boyutu = 150754 bayt, durum tablosu boyutu = 26293 bayt
en uzun gecmis = T375, olay = 49
T375 icin dort soru:
  su anki durum     olay dizisi = teslim-edildi durum tablosu = teslim-edildi
  adres duzeltmesi  olay dizisi = 1             durum tablosu = yanit yok
  aktarma sayisi    olay dizisi = 21            durum tablosu = yanit yok
  teslim denemesi   olay dizisi = 3             durum tablosu = yanit yok
durum tablosunun yanitladigi soru = 1 / 4, olay dizisinin = 4 / 4
aralik =  0  yazilan goruntu =    0, okunan olay = 4699, okunan goruntu =   0, T375 icin okunan olay = 49, durum ayni mi = true
aralik = 20  yazilan goruntu =   26, okunan olay = 4179, okunan goruntu =  16, T375 icin okunan olay =  9, durum ayni mi = true
aralik = 10  yazilan goruntu =  297, okunan olay = 1729, okunan goruntu = 255, T375 icin okunan olay =  9, durum ayni mi = true
aralik =  5  yazilan goruntu =  776, okunan olay =  819, okunan goruntu = 400, T375 icin okunan olay =  4, durum ayni mi = true
```

#### Sayıların Okunması

Dört soru ayrımı gösteriyor. Durum tablosu 4 sorunun 1'ini yanıtladı; kalan üçü şemasında
bulunmadığı için yanıtsız kaldı. Olay dizisi 4'ünü de yanıtladı ve bunu şemasına yeni bir alan
eklemeden yaptı: adres düzeltmesi 1, aktarma sayısı 21, teslim denemesi 3. Aradaki fark
saklanan bilgi miktarı değil, **hangi soruların önceden bilinmek zorunda olduğudur**. Durum
saklayan kalıcılıkta soru şema tasarlanırken bilinmelidir; olay dizisinde sorulduğu anda
yanıtlanır.

Yeniden kurma satırları anlık görüntünün etkisini veriyor. Görüntü olmadan 400 teslimatın
durumunu kurmak 4699 olay okuyor. Aralık 20 iken toplam yalnız 4179'a iniyor: ortalama geçmiş
uzunluğu 11,7 olduğu için teslimatların çoğu yirmi olaya hiç ulaşmıyor, 400 teslimattan yalnız
16'sı bir görüntü kullanabiliyor. Aralık 10 iken okunan olay 1729'a, aralık 5 iken 819'a
iniyor. Kural tek cümleyle yazılır: anlık görüntü aralığı ortalama geçmiş uzunluğunun altına
inmedikçe okunan olay sayısı düşmez.

Bedel öbür sütunda duruyor. Aralık 5'e indiğinde okunan olay 4699'dan 819'a düşerken yazılan
görüntü sayısı 0'dan 776'ya çıkıyor ve her yeniden kurma bir de görüntü okuması ekliyor. Yani
görüntü, okuma yükünü yazma yüküne çeviren bir takastır.

En uzun geçmiş bunu tek teslimatta gösteriyor. T375'in 49 olayı var; görüntüsüz yeniden kurma
49 olay okuyor, aralık 20 ile 9, aralık 10 ile 9, aralık 5 ile 4 olay okuyor. Dört aralıkta da
kurulan durum tam yeniden kurmayla aynı çıktı; görüntü bir kısayoldur, ayrı bir doğruluk
kaynağı değildir.

#### Bedel ve Sınır

Olay dizisi 150754 bayt, durum tablosu 26293 bayt yer tuttu: yaklaşık 5,7 kat. Bu oran zamanla
büyür, çünkü olay dizisi yalnız uzar. Durum tablosu ise teslimat sayısıyla büyür, geçmiş
uzunluğuyla değil.

İkinci bedel şema evrimidir. Yazılmış bir olay değiştirilmediği için bir olay türünün alanları
sonradan değişirse eski olaylar eski biçimiyle kalır ve katlama işlevi iki biçimi de
tanımak zorundadır; `GECIS` tablosunda olay türü başına birden çok sürüm belirir. Durum saklayan
kalıcılıkta aynı değişiklik tek bir göç işlemiyle biter.

Üçüncüsü bir alan kısıtıdır: olay dizisi bir kaydın geçmişini kalıcı hâle getirir. Bir kaydın
tamamen silinmesi gereken durumlarda dizinin eklemeli olması bir engeldir ve bu, tasarım
kararının alanla birlikte verilmesi gerektiğini gösterir.

Kazanç bu üç bedele karşı iki koşulda doğar: geçmişin kendisinin alan için bir değeri olması
ve sonradan sorulacak soruların önceden bilinmemesi. Yalnız son durumun sorulduğu bir kayıtta
olay dizisi 5,7 kat yer ve bir katlama işlevi karşılığında hiçbir yeni yanıt üretmez.

#### Özet

- Olay kaynaklı kalıcılık durumu değil onu üreten olay dizisini saklar; durum katlama işleviyle
  türetilir ve katlama saf fonksiyon olduğu için dizinin ortasından başlatılabilir.
- Dört sorudan durum tablosu 1'ini, olay dizisi 4'ünü yanıtladı; fark saklanan bilgi miktarı
  değil, soruların şema tasarlanırken bilinmek zorunda olup olmamasıdır.
- 400 teslimatın durumunu kurmak görüntüsüz 4699 olay okudu; aralık 20, 10 ve 5 iken bu sayı
  4179, 1729 ve 819 oldu. Aralık ortalama geçmiş uzunluğunun altına inmedikçe kazanç küçüktür.
- Görüntü okuma yükünü yazma yüküne çevirir: okunan olay 819'a inerken yazılan görüntü 776
  oldu. Dört aralıkta da kurulan durum tam yeniden kurmayla aynı çıktı.
- Bedel 5,7 katlık yer, olay türü başına biriken sürüm sayısı ve eklemeli dizinin silmeyi
  güçleştirmesidir; kazanç geçmişin alan için değerli olmasına bağlıdır.

#### Sonraki Adım

Bu konunun dört dersi de aynı biçimde ilerledi: bir model kuruldu, bir ölçü seçildi, iki düzen
aynı ölçüyle karşılaştırıldı. Ama kararların hiçbiri bir kez verilip bitmedi. Kullanım
senaryosundaki karar sayısı, bağlantı noktalarının adları, okuma modelinin satırları ve olay
türlerinin listesi — bunların her biri alan uzmanıyla konuşuldukça değişir ve her değişiklik
kodda bir bedel ister. Bir model iyileştirmesinin gerçekten iyileştirme olup olmadığı, ancak o
bedel sayıldığında bilinir. Sonraki ders modelin gerçekleştirimle birlikte evrimini ele alır:
alan uzmanının bir cümlesinin modelde karşılığı olmadığında yapılan iyileştirmeyi, kuralın
yazım yeri sayısındaki düşüşü, adların örtüşme oranını ve iyileştirmenin düzenlettiği dosya
sayısını ölçer.

### Model Güdümlü Tasarım

Source: https://academia.sh/tr/kurslar/alan-odakli-tasarim/model-gudumlu-tasarim

Modelin gerçekleştirimle birlikte evrimi: alan uzmanının cümlesinde geçen yedi addan kaçının modelin sözlüğünde bulunduğunun sayılması, örtük kalan rota ayağı kavramı açığa çıkarıldığında köşeut dizi indekslemesi ile kural yazım yeri sayısındaki düşüşün ölçülmesi, iyileştirmenin satır bedeli ve üç raporun davranış eşitliğinin sınanması.

Bu konunun dört dersi de aynı biçimde ilerledi: bir model kuruldu, bir ölçü seçildi, iki düzen
aynı ölçüyle karşılaştırıldı. Kararların hiçbiri bir kez verilip bitmedi. Kullanım
senaryosundaki karar sayısı, bağlantı noktalarının adları, okuma modelinin satırları ve olay
türlerinin listesi alan uzmanıyla konuşuldukça değişir; her değişiklik de kodda bir bedel
ister. Bir model iyileştirmesinin gerçekten iyileştirme olup olmadığı ancak o bedel
sayıldığında bilinir.

**Model güdümlü tasarım** (model driven design) tam bu bağı zorunlu kılar: modelin
kavramlarıyla gerçekleştirimin adları arasında birebir karşılık aranır, model değiştiğinde kod
onunla birlikte değişir, kodda karşılığı ucuzca kurulamayan bir model iyileştirmesi de
iyileştirme sayılmaz. Bağ tek yönlü değildir; gerçekleştirimde ortaya çıkan bir zorluk modelde
eksik bir kavramın işaretidir.

#### Örtük Kalan Kavram

Operasyon uzmanı bir gecikme tartışmasında şu cümleyi kurar: rotanın her ayağı bir çıkış ile
bir varış noktası arasında bir taşıyıcıya aittir; ayak planlanan günden sonra gerçekleşirse
gecikme o ayağın taşıyıcısına yazılır. Cümlede yedi ad var: ayak, çıkış, varış, taşıyıcı,
planlanan, gerçekleşen, gecikme.

Kitaplığın modelinde bu adların bir kısmı yok. Rota bir nokta dizisidir; taşıyıcılar,
planlanan günler ve gerçekleşen günler ona köşeut ayrı dizilerdir. Ayak diye bir şey yoktur;
her rapor onu indekslerden yeniden türetir. Böyle bir kavrama **örtük kavram** (implicit
concept), onun açığa çıkarılmasına ise **model kırılması** (breakthrough) denir.

```js
// ortak/kayit.mjs — ortuk modelin alan sozlugu: bir kayit, dort koseut dizi
export function teslimatKaydi(id, rota, tasiyicilar, planlanan, gerceklesen) {
  return { id, rota, tasiyicilar, planlanan, gerceklesen };
}
```

Ölçümün iki sürümde aynı veriyi kullanması gerekir; üretici sabittir.

```js
// ortak/uret.mjs — iki surumun de okudugu sabit kayit kumesi
import { teslimatKaydi } from "./kayit.mjs";

const NOKTALAR = ["34", "06", "35", "01", "16"];
const TASIYICILAR = ["AN", "KG", "MT"];

export function uret(sayi) {
  let x = 29;
  const s = () => (x = (x * 48271 + 11) % 2147483647);
  return Array.from({ length: sayi }, (_, i) => {
    const nokta = 2 + (s() % 3);
    const bas = s() % NOKTALAR.length;
    const rota = Array.from({ length: nokta }, (_, j) => NOKTALAR[(bas + j) % NOKTALAR.length]);
    const tasiyicilar = [], planlanan = [], gerceklesen = [];
    let gun = 0, bitti = false;
    for (let a = 0; a < nokta - 1; a += 1) {
      tasiyicilar.push(TASIYICILAR[s() % TASIYICILAR.length]);
      gun += 1 + (s() % 3);
      planlanan.push(gun);
      if (bitti || s() % 6 === 0) { bitti = true; gerceklesen.push(null); continue; }
      const gecikme = s() % 3 === 0 ? 1 + (s() % 3) : 0;
      gerceklesen.push(gun + gecikme);
    }
    return teslimatKaydi(`T${i + 1}`, rota, tasiyicilar, planlanan, gerceklesen);
  });
}
```

#### Kavram Örtükken

Üç rapor aynı işi yapar: gecikme raporu geciken ayakları listeler, taşıyıcı karnesi gecikmeyi
taşıyıcıya yazar, pano sıradaki ayağı ve teslimatın durumunu gösterir. Üçü de ayağı
indekslerden türetir ve üçü de gecikme kuralını kendi içinde yazar.

```js
// ortuk/raporlar.mjs — uc rapor, uc kez ayak turetmesi ve uc kez gecikme kurali
export function gecikmeRaporu(kayitlar) {
  const satir = [];
  for (const k of kayitlar) {
    for (let i = 0; i < k.rota.length - 1; i += 1) {
      if (k.gerceklesen[i] === null) continue;
      if (k.gerceklesen[i] > k.planlanan[i]) {
        satir.push({
          id: k.id, ayak: `${k.rota[i]}-${k.rota[i + 1]}`, tasiyici: k.tasiyicilar[i],
          gecikmeGunu: k.gerceklesen[i] - k.planlanan[i],
        });
      }
    }
  }
  return satir;
}

export function tasiyiciKarnesi(kayitlar) {
  const karne = new Map();
  for (const k of kayitlar) {
    for (let i = 0; i < k.rota.length - 1; i += 1) {
      const t = k.tasiyicilar[i];
      const s = karne.get(t) ?? { tasiyici: t, ayak: 0, geciken: 0, toplamGecikme: 0 };
      s.ayak += 1;
      if (k.gerceklesen[i] !== null && k.gerceklesen[i] > k.planlanan[i]) {
        s.geciken += 1;
        s.toplamGecikme += k.gerceklesen[i] - k.planlanan[i];
      }
      karne.set(t, s);
    }
  }
  return [...karne.values()].sort((a, b) => a.tasiyici.localeCompare(b.tasiyici));
}

export function pano(kayitlar) {
  return kayitlar.map((k) => {
    let siradaki = null, gecikmeli = false;
    for (let i = 0; i < k.rota.length - 1; i += 1) {
      if (k.gerceklesen[i] === null) { siradaki = `${k.rota[i]}-${k.rota[i + 1]}`; break; }
      if (k.gerceklesen[i] > k.planlanan[i]) gecikmeli = true;
    }
    const durum = siradaki === null ? "tamamlandi" : gecikmeli ? "gecikmeli" : "yolda";
    return { id: k.id, siradakiAyak: siradaki, durum };
  });
}
```

`k.rota.length - 1` ifadesi üç yerde geçiyor ve her geçişinde aynı bilgiyi taşıyor: ayak
sayısı nokta sayısından bir eksiktir. Bu bilgi modelde bir yerde yazılı değil, üç rapor
gövdesinde tekrarlanıyor.

#### Kavramın Açığa Çıkarılması

Ayak bir değer nesnesi olur: kimliği yoktur, çıkışı, varışı, taşıyıcısı, planlanan ve
gerçekleşen günü ile tanımlanır. Cümledeki kurallar da onun yöntemleridir.

```js
// acik/alan/rota-ayagi.mjs — ayak: cikis, varis, tasiyici, planlanan ve gerceklesen gunu tasiyan deger nesnesi
export class RotaAyagi {
  #cikis; #varis; #tasiyici; #planlanan; #gerceklesen;

  constructor({ cikis, varis, tasiyici, planlanan, gerceklesen }) {
    this.#cikis = cikis; this.#varis = varis; this.#tasiyici = tasiyici;
    this.#planlanan = planlanan; this.#gerceklesen = gerceklesen;
  }

  get ad() { return `${this.#cikis}-${this.#varis}`; }
  get tasiyici() { return this.#tasiyici; }
  tamamlandi() { return this.#gerceklesen !== null; }
  gecikti() { return this.tamamlandi() && this.#gerceklesen > this.#planlanan; }
  gecikmeGunu() { return this.gecikti() ? this.#gerceklesen - this.#planlanan : 0; }
}
```

```js
// acik/alan/rota.mjs — koseut dizileri ayak dizisine ceviren tek yer
import { RotaAyagi } from "./rota-ayagi.mjs";

export class Rota {
  #ayaklar;

  static kayittan(k) {
    return new Rota(k.rota.slice(0, -1).map((cikis, i) => new RotaAyagi({
      cikis, varis: k.rota[i + 1], tasiyici: k.tasiyicilar[i],
      planlanan: k.planlanan[i], gerceklesen: k.gerceklesen[i],
    })));
  }

  constructor(ayaklar) { this.#ayaklar = ayaklar; }

  get ayaklar() { return this.#ayaklar; }
  siradakiAyak() { return this.#ayaklar.find((a) => a.tamamlandi() === false) ?? null; }
  gecikenAyaklar() { return this.#ayaklar.filter((a) => a.gecikti()); }
}
```

```js
// acik/raporlar.mjs — ayni uc rapor; turetme ve kural artik rapor dosyasinda degil
import { Rota } from "./alan/rota.mjs";

export function gecikmeRaporu(kayitlar) {
  return kayitlar.flatMap((k) => Rota.kayittan(k).gecikenAyaklar().map((a) => ({
    id: k.id, ayak: a.ad, tasiyici: a.tasiyici, gecikmeGunu: a.gecikmeGunu(),
  })));
}

export function tasiyiciKarnesi(kayitlar) {
  const karne = new Map();
  for (const k of kayitlar) {
    for (const a of Rota.kayittan(k).ayaklar) {
      const s = karne.get(a.tasiyici) ?? { tasiyici: a.tasiyici, ayak: 0, geciken: 0, toplamGecikme: 0 };
      s.ayak += 1;
      if (a.gecikti()) { s.geciken += 1; s.toplamGecikme += a.gecikmeGunu(); }
      karne.set(a.tasiyici, s);
    }
  }
  return [...karne.values()].sort((a, b) => a.tasiyici.localeCompare(b.tasiyici));
}

export function pano(kayitlar) {
  return kayitlar.map((k) => {
    const rota = Rota.kayittan(k), siradaki = rota.siradakiAyak();
    const gecikmeli = rota.gecikenAyaklar().length > 0;
    const durum = siradaki === null ? "tamamlandi" : gecikmeli ? "gecikmeli" : "yolda";
    return { id: k.id, siradakiAyak: siradaki === null ? null : siradaki.ad, durum };
  });
}
```

Kalıcılık biçimi değişmedi: kayıt yine dört köşeut dizidir. Değişen tek şey modelin
sözlüğüdür.

#### Ölçüm

Ölçüm dört sayı çıkarır. Birincisi hiza: cümledeki yedi addan kaçı modelin sözlüğünde
bulunuyor. İkincisi köşeut dizi indekslemesi sayısı. Üçüncüsü kural başına yazım yeri.
Dördüncüsü satır bedeli. Ardından üç rapor iki sürümde aynı kayıtlarla karşılaştırılır.

```js
// evrim-say.mjs — ad ortusmesi, koseut indeksleme, kural yazim yeri, satir bedeli ve davranis esitligi
import { readFileSync } from "node:fs";
import { uret } from "./ortak/uret.mjs";
import * as ortuk from "./ortuk/raporlar.mjs";
import * as acik from "./acik/raporlar.mjs";

const CUMLEDEKI_ADLAR = ["ayak", "cikis", "varis", "tasiyici", "planlanan", "gerceklesen", "gecikme"];
const KURAL = {
  "gecikme": /gerceklesen[^\n]*>[^\n]*planlanan/g,
  "tamamlanma": /gerceklesen[^\n]*(===|!==)[^\n]*null/g,
  "gecikme gunu": /gerceklesen[^\n]*-[^\n]*planlanan/g,
};
const INDEKS = /(rota|tasiyicilar|planlanan|gerceklesen)\[/g;
const SURUM = {
  "ortuk": { sozluk: ["ortak/kayit.mjs"], govde: ["ortuk/raporlar.mjs"] },
  "acik": { sozluk: ["acik/alan/rota-ayagi.mjs", "acik/alan/rota.mjs"], govde: ["acik/raporlar.mjs"] },
};

const oku = (d) => readFileSync(d, "utf8");
const satir = (d) => oku(d).split("\n").filter((s) => s.trim() !== "").length;

for (const [ad, { sozluk, govde }] of Object.entries(SURUM)) {
  const sozlukMetni = sozluk.map(oku).join("\n").toLowerCase();
  const belirtec = new Set(sozlukMetni.match(/[a-z_][a-z0-9_]*/g));
  const bulunan = CUMLEDEKI_ADLAR.filter((a) => [...belirtec].some((b) => b.startsWith(a)));
  const tumu = [...sozluk, ...govde].map(oku).join("\n");
  console.log(`${ad} surum`);
  console.log(`  alan sozlugu = ${sozluk.join(", ")} (${sozluk.reduce((s, d) => s + satir(d), 0)} satir)`);
  console.log(`  cumledeki 7 addan alan sozlugunde bulunan = ${bulunan.length} (${bulunan.join(", ")})`);
  console.log(`  koseut dizi indekslemesi = ${(tumu.match(INDEKS) ?? []).length}`);
  let toplamKural = 0;
  for (const [k, kalip] of Object.entries(KURAL)) {
    const n = (tumu.match(kalip) ?? []).length;
    toplamKural += n;
    console.log(`  kural "${k}" yazim yeri = ${n}`);
  }
  console.log(`  kural yazimi toplami = ${toplamKural}, rapor govdesi = ${govde.reduce((s, d) => s + satir(d), 0)} satir`);
}

const KAYITLAR = uret(60);
const RAPORLAR = ["gecikmeRaporu", "tasiyiciKarnesi", "pano"];
let ayrilan = 0;
for (const r of RAPORLAR) {
  const a = JSON.stringify(ortuk[r](KAYITLAR)), b = JSON.stringify(acik[r](KAYITLAR));
  if (a !== b) ayrilan += 1;
  console.log(`${r.padEnd(15)} esit mi = ${a === b}, satir = ${JSON.parse(a).length}`);
}
console.log(`ayrilan rapor = ${ayrilan} / ${RAPORLAR.length}`);
console.log(`tasiyici karnesi = ${JSON.stringify(acik.tasiyiciKarnesi(KAYITLAR))}`);
console.log(`ornek pano satiri = ${JSON.stringify(acik.pano(KAYITLAR)[0])}`);
```

```sh
node evrim-say.mjs
```

```
ortuk surum
  alan sozlugu = ortak/kayit.mjs (4 satir)
  cumledeki 7 addan alan sozlugunde bulunan = 3 (tasiyici, planlanan, gerceklesen)
  koseut dizi indekslemesi = 19
  kural "gecikme" yazim yeri = 3
  kural "tamamlanma" yazim yeri = 3
  kural "gecikme gunu" yazim yeri = 2
  kural yazimi toplami = 8, rapor govdesi = 43 satir
acik surum
  alan sozlugu = acik/alan/rota-ayagi.mjs, acik/alan/rota.mjs (28 satir)
  cumledeki 7 addan alan sozlugunde bulunan = 7 (ayak, cikis, varis, tasiyici, planlanan, gerceklesen, gecikme)
  koseut dizi indekslemesi = 4
  kural "gecikme" yazim yeri = 1
  kural "tamamlanma" yazim yeri = 1
  kural "gecikme gunu" yazim yeri = 1
  kural yazimi toplami = 3, rapor govdesi = 27 satir
gecikmeRaporu   esit mi = true, satir = 27
tasiyiciKarnesi esit mi = true, satir = 3
pano            esit mi = true, satir = 60
ayrilan rapor = 0 / 3
tasiyici karnesi = [{"tasiyici":"AN","ayak":42,"geciken":13,"toplamGecikme":22},{"tasiyici":"KG","ayak":40,"geciken":7,"toplamGecikme":16},{"tasiyici":"MT","ayak":44,"geciken":7,"toplamGecikme":13}]
ornek pano satiri = {"id":"T1","siradakiAyak":null,"durum":"tamamlandi"}
```

#### Sayıların Okunması

Hiza sayısı 3'ten 7'ye çıktı. Örtük sürümün sözlüğünde cümledeki yedi addan yalnız üçü
vardı; ayak, çıkış, varış ve gecikme kodda bir ad taşımıyordu. Açık sürümde yedisi de var ve
hiçbiri bir raporun içine gömülü değil.

Köşeut dizi indekslemesi 19'dan 4'e indi. Kalan 4 indeksleme tek yerdedir: `Rota.kayittan`
yönteminde, kaydın dizilerini ayak nesnelerine çevirdiği satırlarda. "Ayak sayısı nokta
sayısından bir eksiktir" bilgisi artık modelde bir kez yazılı.

Kural yazımı toplamı 8'den 3'e indi ve dağılım daha önemlidir: gecikme kuralı üç yerden bir
yere, tamamlanma kuralı üç yerden bir yere indi. Bu iki sayı doğrudan bir değişim maliyetidir.
Gecikmenin tanımı değişirse — diyelim tolerans yarım gün eklenirse — örtük sürümde üç rapor
gövdesi, açık sürümde bir değer nesnesi yöntemi düzenlenir.

Bedel de görünür durumda. İyileştirme 2 dosya ve 28 satır ekledi, rapor gövdesi 43 satırdan
27'ye indi; net artış 12 satır. Bedelin küçük kalmasının nedeni kalıcılık biçimine
dokunulmamasıdır: göç işlemi ya da şema değişikliği gerektirmeyen bir iyileştirme ucuzdur.

Son satırlar davranışın korunduğunu gösteriyor: altmış kayıtta üç raporun üçü de aynı çıktıyı
üretti, ayrılan rapor 0. Taşıyıcı karnesinde `AN` 42 ayakta 13 gecikme ve 22 gün, `KG` 40
ayakta 7 gecikme ve 16 gün taşıyor.

#### İyileştirme Ölçütü

Her yeni sınıf bir model iyileştirmesi değildir. Ölçüt bu dört sayıdan çıkar: hiza arttı mı,
türetme tek yere indi mi, kural yazımı azaldı mı, bedel bunların karşılığında makul mü?

Nokta kodunu saran bir `Nokta` sınıfı eklemek bu ölçütte kaybeder: cümlede "nokta" adı zaten
`rota` dizisiyle karşılanıyordu, indeksleme sayısı değişmez, hiçbir kural bir yerden bir yere
taşınmaz. Kalan tek etki 1 dosya ve bir dolaylılık basamağıdır — kazancı 0, bedeli 0'dan
büyük.

Ters yön de vardır. Gerçekleştirimde tekrar eden bir türetme, modelde eksik bir kavramın en
güvenilir işaretidir. Bu dersteki iyileştirme kod okunarak değil, `k.rota.length - 1`
ifadesinin üç yerde göründüğü fark edilerek bulundu. Model güdümlü tasarımın iki yönlü olması
budur: model kodu biçimlendirir, kodda biriken tekrar da modele bir soru sorar.

#### Özet

- Model güdümlü tasarım modelin kavramlarıyla gerçekleştirimin adları arasında birebir karşılık
  arar; kodda ucuzca kurulamayan bir iyileştirme iyileştirme sayılmaz.
- Alan uzmanının cümlesindeki yedi addan örtük sürümün sözlüğünde 3'ü, ayak kavramı açığa
  çıkarıldıktan sonra 7'si bulundu.
- Köşeut dizi indekslemesi 19'dan 4'e indi ve kalan dördü tek bir çevirim yönteminde toplandı;
  kural yazımı toplamı 8'den 3'e, gecikme kuralı üç yerden bir yere indi.
- Bedel 2 dosya ve 28 satır; rapor gövdesi 43'ten 27 satıra indiği için net artış 12 satır
  oldu ve kalıcılık biçimine dokunulmadı.
- Altmış kayıtta üç raporun üçü de aynı çıktıyı verdi; iyileştirme davranışı değiştirmedi.
- Kazancı 0 olan bir sınıf eklemek iyileştirme değildir; gerçekleştirimde tekrar eden türetme
  ise modelde eksik kavramın işaretidir.

#### Kurs Kapanışı

Kurs tek bir soruyla açıldı: bir kod tabanının yapısı alanın kendi ayrımlarından türetilebilir
mi ve türetildiğinde bu bir ölçüyle gösterilebilir mi? Üç konu bu soruyu üç ölçekte ele aldı
ve her ders aynı alanın iki modelini aynı ölçüyle karşılaştırdı.

**Alan Modeli** konusu modelin içini kurdu. Her Yerde Aynı Dil dersinde beş kavramın on üç ad
taşıdığı sayıldı; adlar birleştirildiğinde beş ada indi, iki ad arasındaki beş çeviri noktası
0'a düştü ve dağınık sürümde otuz gönderinin 19'unda ayrılan teklif ile ücret birleşik sürümde
0'ında ayrıldı. Varlıklar ve Değer Nesneleri dersleri kimlikle ve değerle tanımlanmanın
ölçütünü ayırdı. Toplamlar ve Kök dersinde tutarlılık sınırı çizildi: sınırı dışa açan modelde
beş çağrı yolunun 4'ü indirim tavanını aşan bir gönderi üretebiliyordu, kökten geçen modelde
0'ı üretti. Alan Servisleri dersi nesneye ait olmayan davranışın yerini, Fabrikalar ve Depolar
dersi yaratım ile erişimin alan diline uymasını belirledi. Alan Olayları dersinde uzmanın üç
sorusundan genel kütük 1'ini, alan kütüğü 3'ünü yanıtladı. Konu, kuralı servise taşıyan düzenin
bedeliyle kapandı: aynı kuralı yazan üç servis 36 gönderinin 24'ünde ayrıştı, kural nesnenin
üzerine alındığında ayrışma 0.

**Bağlamlar** konusu modelin nerede geçerli olduğunu belirledi. Sınırlı Bağlam dersinde iki
bağlamın tek sınıfta birleştirilmesinin bedeli sayıldı: birleşik sınıfta 105 alan çiftinin
68'i hiçbir senaryoda birlikte geçmiyordu, sınır çizildiğinde çift sayısı 56'ya ve birlikte
geçmeyen çift 19'a indi; on değişmez tek sınıfta altı gerçek gönderinin altısını reddederken
iki sınıfa dağıtıldığında altısı da geçti. Bağlam Haritası ilişki türlerini, Yozlaşma Önleyici
Katman dersi dış modelin sızmasını ele aldı — sızan düzende dış sözlük iki çekirdek dosyanın
ikisinde de geçiyordu, korumalı düzende 0 kaldı; bedel 2 dosya ve 19 satır. Paylaşılan Çekirdek ve Uyum Sağlayan dersi işbirliği kalıplarını, Alt Alanlar
dersi çekirdek, destekleyici ve genel ayrımını kurdu.

**Uygulama Mimarisine Bağlanma** konusu modeli bir uygulamanın içine yerleştirdi. Kararlar
kullanım senaryosundan toplama köküne indirildiğinde uygulama katmanının karar noktası 13'ten
2'ye, beş alan cümlesinin yazım yeri 10'dan 5'e indi. Alanın dışa açıldığı yerler bağlantı
noktası olarak adlandırıldığında dış dünyaya giden doğrudan bağ 3'ten 0'a indi ve aynı alan
dosyası iki bağdaştırıcı takımıyla koştu. Okuma modeli ayrıldığında pano sorgusunun gezdiği
nesne 800 teslimatta 4621'den 5'e indi, karşılığında 5 komutluk bir gerilik ödendi. Olay
dizisi saklandığında dört sorudan yanıtlanan 1'den 4'e çıktı; anlık görüntü aralığı 5'e
düşürüldüğünde yeniden kurma için okunan olay 4699'dan 819'a indi. Bu son ders de modelin
kendisinin durağan olmadığını gösterdi: cümledeki ad örtüşmesi 3'ten 7'ye, kural yazımı 8'den
3'e indi.

Ortak kalıp şudur: her ders bir hizasızlık buldu, ona bir sayı verdi, hizayı düzeltti ve
bedeli aynı cinsten bir sayıyla yazdı. Hiçbir yerde "model daha ifadeli oldu" denmedi; ad
sayısı, çağrı yolu, alan çifti, karar noktası, gezilen nesne ve okunan olay sayıldı.

Kursun bıraktığı soru bir basamak yukarıdadır. Buraya kadarki kararların hepsi **bir**
uygulamanın içindeydi. Bağlam haritası birden çok bağlamı adlandırdı ama hepsi aynı çalışan
programın parçasıydı. Bağlamlar ayrı programlara dağıtıldığında, ya da tek bir programda
tutulmaya karar verildiğinde ölçüler değişir: bir isteğin dokunduğu birim sayısı, geçtiği modül
sınırı sayısı, bir sürümün dağıtılabilmesi için birlikte yayınlanması gereken parça sayısı.
Sonraki kurs **Mimari Biçemler** bu soruyu alır ve mimari düzenleri bu ölçülerle karşılaştırır:
aynı kitaplığın tek parça, katmanlı ve dağıtık düzenlenişleri arasındaki fark nerede sayıya
dökülür.

