Ders 01 / 19
Alan Odaklı Tasarımın Gerekçesi
Modelin alanla hizasının bir ölçü hâline getirilmesi: alan uzmanının tek cümlesindeki on iki adın iki modelde kaçının birebir bulunduğunun sayılması, alan dilinde yazılmış üç değişiklik isteğinin her modelde kaç satıra indiğinin ölçülmesi ve iki modelin aynı ücreti verdiğinin gösterilmesiyle hizanın doğruluktan ayrı bir nitelik olduğunun ortaya konması.
İçindekiler
Tasarım Kalıpları kursu, bir kalıbın ancak bir ölçüyü iyileştirdiği ölçüde kalıp olduğunu göstererek kapandı. Otuz dersin hepsi çözümün biçimi üzerineydi: kural nereye yazılır, nesne nasıl yaratılır, sınır nereye çizilir. Hiçbiri kuralın ne olduğunu sormadı. Gönderi, tarife, bölge, rota ve indirim sözcüklerinin ne anlama geldiği, hangilerinin bir arada değiştiği, aynı sözcüğün iki takımda iki farklı şeyi adlandırıp adlandırmadığı o kursun dışında kaldı.
Bu ders o soruyu alır. Alan odaklı tasarım (domain-driven design), bir kod tabanının yapısını alanın kendi ayrımlarından türetmeyi öneren bir yaklaşımdır; buradaki alan, yazılımın hakkında olduğu iş dünyasıdır. Yaklaşımın iddiası bir estetik iddia değildir, dolayısıyla bir ölçüsü olmalıdır. Kurs boyunca ölçülen şey modelin alanla hizasıdır: alanı konuşan insanın kullandığı ayrımların kodda birebir karşılığı var mı, yok mu.
Alan Uzmanının Cümlesi
Ölçünün dayanağı, alanı işi gereği bilen kişinin — alan uzmanının (domain expert) — kuralı söylerken kullandığı sözcüklerdir. Gönderi ücretlendirme kitaplığında bu kişi ücretlendirme uzmanıdır ve kuralı tek cümlede söylüyor:
Bir gönderinin ücreti, ağırlık kademesinin taban ücretiyle bölge katsayısının çarpımıdır; hacim ağırlıktan büyükse hacim esas alınır, indirim oranlarının toplamı tavanı aşamaz, net ücret asgari ücretin altına inemez.
Cümlede on iki ad geçiyor: gönderi, ağırlık, hacim, kademe, taban ücret, bölge, katsayı, posta kodu, indirim, oran, tavan, asgari ücret. Bu on iki ad ölçünün sabit listesidir.
Aynı Kuralın İki Modeli
Kitaplığın iki sürümü var. İkisi de aynı kuralı uyguluyor; ayrım yalnız adlardadır.
mkdir -p teknik alan
// teknik/hesap.mjs — ayni kural, teknik adlarla const T1 = [[1, 3900], [5, 6400], [Infinity, 11800]]; const T2 = { "34": 1, "06": 1.35, "65": 1.8 }; const LIM = 0.4; const MIN = 3990; const DIV = 5000; export function process(d) { const v = Math.max(d.w, d.vol / DIV); const base = T1.find(([u]) => v <= u)[1]; const f = T2[d.z.slice(0, 2)] ?? 1.8; const r = Math.min(d.ds.reduce((t, x) => t + x.r, 0), LIM); return Math.max(Math.round(base * f * (1 - r)), MIN); }
Dosya kısa, dallanması az ve doğru çalışıyor. Temiz Kod kursunun ölçüleriyle bakıldığında
kusuru yalnız adlandırmadır. Alan odaklı tasarımın söylediği şey bunun bir adlandırma
kusuru olmadığıdır: T1 ile T2 arasındaki ayrımın alanda bir karşılığı vardır ve o ayrım
kodda kayıptır.
// alan/ucret.mjs — ayni kural, alan adlariyla const KADEMELER = [[1, 3900], [5, 6400], [Infinity, 11800]]; const BOLGE_KATSAYISI = { "34": 1, "06": 1.35, "65": 1.8 }; const INDIRIM_TAVANI = 0.4; const ASGARI_UCRET = 3990; const HACIM_BOLENI = 5000; const ucretliAgirlik = (gonderi) => Math.max(gonderi.agirlik, gonderi.hacim / HACIM_BOLENI); const tabanUcret = (agirlik) => KADEMELER.find(([ustAgirlik]) => agirlik <= ustAgirlik)[1]; const bolgeKatsayisi = (postaKodu) => BOLGE_KATSAYISI[postaKodu.slice(0, 2)] ?? 1.8; const indirimOrani = (indirimler) => Math.min(indirimler.reduce((t, i) => t + i.oran, 0), INDIRIM_TAVANI); export function netUcret(gonderi) { const ham = tabanUcret(ucretliAgirlik(gonderi)) * bolgeKatsayisi(gonderi.postaKodu); return Math.max(Math.round(ham * (1 - indirimOrani(gonderi.indirimler))), ASGARI_UCRET); }
Hizanın Ölçülmesi
İki ölçü alınır. Birincisi ad hizası: uzmanın cümlesindeki on iki addan kaçı kaynakta bir tanımlayıcının içinde birebir bulunuyor. İkincisi aday satır sayısı: alan dilinde gelen bir değişiklik isteğinin sözcükleri kaç satırda birlikte geçiyor. İkinci ölçü, isteği alan kişinin dosyada kaç satıra bakması gerektiğini verir.
// hiza-olc.mjs — uzman cumlesinin adlari kodda var mi, istek kac satirda gorunuyor import { readFileSync } from "node:fs"; import { process as teknikUcret } from "./teknik/hesap.mjs"; import { netUcret } from "./alan/ucret.mjs"; const ADLAR = ["gonderi", "agirlik", "hacim", "kademe", "tabanucret", "bolge", "katsayi", "postakodu", "indirim", "oran", "tavan", "asgariucret"]; const ISTEKLER = { "indirim tavani inecek": ["indirim", "tavan"], "asgari ucret cikacak": ["asgari", "ucret"], "hacim boleni degisecek": ["hacim", "bolen"], }; const MODEL = { teknik: "teknik/hesap.mjs", alan: "alan/ucret.mjs" }; for (const [model, dosya] of Object.entries(MODEL)) { const metin = readFileSync(dosya, "utf8"); const ham = metin.match(/[A-Za-z_][A-Za-z0-9_]*/g) ?? []; const kimlik = ham.map((s) => s.toLowerCase().replaceAll("_", "")); const bulunan = ADLAR.filter((ad) => kimlik.some((k) => k.includes(ad))); const satir = metin.split("\n").filter((s) => s.trim() !== "" && !s.startsWith("//")); console.log(`${model}: alan adi ${bulunan.length}/${ADLAR.length}, satir ${satir.length}`); console.log(` eksik ad: ${ADLAR.filter((a) => !bulunan.includes(a)).join(" ") || "yok"}`); for (const [istek, sozcuk] of Object.entries(ISTEKLER)) { const aday = satir.filter((s) => sozcuk.every((w) => s.toLowerCase().includes(w))); console.log(` "${istek}" -> aday satir ${aday.length}/${satir.length}`); } } const GONDERILER = [ { agirlik: 0.6, hacim: 2000, postaKodu: "34710", indirimler: [] }, { agirlik: 3, hacim: 24000, postaKodu: "06800", indirimler: [{ ad: "sozlesme", oran: 0.15 }] }, { agirlik: 12, hacim: 40000, postaKodu: "65100", indirimler: [{ ad: "hacim", oran: 0.5 }] }, { agirlik: 0.4, hacim: 9000, postaKodu: "81600", indirimler: [{ ad: "kampanya", oran: 0.1 }] }, ]; const cevir = (g) => ({ w: g.agirlik, vol: g.hacim, z: g.postaKodu, ds: g.indirimler.map((i) => ({ r: i.oran })) }); let ayrilan = 0; for (const g of GONDERILER) if (teknikUcret(cevir(g)) !== netUcret(g)) ayrilan += 1; console.log(`ayrilan sonuc = ${ayrilan} / ${GONDERILER.length}`);
node hiza-olc.mjs
teknik: alan adi 0/12, satir 12 eksik ad: gonderi agirlik hacim kademe tabanucret bolge katsayi postakodu indirim oran tavan asgariucret "indirim tavani inecek" -> aday satir 0/12 "asgari ucret cikacak" -> aday satir 0/12 "hacim boleni degisecek" -> aday satir 0/12 alan: alan adi 12/12, satir 14 eksik ad: yok "indirim tavani inecek" -> aday satir 2/14 "asgari ucret cikacak" -> aday satir 2/14 "hacim boleni degisecek" -> aday satir 2/14 ayrilan sonuc = 0 / 4
İlk sayı hizanın kendisidir: uzmanın on iki adından teknik sürümde 0’ı, alan sürümünde 12’si kodda bulunuyor. İkinci grup bunun bedelini veriyor. Üç değişiklik isteğinin üçünde de teknik sürümde aday satır 0 çıkıyor; aday yoksa isteği alan kişinin on iki satırın tamamını okuması, her adın neyi tuttuğunu çıkarması ve ancak sonra doğru satırı bulması gerekir. Alan sürümünde aynı üç istek 14 satırın 2’sine iniyor.
Hiza Doğruluk Değildir
Çıktının son satırı ölçünün ne olmadığını söylüyor: dört gönderinin dördünde iki sürüm aynı ücreti veriyor. Teknik sürüm yanlış değildir, yavaş da değildir. Hizasızlığın bedeli çalışma zamanında değil, değişiklik zamanında ödenir.
Bedelin ödendiği yer ölçüm betiğinde de görünüyor: cevir işlevi. İki sürümü aynı
gönderilerle çağırabilmek için agirlik alanının w, hacim alanının vol, postaKodu
alanının z olduğunu bilen bir eşleme yazmak gerekti. Bu eşleme kodda dört satırdır ama
kodda olmayan bir yerde de tutulur: isteği alan kişinin kafasında. Alan odaklı tasarımın
temel önerisi bu eşlemeyi ortadan kaldırmaktır — çeviri yapılmasın diye alanla kodun aynı
sözcükleri kullanması.
Ayrım bir adlandırma alışkanlığından fazlasıdır çünkü kayıp yalnız adlarda değil
ayrımlardadır. Teknik sürümde LIM ile MIN iki sayıdır; alanda biri bir oran tavanı,
öteki bir para tutarıdır ve ikisi ayrı kararlarla, ayrı sıklıkta değişir. Tasarım İlkeleri
kursundaki politika–ayrıntı ayrımı bu iki sayının aynı dosyada durmasını değişim sıklığıyla
sorgulamıştı; alan odaklı tasarım aynı soruyu bir adım önce sorar: bu ikisi alanda aynı tür
şey mi?
Karmaşıklık Nerede
Yaklaşımın bir sınırı vardır ve kendi gerekçesinden çıkar. Alanla hizalanmanın kazancı, alanın kendisinin karmaşık olmasına bağlıdır. Kitaplığın ücretlendirme tarafında kademe, bölge, hacim, tavan ve asgari tutar gibi birbirinden bağımsız ayrımlar var; uzmanın cümlesi uzundur ve sık değişir. Bu yüzden on iki adı taşımak kazanç üretir.
Karmaşıklığın alanda değil teknik tarafta olduğu işlerde hesap tersine döner. Aldığı baytları başka bir yere yazan bir bileşende alan sözlüğü iki sözcükten oluşur; oraya bir alan katmanı kurmak, ölçülecek bir ad hizası olmadığı için bedelini karşılamaz. Ayrım ölçütü kod tabanının boyutu da değildir — uzmanın kuralı söylemek için kaç ayrı ada ihtiyaç duyduğudur.
İkinci sınır, alan uzmanına erişimin kendisidir. Ad hizası ölçüsünün girdisi uzmanın cümlesidir; o cümle alınamıyorsa ölçünün sabit listesi de yoktur. Bu durumda kodda görünen adlar alanın değil, kodu yazan kişinin varsayımlarının adları olur.
Özet
- Alan odaklı tasarım, kod tabanının yapısını alanın kendi ayrımlarından türetmeyi önerir; kurs boyunca ölçülen nitelik modelin alanla hizasıdır.
- Ölçünün girdisi alan uzmanının kuralı söylediği cümledir; ücretlendirme kuralının cümlesi on iki ad taşıyor.
- Teknik adlarla yazılmış sürümde bu adların 0’ı, alan adlarıyla yazılmış sürümde 12’si kaynakta birebir bulundu.
- Alan dilinde gelen üç değişiklik isteği teknik sürümde 0 aday satıra, alan sürümünde 14 satırın 2’sine indi; aday yoksa dosyanın tamamı okunmak zorundadır.
- Dört gönderinin dördünde iki sürüm aynı ücreti verdi: hiza bir doğruluk niteliği değil, değişiklik zamanında ödenen bir maliyet kalemidir.
- Kazanç alanın karmaşıklığına bağlıdır; uzmanın kuralı iki adla söylediği işlerde ölçülecek bir hiza yoktur.
Sonraki Adım
Bu derste hiza tek dosyada ölçüldü ve uzmanın on iki adı bir kez yazıldı. Gerçek bir kod tabanında aynı kavram birden çok yerde adlandırılır: veritabanı sütunu bir ad kullanır, uygulama katmanı ikincisini, dışa açılan arayüz üçüncüsünü. O zaman soru adların alandan gelip gelmediği değil, kaç tanesinin aynı şeyi adlandırdığı olur. Sonraki ders kavram başına eş ad sayısını ve iki ad arasında çeviri yapan nokta sayısını sayar, iki eş adın aynı kurala ayrı ayrı tabi tutulmasının ürettiği tutarsızlığı ölçer ve tek sözlüğün bu sayıları nereye indirdiğini gösterir.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.