Ders 08 / 19
Alan Olayları
Anlamlı iş olaylarının açık modeli: aynı altmış olgudan üretilen iki kütükte alan uzmanının üç sorusunun kaçının tek süzmeyle doğru yanıtlandığının ölçülmesi, yanıt için kaç olayın alanlarının karşılaştırılmak zorunda kaldığının sayılması, olay adlarının alan sözlüğünde bulunma oranının ve kütük uzunluğunun karşılaştırılması.
İçindekiler
Buraya kadarki bütün ölçüler modelin durumu üzerineydi: hangi ad hangi kavramı gösteriyor, hangi değişmez hangi sınırın içinde, nesne hangi yolla kuruluyor. Alanda bir de olup bitenler var — bir gönderi fiyatlandı, sözleşme indirimi kaldırıldı, tarife değiştiği için tutar yeniden hesaplandı. Bu olguların kodda kendi adları olabilir ya da hepsi “kayıt güncellendi” diye geçebilir.
Alanda olup biten, geçmiş zamanda anlatılan ve alan dilinde bir adı olan olguya alan olayı (domain event) denir. Olayın yayımlanması, dinleyicilere ulaştırılması ve işlemle birlikte kesinleşmesi Veri Erişim Katmanı ve İş Mantığı kursundaki Alan Olayları dersinde kurulmuş ve ölçülmüştü. Bu ders o tarafa girmez. Tek soruyu sorar: olayın adı nereden geliyor?
Ölçünün Kurulumu
Ölçü için bir günün ücretlendirme geçmişi altmış olgu olarak yazılır. Her olgunun bir türü, bir gönderi numarası ve öncesi–sonrası görüntüsü vardır. İki kütük de bu tek kaynaktan üretilir; aradaki fark yalnız neyin saklandığıdır.
mkdir -p genel alan
// olgular.mjs — bir gunun ucretlendirme gecmisi: 60 olgu, iki kutugun de kaynagi const OLGU = ["gonderi-fiyatlandi", "sozlesme-indirimi-kaldirildi", "hacim-indirimi-eklendi", "tartim-duzeltmesi-uygulandi", "tarife-degistigi-icin-yeniden-fiyatlandi", "asgari-ucret-uygulandi"]; export const ASGARI_UCRET = 3990; export const OLGULAR = []; for (let i = 0; i < 60; i += 1) { const olgu = OLGU[i % 6]; const gonderiNo = `G-5${101 + (i % 20)}`; const t = 4200 + (i % 5) * 900; let oncesi; let sonrasi; if (olgu === "gonderi-fiyatlandi") { oncesi = { tutar: 0, tarife: null, indirimler: [] }; sonrasi = { tutar: t, tarife: "standart", indirimler: [] }; } else if (olgu === "sozlesme-indirimi-kaldirildi") { oncesi = { tutar: t, tarife: "standart", indirimler: ["sozlesme", "hacim"] }; sonrasi = { tutar: t + 600, tarife: "standart", indirimler: ["hacim"] }; } else if (olgu === "hacim-indirimi-eklendi") { oncesi = { tutar: t, tarife: "standart", indirimler: ["sozlesme"] }; sonrasi = { tutar: t - 400, tarife: "standart", indirimler: ["sozlesme", "hacim"] }; } else if (olgu === "tartim-duzeltmesi-uygulandi") { oncesi = { tutar: t, tarife: "standart", indirimler: ["hacim"] }; sonrasi = { tutar: i % 4 === 3 ? ASGARI_UCRET : t + 250, tarife: "standart", indirimler: ["hacim"] }; } else if (olgu === "tarife-degistigi-icin-yeniden-fiyatlandi") { oncesi = { tutar: t, tarife: "standart", indirimler: ["hacim"] }; sonrasi = { tutar: t - 700, tarife: "bolgesel", indirimler: ["hacim"] }; } else { oncesi = { tutar: t, tarife: "bolgesel", indirimler: ["hacim"] }; sonrasi = { tutar: ASGARI_UCRET, tarife: "bolgesel", indirimler: ["hacim"] }; } OLGULAR.push({ sira: i + 1, olgu, gonderiNo, oncesi, sonrasi }); }
Birinci kütük olayın adını iki genel addan birine indirir ve olup biteni önce–sonra görüntüsüyle anlatır. Bu, kalıcılık tarafından bakıldığında en doğal seçimdir: her yazma bir güncellemedir.
// genel/kutuk.mjs — her olgu ayni iki addan birini alir, yuk oncesi/sonrasi goruntusudur import { OLGULAR } from "../olgular.mjs"; export const KUTUK = OLGULAR.map((o) => ({ tur: o.oncesi.tarife === null ? "GonderiOlusturuldu" : "GonderiGuncellendi", gonderiNo: o.gonderiNo, oncesi: o.oncesi, sonrasi: o.sonrasi, }));
İkinci kütük olayın adını alan dilinden alır. Görüntüye ihtiyaç kalmaz: ad neyin olduğunu söyler, yük yalnız o adın gerektirdiği kadardır.
// alan/kutuk.mjs — olayin adi alan dilinden gelir, yuk o adin gerektirdigi kadardir import { OLGULAR } from "../olgular.mjs"; const AD = { "gonderi-fiyatlandi": "GonderiFiyatlandi", "sozlesme-indirimi-kaldirildi": "SozlesmeIndirimiKaldirildi", "hacim-indirimi-eklendi": "HacimIndirimiEklendi", "tartim-duzeltmesi-uygulandi": "TartimDuzeltmesiUygulandi", "tarife-degistigi-icin-yeniden-fiyatlandi": "TarifeDegistigiIcinYenidenFiyatlandi", "asgari-ucret-uygulandi": "AsgariUcretUygulandi", }; export const KUTUK = OLGULAR.map((o) => ({ tur: AD[o.olgu], gonderiNo: o.gonderiNo, tutar: o.sonrasi.tutar, }));
Ölçüm
Alan uzmanı kütüğe üç soru soruyor: kaç gönderi sözleşme indirimini sonradan kaybetti, tarife değiştiği için kaç kez yeniden fiyatlama yapıldı, kaç kez asgari ücret devreye girdi. Her soru iki kütükte de tek bir süzmeyle yanıtlanmaya çalışılır ve yanıt gerçek sayıyla karşılaştırılır.
// olay-olc.mjs — uzmanin uc sorusu iki kutukte kac dogru yanit ve kac karsilastirma istiyor import { OLGULAR, ASGARI_UCRET } from "./olgular.mjs"; import { KUTUK as GENEL } from "./genel/kutuk.mjs"; import { KUTUK as ALAN } from "./alan/kutuk.mjs"; const gercek = (olgu) => OLGULAR.filter((o) => o.olgu === olgu).length; const ind = (g) => g?.indirimler ?? []; const SORULAR = [ ["sozlesme indirimini kaybeden gonderi", "sozlesme-indirimi-kaldirildi", (k) => k.filter((e) => ind(e.oncesi).includes("sozlesme") && !ind(e.sonrasi).includes("sozlesme")).length, (k) => k.filter((e) => e.tur === "SozlesmeIndirimiKaldirildi").length], ["tarife degistigi icin yeniden fiyatlama", "tarife-degistigi-icin-yeniden-fiyatlandi", (k) => k.filter((e) => e.oncesi.tarife !== e.sonrasi.tarife).length, (k) => k.filter((e) => e.tur === "TarifeDegistigiIcinYenidenFiyatlandi").length], ["asgari ucretin devreye girmesi", "asgari-ucret-uygulandi", (k) => k.filter((e) => e.sonrasi.tutar === ASGARI_UCRET).length, (k) => k.filter((e) => e.tur === "AsgariUcretUygulandi").length], ]; let genelDogru = 0; let alanDogru = 0; for (const [soru, olgu, genelYanit, alanYanit] of SORULAR) { const d = gercek(olgu); const g = genelYanit(GENEL); const a = alanYanit(ALAN); if (g === d) genelDogru += 1; if (a === d) alanDogru += 1; console.log(`${soru}`); console.log(` gercek ${d} genel kutuk ${g} ${g === d ? "dogru" : "YANLIS"} ` + `alan kutugu ${a} ${a === d ? "dogru" : "YANLIS"}`); } console.log(`tek suzmeyle dogru yanit: genel ${genelDogru}/3, alan ${alanDogru}/3`); const goruntulu = (k) => k.filter((e) => "oncesi" in e).length; console.log(`3 soru icin alan karsilastirilan olay: genel ${goruntulu(GENEL) * 3}, ` + `alan ${goruntulu(ALAN) * 3}`); // SOZLUK: alan uzmaninin cumlelerinde gecen sozcukler const SOZLUK = ["gonderi", "tarife", "bolge", "agirlik", "hacim", "indirim", "sozlesme", "tartim", "duzeltme", "asgari", "ucret", "fiyatlandi", "eklendi", "kaldirildi", "uygulandi", "yeniden", "degis", "icin"]; const parcala = (t) => t.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase().split(" "); for (const [ad, k] of Object.entries({ genel: GENEL, alan: ALAN })) { const turler = [...new Set(k.map((e) => e.tur))]; const alanli = turler.filter((t) => parcala(t).every((p) => SOZLUK.some((s) => p.startsWith(s)))); console.log(`${ad}: olay turu ${turler.length}, adinin tamami alan sozlugunde olan ` + `${alanli.length}/${turler.length}`); console.log(` ${turler.join(" ")}`); } console.log(`kutuk uzunlugu: genel ${JSON.stringify(GENEL).length}, ` + `alan ${JSON.stringify(ALAN).length}`);
node olay-olc.mjs
sozlesme indirimini kaybeden gonderi gercek 10 genel kutuk 10 dogru alan kutugu 10 dogru tarife degistigi icin yeniden fiyatlama gercek 10 genel kutuk 20 YANLIS alan kutugu 10 dogru asgari ucretin devreye girmesi gercek 10 genel kutuk 15 YANLIS alan kutugu 10 dogru tek suzmeyle dogru yanit: genel 1/3, alan 3/3 3 soru icin alan karsilastirilan olay: genel 180, alan 0 genel: olay turu 2, adinin tamami alan sozlugunde olan 0/2 GonderiOlusturuldu GonderiGuncellendi alan: olay turu 6, adinin tamami alan sozlugunde olan 6/6 GonderiFiyatlandi SozlesmeIndirimiKaldirildi HacimIndirimiEklendi TartimDuzeltmesiUygulandi TarifeDegistigiIcinYenidenFiyatlandi AsgariUcretUygulandi kutuk uzunlugu: genel 11121, alan 4141
Üç sorunun yalnız birinde genel kütük doğru sayıyı veriyor. İkinci soruda yirmi diyor, oysa tarife değişikliği yüzünden yapılan yeniden fiyatlama on kez olmuş; fark, ilk fiyatlamanın da tarife alanını boştan doluya çevirmesinden geliyor. Üçüncü soruda on beş diyor, oysa asgari ücret on kez devreye girmiş; beş olayda tutar tartım düzeltmesi sonucu asgari ücretle eşitlenmiş ama kural uygulanmamış.
Bu iki yanlış, sorgunun düzeltilmesiyle giderilebilir: ilk fiyatlamayı dışarıda bırakan bir koşul, asgari ücret eşitliğini başka bir alanla doğrulayan bir koşul yazılabilir. Ölçünün gösterdiği şey bu düzeltmelerin nereye yazıldığıdır. Her düzeltme, kütükte bulunmayan bir alan bilgisini sorgunun içine gömer ve kütükten sınanamaz. Alan kütüğünde aynı üç soru tür adına bakan tek bir süzmeyle yanıtlanıyor, üçü de doğru çıkıyor.
İkinci satır bedeli işlem cinsinden veriyor: üç soruyu yanıtlamak için genel kütükte yüz seksen olayın alanları karşılaştırılıyor, alan kütüğünde hiçbiri. Üçüncü ölçü adların kaynağını gösteriyor: genel kütükteki iki türün adı alan sözlüğünde yok — “oluşturuldu” ve “güncellendi” uzmanın kullandığı sözcükler değil, kalıcılık işlemlerinin adları. Alan kütüğündeki altı türün altısı alan sözlüğünden geliyor.
Son satır bir yan kazançtır: adın taşıdığı bilgi, görüntünün taşıdığı bilgiden kısadır. Kütük uzunluğu 11121 karakterden 4141 karaktere iniyor.
Olayın Adı Ne Söyler
Alan olayının adı üç şeyi birlikte taşır: neyin olduğu, hangi kavramda olduğu ve olmuş bitmiş
olduğu. SozlesmeIndirimiKaldirildi adı bunların üçünü de veriyor. Ad geçmiş zamandadır çünkü
olay bir istek değildir; olmasını istemek başka bir kavramdır ve başka bir adı vardır.
Adın alan diline ait olmasının ölçülebilir sonucu, uzmanın kütüğü doğrulayabilmesidir. Alan kütüğündeki altı tür adı uzmanın önüne konduğunda uzman eksik olanı söyleyebilir: “iade ücretinin hesaplanması da bir olgudur, listede yok.” Genel kütükteki iki ad önüne konduğunda söyleyecek bir şeyi olmaz, çünkü o adlar onun dilinde değildir.
Olayın yükü de aynı ölçütten geçer. TarifeDegistigiIcinYenidenFiyatlandi olayının yükünde
yeni tutarın bulunması alanla ilgilidir; kaydın veritabanı satır kimliğinin bulunması değildir.
Kütüğe yük eklendiğinde sorulan soru, o alanın uzmanın cümlesinde geçip geçmediğidir.
Ne Zaman Genel Ad Yeterlidir
Alan olaylarının bedeli tür sayısıdır. Altı tür, altı ad, altı süzme koşulu ve zaman içinde altı ayrı sürüm demektir; genel kütükte iki ad vardır ve yeni bir olgu türü hiçbir şey eklemez.
Bu bedelin karşılıksız kaldığı durum, kütüğe alan sorusu sorulmadığı durumdur. Yalnız “bu kayda en son ne zaman dokunuldu” sorusu için tutulan bir iz kaydında önce–sonra görüntüsü yeterlidir ve tür ayrımı kazanç üretmez. Ayrım ölçütü kütüğün boyutu da değildir — kütüğe sorulan alan sorusunun sayısıdır.
İkinci durum, olgunun alanda bir adı olmamasıdır. Uzmanın “bu bir şey değil, kaydın düzeltilmesi” dediği değişiklikler için alan olayı adı uydurmak, sözlüğe alanda karşılığı olmayan bir kavram eklemek olur; ikinci dersin ölçüsü bunu ad başına kavram sayısı olarak sayar.
Özet
- Alan olayı, alanda olup biten ve alan dilinde bir adı olan olgudur; bu ders yalnız adın nereden geldiğini ölçtü, yayım ve ulaştırma tarafına girmedi.
- Aynı altmış olgudan üretilen iki kütükte uzmanın üç sorusundan genel kütük 1’ini, alan kütüğü 3’ünü tek süzmeyle doğru yanıtladı.
- Genel kütüğün iki yanlış yanıtı düzeltilebilir, ama her düzeltme kütükte bulunmayan bir alan bilgisini sorgunun içine gömer ve kütükten sınanamaz hâle getirir.
- Üç soruyu yanıtlamak genel kütükte 180 olayın alanlarının karşılaştırılmasını gerektirdi, alan kütüğünde 0.
- Genel kütükteki 2 tür adının 0’ı, alan kütüğündeki 6 tür adının 6’sı alan sözlüğünden geliyor; kütük uzunluğu 11121’den 4141 karaktere indi.
- Bedel tür sayısıdır; kütüğe alan sorusu sorulmuyorsa ya da olgunun alanda bir adı yoksa genel ad yeterlidir.
Sonraki Adım
Bu konudaki dersler kimliği, değeri, sınırı, davranışın yerini, yaratımı ve olayı tek tek ölçtü. Hepsinin ortak varsayımı, alan nesnesinin bir davranışı olduğuydu. Yaygın bir düzen bu varsayımı kaldırır: alan sınıfları yalnız alan taşır, bütün kurallar servislere yazılır ve sınıflar bir veri biçiminden fazlası olmaz. Sonraki ders bu düzeni alan modeli bağlamında ölçer: aynı kuralın kaç serviste tekrarlandığını, yeni bir kural eklendiğinde dokunulan dosya sayısını ve uzmanın cümlesindeki fiillerin kaçının alan nesnesinde bir yöntem olarak bulunduğunu sayar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.