---
title: 'Depo Kalıbı'
source: 'https://academia.sh/tr/kurslar/veri-erisim-katmani/depo-kalibi'
course: 'Veri Erişim Katmanı ve İş Mantığı'
language: tr
updated: '2026-08-17T18:06:50+00:00'
license: 'CC BY-SA 4.0'
---

# Depo Kalıbı

Kalıcılık ayrıntısını tek bir arayüzün arkasına toplayan depo kalıbı: iş kurallarının sahte depoyla veritabanı olmadan sınanması, aynı sözleşmenin iki uygulamaya uygulanması ve kalıcılık sızıntısının modül başına ölçülmesi.

Önceki ders nesne modeliyle ilişkisel modelin dört noktada örtüşmediğini gösterdi ve her
uyumsuzluğu kapatan kararı adlandırdı. Bu kararlar bir yere yazılmadıkça sorguyu yazan her
çağrı noktasında yeniden verilir: kalıtımın hangi stratejiyle indirildiği, ilişkinin
sahibinin hangi taraf olduğu, hangi alanın gömüldüğü bilgisi kodun içine dağılır.

Bu dersin konusu o bilgiyi tek bir yerde toplayan arayüzdür. **Depo** (repository), çağıran
tarafa yalnız alan kavramlarıyla konuşan, kalıcılığın nasıl gerçekleştiğini gizleyen bir
sınırdır. Sınırın gerçekten kapalı olup olmadığı iddia edilerek değil, sınanarak
gösterilir: iş kuralları veritabanı olmadan çalışıyorsa sınır kapalıdır.

## Deponun Vaadi

Depo, sorgu yazma aracı değildir. Çağıran taraf ondan tablo, sütun veya birleştirme
istemez; ödünç kaydı ister. Aşağıdaki beş işlem ödünç servisinin veriye ihtiyaç duyduğu
noktaların tamamıdır.

| İşlem | Anlamı |
|---|---|
| `bul(oduncId)` | Kimliği verilen ödünç kaydını döndürür, yoksa `null` |
| `kitabinAcikKaydi(kitapId)` | Kitabın iade edilmemiş kaydı varsa döndürür |
| `uyeninAcikKayitlari(uyeId)` | Üyenin iade edilmemiş kayıtlarının listesi |
| `ekle(kayit)` | Yeni kaydı saklar ve kimliğini döndürür |
| `iadeIsaretle(oduncId, tarih)` | Kaydı kapalı duruma geçirir |

Listede `SELECT` yok, bağlantı yok, satır yok. Bu, deponun tanımıdır: **arayüz alan
diliyle yazılır, uygulama kalıcılık diliyle.**

## Kurallar Depoya Bağlı Değildir

Ödünç verme iki kural taşır: ödünçte olan kitap yeniden verilemez ve bir üye aynı anda
üçten çok kitap tutamaz. Bu kurallar hiçbir kalıcılık ayrıntısı içermez.

```js
// odunc-servisi.mjs — is kurallari; hicbir SQL, hicbir surucu adi icermez
export const UYE_SINIRI = 3;

export class OduncServisi {
  constructor(depo) { this.depo = depo; }

  ver(uyeId, kitapId, tarih) {
    if (this.depo.kitabinAcikKaydi(kitapId) !== null) {
      return { durum: "reddedildi", sebep: "kitap zaten odunc verilmis" };
    }
    const acik = this.depo.uyeninAcikKayitlari(uyeId);
    if (acik.length >= UYE_SINIRI) {
      return { durum: "reddedildi", sebep: `uye siniri asildi (${UYE_SINIRI})` };
    }
    const id = this.depo.ekle({ kitapId, uyeId, alisTarihi: tarih, iadeTarihi: null });
    return { durum: "verildi", oduncId: id };
  }

  iadeAl(oduncId, tarih) {
    const kayit = this.depo.bul(oduncId);
    if (kayit === null) return { durum: "bulunamadi" };
    if (kayit.iadeTarihi !== null) return { durum: "zaten iade edilmis" };
    this.depo.iadeIsaretle(oduncId, tarih);
    return { durum: "iade alindi" };
  }
}
```

Servis, deposunu yapıcı üzerinden alır. Kendisi bir depo kurmaz, hangi deponun geleceğini
bilmez. Bu ayrıntı bütün dersi taşır: depoyu dışarıdan almak, sınama sırasında başka bir
depo vermeyi mümkün kılar.

## Sahte Depo

Sözleşmeyi karşılayan en küçük uygulama bir dizidir.

```js
// bellek-deposu.mjs — deponun sahte uygulamasi; veritabani yok
export class BellekOduncDeposu {
  constructor(baslangic = []) {
    this.kayitlar = baslangic.map((k) => ({ ...k }));
    this.sonrakiId = this.kayitlar.length + 1;
  }
  bul(oduncId) {
    return this.kayitlar.find((k) => k.oduncId === oduncId) ?? null;
  }
  kitabinAcikKaydi(kitapId) {
    return this.kayitlar.find((k) => k.kitapId === kitapId && k.iadeTarihi === null) ?? null;
  }
  uyeninAcikKayitlari(uyeId) {
    return this.kayitlar.filter((k) => k.uyeId === uyeId && k.iadeTarihi === null);
  }
  ekle(kayit) {
    const oduncId = this.sonrakiId++;
    this.kayitlar.push({ oduncId, ...kayit });
    return oduncId;
  }
  iadeIsaretle(oduncId, tarih) {
    const k = this.bul(oduncId);
    if (k !== null) k.iadeTarihi = tarih;
  }
}
```

Sahte depoyla iş kuralları doğrudan sınanabilir. Sınama dosyası önceki iki bloğun
tanımladığı `odunc-servisi.mjs` ve `bellek-deposu.mjs` modüllerini kullanır.

```js
// servis.test.mjs — is kurallari sahte depoyla sinanir
import { test } from "node:test";
import assert from "node:assert/strict";
import { OduncServisi } from "./odunc-servisi.mjs";
import { BellekOduncDeposu } from "./bellek-deposu.mjs";

test("odunte olmayan kitap verilir", () => {
  const servis = new OduncServisi(new BellekOduncDeposu());
  assert.deepEqual(servis.ver(1, 5, "2025-07-01"), { durum: "verildi", oduncId: 1 });
});

test("odunte olan kitap reddedilir", () => {
  const depo = new BellekOduncDeposu([
    { oduncId: 1, kitapId: 5, uyeId: 2, alisTarihi: "2025-06-01", iadeTarihi: null },
  ]);
  const sonuc = new OduncServisi(depo).ver(1, 5, "2025-07-01");
  assert.equal(sonuc.durum, "reddedildi");
  assert.match(sonuc.sebep, /zaten odunc/);
});

test("uye siniri asilinca reddedilir", () => {
  const depo = new BellekOduncDeposu([
    { oduncId: 1, kitapId: 1, uyeId: 4, alisTarihi: "2025-06-01", iadeTarihi: null },
    { oduncId: 2, kitapId: 2, uyeId: 4, alisTarihi: "2025-06-02", iadeTarihi: null },
    { oduncId: 3, kitapId: 3, uyeId: 4, alisTarihi: "2025-06-03", iadeTarihi: null },
  ]);
  const sonuc = new OduncServisi(depo).ver(4, 7, "2025-07-01");
  assert.equal(sonuc.durum, "reddedildi");
  assert.equal(sonuc.sebep, "uye siniri asildi (3)");
});

test("iade edilen kitap yeniden verilebilir", () => {
  const depo = new BellekOduncDeposu([
    { oduncId: 1, kitapId: 5, uyeId: 2, alisTarihi: "2025-06-01", iadeTarihi: null },
  ]);
  const servis = new OduncServisi(depo);
  assert.equal(servis.iadeAl(1, "2025-06-20").durum, "iade alindi");
  assert.equal(servis.ver(1, 5, "2025-07-01").durum, "verildi");
});
```

```sh
node --test servis.test.mjs
```

```
✔ odunte olmayan kitap verilir (0.593333ms)
✔ odunte olan kitap reddedilir (0.103167ms)
✔ uye siniri asilinca reddedilir (0.056542ms)
✔ iade edilen kitap yeniden verilebilir (0.063459ms)
ℹ tests 4
ℹ suites 0
ℹ pass 4
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 35.385792
```

Parantez içindeki süreler ve `duration_ms` alanı her çalıştırmada değişir; anlamlı olan
`pass 4` ve `fail 0` satırlarıdır. Dört kural, veritabanı dosyası olmadan, şema kurulmadan
ve tek bir bağlantı açılmadan sınandı. Sınamanın kurulum adımı bir dizi kayıt yazmaktan
ibaret; kurallara giren başlangıç durumu doğrudan okunabiliyor.

## Sahtenin Sadakati

Sahte depo bir risk taşır: gerçek uygulamadan farklı davranırsa, sınamalar geçer ama
uygulama çalışmaz. Bu riski kapatan yöntem, **aynı sınama kümesini iki uygulamaya da
uygulamaktır**. Önce gerçek uygulama.

```js
// sqlite-deposu.mjs — deponun gercek uygulamasi; butun SQL burada toplanir
const NESNE = (s) => (s === undefined ? null : {
  oduncId: s.odunc_id, kitapId: s.kitap_id, uyeId: s.uye_id,
  alisTarihi: s.alis_tarihi, iadeTarihi: s.iade_tarihi,
});

export class SqliteOduncDeposu {
  constructor(db) { this.db = db; }
  bul(oduncId) {
    return NESNE(this.db.prepare("SELECT * FROM odunc WHERE odunc_id = ?").get(oduncId));
  }
  kitabinAcikKaydi(kitapId) {
    return NESNE(this.db.prepare(
      "SELECT * FROM odunc WHERE kitap_id = ? AND iade_tarihi IS NULL").get(kitapId));
  }
  uyeninAcikKayitlari(uyeId) {
    return this.db.prepare(
      "SELECT * FROM odunc WHERE uye_id = ? AND iade_tarihi IS NULL ORDER BY odunc_id")
      .all(uyeId).map(NESNE);
  }
  ekle(kayit) {
    const sonuc = this.db.prepare(
      "INSERT INTO odunc (kitap_id, uye_id, alis_tarihi, iade_tarihi) VALUES (?,?,?,?)")
      .run(kayit.kitapId, kayit.uyeId, kayit.alisTarihi, kayit.iadeTarihi);
    return Number(sonuc.lastInsertRowid);
  }
  iadeIsaretle(oduncId, tarih) {
    this.db.prepare("UPDATE odunc SET iade_tarihi = ? WHERE odunc_id = ?").run(tarih, oduncId);
  }
}
```

Uygulama bağlantıyı da dışarıdan alıyor. Dosya adını kendi içinde tutsaydı, sınama sırasında
bellek içi bir veritabanına yönlendirilemezdi.

```js
// sozlesme.test.mjs — ayni sinama kumesi iki uygulamaya da uygulanir
import { test, describe } from "node:test";
import assert from "node:assert/strict";
import { DatabaseSync } from "node:sqlite";
import { BellekOduncDeposu } from "./bellek-deposu.mjs";
import { SqliteOduncDeposu } from "./sqlite-deposu.mjs";

const BASLANGIC = [
  { oduncId: 1, kitapId: 5, uyeId: 2, alisTarihi: "2025-06-01", iadeTarihi: null },
  { oduncId: 2, kitapId: 6, uyeId: 2, alisTarihi: "2025-06-02", iadeTarihi: "2025-06-25" },
];

const bellekKur = () => new BellekOduncDeposu(BASLANGIC);

function sqliteKur() {
  const db = new DatabaseSync(":memory:");
  db.exec(`CREATE TABLE odunc (odunc_id INTEGER PRIMARY KEY, kitap_id INTEGER NOT NULL,
           uye_id INTEGER NOT NULL, alis_tarihi TEXT NOT NULL, iade_tarihi TEXT)`);
  for (const k of BASLANGIC) {
    db.prepare("INSERT INTO odunc VALUES (?,?,?,?,?)")
      .run(k.oduncId, k.kitapId, k.uyeId, k.alisTarihi, k.iadeTarihi);
  }
  return new SqliteOduncDeposu(db);
}

for (const [ad, kur] of [["bellek", bellekKur], ["sqlite", sqliteKur]]) {
  describe(`depo sozlesmesi: ${ad}`, () => {
    test("acik kayit bulunur, kapali kayit bulunmaz", () => {
      const d = kur();
      assert.equal(d.kitabinAcikKaydi(5).oduncId, 1);
      assert.equal(d.kitabinAcikKaydi(6), null);
    });
    test("uyenin acik kayitlari yalniz acik olanlardir", () => {
      assert.deepEqual(kur().uyeninAcikKayitlari(2).map((k) => k.oduncId), [1]);
    });
    test("eklenen kayit yeni kimlik alir ve bulunur", () => {
      const d = kur();
      const id = d.ekle({ kitapId: 7, uyeId: 3, alisTarihi: "2025-07-01", iadeTarihi: null });
      assert.equal(d.bul(id).kitapId, 7);
    });
    test("iade isaretlemesi acik kaydi kapatir", () => {
      const d = kur();
      d.iadeIsaretle(1, "2025-07-02");
      assert.equal(d.kitabinAcikKaydi(5), null);
    });
    test("olmayan kimlik null doner", () => {
      assert.equal(kur().bul(999), null);
    });
  });
}
```

```sh
node --test sozlesme.test.mjs
```

```
▶ depo sozlesmesi: bellek
  ✔ acik kayit bulunur, kapali kayit bulunmaz (0.537917ms)
  ✔ uyenin acik kayitlari yalniz acik olanlardir (0.262666ms)
  ✔ eklenen kayit yeni kimlik alir ve bulunur (0.05475ms)
  ✔ iade isaretlemesi acik kaydi kapatir (0.046084ms)
  ✔ olmayan kimlik null doner (0.039042ms)
✔ depo sozlesmesi: bellek (1.312042ms)
▶ depo sozlesmesi: sqlite
  ✔ acik kayit bulunur, kapali kayit bulunmaz (0.342959ms)
  ✔ uyenin acik kayitlari yalniz acik olanlardir (0.113333ms)
  ✔ eklenen kayit yeni kimlik alir ve bulunur (0.107ms)
  ✔ iade isaretlemesi acik kaydi kapatir (0.082458ms)
  ✔ olmayan kimlik null doner (0.083541ms)
✔ depo sozlesmesi: sqlite (0.824958ms)
ℹ tests 10
ℹ suites 2
ℹ pass 10
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 34.280709
```

On sınama, iki uygulama, sıfır başarısızlık. Bu sınama kümesi **deponun sözleşmesidir**;
sahtenin gerçeğe sadık kaldığını her çalıştırmada yeniden doğrular. Yeni bir uygulama
eklendiğinde (başka bir veri deposu, önbellek katmanı, kayıtları dosyada tutan bir sürüm)
aynı küme ona da uygulanır.

Sözleşme sınamasının bir sınırı vardır. Yalıtım düzeyi, kilitlenme davranışı ve eşzamanlı
güncelleme çatışması bellek içi bir dizide üretilemez; bunlar yalnız gerçek uygulama
üzerinde sınanır. Sahte deponun kapsamı **iş kurallarıdır**, kalıcılık davranışı değil.

## Sızıntının Ölçülmesi

Sınırın kapalılığı sayılabilir. Kalıcılığın izleri (SQL anahtar sözcükleri, sürücü tipi,
deyim hazırlama çağrısı) hangi modülde kaç kez geçiyor? Karşılaştırma için, aynı işi depo
olmadan yapan bir sürüm de yazalım.

```js
// sizdiran-servis.mjs — deponun yerine dogrudan baglanti kullanan surum
export class SizdiranOduncServisi {
  constructor(db) { this.db = db; }
  ver(uyeId, kitapId, tarih) {
    const acikKitap = this.db.prepare(
      "SELECT 1 FROM odunc WHERE kitap_id = ? AND iade_tarihi IS NULL").get(kitapId);
    if (acikKitap !== undefined) return { durum: "reddedildi", sebep: "kitap zaten odunc verilmis" };
    const sayi = this.db.prepare(
      "SELECT count(*) AS n FROM odunc WHERE uye_id = ? AND iade_tarihi IS NULL").get(uyeId).n;
    if (sayi >= 3) return { durum: "reddedildi", sebep: "uye siniri asildi (3)" };
    const sonuc = this.db.prepare(
      "INSERT INTO odunc (kitap_id, uye_id, alis_tarihi, iade_tarihi) VALUES (?,?,?,NULL)")
      .run(kitapId, uyeId, tarih);
    return { durum: "verildi", oduncId: Number(sonuc.lastInsertRowid) };
  }
}
```

```js
// sizinti-olc.mjs — hangi modulde kac tane kalicilik izi var
import { readFileSync } from "node:fs";

const IZLER = /\b(SELECT|INSERT|UPDATE|DELETE|prepare|DatabaseSync)\b/g;

for (const dosya of ["odunc-servisi.mjs", "sizdiran-servis.mjs", "sqlite-deposu.mjs", "bellek-deposu.mjs"]) {
  const iz = readFileSync(dosya, "utf8").match(IZLER) ?? [];
  console.log(`${dosya.padEnd(22)} kalicilik izi = ${iz.length}`);
}
```

```sh
node sizinti-olc.mjs
```

```
odunc-servisi.mjs      kalicilik izi = 0
sizdiran-servis.mjs    kalicilik izi = 6
sqlite-deposu.mjs      kalicilik izi = 10
bellek-deposu.mjs      kalicilik izi = 0
```

Sayılar dağılımı gösteriyor. Depo kullanan serviste iz sıfır; kalıcılığın tamamı tek bir
modülde, on iz olarak toplanmış. Sızdıran serviste ise altı iz iş kurallarının arasına
karışmış durumda. Bu altı iz, o servisin sahte bir depoyla sınanamamasının nedenidir:
`prepare` çağrısı bir dizide karşılığı olmayan bir yeteneği ister.

Ölçüm kaba bir araçtır, kesin bir kanıt değil. Bir modül SQL yazmadan da kalıcılığa
bağlanabilir; örneğin `bul` çağrısının dönüş sırasının veritabanının varsayılan
sıralamasına güvenmesi de bir bağımlılıktır. Sayının işlevi, sınırın nereye çizildiğini
gözle görünür kılmaktır.

## Deponun Sınırı ve Sorgu Çoğalması

Aynı servisin gerçek depoyla çalıştırılması ek bir kod değişikliği istemez.

```js
// gercekle-calistir.mjs — ayni servis, bu kez veritabanina bagli depoyla
import { DatabaseSync } from "node:sqlite";
import { OduncServisi } from "./odunc-servisi.mjs";
import { SqliteOduncDeposu } from "./sqlite-deposu.mjs";

const depo = new SqliteOduncDeposu(new DatabaseSync("kutuphane.db"));
const servis = new OduncServisi(depo);

console.log("kitap 1 (acik kaydi var):", JSON.stringify(servis.ver(6, 1, "2025-07-15")));
console.log("kitap 7 (bos):           ", JSON.stringify(servis.ver(6, 7, "2025-07-15")));
console.log("uye 4 acik kayit sayisi: ", depo.uyeninAcikKayitlari(4).length);
```

Bu blok, SQL Temelleri kursundaki kütüphane şemasını taşıyan `kutuphane.db` dosyasını
bekler. Aşağıdaki komut şemayı ve dersin çalıştığı satırları kurar.

```sh
rm -f kutuphane.db
sqlite3 kutuphane.db <<'SQL'
CREATE TABLE odunc (odunc_id INTEGER PRIMARY KEY, kitap_id INTEGER NOT NULL,
                    uye_id INTEGER NOT NULL, alis_tarihi TEXT NOT NULL, iade_tarihi TEXT);
INSERT INTO odunc VALUES (1,1,1,'2025-01-10','2025-01-24'),(2,2,1,'2025-02-02','2025-02-20'),
  (3,1,2,'2025-02-11',NULL),(4,3,3,'2025-03-01','2025-03-15'),(5,4,3,'2025-03-18','2025-04-02'),
  (6,1,4,'2025-04-05','2025-04-19'),(7,5,4,'2025-04-21',NULL),(8,2,5,'2025-05-02','2025-05-30'),
  (9,7,1,'2025-05-14','2025-05-28'),(10,3,5,'2025-06-03',NULL),(11,6,2,'2025-06-11','2025-06-25'),
  (12,4,4,'2025-06-20','2025-07-04');
SQL
node gercekle-calistir.mjs
```

```
kitap 1 (acik kaydi var): {"durum":"reddedildi","sebep":"kitap zaten odunc verilmis"}
kitap 7 (bos):            {"durum":"verildi","oduncId":13}
uye 4 acik kayit sayisi:  1
```

Kitap 1'in açık kaydı bulunduğu için istek reddedildi, boştaki kitap 7 verildi ve on
üçüncü kimliği aldı. Servis kodunda tek satır değişmedi.

Depo kalıbının bedeli burada belirir. Arayüz alan diliyle yazıldığı için, her yeni soru
yeni bir yöntem ister: gecikmiş kayıtlar, belirli şubedeki açık kayıtlar, belirli tarih
aralığında verilenler, hem gecikmiş hem belirli şubede olanlar. Ölçütler birleştirilebilir
olduğundan yöntem sayısı ölçüt sayısının kombinasyonuyla artar. Bir noktadan sonra depo
arayüzü, gizlemeye çalıştığı sorgu diline benzemeye başlar.

## Özet

- Depo, çağıran tarafa alan kavramlarıyla konuşan bir arayüzdür; sorguyu, bağlantıyı ve
  satır–nesne çevrimini kendi içinde tutar.
- Servisin deposunu dışarıdan alması, sınama sırasında bellek içi bir sahte depo
  verilmesini mümkün kılar; dört iş kuralı veritabanı olmadan sınandı.
- Aynı sözleşme sınaması iki uygulamaya da uygulandı ve on sınamanın tamamı geçti; bu,
  sahtenin gerçeğe sadık kaldığının sürekli doğrulanmasıdır.
- Kalıcılık izleri sayıldığında depo kullanan serviste sıfır, sızdıran serviste altı iz
  çıktı; sınırın kapalılığı böyle görünür kılınır.
- Yalıtım ve eşzamanlılık davranışı sahte depoda üretilemez; sahtenin kapsamı iş
  kurallarıdır.

## Sonraki Adım

Deponun arayüzü kapandıkça sorulacak soru sayısı arttı ve her soru yeni bir yöntem istedi.
Gecikmiş kayıtlar, şubeye göre süzülmüş kayıtlar, tarih aralığıyla sınırlanmış kayıtlar ve
bunların birleşimleri ayrı ayrı yazıldığında depo şişer. Sorun ölçütlerin kendisinde değil,
ölçütlerin arayüzde sabit yöntem adlarına gömülmesindedir. Sonraki ders ölçütü bir nesne
hâline getirir: her ölçüt kendi SQL parçasını ve bağlı değişkenlerini üretir, ölçütler
"ve", "veya", "değil" ile birleştirilir ve depo tek bir yöntemle bütün birleşimleri
karşılar.
