---
title: 'Olay Kaynaklı Kalıcılık'
source: 'https://academia.sh/tr/kurslar/alan-odakli-tasarim/olay-kaynakli-kalicilik'
course: 'Alan Odaklı Tasarım'
language: tr
updated: '2026-08-17T18:10:40+00:00'
license: 'CC BY-SA 4.0'
---

# Olay Kaynaklı Kalıcılık

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.
