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

# Katman Sorumlulukları

Sunum, uygulama, alan ve altyapı katmanlarının ayrımı: bağımlılık yönü kuralının ithal grafiği üzerinden denetlenmesi, ihlalli sürümdeki bulguların sayılması ve sözleşmeyi alan katmanına taşıyarak bağımlılığın tersine çevrilmesi.

Başarım Sorunları konusu veri erişim katmanının nasıl çalıştığını ölçtü: sorgu sayısı,
aktarılan bayt, deyim sayısı, sorgu planı, sayfalama maliyeti. Hiçbiri katmanın ne yapması
gerektiğini söylemedi. Ödünç verme kuralı nerede duruyor, doğrulama hangi katmanda
yapılıyor, dışarıya dönen kayıt ile alan modeli aynı şey mi?

Bu konu o soruları ele alıyor ve ilki en temeldeki soru: kod hangi parçalara ayrılır ve bu
parçalar birbirini hangi yönde çağırabilir? Ayrımın kâğıt üzerinde iddia edilmesi yeterli
değildir; bir kural olarak yazılıp **ithal grafiği** (import graph) üzerinden denetlenmesi
gerekir.

## Dört Katman

Yerleşim dört sorumluluğa ayrılır. Adları değişse de ayrımın gerekçesi aynıdır: her katman
farklı bir nedenle değişir.

| Katman | Sorumluluk | Değişme nedeni |
|---|---|---|
| Sunum | Dış dünyanın biçimi: yol, gövde çözümleme, durum kodu | Arayüz ya da protokol değişince |
| Uygulama | İş akışının sırası: kimi çağır, işlemi nerede aç, sonucu kime ver | Kullanım senaryosu değişince |
| Alan | İş kuralları: neyin geçerli, neyin yasak olduğu | Kurum kuralı değişince |
| Altyapı | Dış kaynaklara erişim: veritabanı, dosya, ağ | Araç ya da şema değişince |

Katman sayısı kadar önemli olan, aralarındaki **bağımlılık yönüdür**. Kural tek cümleyle
yazılabilir: alan katmanı hiçbir katmana bağımlı olamaz. Kalan yönler bu kuraldan türer.

```
sunum  →  uygulama  →  alan  ←  altyapı
```

Altyapının oku ters yöne bakıyor. Veritabanı erişimi alan katmanını çağırmaz; alan
katmanının tanımladığı bir sözleşmeyi karşılar. Bu ters yön dersin asıl konusu.

## İhlalli Sürüm

Aşağıdaki dört modül ödünç verme isteğini karşılar. Dizin adları katmanı verir; kod önce
akla gelen sırayla, doğrudan çağrılarla yazılmıştır.

```sh
mkdir -p ihlalli/sunum ihlalli/uygulama ihlalli/alan ihlalli/altyapi
```

```js
// ihlalli/alan/odunc-kurallari.mjs — is kurallari, ama iki alt katmani dogrudan cagiriyor
import { SqliteOduncDeposu } from "../altyapi/sqlite-odunc-deposu.mjs";
import { yanit } from "../sunum/yanit.mjs";

export const UYE_SINIRI = 3;

export function oduncVer(db, uyeId, kitapId, tarih) {
  const depo = new SqliteOduncDeposu(db);
  if (depo.kitabinAcikKaydi(kitapId) !== null) return yanit(409, "kitap zaten odunc verilmis");
  if (depo.uyeninAcikKayitlari(uyeId).length >= UYE_SINIRI) {
    return yanit(409, `uye siniri asildi (${UYE_SINIRI})`);
  }
  return yanit(201, String(depo.ekle({ kitapId, uyeId, alisTarihi: tarih })));
}
```

```js
// ihlalli/altyapi/sqlite-odunc-deposu.mjs — SQL ve surucu burada
import { DatabaseSync } from "node:sqlite";

export const baglan = (dosya) => new DatabaseSync(dosya);

export class SqliteOduncDeposu {
  constructor(db) { this.db = db; }
  kitabinAcikKaydi(kitapId) {
    return this.db.prepare(
      "SELECT odunc_id FROM odunc WHERE kitap_id = ? AND iade_tarihi IS NULL").get(kitapId) ?? null;
  }
  uyeninAcikKayitlari(uyeId) {
    return this.db.prepare(
      "SELECT odunc_id FROM odunc WHERE uye_id = ? AND iade_tarihi IS NULL").all(uyeId);
  }
  ekle(k) {
    return Number(this.db.prepare(
      "INSERT INTO odunc (kitap_id, uye_id, alis_tarihi, iade_tarihi) VALUES (?,?,?,NULL)")
      .run(k.kitapId, k.uyeId, k.alisTarihi).lastInsertRowid);
  }
}
```

```js
// ihlalli/sunum/yanit.mjs — HTTP yaniti bicimleyen sunum yardimcisi
export const yanit = (durum, govde) => ({ durum, govde });
```

```js
// ihlalli/uygulama/odunc-uygulama.mjs — istegi karsilayan uygulama servisi
import { oduncVer } from "../alan/odunc-kurallari.mjs";
import { baglan } from "../altyapi/sqlite-odunc-deposu.mjs";

export function oduncIstegi(istek) {
  const db = baglan("kutuphane.db");
  return oduncVer(db, istek.uyeId, istek.kitapId, istek.tarih);
}
```

```js
// ihlalli/sunum/http-ucu.mjs — govdeyi cozup uygulama servisini cagirir
import { oduncIstegi } from "../uygulama/odunc-uygulama.mjs";

export const POST_odunc = (govde) => oduncIstegi(JSON.parse(govde));
```

Kod çalışır. Sorun davranışta değil, bağımlılık yönünde: iş kuralı hem SQL sınıfını hem
HTTP biçimleyicisini ithal ediyor.

## Kuralın Denetlenmesi

Bağımlılık yönü bir belge maddesi olarak kaldığı sürece ihlal edilir. Denetlenebilmesi için
ölçülebilir olması gerekir. Aşağıdaki betik dizin ağacındaki bütün modülleri okur, ithal
deyimlerini çıkarır, her modülü dizinine göre bir katmana yerleştirir ve izin verilmeyen
her kenarı bir bulgu olarak bildirir.

```js
// katman-denetle.mjs — ithal grafigini cikarir ve bagimlilik yonunu denetler
import { readdirSync, readFileSync, statSync } from "node:fs";
import { join, relative, dirname, resolve } from "node:path";

// Her katmanin ithal edebilecegi katmanlar. Liste disindaki her kenar bir ihlaldir.
const IZIN = {
  sunum: ["sunum", "uygulama"],
  uygulama: ["uygulama", "alan"],
  altyapi: ["altyapi", "alan", "standart"],
  alan: ["alan", "standart"],
  kok: ["kok", "sunum", "uygulama", "alan", "altyapi", "standart"],
};

const katman = (yol) => yol.split("/")[0] in IZIN ? yol.split("/")[0] : "kok";
const disKatman = (ad) => (ad === "node:sqlite" ? "altyapi" : "standart");

function dosyalar(kok, dizin = kok) {
  return readdirSync(dizin).flatMap((ad) => {
    const tam = join(dizin, ad);
    if (statSync(tam).isDirectory()) return dosyalar(kok, tam);
    return ad.endsWith(".mjs") ? [relative(kok, tam)] : [];
  });
}

const ITHAL = /^\s*(?:import|export)[^;'"]*from\s+["']([^"']+)["']/gm;

function ithaller(kok, dosya) {
  const metin = readFileSync(join(kok, dosya), "utf8");
  return [...metin.matchAll(ITHAL)].map((m) => m[1]).map((h) =>
    h.startsWith(".") ? relative(kok, resolve(kok, dirname(dosya), h)) : h);
}

export function denetle(kok) {
  const bulgular = [];
  for (const d of dosyalar(kok)) {
    for (const h of ithaller(kok, d)) {
      const kaynak = katman(d);
      const hedef = h.startsWith("node:") ? disKatman(h) : katman(h);
      if (!IZIN[kaynak].includes(hedef)) bulgular.push({ d, h, kaynak, hedef });
    }
  }
  return bulgular;
}

// Bir modulden baslayarak ulasilan butun modullerin kumesi.
export function kapanis(kok, giris) {
  const gorulen = new Set();
  const yigit = [giris];
  while (yigit.length > 0) {
    const su = yigit.pop();
    if (gorulen.has(su) || su.startsWith("node:")) { gorulen.add(su); continue; }
    gorulen.add(su);
    for (const h of ithaller(kok, su)) yigit.push(h);
  }
  return gorulen;
}

const kok = process.argv[2];
const bulgular = denetle(kok);
for (const b of bulgular) console.log(`ihlal: ${b.d} -> ${b.h}   (${b.kaynak} -> ${b.hedef})`);
console.log(`bulgu sayisi = ${bulgular.length}`);

const k = kapanis(kok, "alan/odunc-kurallari.mjs");
console.log(`alan kapanisi = ${k.size} modul, node:sqlite iceride = ${k.has("node:sqlite") ? "evet" : "hayir"}`);
```

Sürücü ithali de bir katman sayılır: `node:sqlite` altyapı, kalan standart kitaplık
modülleri her yerden ithal edilebilen "standart" olarak işaretlenir. Ağacın kökündeki
dosyalar hiçbir katmana ait değildir; bunlar **bileşim kökü** (composition root) sayılır ve
her katmanı ithal edebilir.

```sh
node katman-denetle.mjs ihlalli
```

```
ihlal: alan/odunc-kurallari.mjs -> altyapi/sqlite-odunc-deposu.mjs   (alan -> altyapi)
ihlal: alan/odunc-kurallari.mjs -> sunum/yanit.mjs   (alan -> sunum)
ihlal: uygulama/odunc-uygulama.mjs -> altyapi/sqlite-odunc-deposu.mjs   (uygulama -> altyapi)
bulgu sayisi = 3
alan kapanisi = 4 modul, node:sqlite iceride = evet
```

Üç bulgu ve bir ölçü. Son satır ihlallerin sonucunu gösteriyor: iş kuralını içeren modüle
dokunmak için dört modülün tamamı ve veritabanı sürücüsü gerekiyor.

## Bağımlılığın Tersine Çevrilmesi

Bulguların ikisi aynı nedene dayanıyor: alan katmanı ihtiyaç duyduğu şeyi **kendisi
kurmaya** çalışıyor. Çözüm, ihtiyacın alan katmanında **sözleşme** olarak yazılması,
gerçekleştirimin altyapıda kalmasıdır. Depo Kalıbı dersinde kurulan depo arayüzü bunun bir
örneğiydi; burada sözleşme açıkça bir modüle taşınıyor.

```sh
mkdir -p duzeltilmis/sunum duzeltilmis/uygulama duzeltilmis/alan duzeltilmis/altyapi
```

```js
// duzeltilmis/alan/odunc-deposu.mjs — alan katmaninin ihtiyac duydugu depo sozlesmesi
export const DEPO_SOZLESMESI = ["kitabinAcikKaydi", "uyeninAcikKayitlari", "ekle"];

export function depoyuDogrula(depo) {
  const eksik = DEPO_SOZLESMESI.filter((ad) => typeof depo?.[ad] !== "function");
  if (eksik.length > 0) throw new TypeError(`depo sozlesmesi eksik: ${eksik.join(", ")}`);
  return depo;
}
```

```js
// duzeltilmis/alan/odunc-kurallari.mjs — yalniz is kurallari; HTTP yok, SQL yok
import { depoyuDogrula } from "./odunc-deposu.mjs";

export const UYE_SINIRI = 3;

export function oduncVer(depo, uyeId, kitapId, tarih) {
  depoyuDogrula(depo);
  if (depo.kitabinAcikKaydi(kitapId) !== null) {
    return { sonuc: "reddedildi", sebep: "kitap_oduncte" };
  }
  if (depo.uyeninAcikKayitlari(uyeId).length >= UYE_SINIRI) {
    return { sonuc: "reddedildi", sebep: "uye_siniri", sinir: UYE_SINIRI };
  }
  return { sonuc: "verildi", oduncId: depo.ekle({ kitapId, uyeId, alisTarihi: tarih }) };
}
```

Dönüş değeri de değişti. İhlalli sürüm HTTP durum kodu üretiyordu; bu sürüm alan diliyle
konuşuyor. Durum kodu eşlemesi sunum katmanının işidir.

```js
// duzeltilmis/altyapi/sqlite-odunc-deposu.mjs — sozlesmenin SQL ile gerceklestirimi
import { DatabaseSync } from "node:sqlite";
import { depoyuDogrula } from "../alan/odunc-deposu.mjs";

export const baglan = (dosya) => new DatabaseSync(dosya);

export class SqliteOduncDeposu {
  constructor(db) { this.db = db; depoyuDogrula(this); }
  kitabinAcikKaydi(kitapId) {
    return this.db.prepare(
      "SELECT odunc_id FROM odunc WHERE kitap_id = ? AND iade_tarihi IS NULL").get(kitapId) ?? null;
  }
  uyeninAcikKayitlari(uyeId) {
    return this.db.prepare(
      "SELECT odunc_id FROM odunc WHERE uye_id = ? AND iade_tarihi IS NULL").all(uyeId);
  }
  ekle(k) {
    return Number(this.db.prepare(
      "INSERT INTO odunc (kitap_id, uye_id, alis_tarihi, iade_tarihi) VALUES (?,?,?,NULL)")
      .run(k.kitapId, k.uyeId, k.alisTarihi).lastInsertRowid);
  }
}
```

Altyapı modülü artık alan katmanını ithal ediyor. Ok tersine döndü ve bu, izinli yöndür.

```js
// duzeltilmis/uygulama/odunc-uygulama.mjs — depoyu disaridan alir, kurali cagirir
import { oduncVer } from "../alan/odunc-kurallari.mjs";

export class OduncUygulamasi {
  constructor(depo) { this.depo = depo; }
  ver(istek) { return oduncVer(this.depo, istek.uyeId, istek.kitapId, istek.tarih); }
}
```

```js
// duzeltilmis/sunum/http-ucu.mjs — alan sonucunu HTTP yanitina cevirir
const DURUM = { verildi: 201, kitap_oduncte: 409, uye_siniri: 409 };

export function POST_odunc(uygulama, govde) {
  const s = uygulama.ver(JSON.parse(govde));
  return s.sonuc === "verildi"
    ? { durum: DURUM.verildi, govde: { oduncId: s.oduncId } }
    : { durum: DURUM[s.sebep], govde: { sebep: s.sebep } };
}
```

Hiçbir katman kendi bağımlılığını kurmuyor. Bağlantıyı, depoyu ve uygulama servisini bir
araya getiren tek yer bileşim köküdür.

```js
// duzeltilmis/kur.mjs — bilesim koku: katmanlari yalniz burasi birbirine baglar
import { baglan, SqliteOduncDeposu } from "./altyapi/sqlite-odunc-deposu.mjs";
import { OduncUygulamasi } from "./uygulama/odunc-uygulama.mjs";
import { POST_odunc } from "./sunum/http-ucu.mjs";

const uygulama = new OduncUygulamasi(new SqliteOduncDeposu(baglan(process.argv[2])));
for (const govde of ['{"uyeId":6,"kitapId":1,"tarih":"2025-07-15"}',
                     '{"uyeId":6,"kitapId":7,"tarih":"2025-07-15"}']) {
  console.log(govde, "->", JSON.stringify(POST_odunc(uygulama, govde)));
}
```

Denetim yeniden çalıştırılır.

```sh
node katman-denetle.mjs duzeltilmis
```

```
bulgu sayisi = 0
alan kapanisi = 2 modul, node:sqlite iceride = hayir
```

Bulgu sıfır. İkinci satır ilkinden daha çok şey söylüyor: iş kuralına ulaşmak için gereken
modül sayısı dörtten ikiye indi ve veritabanı sürücüsü kapanışın dışında kaldı.

Bu sürüm gerçek veriyle de çalışır. Aşağıdaki komut SQL Temelleri kursundaki kütüphane
şemasının ödünç bağıntısını kurar ve bileşim kökünü çalıştırır.

```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 duzeltilmis/kur.mjs kutuphane.db
```

```
{"uyeId":6,"kitapId":1,"tarih":"2025-07-15"} -> {"durum":409,"govde":{"sebep":"kitap_oduncte"}}
{"uyeId":6,"kitapId":7,"tarih":"2025-07-15"} -> {"durum":201,"govde":{"oduncId":13}}
```

## Kapanışın Pratik Karşılığı

Modül sayısı soyut bir ölçü gibi görünür. Karşılığı somuttur: kapanış küçüldükçe iş kuralı
başka bir şey vermeden çağrılabilir hâle gelir. İki sürüme de sözleşmeyi karşılayan sahte
bir nesne verildiğinde fark görünür.

```js
// sahteyle-dene.mjs — ayni is kurali, veritabani olmadan, sahte bir depoyla
import { oduncVer as ihlalli } from "./ihlalli/alan/odunc-kurallari.mjs";
import { oduncVer as duzeltilmis } from "./duzeltilmis/alan/odunc-kurallari.mjs";

const sahteDepo = {
  kitabinAcikKaydi: () => null,
  uyeninAcikKayitlari: () => [],
  ekle: () => 42,
};

for (const [ad, kural] of [["ihlalli", ihlalli], ["duzeltilmis", duzeltilmis]]) {
  try {
    console.log(`${ad.padEnd(12)} ->`, JSON.stringify(kural(sahteDepo, 6, 7, "2025-07-15")));
  } catch (e) {
    console.log(`${ad.padEnd(12)} -> ${e.constructor.name}: ${e.message}`);
  }
}
```

```sh
node sahteyle-dene.mjs
```

```
ihlalli      -> TypeError: this.db.prepare is not a function
duzeltilmis  -> {"sonuc":"verildi","oduncId":42}
```

İhlalli sürüm sahte depoyu kabul etmiyor, çünkü verilen nesneyi bir bağlantı sanıp kendi
SQL sınıfını kuruyor. Düzeltilmiş sürüm sözleşmeye baktığı için sahteyi kabul ediyor.
Bağımlılık yönü kuralının sınanabilirlikle ilişkisi budur: kural bir üslup tercihi değil,
alan katmanının bağımsız çalışabilmesinin ön koşuludur.

## Uygulama ile Alan Arasındaki Sınır

Dört katman içinde en çok karışan ikisi uygulama ile alandır. Ayrım şu soruyla yapılır:
kural, kurumun kuralı mı yoksa bu senaryonun akışı mı?

"Ödünçteki kitap yeniden verilemez" kurumun kuralıdır; kütüphane bunu ödünç verme
ekranında da, toplu içe aktarımda da uygular. Alan katmanına aittir. "Önce üyeyi bul, sonra
kuralı çalıştır, sonra işlemi kesinleştir, sonra bildirim gönder" ise bu senaryonun
sırasıdır; başka bir senaryoda sıra değişir. Uygulama katmanına aittir.

İşlem sınırı da uygulama katmanının kararıdır. İşlem Sınırları dersinde iş biriminin
sınırının servis katmanında tutulması gerektiği gösterilmişti; bu ders o kararı katman
adıyla yerleştiriyor. Alan katmanı `BEGIN` yazmaz, çünkü bir kuralın işlem içinde mi yoksa
dışında mı değerlendirildiğini bilmesi gerekmez.

Sunum ile uygulama arasındaki sınır daha kolaydır. Sunum katmanı protokolün biçimini bilir:
hangi yolun hangi işleve karşılık geldiği, gövdenin nasıl çözüleceği, sonucun hangi durum
koduna eşleneceği. Bu bilginin uygulama katmanına sızması, aynı senaryonun ikinci bir giriş
noktasından (zamanlanmış iş, kabuk komutu, kuyruk tüketicisi) çağrılmasını engeller.

## Özet

- Dört katman farklı değişme nedenlerine göre ayrılır; ayrımın çekirdeği katman sayısı
  değil, aralarındaki bağımlılık yönüdür.
- Bağımlılık yönü ithal grafiğinden denetlenebilir: ihlalli sürümde üç bulgu çıktı, sözleşme
  alan katmanına taşınınca bulgu sıfıra indi.
- Alan katmanının ithal kapanışı dört modülden iki modüle indi ve veritabanı sürücüsü
  kapanışın dışında kaldı.
- İhlalli sürümdeki iş kuralı sahte bir depoyla çağrılamadı; düzeltilmiş sürüm aynı sahteyle
  çalıştı. Bağımlılık yönü kuralı, alan katmanının bağımsız sınanabilmesinin ön koşuludur.
- İş kuralı alan katmanına, senaryo sırası ve işlem sınırı uygulama katmanına, protokol
  biçimi sunum katmanına aittir; bağımlılıkları birbirine bağlayan tek yer bileşim köküdür.

## Sonraki Adım

Düzeltilmiş sürümde alan katmanı `{ sonuc, sebep }` biçiminde bir nesne döndürdü, sunum
katmanı bunu HTTP gövdesine çevirdi. Bu çeviri şimdilik üç alanlıydı ve neredeyse birebirdi;
kayıt büyüdüğünde soru keskinleşir. Ödünç kaydının dışarıya dönen biçimi ile alan
modelinin kendisi aynı şey midir? Alan modeline yeni bir alan eklendiğinde istemcinin
gördüğü gövde değişmeli midir? Sonraki ders aynı alan nesnesinden iki farklı dış sözleşme
üretir, iç modele alan eklendiğinde dış sözleşmenin değişmediğini sınamayla gösterir ve
alan nesnesini doğrudan döndürmenin kaç iç alanı dışarıya sızdırdığını sayar.
