---
title: 'Proje Yapısı'
source: 'https://academia.sh/tr/kurslar/sunucu-temelleri/proje-yapisi'
course: 'Sunucu Tarafı Temelleri'
language: tr
updated: '2026-08-17T18:06:52+00:00'
license: 'CC BY-SA 4.0'
---

# Proje Yapısı

Dosyaların katman odaklı ve özellik odaklı iki düzeni ölçülerek karşılaştırılır; alan, veri, taşıma ve birleştirme sorumlulukları ayrılır ve bağımlılık yönü kuralı, ihlali çıkış koduyla bildiren bir denetim programına bağlanır.

Önceki bölüm bir isteğin sunucu tarafındaki yolunu bileşenlerine ayırdı. Ortaya çıkan
parçalar — zincir halkaları, işleyiciler, iş kuralları, veri erişimi — henüz tek bir dosyada,
yan yana duruyor. Bir dosya büyüdükçe değişmesi zorlaşır; asıl sorun ise satır sayısı değil,
birbirini bilmemesi gereken parçaların birbirine erişebilmesidir.

Bu ders kütüphane ödünç servisini taşıyabilecek bir dizin düzeni kurar ve tek bir soruya
yanıt arar: hangi modül hangisini çağırabilir? Yanıt bir belge olarak değil, ihlali çıkış
koduyla bildiren bir denetim programı olarak yazılacaktır.

## İki Düzen: Katman Odaklı ve Özellik Odaklı

Bir uygulamanın dosyalarını bölmenin iki yaygın ölçütü vardır. **Katman odaklı** düzen üst
dizinleri teknik ilgiye göre açar: bütün iş kuralları bir dizinde, bütün veri erişimi
başkasında. **Özellik odaklı** düzen üst dizinleri iş alanına göre açar: ödünç işlemine ait
her şey bir dizinde, katalog işlemine ait her şey başkasında.

Aynı dokuz modül iki düzene de yerleştirilebilir. Aşağıdaki betik ikisini de kurar ve iki
farklı değişikliğin kaç dizine dokunduğunu sayar.

```bash
#!/usr/bin/env bash
# Ayni dokuz modulu iki ayri duzende yerlestirir ve bir degisikligin kac dizine dokundugunu sayar.
mkdir -p duzenler

for ozellik in odunc katalog uye; do
  for ilgi in alan veri http; do
    mkdir -p "duzenler/katman-odakli/kaynak/$ilgi" "duzenler/ozellik-odakli/kaynak/$ozellik"
    printf '// %s ozelligi, %s ilgisi\n' "$ozellik" "$ilgi" \
      > "duzenler/katman-odakli/kaynak/$ilgi/$ozellik.mjs"
    printf '// %s ozelligi, %s ilgisi\n' "$ozellik" "$ilgi" \
      > "duzenler/ozellik-odakli/kaynak/$ozellik/$ilgi.mjs"
  done
done

echo "--- katman odakli agac ---"
find duzenler/katman-odakli -name '*.mjs' | sort
echo "--- ozellik odakli agac ---"
find duzenler/ozellik-odakli -name '*.mjs' | sort

dizinSayisi() {  # $1 = agac koku, $2 = aranan isaret
  grep -rl "$2" "$1" | xargs -n1 dirname | sort -u | wc -l | tr -d ' '
}

echo "--- bir degisiklik kac dizine dokunur ---"
printf '%-16s %-22s %s\n' "duzen" "odunc ozelligi" "veri ilgisi"
for duzen in katman-odakli ozellik-odakli; do
  printf '%-16s %-22s %s\n' "$duzen" \
    "$(dizinSayisi "duzenler/$duzen" 'odunc ozelligi') dizin" \
    "$(dizinSayisi "duzenler/$duzen" 'veri ilgisi') dizin"
done
```

```
--- katman odakli agac ---
duzenler/katman-odakli/kaynak/alan/katalog.mjs
duzenler/katman-odakli/kaynak/alan/odunc.mjs
duzenler/katman-odakli/kaynak/alan/uye.mjs
duzenler/katman-odakli/kaynak/http/katalog.mjs
duzenler/katman-odakli/kaynak/http/odunc.mjs
duzenler/katman-odakli/kaynak/http/uye.mjs
duzenler/katman-odakli/kaynak/veri/katalog.mjs
duzenler/katman-odakli/kaynak/veri/odunc.mjs
duzenler/katman-odakli/kaynak/veri/uye.mjs
--- ozellik odakli agac ---
duzenler/ozellik-odakli/kaynak/katalog/alan.mjs
duzenler/ozellik-odakli/kaynak/katalog/http.mjs
duzenler/ozellik-odakli/kaynak/katalog/veri.mjs
duzenler/ozellik-odakli/kaynak/odunc/alan.mjs
duzenler/ozellik-odakli/kaynak/odunc/http.mjs
duzenler/ozellik-odakli/kaynak/odunc/veri.mjs
duzenler/ozellik-odakli/kaynak/uye/alan.mjs
duzenler/ozellik-odakli/kaynak/uye/http.mjs
duzenler/ozellik-odakli/kaynak/uye/veri.mjs
--- bir degisiklik kac dizine dokunur ---
duzen            odunc ozelligi         veri ilgisi
katman-odakli    3 dizin                1 dizin
ozellik-odakli   1 dizin                3 dizin
```

Tablo simetriktir ve seçimin niteliğini gösterir: hiçbiri diğerinden iyi değildir, ikisi
farklı değişiklik biçimlerini ucuzlatır. Ödünç kuralına yeni bir koşul eklemek özellik odaklı
düzende tek dizine dokunur; veri erişiminin biçimini değiştirmek katman odaklı düzende tek
dizine dokunur.

Seçimi belirleyen soru şudur: bu uygulamada hangi değişiklik daha sık olur? Az sayıda iş
alanı ve sık değişen teknik ilgiler varsa katman odaklı düzen daha az dosya dolaştırır;
birbirinden bağımsız çok sayıda iş alanı varsa özellik odaklı düzen aynı işi yapar. Bu dersin
geri kalanı katman odaklı düzeni kullanır, çünkü kurulacak kural — bağımlılık yönü — orada
dizin adlarıyla doğrudan ifade edilebilir.

## Dört Sorumluluk

Katman odaklı düzende dört dizin vardır ve her biri bilmediği bir şeyle tanımlanır.

**Alan** (`kaynak/alan`) iş kurallarını taşır ve başka hiçbir katmanı çağırmaz. Veritabanının
nerede olduğunu, isteğin HTTP ile geldiğini, yanıtın JSON olduğunu bilmez.

```js
// kaynak/alan/odunc.mjs — odunc verme kurali; hicbir katmani cagirmaz
export const ODUNC_GUN = 14;
export const UYE_ODUNC_SINIRI = 5;

export const oduncVerilebilir = (kitap, acikOduncSayisi) => {
  if (!kitap) return { olur: false, kod: "kitap_yok" };
  if (kitap.rafta < 1) return { olur: false, kod: "raf_bos" };
  if (acikOduncSayisi >= UYE_ODUNC_SINIRI) return { olur: false, kod: "sinir_asildi" };
  return { olur: true };
};

export const sonTarih = (baslangic) =>
  new Date(baslangic.getTime() + ODUNC_GUN * 86400000);
```

Bu fonksiyonların hiçbiri eşzamansız değildir ve hiçbiri dış dünyaya dokunmaz. Girdileri
verilmiş bir kararı hesaplarlar; bu yüzden sınanmaları için ne sunucu ne veritabanı gerekir.

**Veri** (`kaynak/veri`) kalıcılığı üstlenir ve iş kurallarını bilmez. Kaç ödünç sınırının
olduğu ona sorulmaz; ona yalnız sayı sorulur.

```js
// kaynak/veri/kitap-deposu.mjs — kaliciligi ustlenir; is kurallarini bilmez
const KITAPLAR = new Map([
  ["978-0262033848", { ad: "Algoritmalara Giris", rafta: 1 }],
  ["978-0201835953", { ad: "Efsanevi Adam-Ay", rafta: 0 }],
]);
const ODUNCLER = [];

export const kitapBul = async (isbn) => KITAPLAR.get(isbn) ?? null;

export const acikOduncSayisi = async (uye) =>
  ODUNCLER.filter((o) => o.uye === uye && !o.iadeEdildi).length;

export const oduncKaydet = async (kayit) => {
  KITAPLAR.get(kayit.isbn).rafta -= 1;
  ODUNCLER.push(kayit);
  return kayit;
};
```

Depo burada bellekte tutulur. Önceki derste kurulan kural gereği bu, birden çok süreçle
çalışmayan geçici bir uygulamadır; kalıcı bir depoya geçildiğinde değişmesi gereken tek
dizin budur ve alan katmanı bu değişiklikten etkilenmez.

**Taşıma** (`kaynak/http`) protokolü alan diline çevirir ve iş kuralı içermez. Yaptığı iş
veriyi toplamak, karar almayı alan katmanına bırakmak ve sonucu bir durum koduna eşlemektir.

```js
// kaynak/http/odunc-ucu.mjs — HTTP istemini alan diline cevirir; is kurali icermez
import { oduncVerilebilir, sonTarih } from "../alan/odunc.mjs";
import { kitapBul, acikOduncSayisi, oduncKaydet } from "../veri/kitap-deposu.mjs";

export const oduncVer = async ({ isbn, uye }, bugun = new Date()) => {
  const kitap = await kitapBul(isbn);
  const karar = oduncVerilebilir(kitap, await acikOduncSayisi(uye));
  if (!karar.olur) return { durum: 409, govde: { hata: karar.kod } };

  const kayit = await oduncKaydet({ isbn, uye, sonTarih: sonTarih(bugun), iadeEdildi: false });
  return { durum: 201, govde: { isbn, uye, sonTarih: kayit.sonTarih.toISOString() } };
};
```

**Birleştirme** (`kaynak/kurulum`) parçaları bir araya getiren tek yerdir. Bir uygulamada
bağımlılıkların bağlandığı, yapılandırmanın okunduğu ve sunucunun ayağa kaldırıldığı nokta
buradadır.

```js
// kaynak/kurulum/uygulama.mjs — parcalari birlestiren tek yer
import { oduncVer } from "../http/odunc-ucu.mjs";

const bugun = new Date("2026-03-01T00:00:00Z");
for (const istem of [
  { isbn: "978-0262033848", uye: "U-4711" },   // rafta tek kopya var
  { isbn: "978-0262033848", uye: "U-4712" },   // ayni kopya artik odunc verildi
  { isbn: "978-0000000000", uye: "U-4713" },   // katalogda yok
]) {
  console.log(JSON.stringify(await oduncVer(istem, bugun)));
}
```

## Bağımlılık Yönü

Dizinlerin varlığı tek başına hiçbir şeyi güvenceye almaz. Ayrımı anlamlı kılan, aralarındaki
içe aktarımların **tek yönlü** olmasıdır: dış katman iç katmanı çağırabilir, iç katman dışı
çağıramaz.

| Katman | İçe aktarabildikleri |
|---|---|
| `alan` | — |
| `veri` | `alan` |
| `http` | `alan`, `veri` |
| `kurulum` | `alan`, `veri`, `http` |

Kuralın gerekçesi somuttur. Alan katmanı veri katmanını çağırırsa iş kuralını sınamak için
bir depo gerekir; depo değiştiğinde kural da değişir. Ödünç sınırının beş olduğunu bilen
fonksiyonun aynı zamanda kaydın nerede tutulduğunu da bilmesi, iki ayrı nedenle değişen bir
modül üretir.

Kural, alan katmanının ihtiyaç duyduğu veriyi **parametre olarak alması** ile sağlanır.
`oduncVerilebilir` fonksiyonu üyenin açık ödünç sayısını kendisi sorgulamaz; sayıyı çağırandan
alır. Sorgulama işi bir dış katmana, burada taşıma katmanına düşer.

## Kuralı Denetime Bağlamak

Belgede yazan bir kural, ihlal edildiğinde kimseyi uyarmaz. Aşağıdaki program içe aktarım
satırlarını okur, her dosyanın katmanını dizin adından çıkarır ve izin tablosuna uymayan her
satırı bildirir. İhlal varsa sıfırdan farklı bir çıkış kodu üretir; bu, denetimin bir sürekli
tümleştirme adımına bağlanabilmesi için gereken tek şeydir.

```js
// denetim/bagimlilik.mjs — katmanlar arasi bagimlilik yonunu sinar; ihlalde 1 ile cikar
import { readdir, readFile } from "node:fs/promises";
import { join, relative, sep } from "node:path";

const IZIN = {                          // her katmanin ice aktarabilecegi katmanlar
  alan: [],
  veri: ["alan"],
  http: ["alan", "veri"],
  kurulum: ["alan", "veri", "http"],
};

const ICE_AKTARIM = /^\s*import[^"']*["']([^"']+)["']/gm;

const girdiler = await readdir("kaynak", { withFileTypes: true, recursive: true });
const dosyalar = girdiler.filter((g) => g.isFile() && g.name.endsWith(".mjs"))
  .map((g) => join(g.parentPath, g.name)).sort();

let ihlal = 0;
for (const yol of dosyalar) {
  const kaynakKatman = relative("kaynak", yol).split(sep)[0];
  const metin = await readFile(yol, "utf8");
  for (const [, tanimlayici] of metin.matchAll(ICE_AKTARIM)) {
    if (!tanimlayici.startsWith(".")) continue;            // paket ve yerlesik moduller disinda
    const hedef = tanimlayici.replace(/^(\.\.\/|\.\/)+/, "").split("/")[0];
    if (!(hedef in IZIN) || hedef === kaynakKatman) continue;
    if (!IZIN[kaynakKatman].includes(hedef)) {
      console.log(`IHLAL  ${yol}: ${kaynakKatman} -> ${hedef}`);
      ihlal++;
    }
  }
}

console.log(ihlal === 0
  ? `${dosyalar.length} dosya tarandi, bagimlilik yonu kurala uygun`
  : `${dosyalar.length} dosya tarandi, ${ihlal} ihlal bulundu`);
process.exit(ihlal === 0 ? 0 : 1);
```

Denetimin gerçekten çalıştığını görmek için kuralın kırılması gerekir. Aşağıdaki betik önce
uygulamayı çalıştırır, sonra denetimi geçirir, ardından alan katmanına veri katmanından bir
içe aktarım ekleyip denetimi yeniden çalıştırır ve ağacı eski hâline döndürür. Betik yukarıda
gösterilen dört kaynak dosyası ile `denetim/bagimlilik.mjs` dosyasının bulunduğu dizinde
çalıştırılır.

```bash
#!/usr/bin/env bash
# Iskeleti calistirir, denetimi gecirir, sonra kurali bilerek kirip denetimin yakalamasini gosterir.
echo "--- uygulama ---"
node kaynak/kurulum/uygulama.mjs

echo "--- denetim: kurala uygun agac ---"
node denetim/bagimlilik.mjs; echo "cikis kodu=$?"

echo "--- alan katmanina veri katmani ice aktarimi eklenir ---"
cp kaynak/alan/odunc.mjs odunc.yedek
printf '%s\n' 'import { kitapBul } from "../veri/kitap-deposu.mjs";' > odunc.ihlalli
cat odunc.yedek >> odunc.ihlalli
cp odunc.ihlalli kaynak/alan/odunc.mjs

echo "--- denetim: kural kirilmis agac ---"
node denetim/bagimlilik.mjs; echo "cikis kodu=$?"

cp odunc.yedek kaynak/alan/odunc.mjs           # agac eski haline dondurulur
rm -f odunc.yedek odunc.ihlalli
```

```
--- uygulama ---
{"durum":201,"govde":{"isbn":"978-0262033848","uye":"U-4711","sonTarih":"2026-03-15T00:00:00.000Z"}}
{"durum":409,"govde":{"hata":"raf_bos"}}
{"durum":409,"govde":{"hata":"kitap_yok"}}
--- denetim: kurala uygun agac ---
4 dosya tarandi, bagimlilik yonu kurala uygun
cikis kodu=0
--- alan katmanina veri katmani ice aktarimi eklenir ---
--- denetim: kural kirilmis agac ---
IHLAL  kaynak/alan/odunc.mjs: alan -> veri
4 dosya tarandi, 1 ihlal bulundu
cikis kodu=1
```

Uygulamanın çıktısı katmanların iş bölümünü de doğrular: rafta tek kopyası olan kitap birinci
üyeye verilmiş (`201`), aynı kitabı isteyen ikinci üye `raf_bos` yanıtı almış, katalogda
bulunmayan istek `kitap_yok` ile karşılanmıştır. Üç kararın üçü de alan katmanında
hesaplanmış, durum koduna çevrilmeleri taşıma katmanında yapılmıştır.

Denetimin iki çalıştırması arasındaki tek fark tek bir içe aktarım satırıdır. Kural kırıldığı
anda ihlalin hangi dosyada ve hangi yönde olduğu adıyla bildirilmiş, çıkış kodu `0`'dan `1`'e
dönmüştür. Bir yapı adımı bu kodu gördüğünde durur; kural artık bir öneri değil, bir
kısıttır.

## Özet

- Katman odaklı düzen üst dizinleri teknik ilgiye, özellik odaklı düzen iş alanına göre açar;
  ölçümde ödünç özelliğine dokunan bir değişiklik birinde üç, diğerinde tek dizine dokunmuş,
  veri erişimine dokunan değişiklikte oran tersine dönmüştür.
- Katman odaklı düzende dört sorumluluk ayrılır: alan iş kuralını taşır ve hiçbir katmanı
  çağırmaz, veri kalıcılığı üstlenir, taşıma protokolü alan diline çevirir, birleştirme
  parçaları tek yerde bağlar.
- Bağımlılık yönü tek yönlüdür: dış katman içi çağırır, iç katman dışı çağıramaz. Alan
  katmanının ihtiyaç duyduğu veri ona parametre olarak verilir.
- Kural bir denetim programına bağlandığında ihlal, dosya adı ve yönüyle bildirilir ve çıkış
  kodu `1` olur; bu kod bir yapı adımını durdurabildiği için kural kısıt hâline gelir.
- Bellekteki depo yalnız tek süreçli bir uygulamada geçerlidir; kalıcı depoya geçiş yalnız
  veri dizinini değiştirir ve alan katmanına dokunmaz.

## Sonraki Adım

İskelet ayakta ama bütün değerleri kodun içinde yazılı: bağlantı noktası, ödünç günü, üye
ödünç sınırı, deponun yeri. Aynı uygulamanın yerelde, sınama ortamında ve yayında farklı
değerlerle çalışması gerekir ve bu değerler kodun içinden okunamaz. Sonraki ders
yapılandırmayı ortamdan okumayı ele alır: hangi değer değişken, hangisi sabit; eksik ya da
yanlış tipte bir değişkenle uygulama ne yapmalı? Ölçüm, eksik bir değişkenle başlatılan
uygulamanın açılışta hangi iletiyle ve hangi çıkış koduyla düştüğünü gösterecek.
