Ders 02 / 18
Modüler Tek Parça
Sınırların kod içinde korunması: alanlar arası ithal grafiğinin ölçülmesi, bir modülün başkasının içine uzandığı sınır ihlallerinin sayılması, kural konduğunda dokunulan dosyanın ve genişleyen genel yüzün ölçüsü; süreç ve dağıtım birimi hâlâ tek.
İçindekiler
Önceki ders tek parçayı ölçtü: yedi modül, on ithal kenarı, tek süreç, tek dağıtım birimi. Ölçünün açık bıraktığı yer, o on kenarın hangi kurala göre çizildiğiydi. Cevap şuydu: hiçbir kurala göre. Bir modül başka bir modülün herhangi bir dosyasını ithal edebilir; hiçbir şey engellemez.
Bu ders o boşluğu kapatır. Aynı ödünç iş akışı, alanları dizinlere ayrılmış olarak yeniden kurulur; önce sınırların kendiliğinden aşındığı hâli ölçülür, sonra sınır bir kuralla korunur. Sınırların kod içinde korunduğu bu düzene modüler tek parça (modular monolith) denir. Süreç sayısı, dağıtım birimi sayısı ve iş akışının çıktısı hiç değişmez; değişen tek şey ithal grafiğidir. Bu, mimarinin dağıtımdan bağımsız olarak da ölçülebildiği tek yerdir.
Buradaki tek parça, Başarım Karşıt Kalıpları ve İzleme kursundaki tek parça kalıcılık
ile aynı sözcüğü taşır ama ayrı şeydir: orada tek olan veri deposu, burada tek olan dağıtım
birimidir.
Düzenek
MS3. Durum, tek koşum içinde çalışıldığı için bellekte tutulur. Birinci dersteki dosya deposunun yerini alan bu sadeleştirme ölçüyü etkilemez: ölçülen şey ithal grafiğidir.
Beş alanın her biri bir dizin. Her dizinde dışa açılan index.mjs ile alanın iç dosyaları
bulunur.
# kur.sh — 01'deki ayni is akisi, alanlar dizin oldu; durum tek kosumda bellekte tutulur rm -rf bozulmus && mkdir -p bozulmus/katalog bozulmus/uyelik bozulmus/ucretlendirme \ bozulmus/bildirim bozulmus/odunc && cd bozulmus cat > depo.mjs <<'JS' export const veri = { kitap: { 1: { durum: "rafta" } }, uye: { 4: { ad: "Emre", acik: 1, sinir: 3, borc: 0 } } }; JS cat > katalog/kayit.mjs <<'JS' import { veri } from "../depo.mjs"; export const kitap = (id) => veri.kitap[id]; JS cat > katalog/index.mjs <<'JS' import { kitap } from "./kayit.mjs"; export const durum = (id) => kitap(id).durum; JS cat > uyelik/kayit.mjs <<'JS' import { veri } from "../depo.mjs"; export const uye = (id) => veri.uye[id]; JS cat > uyelik/index.mjs <<'JS' import { uye } from "./kayit.mjs"; export const uygunMu = (id) => uye(id).acik < uye(id).sinir; export const oduncEkle = (id) => { uye(id).acik += 1; }; JS cat > ucretlendirme/index.mjs <<'JS' import { uye } from "../uyelik/kayit.mjs"; export const ucret = (gun) => (gun > 0 ? gun * 2 : 0); export const borcEkle = (id, tutar) => { uye(id).borc += tutar; }; JS cat > bildirim/index.mjs <<'JS' import { uye } from "../uyelik/kayit.mjs"; export const gonder = (id, metin) => console.log(` bildirim -> ${uye(id).ad}: ${metin}`); JS cat > odunc/index.mjs <<'JS' import { kitap } from "../katalog/kayit.mjs"; import * as uyelik from "../uyelik/index.mjs"; import * as ucretlendirme from "../ucretlendirme/index.mjs"; import * as bildirim from "../bildirim/index.mjs"; export function oduncVer(kitapId, uyeId, gecikmeGun = 0) { if (uyelik.uygunMu(uyeId) === false) throw new Error("odunc siniri asildi"); if (kitap(kitapId).durum !== "rafta") throw new Error("kitap rafta degil"); kitap(kitapId).durum = "odunc"; uyelik.oduncEkle(uyeId); const tutar = ucretlendirme.ucret(gecikmeGun); if (tutar > 0) ucretlendirme.borcEkle(uyeId, tutar); bildirim.gonder(uyeId, `kitap ${kitapId} odunc verildi`); return tutar; } JS cat > uygulama.mjs <<'JS' import { oduncVer } from "./odunc/index.mjs"; import { durum } from "./katalog/index.mjs"; const tutar = oduncVer(1, 4, 3); console.log(`odunc verildi: kitap 1 -> uye 4, ucret ${tutar}`); console.log(` katalog: kitap 1 = ${durum(1)}`); JS find . -name "*.mjs" | sort node uygulama.mjs
./bildirim/index.mjs ./depo.mjs ./katalog/index.mjs ./katalog/kayit.mjs ./odunc/index.mjs ./ucretlendirme/index.mjs ./uyelik/index.mjs ./uyelik/kayit.mjs ./uygulama.mjs bildirim -> Emre: kitap 1 odunc verildi odunc verildi: kitap 1 -> uye 4, ucret 6 katalog: kitap 1 = odunc
Listeleme bozulmus/ dizinine göredir. İş akışı çalışıyor, ücret hesaplanıyor, bildirim
gidiyor. Dizin ayrımı bakıldığında düzenli görünüyor.
Sınır İhlalinin Ölçüsü
Ölçülecek kural şudur: bir alan, başka bir alandan yalnız index.mjs dosyasını ithal
edebilir. Bunun dışındaki her ithal bir sınır ihlalidir — bir modülün başkasının içine
uzanması.
// sinir.mjs — alan siniri kurali: baska alandan yalniz index.mjs ithal edilebilir import { readdirSync, readFileSync, statSync } from "node:fs"; const kok = process.argv[2] ?? "."; const alanlar = readdirSync(kok).filter((a) => statSync(`${kok}/${a}`).isDirectory()).sort(); let kenar = 0, ihlal = 0, yuz = 0; for (const alan of alanlar) { for (const dosya of readdirSync(`${kok}/${alan}`).sort()) { if (dosya.endsWith(".mjs") === false) continue; const metin = readFileSync(`${kok}/${alan}/${dosya}`, "utf8"); if (dosya === "index.mjs") yuz += [...metin.matchAll(/^export /gm)].length; for (const [, hedef] of metin.matchAll(/from "(\.[^"]+)"/g)) { kenar += 1; const p = hedef.split("/"); if (p[0] === ".." && p.length === 3 && p[2] !== "index.mjs") { ihlal += 1; console.log(`IHLAL ${alan}/${dosya} -> ${hedef}`); } } } } console.log(`alan: ${alanlar.length} ithal kenari: ${kenar} genel yuz: ${yuz} sinir ihlali: ${ihlal}`);
node sinir.mjs bozulmus
IHLAL bildirim/index.mjs -> ../uyelik/kayit.mjs IHLAL odunc/index.mjs -> ../katalog/kayit.mjs IHLAL ucretlendirme/index.mjs -> ../uyelik/kayit.mjs alan: 5 ithal kenari: 10 genel yuz: 7 sinir ihlali: 3
Üç ihlal. Üçü de sonradan eklenmiş, tek satırlık, kimseyi rahatsız etmemiş ithallerdir. Ücretlendirme üyenin borcunu artırmak için üye kaydına doğrudan uzandı; bildirim üyenin adını almak için aynısını yaptı; ödünç, kitabın durumunu okumak için katalog kaydını açtı. Hiçbiri hata üretmedi, çünkü aynı süreçte her şey erişilebilirdir.
Bunun bedeli, ihlal sayısında değil, değişimin yayılmasında görünür. uyelik/kayit.mjs
içindeki kayıt biçimi değişirse, üyelik alanının kendi index.mjs dosyasıyla birlikte iki
yabancı alan da düzeltilmek zorundadır.
Kuralın Konması
Düzeltme, dışa uzanan üç ithali alanın genel yüzüne çevirir: eksik olan işlemler ilgili
alanın index.mjs dosyasına eklenir, ihlal eden üç dosya bu yeni yüz üzerinden yazılır.
# duzelt.sh — kural konur: her alan disariya yalniz index.mjs uzerinden acilir rm -rf moduler && cp -r bozulmus moduler && cd moduler cat > katalog/index.mjs <<'JS' import { kitap } from "./kayit.mjs"; export const durum = (id) => kitap(id).durum; export const raftaMi = (id) => kitap(id).durum === "rafta"; export const oduncIsaretle = (id) => { kitap(id).durum = "odunc"; }; JS cat > uyelik/index.mjs <<'JS' import { uye } from "./kayit.mjs"; export const uygunMu = (id) => uye(id).acik < uye(id).sinir; export const oduncEkle = (id) => { uye(id).acik += 1; }; export const ad = (id) => uye(id).ad; export const borcEkle = (id, tutar) => { uye(id).borc += tutar; }; JS cat > ucretlendirme/index.mjs <<'JS' import { borcEkle as uyeyeBorcEkle } from "../uyelik/index.mjs"; export const ucret = (gun) => (gun > 0 ? gun * 2 : 0); export const borcEkle = (id, tutar) => uyeyeBorcEkle(id, tutar); JS cat > bildirim/index.mjs <<'JS' import { ad } from "../uyelik/index.mjs"; export const gonder = (id, metin) => console.log(` bildirim -> ${ad(id)}: ${metin}`); JS cat > odunc/index.mjs <<'JS' import * as katalog from "../katalog/index.mjs"; import * as uyelik from "../uyelik/index.mjs"; import * as ucretlendirme from "../ucretlendirme/index.mjs"; import * as bildirim from "../bildirim/index.mjs"; export function oduncVer(kitapId, uyeId, gecikmeGun = 0) { if (uyelik.uygunMu(uyeId) === false) throw new Error("odunc siniri asildi"); if (katalog.raftaMi(kitapId) === false) throw new Error("kitap rafta degil"); katalog.oduncIsaretle(kitapId); uyelik.oduncEkle(uyeId); const tutar = ucretlendirme.ucret(gecikmeGun); if (tutar > 0) ucretlendirme.borcEkle(uyeId, tutar); bildirim.gonder(uyeId, `kitap ${kitapId} odunc verildi`); return tutar; } JS echo "yeniden yazilan dosya: 5" node uygulama.mjs
yeniden yazilan dosya: 5 bildirim -> Emre: kitap 1 odunc verildi odunc verildi: kitap 1 -> uye 4, ucret 6 katalog: kitap 1 = odunc
Çıktı birinci koşumun aynısı. Kullanıcı tarafında hiçbir şey değişmedi.
node sinir.mjs moduler echo "uyelik ic dosyasina uzanan dis dosya -> bozulmus: $(grep -l 'uyelik/kayit.mjs' bozulmus/*/*.mjs | wc -l | tr -d ' ') moduler: $(grep -l 'uyelik/kayit.mjs' moduler/*/*.mjs | wc -l | tr -d ' ')"
alan: 5 ithal kenari: 10 genel yuz: 11 sinir ihlali: 0 uyelik ic dosyasina uzanan dis dosya -> bozulmus: 2 moduler: 0
Üç sayı okunmalı. İhlal 3’ten 0’a indi. İthal kenarı 10’da kaldı: kural kenar sayısını azaltmaz, kenarların ucunu değiştirir; üç kenar bir alanın iç dosyasından o alanın genel yüzüne kaydı. Genel yüz ise 7’den 11’e çıktı — dört yeni dışa verilen ad.
Son satır asıl kazancı verir. Üyelik alanının iç dosyasına dışarıdan uzanan dosya sayısı
2’den 0’a indi. uyelik/kayit.mjs içindeki kayıt biçimi değiştiğinde dokunulacak dosya,
bozulmuş sürümde 3 (kendi index.mjs dosyası artı iki yabancı alan), modüler sürümde 1’dir.
Ücretlendirme alanındaki borcEkle, üyeliğin aynı adlı işlevini çağıran bir sarmalayıcıya
dönüştü. Bu, kuralın doğrudan bedelidir: alanlar arası her yeni ihtiyaç bir dolaylılık
katmanı ekler ve genel yüzü genişletir.
Kuralın Ayakta Kalması
Bu düzenin bir zayıflığı var: kural derleyicinin değil, denetleyicinin kuralıdır.
# kacak.sh — moduler surume tek satirlik bir ithal eklenir; program calismaya devam eder sed -i.yedek '1i\ import { uye } from "../uyelik/kayit.mjs"; ' moduler/bildirim/index.mjs rm -f moduler/bildirim/index.mjs.yedek ( cd moduler && node uygulama.mjs | head -n 1 ) node sinir.mjs moduler | tail -n 2
bildirim -> Emre: kitap 1 odunc verildi IHLAL bildirim/index.mjs -> ../uyelik/kayit.mjs alan: 5 ithal kenari: 11 genel yuz: 11 sinir ihlali: 1
Program çalışmaya devam etti; hiçbir test kırılmadı. Yalnız denetleyici konuştu. İhlal
sayısı 0’dan 1’e, ithal kenarı 10’dan 11’e çıktı. Sınır ihlali bir çalışma zamanı hatası
değil, bir ölçüdür: ölçülmediği anda serbestçe büyür. Yerinde düzenlemede kullanılan -i
seçeneği GNU ve BSD sürümlerinde farklı davrandığı için burada yedek uzantısı verilip yedek
silinmiştir; iki ortamda da aynı sonucu üretir.
Üç Sütun
| Modüler tek parça | |
|---|---|
| Ucuzlattığı | Bir alanın iç yapısı değiştiğinde dokunulan dosya 3’ten 1’e indi; sınır sayılabilir bir ölçü oldu |
| Pahalılaştırdığı | Genel yüz 7’den 11’e çıktı; alanlar arası her ihtiyaç bir dolaylılık katmanı ekliyor |
| Doğan arıza kipi | Kural yalnız denetleyici koştuğunda geçerli; tek satırlık bir ithal hiçbir şeyi kırmadan sınırı sessizce geri alıyor |
Değişmeyen sayılar da kayda değer: süreç 1, dağıtım birimi 1, ağ atlaması 0. Modüler tek parça bir dağıtım kararı değil, bir kod düzeni kararıdır. Bütün alanlar hâlâ birlikte yayına çıkar ve bir alandaki sonsuz döngü hepsini birlikte durdurur.
Özet
- Alanlar dizinlere ayrıldığında bile ithal grafiği kendiliğinden aşınır: üç alan komşusunun iç dosyasına uzanmıştı, hiçbiri hata üretmiyordu.
- Sınır ihlali ölçülebilir bir birimdir: başka bir alandan
index.mjsdışında bir dosyayı ithal eden her satır bir ihlaldir. - Kural konduğunda 5 dosya yeniden yazıldı, ihlal 3’ten 0’a indi, ithal kenarı 10’da kaldı; kural kenarları azaltmaz, uçlarını genel yüze taşır.
- Bedel genel yüzün 7’den 11’e genişlemesi ve alanlar arası her ihtiyaçta bir sarmalayıcı eklenmesidir.
- Kural denetleyiciye bağlıdır; tek satırlık bir ithal programı kırmadan ihlali geri getirdi.
Sonraki Adım
Modüler tek parça sınırı kod içinde korudu, ama bir şeyi hiç sorgulamadı: depo.mjs bütün
alanların ortak veri yapısını tutmayı sürdürüyor. Katalog da üyelik de aynı nesnenin farklı
dallarına yazıyor, yani sınır ithal grafiğinde var, veride yok. Sonraki ders bu ortaklığı
büyütür: alanlar ayrı birimler hâline geldiğinde bile paylaşılan bir sözleşme ve ortak bir
veri modeli üzerinden konuşurlarsa ne olur — ortak katmandaki tek bir değişikliğin kaç birimi
dokunulur kıldığını ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.