---
title: 'Katmanlı Mimari'
source: 'https://academia.sh/tr/kurslar/mimari-bicemler/katmanli-mimari'
course: 'Mimari Biçemler'
language: tr
updated: '2026-08-17T18:10:37+00:00'
license: 'CC BY-SA 4.0'
---

# Katmanlı Mimari

Katmanlı biçemin ilan edilmiş bir izinli kenar kümesi olarak yazılması: gönderi ücretlendirme kitaplığının dört katmana ayrılması, bir isteğin dokunduğu katman ile geçtiği sınır sayısının ölçülmesi, hiçbir şey değiştirmeden geçen katmanların sayılması ve açık katman ilanı ile ilan edilmemiş sızıntının ihlal sayısıyla ayrılması.

Alan Odaklı Tasarım kursu bir soruyu açık bıraktı: buraya kadarki bütün kararlar **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çülen şeyler 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ı.

Bu kurs mimari düzenleri bu ölçülerle karşılaştırır ve her ders aynı kitaplığın — gönderi
ücretlendirme ve yönlendirme kitaplığının — o düzende kurulmuş hâlini ölçer. İlk düzen
birimleri sorumluluklarına göre sıralı yığınlara ayırır ve aralarındaki çağrı yönünü bir
kuralla sınırlar.

## Biçem Olarak Katman

Katmanların sorumlulukları ve aralarındaki bağımlılık yönü Veri Erişim Katmanı ve İş Mantığı
kursundaki Katman Sorumlulukları dersinde kurulmuştu. İthal satırlarından katman kenarı
çıkarıp ilan edilmiş bir kurala göre ihlal sayma yordamı da Tasarım İlkeleri kursundaki
Bağımlılık Çizgesi Sağlığı dersinde yazılmıştı. Burada ikisi de yeniden anlatılmıyor;
kullanılıyor.

Yeni olan biçemin kendisidir. Katmanlı mimari üç şeyi birlikte söyler. Birincisi, her birim
tam bir katmana aittir; ikisine ait olan birim yoktur. İkincisi, katmanlar sıralıdır. Üçüncüsü
ve belirleyici olanı, hangi katmanın hangisini çağırabileceği **önceden ilan edilmiş bir kenar
kümesidir**. Üçüncü madde olmadan ilk ikisi bir dizin düzeninden fazlası değildir: kural
yazılmamışsa ihlal de tanımlı değildir.

Kenar kümesinin biçimi bir ayrım getirir. Bir katman **kapalı** (closed) ise üstündeki katman
onu atlayamaz, isteğin ondan geçmesi zorunludur. **Açık** (open) ise atlanmasına izin
verilmiştir. Açık katman kuralın ihlali değil, kuralın bir parçasıdır: izinli kenar kümesine
yazılır. Aynı çağrının izinli mi ihlal mi olduğu koda bakılarak anlaşılmaz, kenar kümesine
bakılarak anlaşılır.

## Kitaplığın Katmanlı Düzeni

Ücretlendirme bağlamı dört katmana ayrılıyor. Bağımlılıklar ithalle değil parametreyle
kuruluyor; birimleri birbirine bağlayan tek yer bileşim köküdür.

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

```js
// katmanli/altyapi/tarife-deposu.mjs — altyapi katmani: tarife satirlari ve posta kodu esdegeri
const KADEMELER = [
  { ustAgirlik: 1, ucret: 4990 },
  { ustAgirlik: 5, ucret: 8490 },
  { ustAgirlik: 15, ucret: 14990 },
  { ustAgirlik: 30, ucret: 24990 },
];
const KATSAYI = { yakin: 1, orta: 1.35, uzak: 1.8 };
const POSTA_BOLGE = { 34: "yakin", "06": "orta", 35: "orta", 65: "uzak" };

export const tarifeDeposu = {
  kademeler: () => KADEMELER.map((k) => ({ ...k })),
  katsayi: (postaKodu) => KATSAYI[POSTA_BOLGE[postaKodu.slice(0, 2)] ?? "uzak"],
};
```

```js
// katmanli/alan/ucret-kurali.mjs — alan katmani: ucret kurali, hicbir katmani ithal etmiyor
const ASGARI_UCRET = 3990;
const YUVARLAMA_ADIMI = 50;

export const ucretKurali = {
  hesapla: (gonderi, kademeler, katsayi) => {
    const kademe = kademeler.find((k) => gonderi.agirlik <= k.ustAgirlik);
    if (kademe === undefined) throw new RangeError("agirlik kademesi yok");
    const ham = kademe.ucret * katsayi;
    return Math.max(Math.round(ham / YUVARLAMA_ADIMI) * YUVARLAMA_ADIMI, ASGARI_UCRET);
  },
};
```

```js
// katmanli/uygulama/teklif-ver.mjs — uygulama katmani: senaryonun sirasi
export const uygulamaKatmani = (alan, altyapi) => ({
  teklifVer: (gonderi) => {
    const kademeler = altyapi.kademeler();
    const katsayi = altyapi.katsayi(gonderi.postaKodu);
    return { tutar: alan.hesapla(gonderi, kademeler, katsayi) };
  },
  kademeListele: () => altyapi.kademeler(),
});
```

```js
// katmanli/sunum/masa.mjs — sunum katmani: govde cozumleme ve tutar bicimleme
export const sunumKatmani = (uygulama) => ({
  teklifIstegi: (govde) => {
    const gonderi = { agirlik: Number(govde.agirlik), postaKodu: String(govde.postaKodu) };
    const { tutar } = uygulama.teklifVer(gonderi);
    return { durum: 200, tutar: `${(tutar / 100).toFixed(2)} TL` };
  },
  kademeIstegi: () => uygulama.kademeListele(),
});

// acik katman: kural bu kenari ilan ettiginde okuma istegi uygulama katmanini atlar
export const acikKademeMasasi = (altyapi) => ({
  kademeIstegi: () => altyapi.kademeler(),
});
```

Sunum modülünde iki masa var. Birincisi kapalı düzenin masası, uygulama katmanını çağırır.
İkincisi açık katman ilanının karşılığıdır ve yalnız okuma isteği için altyapıyı doğrudan
çağırır. Aynı dosyada durmaları rastlantı değil: ikisi de aynı kuralın kapsamındadır ve fark
kuralın hangisini izinli saydığındadır.

## Bir İsteğin Yolu

Bağımlılıklar parametreyle geldiği için sınırlara sayaç konabilir. Ölçüm betiği bileşim
kökünün işini yapar ve her katman nesnesini bir sarmalayıcıdan geçirir; sarmalayıcı çağrı
ağacını kurar. Ağaçtan üç sayı çıkar: isteğin dokunduğu katman sayısı, katmanlar arasındaki
çağrı sayısı ve **geçirgen katman** sayısı. Bir düğüm tek çocuklu ise, aldığı argümanlar ile
çocuğa verdiği argümanlar aynıysa ve çocuktan aldığı değeri değiştirmeden döndürüyorsa o
katman geçirgendir: isteğe hiçbir şey eklememiştir.

```js
// sinir-say.mjs — bir istegin dokundugu katman, gectigi sinir ve gecirgen katman sayisi
import { tarifeDeposu } from "./katmanli/altyapi/tarife-deposu.mjs";
import { ucretKurali } from "./katmanli/alan/ucret-kurali.mjs";
import { uygulamaKatmani } from "./katmanli/uygulama/teklif-ver.mjs";
import { sunumKatmani, acikKademeMasasi } from "./katmanli/sunum/masa.mjs";

const kokler = [];
const yigit = [];

function sar(katman, nesne) {
  const sarili = {};
  for (const [ad, fn] of Object.entries(nesne)) {
    sarili[ad] = (...args) => {
      const dugum = { katman, ad, args, cocuk: [], deger: null };
      (yigit.at(-1)?.cocuk ?? kokler).push(dugum);
      yigit.push(dugum);
      try { return (dugum.deger = fn(...args)); } finally { yigit.pop(); }
    };
  }
  return sarili;
}

function dolas(dugum, gorulen = new Set(), gecis = [], gecirgen = []) {
  gorulen.add(dugum.katman);
  for (const c of dugum.cocuk) {
    gecis.push(`${dugum.katman}->${c.katman}`);
    dolas(c, gorulen, gecis, gecirgen);
  }
  const tek = dugum.cocuk.length === 1 ? dugum.cocuk[0] : null;
  if (tek !== null && JSON.stringify(dugum.args) === JSON.stringify(tek.args)
      && JSON.stringify(dugum.deger) === JSON.stringify(tek.deger)) {
    gecirgen.push(dugum.katman);
  }
  return { gorulen, gecis, gecirgen };
}

const alan = sar("alan", ucretKurali);
const altyapi = sar("altyapi", tarifeDeposu);
const uygulama = sar("uygulama", uygulamaKatmani(alan, altyapi));
const sunum = sar("sunum", sunumKatmani(uygulama));
const acikSunum = sar("sunum", acikKademeMasasi(altyapi));

const ISTEKLER = [
  ["teklif / kapali    ", () => sunum.teklifIstegi({ agirlik: "3", postaKodu: "06800" })],
  ["kademe / kapali    ", () => sunum.kademeIstegi()],
  ["kademe / acik katman", () => acikSunum.kademeIstegi()],
];

for (const [ad, cagir] of ISTEKLER) {
  kokler.length = 0;
  const sonuc = cagir();
  const { gorulen, gecis, gecirgen } = dolas(kokler[0]);
  const ozet = Array.isArray(sonuc) ? `${sonuc.length} kademe` : JSON.stringify(sonuc);
  console.log(`${ad}  sonuc = ${ozet}`);
  console.log(`  dokunulan katman = ${gorulen.size}, sinir gecisi = ${gecis.length}, gecirgen katman = ${gecirgen.length}`);
  console.log(`  yol = ${gecis.join(", ")}`);
  if (gecirgen.length > 0) console.log(`  gecirgen = ${gecirgen.join(", ")}`);
}
```

```sh
node sinir-say.mjs
```

```
teklif / kapali      sonuc = {"durum":200,"tutar":"114.50 TL"}
  dokunulan katman = 4, sinir gecisi = 4, gecirgen katman = 0
  yol = sunum->uygulama, uygulama->altyapi, uygulama->altyapi, uygulama->alan
kademe / kapali      sonuc = 4 kademe
  dokunulan katman = 3, sinir gecisi = 2, gecirgen katman = 2
  yol = sunum->uygulama, uygulama->altyapi
  gecirgen = uygulama, sunum
kademe / acik katman  sonuc = 4 kademe
  dokunulan katman = 2, sinir gecisi = 1, gecirgen katman = 1
  yol = sunum->altyapi
  gecirgen = sunum
```

## Geçirgen Katman

İlk istek biçemin işini gösteriyor. Teklif isteği dört katmana dokundu, dört sınır geçti ve
geçirgen katman sayısı 0 çıktı: sunum gövdeyi çözümledi ve tutarı biçimledi, uygulama çağrı
sırasını kurdu, altyapı iki tabloyu verdi, alan kuralı hesapladı. Her katman isteğe bir şey
ekledi.

İkinci istek aynı biçemin bedelini gösteriyor. Kademe listesi isteği üç katmana dokundu ama
iki katman geçirgen çıktı: uygulama katmanı aldığı boş argüman listesini olduğu gibi aşağı
geçirdi ve dönen listeyi değiştirmeden yukarı verdi; sunum katmanı da aynısını yaptı. Üç
katmanın ikisi yalnız bir aktarma noktasıydı. Katmanlı biçemin bu belirtisine **katman
atlama** deme eğilimi yanlıştır: burada atlanan bir şey yok, tersine hiçbir şey yapmayan iki
durak var.

Üçüncü koşum ölçünün ne kadar düştüğünü söylüyor. Aynı sonuç, açık katman ilanıyla iki
katmana dokunularak ve bir sınır geçilerek üretildi: dokunulan katman 3'ten 2'ye, sınır geçişi
2'den 1'e, geçirgen katman 2'den 1'e indi. Kazanç bedava değil; iki masa arasındaki fark, aynı
okuma isteğinin iki ayrı yerden karşılanabilmesidir ve okumaya bir iş kuralı eklendiği gün o
kural iki yere yazılır ya da açık kenar geri alınır.

## Sızıntının İthal Grafiğinde Görünmesi

Açık katman ile sızıntı çalışma zamanında birbirine benzer: ikisinde de bir katman atlanır.
Fark ilan edilip edilmediğidir ve bu fark ithal grafiğinde ölçülür. Aşağıdaki blok kapalı
düzenin bir kopyasını alıp iki dosyayı değiştiriyor.

```sh
cp -r katmanli sizintili
cat > sizintili/sunum/masa.mjs <<'EOF'
// sizintili/sunum/masa.mjs — okuma istegi uygulama katmanini atlayip altyapiyi cagiriyor
import { tarifeDeposu } from "../altyapi/tarife-deposu.mjs";

export const sunumKatmani = (uygulama) => ({
  teklifIstegi: (govde) => {
    const gonderi = { agirlik: Number(govde.agirlik), postaKodu: String(govde.postaKodu) };
    const { tutar } = uygulama.teklifVer(gonderi);
    return { durum: 200, tutar: `${(tutar / 100).toFixed(2)} TL` };
  },
  kademeIstegi: () => tarifeDeposu.kademeler(),
});
EOF
cat > sizintili/alan/bolge-cozumleyici.mjs <<'EOF'
// sizintili/alan/bolge-cozumleyici.mjs — alan katmanindaki yardimci altyapi tablosuna uzaniyor
import { tarifeDeposu } from "../altyapi/tarife-deposu.mjs";

export const enPahaliKademe = () =>
  tarifeDeposu.kademeler().reduce((a, b) => (a.ucret >= b.ucret ? a : b));
EOF
find sizintili -name '*.mjs' | sort
```

```
sizintili/alan/bolge-cozumleyici.mjs
sizintili/alan/ucret-kurali.mjs
sizintili/altyapi/tarife-deposu.mjs
sizintili/sunum/masa.mjs
sizintili/uygulama/teklif-ver.mjs
```

İki değişiklik de tek başına bakıldığında masum görünür. Sunum masası bir liste isteği için
araya bir katman koymaya gerek görmüyor; alan katmanındaki yardımcı en pahalı kademeyi
öğrenmek istiyor. Denetim aracı her dosyanın bulunduğu dizinden katmanını okuyor, ithal
satırlarını katman kenarına indirgiyor ve iki ayrı kenar kümesine karşı sayıyor.

```js
// katman-ihlali.mjs — ithal grafigini katman kenarina indirger, ilan edilmis kurala gore ihlali sayar
import { readdirSync, readFileSync } from "node:fs";
import { basename, dirname, join, normalize } from "node:path";

const KAPALI = new Set(["sunum->uygulama", "uygulama->alan", "uygulama->altyapi"]);
const ACIK = new Set([...KAPALI, "sunum->altyapi"]);

function katmanKenarlari(kok) {
  const kenar = new Set();
  for (const katman of readdirSync(kok).sort()) {
    for (const dosya of readdirSync(join(kok, katman)).sort()) {
      const metin = readFileSync(join(kok, katman, dosya), "utf8");
      for (const m of metin.matchAll(/^import\s.*?from\s+"(\.[^"]+)"/gm)) {
        const hedef = basename(dirname(normalize(join(kok, katman, m[1]))));
        if (hedef !== katman) kenar.add(`${katman}->${hedef}`);
      }
    }
  }
  return [...kenar];
}

for (const kok of ["katmanli", "sizintili"]) {
  const kenarlar = katmanKenarlari(kok);
  console.log(`${kok}/  katman kenari = ${kenarlar.length}  (${kenarlar.join(", ") || "yok"})`);
  for (const [ad, kural] of [["kapali kural", KAPALI], ["acik katman kurali", ACIK]]) {
    const ihlal = kenarlar.filter((k) => kural.has(k) === false);
    console.log(`  ${ad.padEnd(19)} izinli kenar = ${kural.size}, ihlal = ${ihlal.length}${ihlal.length ? ` (${ihlal.join(", ")})` : ""}`);
  }
}
```

```sh
node katman-ihlali.mjs
```

```
katmanli/  katman kenari = 0  (yok)
  kapali kural        izinli kenar = 3, ihlal = 0
  acik katman kurali  izinli kenar = 4, ihlal = 0
sizintili/  katman kenari = 2  (alan->altyapi, sunum->altyapi)
  kapali kural        izinli kenar = 3, ihlal = 2 (alan->altyapi, sunum->altyapi)
  acik katman kurali  izinli kenar = 4, ihlal = 1 (alan->altyapi)
```

Kapalı düzenin katman kenarı 0 çıktı. Bu bir eksiklik değil, bileşim kökü düzeninin
sonucudur: hiçbir katman modülü başka bir katmanı ithal etmiyor, bağlar tek bir dosyada
kuruluyor. Böyle bir düzende katman kuralının denetlendiği yer ithal grafiği değil, o tek
dosyadır — ve sızıntı tam olarak o dosyanın atlandığı anda ithal grafiğinde görünür hâle
gelir.

Sızıntılı düzenin iki kenarı iki ayrı cinsten. `sunum->altyapi` kenarı açık katman kuralında
izinlidir, kapalı kuralda ihlaldir: aynı satır, iki kurala göre iki farklı yargı. Buna karşılık
`alan->altyapi` kenarı iki kuralda da ihlal, çünkü hiçbir kenar kümesi alan katmanından dışa
çıkan bir kenar ilan etmiyor. Sızıntının iki cinsi arasındaki fark budur: birincisi kuralın
gözden geçirilmesi gereken bir yerdir, ikincisi biçemin taşıyıcı kuralını bozar. Alan
katmanının bir tablo modülüne uzanması, o katmanın tek başına ithal edilebilmesini de bitirir;
kuralı sınamak için tabloyu da yüklemek gerekir.

## Ödünleşimin Sayıları

Biçem bir kalite niteliğini iyileştirir, başka birini kötüleştirir. Sürdürülebilirlik tarafında
kazanç ölçüldü: ücret kuralı tek dosyada, dışa çıkan kenarı 0, kapalı düzende katman ihlali 0.
Kural değiştiğinde dokunulan dosya bir tanedir ve tabloların biçimi kurala hiç girmez.

Bedel de aynı ölçülerle yazılıyor. Aynı biçem, iş kuralı gerektirmeyen bir okuma isteğinde 3
katmana dokunuyor ve bu üçün 2'si isteğe hiçbir şey eklemiyor. Katman sayısı arttıkça bu oran
büyür: her yeni katman, kural içermeyen isteklerde bir geçirgen durak daha demektir. Biçemin
kendisi bu iki sayı arasındaki ödünleşimdir — ilan edilmiş kenar kümesi ne kadar sıkıysa
kuralın yeri o kadar kesin, geçirgen durak sayısı o kadar yüksektir.

## Özet

- Katmanlı biçem üç şeyi birlikte söyler: her birim tam bir katmana aittir, katmanlar
  sıralıdır ve izinli çağrı kenarları önceden ilan edilmiştir; üçüncüsü olmadan ihlal tanımlı
  değildir.
- Teklif isteği 4 katmana dokundu, 4 sınır geçti ve geçirgen katman sayısı 0 çıktı; kademe
  listesi isteği 3 katmana dokundu ve 3 katmanın 2'si isteğe hiçbir şey eklemedi.
- Açık katman ilanı aynı okuma isteğini 2 katmana ve 1 sınıra indirdi; bedeli, aynı isteğin
  iki ayrı yerden karşılanabilir hâle gelmesidir.
- Kapalı düzende katman kenarı 0 çıktı, çünkü bağlar bileşim kökünde kuruluyor; sızıntı o
  kökün atlandığı anda ithal grafiğinde görünür.
- Sızıntılı düzendeki iki kenardan `sunum->altyapi` açık kuralda izinli, kapalı kuralda ihlal;
  `alan->altyapi` her iki kuralda da ihlal ve alan katmanının tek başına yüklenmesini bitiriyor.

## Sonraki Adım

Kapalı düzenin izinli kenar kümesinde bir kenar dikkat çekiyor: `uygulama->altyapi`. Kural, iş
akışını kuran katmanın tabloya doğrudan uzanmasına izin veriyor; yani sıralamada altyapı,
alanın yanında bir başka aşağı katmandır. Sıra bu biçimde kurulduğunda kitaplığın merkezinde
alan modeli değil, isteğin geçtiği yol durur. Sonraki ders sıralamayı tersine çeviren iki
düzeni ele alır: bütün bağların içe, alana doğru baktığı halkalı yerleşimleri. Altıgen
mimarinin dışında sırasız bir bağdaştırıcı kuşağı vardır, soğan mimaride ise halkalar
sıralıdır; iki düzen aynı üç değişiklik senaryosunda düzenlenen ve eklenen dosya sayısıyla
karşılaştırılır.
