İçeriğe geç
academia.sh

Ders 07 / 16

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.

İçindekiler

Ö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.

#!/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.

// 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.

// 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.

// 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.

// 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.

// 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.

#!/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.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat