Ders 05 / 19
Toplamlar ve Kök
Tutarlılık sınırının çizilmesi: indirim tavanı değişmezinin kaç ayrı çağrı yolundan bozulabildiğinin beş yol üzerinde sayılması, kökten geçen erişimde bu sayının sıfıra inmesi, bir işlemde dokunulan toplama sayısının ölçülmesi ve toplamalar arası bağın kimlik atfına çevrilmesinin bedeli.
İçindekiler
Buraya kadar gönderi tek başına ele alındı. Alanda tek başına değildir: indirimleri, sözleşmesi ve beyan kalemleri vardır ve aralarında bir kural durur — indirim oranlarının toplamı tavanı aşamaz. Bu kural gönderiye de indirimlere de tek başına ait değildir; gönderiyle indirimlerinin bütününe aittir.
Böyle bir bütüne toplama (aggregate), o bütüne dışarıdan erişimin geçtiği tek nesneye toplama kökü (aggregate root) denir. Toplama kavramı Tasarım Kalıpları kursundaki Depo ve İş Birimi dersinde erişim ve yazma birimi olarak kurulmuştu. Bu dersin sorusu farklıdır: sınır tutarlılık ölçütüne göre nereye çizilir — hangi değişmez sınırın içindedir ve sınır korunuyor mu.
Değişmez Kimin İçinde
Ölçüt tek cümlededir: bir değişmez, doğruluğunu denetlemek için gerekli bütün verinin bulunduğu en küçük sınırın içine konur. İndirim tavanı için gereken veri bir gönderinin indirim listesidir; başka gönderiye, başka sözleşmeye bakmak gerekmez. Sınır bu yüzden gönderi ile indirimlerini kapsar, sözleşmeyi kapsamaz.
Sözleşmenin kendi değişmezi vardır: taahhüt edilen hacim aşılamaz. O kuralın girdisi sözleşmeye bağlı bütün gönderilerin sayısıdır, dolayısıyla ikinci bir sınır doğar. İki sınır arasındaki bağ nesne başvurusu değil kimlik atfıdır: gönderi sözleşme numarasını taşır, sözleşme nesnesini taşımaz.
Sınırı Dışa Açan Model
Birinci sürümde indirim listesi doğrudan erişilebilir ve tavan kuralı yalnız bir yardımcı işlevin içindedir.
mkdir -p acik kok
// acik/gonderi.mjs — indirimler disa acik; tavan kurali yalniz yardimci islevde export const INDIRIM_TAVANI = 0.4; export const gonderi = (gonderiNo, sozlesmeNo) => ({ gonderiNo, sozlesmeNo, indirimler: [] }); export const toplamOran = (g) => g.indirimler.reduce((t, i) => t + i.oran, 0); export function indirimEkle(g, indirim) { if (toplamOran(g) + indirim.oran > INDIRIM_TAVANI) throw new RangeError("tavan asildi"); g.indirimler.push(indirim); return g; }
// acik/senaryo.mjs — sozlesmeli indirim: iki toplama ayni islemde yazilir import { indirimEkle } from "./gonderi.mjs"; export function sozlesmeliIndirimUygula(g, sozlesme, oran, yaz) { indirimEkle(g, { ad: "sozlesme", oran }); sozlesme.kullanilanHacim += 1; yaz("gonderi", g.gonderiNo); yaz("sozlesme", sozlesme.sozlesmeNo); }
Kökten Geçen Model
İkinci sürümde indirim listesi özel bir alandır. Dışa verilen liste bir kopyadır ve ögeleri dondurulmuştur; ekleme yalnız kökün yönteminden geçer.
// kok/gonderi.mjs — toplama koku: indirimlere yalniz kokten ulasilir const INDIRIM_TAVANI = 0.4; export class Gonderi { #indirimler = []; constructor(gonderiNo, sozlesmeNo) { this.gonderiNo = gonderiNo; this.sozlesmeNo = sozlesmeNo; } toplamOran() { return this.#indirimler.reduce((t, i) => t + i.oran, 0); } indirimler() { return this.#indirimler.map((i) => Object.freeze({ ...i })); } indirimEkle(indirim) { if (this.toplamOran() + indirim.oran > INDIRIM_TAVANI) throw new RangeError("tavan asildi"); this.#indirimler.push(Object.freeze({ ...indirim })); return this; } }
// kok/senaryo.mjs — her islem tek toplama yazar; sozlesmeye kimlikle atif yapilir export function sozlesmeliIndirimUygula(gonderi, oran, yaz) { gonderi.indirimEkle({ ad: "sozlesme", oran }); yaz("gonderi", gonderi.gonderiNo); } export function sozlesmeKullanimiIsle(sozlesme, yaz) { sozlesme.kullanilanHacim += 1; yaz("sozlesme", sozlesme.sozlesmeNo); }
Ölçüm
İki ölçü alınır. Birincisi ulaşılabilirlik: 0,30 oranlı bir indirimi olan gönderide tavanı aşan bir durum beş ayrı çağrı yolundan kaçında üretilebiliyor. İkincisi işlem başına toplama: bir senaryo kaç ayrı toplamayı yazıyor.
// sinir-olc.mjs — degismez kac yoldan bozulabiliyor, bir islemde kac toplama yaziliyor import * as acik from "./acik/gonderi.mjs"; import { Gonderi } from "./kok/gonderi.mjs"; import { sozlesmeliIndirimUygula as acikSenaryo } from "./acik/senaryo.mjs"; import * as kokSenaryo from "./kok/senaryo.mjs"; const TAVAN = 0.4; const EK = { ad: "hacim", oran: 0.5 }; const YENI = [{ ad: "hacim", oran: 0.9 }]; const ACIK_YOLLAR = { "alan yontemi": (g) => acik.indirimEkle(g, EK), "diziye ekleme": (g) => { g.indirimler.push({ ...EK }); }, "ogenin orani": (g) => { g.indirimler[0].oran = 0.5; }, "dizinin degistirilmesi": (g) => { g.indirimler = YENI.map((i) => ({ ...i })); }, "toplu atama": (g) => { Object.assign(g, { indirimler: YENI.map((i) => ({ ...i })) }); }, }; const KOK_YOLLAR = { "alan yontemi": (g) => g.indirimEkle(EK), "diziye ekleme": (g) => { g.indirimler().push({ ...EK }); }, "ogenin orani": (g) => { g.indirimler()[0].oran = 0.5; }, "dizinin degistirilmesi": (g) => { g.indirimler = YENI.map((i) => ({ ...i })); }, "toplu atama": (g) => { Object.assign(g, { indirimler: YENI.map((i) => ({ ...i })) }); }, }; function tara(ad, kur, oran, yollar) { let ihlal = 0; let hata = 0; for (const [yol, dene] of Object.entries(yollar)) { const g = kur(); try { dene(g); } catch { hata += 1; } const bozuk = oran(g) > TAVAN; if (bozuk) ihlal += 1; console.log(` ${yol.padEnd(24)} toplam oran ${oran(g).toFixed(2)} ` + `${bozuk ? "IHLAL" : "korundu"}`); } console.log(`${ad}: 5 yoldan ${ihlal} tanesi degismezi bozdu, ${hata} tanesi hata verdi`); } console.log("acik model"); tara("acik", () => acik.indirimEkle(acik.gonderi("G-2001", "S-77"), { ad: "sozlesme", oran: 0.3 }), acik.toplamOran, ACIK_YOLLAR); console.log("kok modeli"); tara("kok", () => new Gonderi("G-2001", "S-77").indirimEkle({ ad: "sozlesme", oran: 0.3 }), (g) => g.toplamOran(), KOK_YOLLAR); function islemOlc(ad, kos) { const yazilan = []; kos((tur) => yazilan.push(tur)); const tekil = new Set(yazilan); console.log(`${ad}: yazma cagrisi ${yazilan.length}, dokunulan toplama ${tekil.size} ` + `[${[...tekil].join(" ")}]`); } islemOlc("acik islem", (yaz) => { const g = acik.gonderi("G-2002", "S-77"); acikSenaryo(g, { sozlesmeNo: "S-77", kullanilanHacim: 4 }, 0.25, yaz); }); islemOlc("kok islemi", (yaz) => { const g = new Gonderi("G-2002", "S-77"); kokSenaryo.sozlesmeliIndirimUygula(g, 0.25, yaz); }); islemOlc("kok ikinci islemi", (yaz) => { kokSenaryo.sozlesmeKullanimiIsle({ sozlesmeNo: "S-77", kullanilanHacim: 4 }, yaz); });
node sinir-olc.mjs
acik model alan yontemi toplam oran 0.30 korundu diziye ekleme toplam oran 0.80 IHLAL ogenin orani toplam oran 0.50 IHLAL dizinin degistirilmesi toplam oran 0.90 IHLAL toplu atama toplam oran 0.90 IHLAL acik: 5 yoldan 4 tanesi degismezi bozdu, 1 tanesi hata verdi kok modeli alan yontemi toplam oran 0.30 korundu diziye ekleme toplam oran 0.30 korundu ogenin orani toplam oran 0.30 korundu dizinin degistirilmesi toplam oran 0.30 korundu toplu atama toplam oran 0.30 korundu kok: 5 yoldan 0 tanesi degismezi bozdu, 2 tanesi hata verdi acik islem: yazma cagrisi 2, dokunulan toplama 2 [gonderi sozlesme] kok islemi: yazma cagrisi 1, dokunulan toplama 1 [gonderi] kok ikinci islemi: yazma cagrisi 1, dokunulan toplama 1 [sozlesme]
Açık modelde beş yolun dördü tavanı aşan bir gönderi üretiyor. Kuralın yazılı olduğu tek yol — yardımcı işlev — hata veriyor; kalan dördü listeye doğrudan dokunuyor ve kuralı hiç görmüyor. Kök modelinde aynı beş denemenin sıfırı değişmezi bozuyor: iki deneme hata veriyor, ikisi kopya üzerinde çalıştığı için etkisiz kalıyor, biri kökün özel alanına erişemiyor. Değişmezin korunması bir dikkat sorusu olmaktan çıkıp yapının verdiği bir güvence oluyor.
Son üç satır sınırın genişliğini veriyor. Açık modelde tek bir işlem iki toplamayı yazıyor: gönderi ile sözleşme birlikte kesinleşiyor. Kök modelinde aynı iş iki işleme bölünüyor ve her biri tek toplamaya dokunuyor.
Sınırın Bedeli
İşlem başına tek toplama kuralının bedeli açıktır: sözleşmenin kullanılan hacmi, gönderiye indirim eklendiği anda güncellenmiş olmaz. İki toplama arasındaki tutarlılık anlık değildir; ikinci işlem çalışana kadar sözleşme eski sayıyı taşır.
Bu, işlem sınırı seçiminden ayrı bir karardır. İşlem sınırının nereye çizileceği ve iki yazmanın birlikte kesinleşmesi Veri Erişim Katmanı ve İş Mantığı kursundaki İşlem Sınırları dersinde ölçülmüştü. Tutarlılık sınırı ondan önce gelir: hangi kuralın anlık korunması gerektiğini söyler. İndirim tavanı anlık korunmalıdır, çünkü tavanı aşan bir gönderi bir an için bile var olmamalıdır. Sözleşme hacminin sayımı anlık korunmak zorunda değildir, çünkü alan bunu bir mutabakat sorusu olarak görür.
Ölçü bu yüzden iki yönlüdür. Sınır genişletilirse işlem başına toplama sayısı düşer ama kilitlenen veri büyür; daraltılırsa yazma tek toplamaya iner ama anlık korunmayan kural sayısı artar. Seçim, hangi kuralın bir an için bile bozulamayacağı sorusunun yanıtıdır.
Kökün Kuralları
Ölçümdeki güvenceyi üreten üç kısıt vardır. Sınırın içindeki nesnelere dışarıdan başvuru verilmez; verilecekse kopya verilir. Sınır dışındaki bir toplamaya kimlikle atıf yapılır, nesneyle değil. Sınırın içini değiştiren her işlem kökün bir yönteminden geçer.
Üçüncü kısıt yukarıdaki ölçümde doğrudan görünüyor: indirimler() bir kopya döndürdüğü için
“diziye ekleme” yolu etkisiz kalıyor, ögeleri dondurulmuş olduğu için “ögenin oranı” yolu hata
veriyor. Kopya olmasa iki yol da değişmezi bozardı.
Ne Zaman Uygulanmaz
Kökten geçen erişimin bedeli, sınırın içine bakmak isteyen her okumanın kökten geçmesi ve her okumada kopya üretilmesidir. Değişmezi olmayan bir bütünde bu bedel karşılıksızdır: yalnız birlikte okunan, hiçbir kuralı paylaşmayan nesneler için sınır çizmek bir dolaylılık basamağından fazlasını üretmez.
İkinci durum sınırın büyüklüğüyle ilgilidir. Bir toplama, içindeki nesne sayısı büyüdükçe pahalanır: her yazma bütünü yükler, her okuma kopya üretir. Sözleşmeye bağlı bütün gönderileri tek toplamaya koymak indirim tavanını da sözleşme hacmini de anlık korur, karşılığında aynı sözleşmedeki iki gönderinin aynı anda işlenmesini engeller. Sınırın doğru yeri, anlık korunması gereken kural kümesinin en küçük kapsayıcısıdır.
Özet
- Toplama, bir değişmezi paylaşan nesnelerin bütünüdür; toplama kökü o bütüne dışarıdan erişimin geçtiği tek nesnedir.
- Sınır ölçütü tutarlılıktır: bir değişmez, denetimi için gereken bütün verinin bulunduğu en küçük sınırın içine konur. İndirim tavanı gönderi ile indirimlerini kapsar, sözleşmeyi kapsamaz.
- Sınırı dışa açan modelde beş çağrı yolunun 4’ü tavanı aşan bir gönderi üretti; kökten geçen modelde 0’ı üretti.
- Açık modelde bir işlem 2 toplamaya dokundu; kök modelinde aynı iş iki işleme bölündü ve her biri 1 toplamaya dokundu.
- Bedel anlık olmayan tutarlılıktır: sözleşmenin hacim sayımı, gönderi yazıldığı anda güncellenmiş olmaz.
- Güvenceyi üç kısıt üretir — içeriye başvuru verilmez, sınır dışına kimlikle atıf yapılır, değişiklik kökten geçer.
Sonraki Adım
Kökün yöntemleri kendi sınırının içindeki veriyle karar veriyor. Kitaplıkta bir de bu kalıba uymayan bir davranış var: bir gönderiye uygulanacak en uygun tarifenin seçilmesi. Bu karar gönderiyi, tarife kataloğunu ve müşterinin sözleşmesini birlikte gerektirir; üçünden hiçbiri ötekilerin sahibi değildir. Kararı gönderiye yüklemek, gönderinin bilmek zorunda olduğu dış ad sayısını artırır. Sonraki ders bu sayıyı ölçer, davranışın nesneye mi yoksa ayrı bir alan servisine mi ait olduğunu ortak çalışan sayısıyla belirler ve alan servisini uygulama servisinden ayıran ölçüyü kurar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.