Ders 14 / 19
Alt Alanlar
Çekirdek, destekleyici ve genel alt alanların ayrılması: alt alan ile alan katmanının farkının kurulması, alt alan başına satır ve değişmez sayısından emek payının çıkarılması, değişiklik günlüğünden değişim payının hesaplanması ve genel alt alan inceltildiğinde emek ile değişim dağılımının hizalanmasının ölçülmesi.
İçindekiler
Önceki dört ders bağlamların sınırını çizdi, aralarındaki ilişkileri adlandırdı ve üç işbirliği biçiminin bedelini saydı. Bu kararların hepsi bir soruyu yanıtsız bıraktı: hangi parçaya ne kadar tasarım emeği verilecek? Ölçü biriminin tanımı ile ücret kuralı aynı özenle mi modellenir? Posta kodu biçim denetimi ile sözleşmeli indirim kuralı aynı incelikle mi yazılır?
Alan kendi içinde eşit değildir. Bir kısmı kurumun kazandığı işin kendisidir, bir kısmı onu ayakta tutar, bir kısmı ise her kurumda aynıdır. Bu üç kısma alt alan (subdomain) denir ve ayrım bir sınıflandırma egzersizi değil, emek dağıtma kararıdır. Bu ders emeği satır ve değişmez sayısıyla, ihtiyacı değişiklik günlüğüyle ölçer ve ikisinin oranını çıkarır.
Alt Alan, Alan Katmanı ve Sınırlı Bağlam
Üç kavram birbirine benzer adlar taşıyor; ayrımı baştan yapmak gerekiyor.
Alan katmanı (Veri Erişim Katmanı ve İş Mantığı kursunda kurulmuştu) kodun düzenlenişindeki bir yerdir: iş kurallarının durduğu, sunum ve altyapıya bakmayan katman. Alt alan kodda değil, işin kendisindedir: kurumun yaptığı işin bir bölümü. Bir alt alanın kodu genellikle birkaç katmana dağılır — ücretlendirmenin kuralı alan katmanında, tarife tablosunun okunması altyapı katmanındadır — ve bir alan katmanı birden çok alt alanın modelini barındırabilir.
Sınırlı bağlam ise çözüm tarafındadır: bir modelin geçerli olduğu alan. Alt alan problemin bölümü, sınırlı bağlam çözümün bölümüdür. İkisinin birebir eşlenmesi iyi bir hedeftir ama zorunlu değildir; bir bağlam iki alt alanın modelini taşıyabilir, bir alt alan iki bağlama yayılabilir. Bu dersin ölçtüğü şey bağlam sınırı değil, alt alan başına harcanan emektir.
Üç Tür
Çekirdek alt alan (core subdomain), kurumun rakiplerinden ayrıldığı yerdir. Ücretlendirme kitaplığında ücret kuralı ile sözleşmeli indirim kuralı budur: kurum işi bu iki kuralla kazanır, ikisi de kurumun kendi kararıdır ve dışarıda karşılığı yoktur.
Destekleyici alt alan (supporting subdomain), iş için gerekli ama fark yaratmayan yerdir. Servis seviyesinden teslim penceresinin türetilmesi böyledir: bulunmazsa iş yürümez, ama kimse onun yüzünden bu kurumu seçmez.
Genel alt alan (generic subdomain), her kurumda aynı olan yerdir. Posta kodu biçiminin doğrulanması ve kuruş aritmetiği böyledir. Ayırt edici ölçüt şudur: bir başkasının çözümü olduğu gibi alınabiliyorsa o parça genel alt alandır.
Ayrımın kararı emek dağıtmaktır: çekirdek alt alana en fazla modelleme emeği verilir, genel alt alana en az. Bu bir tercih değil, ölçülebilir bir hizadır — ve pratikte sık ters döner.
Kalın Modellenmiş Genel Alt Alan
Aşağıdaki iki dosya, genel alt alanın özenle modellenmiş hâlini gösteriyor: posta kodu bir değer nesnesi olarak kurulmuş, beş değişmez taşıyor, eşitlik karşılaştırması ve metne çevirme yöntemleri var. Para da öyle: dört değişmez, para birimi alanı, aritmetik yöntemler.
mkdir -p alan genel-kalin genel-ince
// genel-kalin/posta-kodu.mjs — genel alt alan, kalin modellenmis: posta kodu degeri const IL_ONEKLERI = new Set(["01", "06", "34", "35", "65", "81"]); export class PostaKodu { #metin; constructor(metin) { const ihlal = PostaKodu.dogrula(metin); if (ihlal.length > 0) throw new RangeError(`posta kodu gecersiz: ${ihlal.join(",")}`); this.#metin = String(metin).trim(); } static dogrula(metin) { const ihlal = []; const d = typeof metin === "string" ? metin.trim() : ""; if (d.length === 0) ihlal.push("G1-bos"); if (d.length !== 5) ihlal.push("G2-uzunluk"); if (/\D/.test(d)) ihlal.push("G3-rakam"); if (d.startsWith("00")) ihlal.push("G4-sifir-onek"); if (d.length === 5 && !/\D/.test(d) && !IL_ONEKLERI.has(d.slice(0, 2))) ihlal.push("G5-il"); return ihlal; } get onEk() { return this.#metin.slice(0, 2); } toString() { return this.#metin; } esitMi(oteki) { return oteki instanceof PostaKodu && oteki.toString() === this.#metin; } } export const onEk = (metin) => (PostaKodu.dogrula(metin).length > 0 ? null : new PostaKodu(metin).onEk);
// genel-kalin/para-birimi.mjs — genel alt alan, kalin modellenmis: para degeri export class Para { #kurus; #birim; constructor(kurus, birim = "TRY") { const ihlal = Para.dogrula(kurus, birim); if (ihlal.length > 0) throw new RangeError(`para gecersiz: ${ihlal.join(",")}`); this.#kurus = kurus; this.#birim = birim; } static dogrula(kurus, birim) { const ihlal = []; if (!Number.isInteger(kurus)) ihlal.push("G1-tam-sayi"); if (kurus < 0) ihlal.push("G2-negatif"); if (kurus > 1e12) ihlal.push("G3-tasma"); if (birim !== "TRY") ihlal.push("G4-birim"); return ihlal; } carp(oran) { return new Para(Math.round(this.#kurus * oran), this.#birim); } yuvarla(adim) { return new Para(Math.round(this.#kurus / adim) * adim, this.#birim); } get kurus() { return this.#kurus; } } export const carp = (kurus, oran) => new Para(kurus).carp(oran).kurus; export const yuvarla = (kurus, adim) => new Para(kurus).yuvarla(adim).kurus;
İki dosyanın da kötü yazılmış olduğunu söylemek yanlış olur. İkisi de düzgün değer nesneleridir, değişmezleri açıktır, kimlik yerine değerle karşılaştırılırlar. Sorun kalitede değil, yerdedir.
Çekirdek ve Destekleyici
Çekirdek alt alanın iki dosyası genel alt alanı dışarıdan alıyor: genel adlı bir nesne
üzerinden ön ek çıkarma, çarpma ve yuvarlama işlemlerini çağırıyorlar. Bu, iki genel sürümün
aynı kuralla sınanabilmesini sağlıyor.
// alan/ucret-kurali.mjs — cekirdek alt alan: kurumun kazandigi isin kurali const KADEMELER = [ { ustEsasGram: 1000, ucretKurus: 4990 }, { ustEsasGram: 5000, ucretKurus: 8490 }, { ustEsasGram: 30000, ucretKurus: 24990 }, ]; const BOLGE_CARPANI = { "01": 1.35, "06": 1.35, "34": 1, "35": 1.35, "65": 1.8, "81": 1.8 }; const ASGARI_KURUS = 3990; export function dogrula(gonderi) { const ihlal = []; if (!(Number.isInteger(gonderi.esasGram) && gonderi.esasGram > 0)) ihlal.push("C1-esas-agirlik"); if (gonderi.esasGram > 30000) ihlal.push("C2-kademe-disi"); if (typeof gonderi.postaKodu !== "string") ihlal.push("C3-adres"); return ihlal; } export function ucretHesapla(gonderi, genel) { if (dogrula(gonderi).length > 0) return null; const onEk = genel.onEk(gonderi.postaKodu); if (onEk === null) return null; const kademe = KADEMELER.find((k) => gonderi.esasGram <= k.ustEsasGram); const ham = genel.carp(kademe.ucretKurus, BOLGE_CARPANI[onEk]); return Math.max(genel.yuvarla(ham, 50), ASGARI_KURUS); }
// alan/indirim-kurali.mjs — cekirdek alt alan: sozlesmeli indirim kurali const HACIM_KADEMELERI = [ { aylikGonderi: 500, oran: 0.05 }, { aylikGonderi: 2000, oran: 0.12 }, { aylikGonderi: Infinity, oran: 0.2 }, ]; const UST_SINIR = 0.3; export function dogrula(sozlesme) { const ihlal = []; if (!Number.isInteger(sozlesme.aylikGonderi) || sozlesme.aylikGonderi < 0) ihlal.push("C1-hacim"); if (!(sozlesme.pazarlikOrani >= 0 && sozlesme.pazarlikOrani <= UST_SINIR)) ihlal.push("C2-pazarlik"); if (sozlesme.pazarlikOrani > 0 && sozlesme.sozlesmeNo === null) ihlal.push("C3-sozlesmesiz-pazarlik"); return ihlal; } export function indirimliUcret(brutKurus, sozlesme, genel) { if (dogrula(sozlesme).length > 0) return null; const kademe = HACIM_KADEMELERI.find((k) => sozlesme.aylikGonderi <= k.aylikGonderi); const oran = Math.min(kademe.oran + sozlesme.pazarlikOrani, UST_SINIR); return genel.carp(brutKurus, 1 - oran); }
// alan/teslim-penceresi.mjs — destekleyici alt alan: servis seviyesinden teslim penceresi const PENCERE_SAAT = { SS1: 24, SS2: 48, SS3: 72 }; export function dogrula(servisSeviyeKodu) { const ihlal = []; if (typeof servisSeviyeKodu !== "string") ihlal.push("D1-tip"); if (PENCERE_SAAT[servisSeviyeKodu] === undefined) ihlal.push("D2-taninmayan-seviye"); return ihlal; } export const pencereSaat = (servisSeviyeKodu) => (dogrula(servisSeviyeKodu).length > 0 ? 72 : PENCERE_SAAT[servisSeviyeKodu]);
Genel Alt Alanın İnceltilmesi
İnce sürüm genel alt alandan yalnız çekirdeğin gerçekten ihtiyaç duyduğu kadarını bırakıyor: ön ek çıkarma ve iki aritmetik işlem. Değer nesnesi, eşitlik karşılaştırması ve para birimi alanı yok; çünkü kitaplık bunları hiç kullanmıyordu.
// genel-ince/posta-kodu.mjs — genel alt alan, ince: yalniz alanin ihtiyaci kadar const IL_ONEKLERI = new Set(["01", "06", "34", "35", "65", "81"]); export function onEk(metin) { const d = typeof metin === "string" ? metin.trim() : ""; if (!/^\d{5}$/.test(d) || d.startsWith("00") || !IL_ONEKLERI.has(d.slice(0, 2))) return null; return d.slice(0, 2); }
// genel-ince/para-birimi.mjs — genel alt alan, ince: yalniz alanin ihtiyaci kadar const gecerli = (kurus) => Number.isInteger(kurus) && kurus >= 0 && kurus <= 1e12; export const carp = (kurus, oran) => { if (!gecerli(kurus)) throw new RangeError("para gecersiz"); return Math.round(kurus * oran); }; export const yuvarla = (kurus, adim) => Math.round(kurus / adim) * adim;
Emeğin ve Değişimin Dağılımı
Emek iki göstergeyle ölçülüyor: alt alan başına satır sayısı ve tanımlanmış değişmez sayısı. İhtiyaç, yirmi model değişikliğinin alt alanlara dağılımıyla ölçülüyor. Üçüncü sayı ikisinin oranıdır ve biri kesin bir eşiktir: emek payı değişim payına eşitse oran 1’dir.
// emek-say.mjs — alt alan basina emek payi, degisim payi ve ikisinin orani import { readFileSync } from "node:fs"; const ALT_ALAN = { "alan/ucret-kurali.mjs": "cekirdek", "alan/indirim-kurali.mjs": "cekirdek", "alan/teslim-penceresi.mjs": "destekleyici", "genel-kalin/posta-kodu.mjs": "genel", "genel-kalin/para-birimi.mjs": "genel", "genel-ince/posta-kodu.mjs": "genel", "genel-ince/para-birimi.mjs": "genel", }; const GUNLUK = [ ["ucret-kurali", 6], ["indirim-kurali", 5], ["teslim-penceresi", 4], ["posta-kodu", 3], ["para-birimi", 2], ]; const KAVRAM_ALT_ALAN = { "ucret-kurali": "cekirdek", "indirim-kurali": "cekirdek", "teslim-penceresi": "destekleyici", "posta-kodu": "genel", "para-birimi": "genel" }; const TOPLAM_DEGISIKLIK = GUNLUK.reduce((t, [, n]) => t + n, 0); const TURLER = ["cekirdek", "destekleyici", "genel"]; const yuzde = (a, b) => `${((a / b) * 100).toFixed(1).padStart(5)}%`; for (const [ad, dosyalar] of [ ["kalin genel", ["alan/ucret-kurali.mjs", "alan/indirim-kurali.mjs", "alan/teslim-penceresi.mjs", "genel-kalin/posta-kodu.mjs", "genel-kalin/para-birimi.mjs"]], ["ince genel", ["alan/ucret-kurali.mjs", "alan/indirim-kurali.mjs", "alan/teslim-penceresi.mjs", "genel-ince/posta-kodu.mjs", "genel-ince/para-birimi.mjs"]], ]) { const satir = {}, degismez = {}; for (const t of TURLER) { satir[t] = 0; degismez[t] = 0; } for (const d of dosyalar) { const metin = readFileSync(d, "utf8"); const t = ALT_ALAN[d]; satir[t] += metin.trimEnd().split("\n").length; degismez[t] += (metin.match(/ihlal\.push/g) ?? []).length; } const toplamSatir = TURLER.reduce((s, t) => s + satir[t], 0); const toplamDegismez = TURLER.reduce((s, t) => s + degismez[t], 0); console.log(`${ad}: ${toplamSatir} satir, ${toplamDegismez} degismez, ${TOPLAM_DEGISIKLIK} degisiklik`); for (const t of TURLER) { const dgs = GUNLUK.filter(([k]) => KAVRAM_ALT_ALAN[k] === t).reduce((s, [, n]) => s + n, 0); const emekPayi = satir[t] / toplamSatir, degisimPayi = dgs / TOPLAM_DEGISIKLIK; console.log(` ${t.padEnd(12)} satir ${String(satir[t]).padStart(3)} (${yuzde(satir[t], toplamSatir)})` + ` degismez ${String(degismez[t]).padStart(2)} (${yuzde(degismez[t], toplamDegismez)})` + ` degisim ${String(dgs).padStart(2)} (${yuzde(dgs, TOPLAM_DEGISIKLIK)})` + ` emek/degisim ${(emekPayi / degisimPayi).toFixed(2)}`); } }
node emek-say.mjs
kalin genel: 108 satir, 17 degismez, 20 degisiklik cekirdek satir 47 ( 43.5%) degismez 6 ( 35.3%) degisim 11 ( 55.0%) emek/degisim 0.79 destekleyici satir 12 ( 11.1%) degismez 2 ( 11.8%) degisim 4 ( 20.0%) emek/degisim 0.56 genel satir 49 ( 45.4%) degismez 9 ( 52.9%) degisim 5 ( 25.0%) emek/degisim 1.81 ince genel: 75 satir, 8 degismez, 20 degisiklik cekirdek satir 47 ( 62.7%) degismez 6 ( 75.0%) degisim 11 ( 55.0%) emek/degisim 1.14 destekleyici satir 12 ( 16.0%) degismez 2 ( 25.0%) degisim 4 ( 20.0%) emek/degisim 0.80 genel satir 16 ( 21.3%) degismez 0 ( 0.0%) degisim 5 ( 25.0%) emek/degisim 0.85
Kalın düzenin ilk satırı hizasızlığı veriyor: kodun yüzde 45,4’ü genel alt alanda, oysa değişikliklerin yalnız yüzde 25’i oraya dokunuyor. Çekirdek alt alan tersinden aynı durumda: değişikliklerin yüzde 55’ini alıyor, kodun yüzde 43,5’ünü tutuyor. Oranlar 1,81 ve 0,79; aradaki fark iki kattan fazla.
Değişmez sayısı daha keskin bir hizasızlık gösteriyor. Genel alt alan dokuz değişmez tanımlamış, çekirdek alt alan altı. Kurumun işi kazandığı iki kural için altı değişmez yazılırken, her kurumda aynı olan posta kodu ve para için dokuz değişmez yazılmış durumda.
İnce düzende üç oran 1,14 – 0,80 – 0,85 aralığına giriyor; genel alt alanın satır payı yüzde 45,4’ten yüzde 21,3’e düşüyor. Genel alt alanın tanımladığı alan değişmezi kalmıyor: ince sürümdeki iki denetim birer koruma cümlesidir, alanın kuralı değil. Toplam kod 108 satırdan 75 satıra iniyor ve boşalan emek çekirdeğe kalıyor.
Çekirdek Değişmedi mi
İnceltmenin çekirdeği bozmadığı iddia edilmez, sınanır. Aynı çekirdek kuralları iki genel sürümle de koşuyor.
// kosum.mjs — genel alt alan incelestirilince cekirdegin sonuclari degisti mi import { ucretHesapla } from "./alan/ucret-kurali.mjs"; import { indirimliUcret } from "./alan/indirim-kurali.mjs"; import * as kalinPosta from "./genel-kalin/posta-kodu.mjs"; import * as kalinPara from "./genel-kalin/para-birimi.mjs"; import * as incePosta from "./genel-ince/posta-kodu.mjs"; import * as incePara from "./genel-ince/para-birimi.mjs"; const KALIN = { onEk: kalinPosta.onEk, carp: kalinPara.carp, yuvarla: kalinPara.yuvarla }; const INCE = { onEk: incePosta.onEk, carp: incePara.carp, yuvarla: incePara.yuvarla }; const GONDERILER = [ { esasGram: 800, postaKodu: "34710" }, { esasGram: 4000, postaKodu: "06800" }, { esasGram: 20000, postaKodu: "65100" }, { esasGram: 20000, postaKodu: "99999" }, { esasGram: 0, postaKodu: "34710" }, { esasGram: 40000, postaKodu: "34710" }, ]; const SOZLESMELER = [ { aylikGonderi: 1500, pazarlikOrani: 0.05, sozlesmeNo: "S-7" }, { aylikGonderi: 100, pazarlikOrani: 0, sozlesmeNo: null }, { aylikGonderi: 100, pazarlikOrani: 0.1, sozlesmeNo: null }, ]; let ayrilan = 0; for (const g of GONDERILER) { const a = ucretHesapla(g, KALIN), b = ucretHesapla(g, INCE); if (a !== b) ayrilan += 1; console.log(`${String(g.esasGram).padStart(5)} g ${g.postaKodu} kalin ${String(a).padStart(5)} ince ${String(b).padStart(5)}`); } for (const s of SOZLESMELER) { const a = indirimliUcret(45000, s, KALIN), b = indirimliUcret(45000, s, INCE); if (a !== b) ayrilan += 1; console.log(`hacim ${String(s.aylikGonderi).padStart(4)} pazarlik ${s.pazarlikOrani} kalin ${String(a).padStart(5)} ince ${String(b).padStart(5)}`); } console.log(`ayrilan sonuc = ${ayrilan} / ${GONDERILER.length + SOZLESMELER.length}`);
node kosum.mjs
800 g 34710 kalin 5000 ince 5000
4000 g 06800 kalin 11450 ince 11450
20000 g 65100 kalin 45000 ince 45000
20000 g 99999 kalin null ince null
0 g 34710 kalin null ince null
40000 g 34710 kalin null ince null
hacim 1500 pazarlik 0.05 kalin 37350 ince 37350
hacim 100 pazarlik 0 kalin 42750 ince 42750
hacim 100 pazarlik 0.1 kalin null ince null
ayrilan sonuc = 0 / 9
Dokuz durumda da sonuç aynı: geçerli üç ücret, tanınmayan il ön eki, sıfır ağırlık, kademe dışı ağırlık, iki indirim hesabı ve sözleşmesiz pazarlık denemesi dâhil. Kalın sürümün beş değişmezinin dördü hiçbir çağrıda karşılığını vermedi, çünkü çekirdek onları hiç sınamıyordu.
Ayrımın Sınırı
Alt alan türü sabit bir etiket değildir. Bir kurum için genel olan, başka bir kurum için çekirdek olabilir; adres çözümleme, teslimat işini adres kalitesiyle kazanan bir kurumda genel alt alan değildir. Ayrım, alanın kendisinden değil kurumun rekabet ettiği yerden okunur ve zamanla değişir.
İkinci sınır ölçünün kendisindedir. Satır sayısı emeğin kaba bir göstergesidir; bir kuralın zorluğu satır sayısıyla artmaz. Bu yüzden ölçü tek başına bir karar vermez, bir soru üretir: neden en çok değişen parça en az modelleme taşıyor?
Özet
- Alt alan, işin bir bölümüdür; alan katmanı kodun düzenlenişindeki bir yer, sınırlı bağlam ise bir modelin geçerlilik alanıdır. Bir alt alanın kodu birkaç katmana dağılır.
- Üç tür ayırt edilir: çekirdek alt alan kurumun işi kazandığı yer, destekleyici alt alan gerekli ama fark yaratmayan yer, genel alt alan başkasının çözümü olduğu gibi alınabilen yerdir.
- Kalın düzende kodun yüzde 45,4’ü genel alt alandaydı ama değişikliklerin yüzde 25’i oraya dokunuyordu; çekirdek alt alan değişikliklerin yüzde 55’ini alıp kodun yüzde 43,5’ini tutuyordu. Oranlar 1,81 ile 0,79.
- Değişmez sayımı hizasızlığı doğruladı: genel alt alan dokuz, çekirdek alt alan altı değişmez tanımlamıştı.
- Genel alt alan inceltildiğinde üç oran 1,14 – 0,80 – 0,85 aralığına girdi, toplam kod 108 satırdan 75 satıra indi ve dokuz durumda çekirdeğin sonuçları değişmedi.
- Alt alan türü kuruma göredir ve zamanla değişir; satır sayısı emeğin kaba göstergesidir, ölçü karar vermez, soru üretir.
Sonraki Adım
Bu konu modelin nerede geçerli olduğunu kurdu: sınır bir adın anlamının değiştiği yerden geçiyor, bağlamlar arası ilişkiler yön ve türle adlandırılıyor, dış modelin sızması bir katmanla durduruluyor, alt alan ayrımı hangi parçaya ne kadar emek verileceğini söylüyor. Açıkta kalan soru bir bağlamın içindedir: o bağlamdaki model bir uygulamanın içine nasıl yerleşir? Bir istek nereden girer, hangi sırayla hangi nesneye iner, işlemin sınırını kim çizer, sonuç kime döner? Buraya kadarki bütün örneklerde bu düzen örtük kaldı; kurallar doğrudan çağrıldı. Sonraki konu uygulama katmanının sorumluluğunu tanımlar ve ilk ders kullanım senaryolarını bu katmanın birimi olarak alır: alan modelini çağıran, işlemi başlatıp bitiren, ama iş kuralını kendisi taşımayan ince bir kabuk.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.