---
title: 'Aktif Kayıt ve Veri Eşleyici'
source: 'https://academia.sh/tr/kurslar/tasarim-kaliplari/aktif-kayit-ve-veri-esleyici'
course: 'Tasarım Kalıpları'
language: tr
updated: '2026-08-17T18:10:38+00:00'
license: 'CC BY-SA 4.0'
---

# Aktif Kayıt ve Veri Eşleyici

Nesne ile saklanan kayıt arasındaki çevrimin nereye konulacağı: çevrimi nesnenin üzerine koyan aktif kayıt ile ayrı bir eşleyiciye veren veri eşleyicinin karşılaştırılması, alan modülündeki kalıcılık izinin ve ithal kapanışının sayılması, iki sürümün gidiş-dönüşte eşitliği ve ikinci bir saklama biçiminin alan modülüne dokunmadan eklenmesi.

Önceki ders iş mantığının iki düzenlenişini karşılaştırdı ve ikisini de bellekte bıraktı.
Gönderi saklanmaya başladığı anda nesne ile saklanan kayıt arasında bir **çevrim** doğar:
nesnenin alanları bir satıra, satır geri nesneye dönüştürülmelidir. Bu çevrimin nereye
konulacağı iki kalıp üretir. **Aktif kayıt** (active record) çevrimi ve saklama erişimini
nesnenin kendi üzerine koyar; nesne kendi satırını okur ve yazar. **Veri eşleyici** (data
mapper) çevrimi ayrı bir nesneye verir; alan nesnesi saklandığından habersiz kalır.

Bu iki kalıp Veri Erişim Katmanı kursunda kurulmuş ve orada üretilen sorgu sayısıyla
ölçülmüştü. Burada ölçü bir tasarım ölçüsüdür: alan modülünün kalıcılık hakkında bilmek
zorunda olduğu ad sayısı, ithal kapanışının boyutu ve saklama biçimi değiştiğinde dokunulan
dosya sayısı. Bağlantı havuzu, sorgu planı ve dizin ayrıntısı o kursa aittir; burada saklama
yerine sade bir bellek içi depo konur.

```sh
mkdir -p aktif esleyici
```

```js
// bellek-depo.mjs — sade nesne deposu: tablo adi ve kimlige gore satir tutar
const TABLOLAR = new Map();
const tablo = (ad) => TABLOLAR.get(ad) ?? TABLOLAR.set(ad, new Map()).get(ad);

export const depo = {
  oku: (ad, id) => tablo(ad).get(id) ?? null,
  yaz: (ad, satir) => { tablo(ad).set(satir.id, { ...satir }); },
  say: (ad) => tablo(ad).size,
};
```

## Aktif Kayıt

Kalıbın çözümü tek cümledir: satırın alanlarını taşıyan sınıf, o satırı okuyan ve yazan
yöntemleri de taşır.

```js
// aktif/gonderi.mjs — nesne kendi satirini okur ve yazar
import { depo } from "../bellek-depo.mjs";

const KADEME = [[1, 3900], [5, 6400], [Infinity, 11800]];
const ASGARI_UCRET = 3990;

export class Gonderi {
  constructor(satir) { Object.assign(this, satir); }
  net() {
    const taban = KADEME.find(([ust]) => this.agirlik <= ust)[1];
    return Math.max(Math.round(taban * this.katsayi * (1 - this.indirim)), ASGARI_UCRET);
  }
  kaydet() {
    depo.yaz("gonderi", { id: this.id, agirlik: this.agirlik, katsayi: this.katsayi, indirim: this.indirim });
    return this;
  }
  static bul(id) {
    const satir = depo.oku("gonderi", id);
    return satir === null ? null : new Gonderi(satir);
  }
}
```

Kazanç dosya sayısındadır: bir kavram, bir sınıf, bir dosya. Kaybı, kural ile kalıcılığın
aynı dosyada durmasıdır.

## Veri Eşleyici

Aynı iş iki dosyaya bölünür. Alan nesnesi yalnız kuralı taşır.

```js
// esleyici/gonderi.mjs — alan nesnesi: yalniz kural, kalicilik adi yok
const KADEME = [[1, 3900], [5, 6400], [Infinity, 11800]];
const ASGARI_UCRET = 3990;

export class Gonderi {
  constructor({ id, agirlik, katsayi, indirim }) {
    Object.assign(this, { id, agirlik, katsayi, indirim });
  }
  net() {
    const taban = KADEME.find(([ust]) => this.agirlik <= ust)[1];
    return Math.max(Math.round(taban * this.katsayi * (1 - this.indirim)), ASGARI_UCRET);
  }
}
```

```js
// esleyici/gonderi-esleyici.mjs — cevrimi ve depo erisimini tasiyan esleyici
import { Gonderi } from "./gonderi.mjs";
import { depo } from "../bellek-depo.mjs";

export const gonderiEsleyici = {
  nesneye: (satir) => new Gonderi(satir),
  satira: (g) => ({ id: g.id, agirlik: g.agirlik, katsayi: g.katsayi, indirim: g.indirim }),
  bul(id) {
    const satir = depo.oku("gonderi", id);
    return satir === null ? null : this.nesneye(satir);
  },
  kaydet(g) { depo.yaz("gonderi", this.satira(g)); return g; },
};
```

Bağımlılık oku yön değiştirdi: alan nesnesi eşleyiciyi bilmiyor, eşleyici alan nesnesini
biliyor.

## İzin ve Kapanışın Sayılması

Ölçü üç sayıdır. Birincisi **kalıcılık izi**: alan modülünde depo adının, tablo adının ve
satır sözcüğünün kaç kez geçtiği. İkincisi alan modülünün **ithal kapanışı**: o modülü
yüklemek için yüklenmesi gereken başka modül sayısı. Üçüncüsü hem kuralı hem depo erişimini
taşıyan dosya sayısı.

```js
// iz-say.mjs — alan modulunde kalicilik izi, ithal kapanisi ve depoya baglanan dosya sayisi
import { readFileSync } from "node:fs";
import { dirname, join, normalize } from "node:path";

const IZ = { "depo adi": /\bdepo\b/g, "tablo adi": /"gonderi"/g, "satir sozcugu": /\bsatir\b/g };
const ITHAL = /from "([^"]+)"/g;

function kapanis(baslangic) {
  const gorulen = new Set(), yigit = [normalize(baslangic)];
  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(".")) yigit.push(normalize(join(dirname(dosya), yol)));
    }
  }
  gorulen.delete(normalize(baslangic));
  return gorulen;
}

const SURUM = {
  "aktif kayit": { alan: "aktif/gonderi.mjs", dosyalar: ["aktif/gonderi.mjs"] },
  "veri esleyici": {
    alan: "esleyici/gonderi.mjs",
    dosyalar: ["esleyici/gonderi.mjs", "esleyici/gonderi-esleyici.mjs"],
  },
};

for (const [ad, s] of Object.entries(SURUM)) {
  const metin = readFileSync(s.alan, "utf8");
  const izler = Object.entries(IZ).map(([k, r]) => `${k} = ${[...metin.matchAll(r)].length}`);
  const baglanan = s.dosyalar.filter((d) => readFileSync(d, "utf8").includes("bellek-depo.mjs"));
  const kuralDosyasi = s.dosyalar.filter((d) => readFileSync(d, "utf8").includes("ASGARI_UCRET"));
  const ikisi = kuralDosyasi.filter((d) => baglanan.includes(d));
  console.log(ad);
  console.log(`  alan modulu (${s.alan}) izleri: ${izler.join(", ")}`);
  console.log(`  alan modulu ithal kapanisi = ${kapanis(s.alan).size}`);
  console.log(`  depoya baglanan dosya = ${baglanan.length} / ${s.dosyalar.length} (${baglanan.join(", ")})`);
  console.log(`  hem kural hem depo tasiyan dosya = ${ikisi.length}`);
}
```

```sh
node iz-say.mjs
```

```
aktif kayit
  alan modulu (aktif/gonderi.mjs) izleri: depo adi = 4, tablo adi = 2, satir sozcugu = 5
  alan modulu ithal kapanisi = 1
  depoya baglanan dosya = 1 / 1 (aktif/gonderi.mjs)
  hem kural hem depo tasiyan dosya = 1
veri esleyici
  alan modulu (esleyici/gonderi.mjs) izleri: depo adi = 0, tablo adi = 0, satir sozcugu = 0
  alan modulu ithal kapanisi = 0
  depoya baglanan dosya = 1 / 2 (esleyici/gonderi-esleyici.mjs)
  hem kural hem depo tasiyan dosya = 0
```

Aktif kayıtta alan modülü depo sözcüğünü 4, tablo adını 2, satır sözcüğünü 5 kez taşıyor;
eşleyici sürümünde üçü de 0. İthal kapanışı 1'den 0'a iniyor: kural dosyasını yüklemek için
depo modülünün yüklenmesi gerekmiyor. Son satırlar farkın adını veriyor: aktif kayıtta kural
ile depo erişimi aynı dosyada, eşleyici sürümünde ayrı dosyalarda.

## Gidiş-Dönüş Eşitliği

İki kalıp aynı işi yapıyor mu, sınanır.

```js
// gidis-donus.mjs — iki surum ayni satirlari yazip okuyunca ayni neti veriyor mu
import { Gonderi as AktifGonderi } from "./aktif/gonderi.mjs";
import { Gonderi as AlanGonderi } from "./esleyici/gonderi.mjs";
import { gonderiEsleyici } from "./esleyici/gonderi-esleyici.mjs";

const SATIRLAR = [
  { id: "G1", agirlik: 0.6, katsayi: 1, indirim: 0 },
  { id: "G2", agirlik: 3, katsayi: 1.35, indirim: 0.1 },
  { id: "G3", agirlik: 12, katsayi: 1.8, indirim: 0.4 },
];

let ayrilan = 0;
for (const satir of SATIRLAR) {
  new AktifGonderi(satir).kaydet();
  const a = AktifGonderi.bul(satir.id).net();
  gonderiEsleyici.kaydet(new AlanGonderi(satir));
  const e = gonderiEsleyici.bul(satir.id).net();
  if (a !== e) ayrilan += 1;
  console.log(`${satir.id}  aktif kayit ${String(a).padStart(6)}   veri esleyici ${String(e).padStart(6)}`);
}
console.log(`ayrilan sonuc = ${ayrilan} / ${SATIRLAR.length}`);
```

```sh
node gidis-donus.mjs
```

```
G1  aktif kayit   3990   veri esleyici   3990
G2  aktif kayit   7776   veri esleyici   7776
G3  aktif kayit  12744   veri esleyici  12744
ayrilan sonuc = 0 / 3
```

Üç satırın üçünde sonuç aynı. Fark doğrulukta değil, saklama kararının nereye yazıldığında.

## İkinci Saklama Biçimi

Kalıcılık ayrımının bedelini ödemenin karşılığı burada görünür. İkinci bir depo eklenir:
yazmaları bir kütüğe ekleyen, okumada son satırı veren bir depo.

```js
// kutuk-depo.mjs — ikinci saklama bicimi: her yazma bir kutuk satiri, okuma son satiri verir
const KUTUK = [];

export const kutukDepo = {
  yaz: (ad, satir) => { KUTUK.push({ ad, satir: { ...satir } }); },
  oku: (ad, id) => [...KUTUK].reverse().find((k) => k.ad === ad && k.satir.id === id)?.satir ?? null,
  uzunluk: () => KUTUK.length,
};
```

```js
// esleyici/kutuk-esleyici.mjs — ayni alan nesnesi, ikinci depoya yazan ikinci esleyici
import { Gonderi } from "./gonderi.mjs";
import { kutukDepo } from "../kutuk-depo.mjs";

export const kutukEsleyici = {
  satira: (g) => ({ id: g.id, agirlik: g.agirlik, katsayi: g.katsayi, indirim: g.indirim }),
  bul(id) {
    const satir = kutukDepo.oku("gonderi", id);
    return satir === null ? null : new Gonderi(satir);
  },
  kaydet(g) { kutukDepo.yaz("gonderi", this.satira(g)); return g; },
};
```

```js
// ikinci-depo.mjs — alan nesnesi degismeden ikinci depoya yazilabiliyor mu
import { readFileSync } from "node:fs";
import { Gonderi } from "./esleyici/gonderi.mjs";
import { gonderiEsleyici } from "./esleyici/gonderi-esleyici.mjs";
import { kutukEsleyici } from "./esleyici/kutuk-esleyici.mjs";
import { kutukDepo } from "./kutuk-depo.mjs";

const SATIR = { id: "G2", agirlik: 3, katsayi: 1.35, indirim: 0.1 };
const g = new Gonderi(SATIR);
gonderiEsleyici.kaydet(g);
kutukEsleyici.kaydet(g);
kutukEsleyici.kaydet(new Gonderi({ ...SATIR, indirim: 0.2 }));

console.log(`bellek deposundan net = ${gonderiEsleyici.bul("G2").net()}`);
console.log(`kutuk deposundan net  = ${kutukEsleyici.bul("G2").net()}`);
console.log(`kutuk satiri = ${kutukDepo.uzunluk()}`);
const alan = readFileSync("esleyici/gonderi.mjs", "utf8");
console.log(`alan modulunde depo adi = ${[...alan.matchAll(/\bdepo\b/gi)].length}`);
```

```sh
node ikinci-depo.mjs
```

```
bellek deposundan net = 7776
kutuk deposundan net  = 6912
kutuk satiri = 2
alan modulunde depo adi = 0
```

Aynı alan nesnesi iki depoya yazıldı ve ikisinden de okundu; alan modülünde depo adı hâlâ 0.
İkinci saklama biçimini eklemek için düzenlenen var olan dosya sayısı 0, eklenen dosya sayısı
2 oldu. Aktif kayıtta aynı gereksinim `aktif/gonderi.mjs` dosyasının düzenlenmesini gerektirir
ve o dosya kuralı taşıdığı için kural da yeniden doğrulanmak zorunda kalır.

## Ne Zaman Uygulanmaz

Veri eşleyicinin bedeli sayılabilir. Aktif kayıt sürümü 1 dosya tutuyor, eşleyici sürümü depo
ile birlikte 3 dosya. Bir yazma aktif kayıtta 1 çağrıdır, eşleyicide 2 çağrıya bölünür: önce
satıra çevirme, sonra yazma. Kaydı okuyup nesneyi elde etmek için izlenmesi gereken dosya
sayısı 1'den 2'ye çıkar.

Satır ile nesne arasında bire bir örtüşme varsa ve kural tek satırlık bir hesaptan
oluşuyorsa, eşleyicinin çevirdiği hiçbir şey yoktur: `satira` yöntemi alanları aynı adlarla
kopyalar. Bu durumda ödenen 2 ek dosya ve 1 ek dolaylılık basamağı karşılıksızdır; aktif kayıt
daha az parçayla aynı işi yapar.

Aktif kayıt ise nesne modeli ile satır düzeni ayrıştığında pahalanır. Kural birden çok satırın
birleşiminden karar üretiyorsa nesne artık bir satırı temsil etmez ve kaydetme yönteminin
hangi satırları yazacağı belirsizleşir. Bu ayrışmanın adı Veri Erişim Katmanı kursunda
konulmuştu: nesne–ilişki uyumsuzluğu. Ayrışma büyüdükçe kalıcılık kodu kural dosyasının
içinde şişer ve ölçüm ilk çıktıdaki iz sayılarını yukarı taşır.

## Özet

- Aktif kayıt çevrimi ve saklama erişimini alan nesnesinin üzerine koyar; veri eşleyici ayrı
  bir nesneye verir ve bağımlılık okunu alan nesnesinden dışarı çevirir.
- Alan modülünde kalıcılık izi aktif kayıtta depo adı 4, tablo adı 2, satır sözcüğü 5 iken
  eşleyici sürümünde üçü de 0 çıktı; ithal kapanışı 1'den 0'a indi.
- Üç satırın üçünde iki sürüm aynı net ücreti verdi; kalıp seçimi doğruluğu değil, saklama
  kararının hangi dosyada durduğunu değiştirir.
- İkinci saklama biçimi eşleyici sürümünde 0 var olan dosya düzenlenerek ve 2 dosya eklenerek
  bağlandı; alan modülünde depo adı 0 kaldı.
- Eşleyicinin bedeli 2 ek dosya, yazma başına 1 ek çağrı ve okunması gereken 1 ek dosyadır;
  satır ile nesne bire bir örtüşüyorsa bu bedel karşılıksız kalır.

## Sonraki Adım

Bu dersin eşleyicisi tek bir nesneyi tek bir satıra çeviriyor. Gerçek bir senaryo tek nesneye
dokunmaz: bir gönderi kaydedilirken rotası ve indirim satırları da yazılır, gönderi satırındaki
indirim toplamı da yenilenir. Çağıran taraf hangi nesneleri hangi sırayla
kaydedeceğini bilmek zorunda kalırsa kalıcılık kararı yeniden yukarı sızar. Sonraki ders
erişimi alan diliyle veren depo kalıbını ve değişen satırları tek bir yazma noktasında
toplayan iş birimini kurar, elle kaydetmeyle karşılaştırıp uygulama katmanındaki yazma
noktalarını ve aynı senaryonun üç düzende ürettiği yazma çağrısı sayısını sayar.
