Ders 10 / 19
Sınırlı Bağlam
Bir modelin geçerli olduğu alanın sınırı: aynı sözcüğün iki anlamı tek sınıfta birleştirildiğinde sınıfın taşıdığı alan sayısının ve hiçbir kullanımda birlikte geçmeyen alan çiftlerinin sayılması, iki bağlamın değişmezlerinin tek sınıfta kaç nesneyi reddettiğinin ölçülmesi ve sınırın çizilmesinin ücreti değiştirmediğinin gösterilmesi.
İçindekiler
Alan Modeli konusu anemik model eleştirisiyle kapandı. O konu boyunca tek bir modelin iç düzeni kuruldu — kimlik hangi nesnede, değişmez hangi sınırın içinde, davranış nereye ait — ve her karar modelin alanla hizasıyla ölçüldü. Sorulmayan tek soru şuydu: bu, hangi alanın modeli?
Soru boş değil. Gönderi ücretlendirme ve yönlendirme kitaplığında gonderi sözcüğü iki
takımın ağzında iki farklı nesneyi adlandırıyor. Ücretlendirme takımı için gönderi; ağırlık,
hacim, bölge ve beyan değeri taşıyan fiyatlanabilir bir birimdir. Teslimat operasyonu takımı
için gönderi; rotası, aktarma noktaları ve durum zaman damgaları olan fiziksel bir pakettir.
Sözcük aynı olduğu için tek bir sınıf yazılır ve o sınıf iki işi birden yapmaya zorlanır. Bu
ders o zorlamanın bedelini iki sayıyla ölçer: sınıfın taşıdığı alan sayısı ve hiçbir
kullanımda birlikte geçmeyen alan çiftlerinin sayısı.
Sınırlı Bağlam Bir Dil Sınırıdır
Bir modelin ve o modelin dilinin geçerli olduğu alana sınırlı bağlam (bounded context) denir. Sınırın içinde her ad tek bir şeyi gösterir; dışında aynı ad başka bir şeyi gösterebilir ve bu bir tutarsızlık değildir.
Tasarım İlkeleri kursundaki modül, bileşen ve politika–ayrıntı sınırları, sınırın nereye çizileceğini değişim sıklığı ve birlikte değişme ile yanıtlıyordu; sınırlı bağlam ise bir dil sınırıdır ve bir adın anlamının değiştiği yerden geçer, birlikte paketlenen dosya kümesinden değil. İki bağlam aynı bileşende de durabilir; ayrımı yapan şey paketleme değil, sözlüktür.
Her Yerde Aynı Dil dersinde kurulan kural burada bir koşul kazanır: dilin her yerde aynı olması, bir bağlamın içinde her yerde aynı olması anlamına gelir. İki takımın sözlüğü karşılaştırıldığında ortaklık sanıldığından azdır.
| Sözcük | Ücretlendirme bağlamı | Teslimat operasyonu bağlamı |
|---|---|---|
gonderi |
ağırlık, hacim, bölge ve beyan değeri taşıyan fiyatlanabilir birim | rota, aktarma sayısı ve durum geçmişi taşıyan fiziksel paket |
bolge |
ücret katsayısını veren tarife bölgesi | karşılığı yok; yerine dağıtım merkezi kodu var |
tarife |
kademe ve katsayı tablosu | karşılığı yok |
rota |
karşılığı yok | çıkış, aktarma ve dağıtım noktalarının dizisi |
teslimat |
karşılığı yok | durum ve zaman damgası kaydı |
Beş sözcükten yalnız biri iki bağlamda da geçiyor ve o bir sözcük iki farklı nesneyi adlandırıyor. Aynı alanın iki düzenlenişi bu tablodan çıkar: sözcüğü tek sınıfta birleştiren düzen ve sınırı sözcüğün anlamının değiştiği yerden geçiren düzen.
Tek Sınıfta Birleştirilmiş Düzen
Birinci düzende iki sözlük tek sınıfta toplanıyor. Sınıf her iki tarafın alanlarını, her iki
tarafın davranışını ve her iki tarafın değişmezlerini taşıyor. Değişmezler dogrula
işlevinde toplanmış; ücretlendirme tarafından beşi U, teslimat tarafından beşi T
önekiyle işaretli.
mkdir -p birlesik ucretlendirme teslimat
// birlesik/gonderi.mjs — iki baglamin alanlarini ve degismezlerini tek sinifta tasiyan gonderi const BOLGELER = ["yakin", "orta", "uzak"]; const DURUMLAR = ["kabul", "aktarmada", "dagitimda", "teslim-edildi"]; const BOS = { gonderiNo: null, agirlikGram: null, hacimDesimetreKup: null, ucretBolgesi: null, beyanDegeriKurus: null, tarifeKodu: null, sozlesmeNo: null, indirimOrani: null, tasiyiciKodu: null, rotaNoktalari: null, aktarmaSayisi: null, sonDurum: null, durumGecmisi: null, teslimAlanAd: null, teslimZamani: null, }; export class Gonderi { constructor(v) { Object.assign(this, BOS, v); } ucret(tarife) { const esasGram = Math.max(this.agirlikGram, this.hacimDesimetreKup * 200); const birim = tarife[this.tarifeKodu][this.ucretBolgesi]; return Math.round((esasGram / 1000) * birim * (1 - this.indirimOrani)); } teslimIsaretle(ad, zaman) { this.teslimAlanAd = ad; this.teslimZamani = zaman; this.sonDurum = "teslim-edildi"; this.durumGecmisi = [...this.durumGecmisi, ["teslim-edildi", zaman]]; } dogrula() { const ihlal = []; if (!(Number.isInteger(this.agirlikGram) && this.agirlikGram > 0)) ihlal.push("U1-agirlik"); if (!(this.hacimDesimetreKup > 0)) ihlal.push("U2-hacim"); if (!BOLGELER.includes(this.ucretBolgesi)) ihlal.push("U3-bolge"); if (!Number.isInteger(this.beyanDegeriKurus)) ihlal.push("U4-beyan"); if (!(this.indirimOrani >= 0 && (this.indirimOrani === 0 || this.sozlesmeNo !== null))) ihlal.push("U5-indirim"); if (!(this.rotaNoktalari?.length >= 2)) ihlal.push("T1-rota"); if (this.aktarmaSayisi !== (this.rotaNoktalari?.length ?? 0) - 2) ihlal.push("T2-aktarma"); if (!DURUMLAR.includes(this.sonDurum)) ihlal.push("T3-durum"); if (this.durumGecmisi?.at(-1)?.[0] !== this.sonDurum) ihlal.push("T4-gecmis"); if (this.sonDurum === "teslim-edildi" && this.teslimAlanAd === null) ihlal.push("T5-teslim"); return ihlal; } }
Sınıfın kurucusundaki BOS nesnesi dikkate değer: her alanın varsayılanı null. Bu bir
gerçekleştirim ayrıntısı değil, birleştirmenin zorunlu sonucudur. Ücretlendirme kaydında rota
yoktur, teslimat kaydında beyan değeri yoktur; tek sınıf ikisini de kabul edecekse her alanı
yokluğa açmak zorundadır.
Sınırın Çizildiği Düzen
İkinci düzende sınır sözcüğün anlamının değiştiği yerden geçiyor. İki dizin, iki Gonderi
sınıfı, iki değişmez kümesi. Aynı adın iki dosyada bulunması bir yineleme değildir; iki
farklı nesnenin iki farklı bağlamdaki adıdır.
// ucretlendirme/gonderi.mjs — ucretlendirme baglaminda gonderi: fiyatlanabilir birim const BOLGELER = ["yakin", "orta", "uzak"]; export class Gonderi { constructor(v) { Object.assign(this, { sozlesmeNo: null, indirimOrani: 0 }, v); } ucret(tarife) { const esasGram = Math.max(this.agirlikGram, this.hacimDesimetreKup * 200); const birim = tarife[this.tarifeKodu][this.ucretBolgesi]; return Math.round((esasGram / 1000) * birim * (1 - this.indirimOrani)); } dogrula() { const ihlal = []; if (!(Number.isInteger(this.agirlikGram) && this.agirlikGram > 0)) ihlal.push("U1-agirlik"); if (!(this.hacimDesimetreKup > 0)) ihlal.push("U2-hacim"); if (!BOLGELER.includes(this.ucretBolgesi)) ihlal.push("U3-bolge"); if (!Number.isInteger(this.beyanDegeriKurus)) ihlal.push("U4-beyan"); if (!(this.indirimOrani >= 0 && (this.indirimOrani === 0 || this.sozlesmeNo !== null))) ihlal.push("U5-indirim"); return ihlal; } }
// teslimat/gonderi.mjs — teslimat operasyonu baglaminda gonderi: fiziksel paket const DURUMLAR = ["kabul", "aktarmada", "dagitimda", "teslim-edildi"]; export class Gonderi { constructor(v) { Object.assign(this, { teslimAlanAd: null, teslimZamani: null }, v); } teslimIsaretle(ad, zaman) { this.teslimAlanAd = ad; this.teslimZamani = zaman; this.sonDurum = "teslim-edildi"; this.durumGecmisi = [...this.durumGecmisi, ["teslim-edildi", zaman]]; } dogrula() { const ihlal = []; if (!(this.rotaNoktalari?.length >= 2)) ihlal.push("T1-rota"); if (this.aktarmaSayisi !== (this.rotaNoktalari?.length ?? 0) - 2) ihlal.push("T2-aktarma"); if (!DURUMLAR.includes(this.sonDurum)) ihlal.push("T3-durum"); if (this.durumGecmisi?.at(-1)?.[0] !== this.sonDurum) ihlal.push("T4-gecmis"); if (this.sonDurum === "teslim-edildi" && this.teslimAlanAd === null) ihlal.push("T5-teslim"); return ihlal; } }
İki sınıfın karşılaştırılabilmesi için ölçüm verisi gerekiyor: iki takımın kendi kayıtlarından aynı üç gönderi ve ücretlendirmenin tarife tablosu.
// veri.mjs — iki takimin kendi kayitlarindan ayni uc gonderi ve ortak tarife export const TARIFE = { standart: { yakin: 1800, orta: 2400, uzak: 3200 }, agir: { yakin: 1400, orta: 1900, uzak: 2600 }, }; export const UCRET_SATIRLARI = [ { gonderiNo: "G-1041", agirlikGram: 2400, hacimDesimetreKup: 9, ucretBolgesi: "orta", beyanDegeriKurus: 180000, tarifeKodu: "standart", sozlesmeNo: "S-77", indirimOrani: 0.12 }, { gonderiNo: "G-1042", agirlikGram: 700, hacimDesimetreKup: 2, ucretBolgesi: "yakin", beyanDegeriKurus: 0, tarifeKodu: "standart", sozlesmeNo: null, indirimOrani: 0 }, { gonderiNo: "G-1043", agirlikGram: 18000, hacimDesimetreKup: 60, ucretBolgesi: "uzak", beyanDegeriKurus: 950000, tarifeKodu: "agir", sozlesmeNo: "S-12", indirimOrani: 0.2 }, ]; export const TESLIMAT_SATIRLARI = [ { gonderiNo: "G-1041", tasiyiciKodu: "T-A", rotaNoktalari: ["34-mrk", "06-akt", "06-dgt"], aktarmaSayisi: 1, sonDurum: "dagitimda", durumGecmisi: [["kabul", 1], ["aktarmada", 2], ["dagitimda", 3]] }, { gonderiNo: "G-1042", tasiyiciKodu: "T-B", rotaNoktalari: ["34-mrk", "34-dgt"], aktarmaSayisi: 0, sonDurum: "kabul", durumGecmisi: [["kabul", 1]] }, { gonderiNo: "G-1043", tasiyiciKodu: "T-A", rotaNoktalari: ["34-mrk", "35-akt", "65-akt", "65-dgt"], aktarmaSayisi: 2, sonDurum: "aktarmada", durumGecmisi: [["kabul", 1], ["aktarmada", 2]] }, ];
Alanların ve Alan Çiftlerinin Sayılması
Birinci ölçü sınıfın taşıdığı alan sayısıdır. İkinci ölçü daha ayırt edicidir: iki alan hiçbir kullanımda birlikte okunmuyorsa aynı sınıfta durmalarının gerekçesi yoktur. Aşağıdaki betik altı kullanım senaryosunu ve her senaryonun okuduğu alanları taşıyor, sonra her alan çiftinin en az bir senaryoda birlikte geçip geçmediğini sayıyor.
// alan-say.mjs — alan sayisi, kullanim basina okunmayan alan ve birlikte hic gecmeyen alan cifti import { Gonderi as Birlesik } from "./birlesik/gonderi.mjs"; import { Gonderi as UcretGonderi } from "./ucretlendirme/gonderi.mjs"; import { Gonderi as TeslimatGonderi } from "./teslimat/gonderi.mjs"; import { UCRET_SATIRLARI, TESLIMAT_SATIRLARI } from "./veri.mjs"; const KULLANIM = [ ["ucretHesapla", ["gonderiNo", "agirlikGram", "hacimDesimetreKup", "ucretBolgesi", "tarifeKodu"]], ["indirimUygula", ["gonderiNo", "tarifeKodu", "sozlesmeNo", "indirimOrani"]], ["sigortaPrimi", ["gonderiNo", "beyanDegeriKurus", "ucretBolgesi"]], ["rotaAta", ["gonderiNo", "tasiyiciKodu", "rotaNoktalari", "aktarmaSayisi"]], ["aktarmaKaydet", ["gonderiNo", "rotaNoktalari", "aktarmaSayisi", "sonDurum", "durumGecmisi"]], ["teslimIsaretle", ["gonderiNo", "sonDurum", "durumGecmisi", "teslimAlanAd", "teslimZamani"]], ]; const alanlari = (Sinif, satir) => Object.keys(new Sinif(satir)); function olc(ad, alanlar, senaryolar) { let cift = 0, birlikte = 0; for (let i = 0; i < alanlar.length; i += 1) { for (let j = i + 1; j < alanlar.length; j += 1) { cift += 1; if (senaryolar.some(([, o]) => o.includes(alanlar[i]) && o.includes(alanlar[j]))) birlikte += 1; } } console.log(`${ad}: ${alanlar.length} alan, ${cift} alan cifti, ${senaryolar.length} senaryo`); for (const [senaryo, okunan] of senaryolar) { const icerde = okunan.filter((a) => alanlar.includes(a)).length; console.log(` ${senaryo.padEnd(15)} okunan ${String(icerde).padStart(2)}, okunmayan ${String(alanlar.length - icerde).padStart(2)}`); } console.log(` hic birlikte gecmeyen cift = ${cift - birlikte} / ${cift}`); return alanlar; } const hepsi = olc("birlesik", alanlari(Birlesik, {}), KULLANIM); const ucret = alanlari(UcretGonderi, UCRET_SATIRLARI[0]); const teslimat = alanlari(TeslimatGonderi, TESLIMAT_SATIRLARI[0]); const yalnizUcret = ucret.filter((a) => !teslimat.includes(a)); const yalnizTeslimat = teslimat.filter((a) => !ucret.includes(a)); console.log(` bunun ${yalnizUcret.length * yalnizTeslimat.length}'i baglam disi cift (${yalnizUcret.length} x ${yalnizTeslimat.length})`); olc("ucretlendirme", ucret, KULLANIM.filter(([, o]) => o.every((a) => ucret.includes(a)))); olc("teslimat", teslimat, KULLANIM.filter(([, o]) => o.every((a) => teslimat.includes(a)))); console.log(`iki baglamda ortak alan = ${hepsi.filter((a) => ucret.includes(a) && teslimat.includes(a)).join(", ")}`);
node alan-say.mjs
birlesik: 15 alan, 105 alan cifti, 6 senaryo ucretHesapla okunan 5, okunmayan 10 indirimUygula okunan 4, okunmayan 11 sigortaPrimi okunan 3, okunmayan 12 rotaAta okunan 4, okunmayan 11 aktarmaKaydet okunan 5, okunmayan 10 teslimIsaretle okunan 5, okunmayan 10 hic birlikte gecmeyen cift = 68 / 105 bunun 49'i baglam disi cift (7 x 7) ucretlendirme: 8 alan, 28 alan cifti, 3 senaryo ucretHesapla okunan 5, okunmayan 3 indirimUygula okunan 4, okunmayan 4 sigortaPrimi okunan 3, okunmayan 5 hic birlikte gecmeyen cift = 11 / 28 teslimat: 8 alan, 28 alan cifti, 3 senaryo rotaAta okunan 4, okunmayan 4 aktarmaKaydet okunan 5, okunmayan 3 teslimIsaretle okunan 5, okunmayan 3 hic birlikte gecmeyen cift = 8 / 28 iki baglamda ortak alan = gonderiNo
Birleşik sınıf 15 alan taşıyor ve altı senaryonun hiçbiri beşten fazlasını okumuyor: her
kullanımda en az on alan ilgisiz duruyor. Asıl sayı son satırdadır. 105 alan çiftinin 68’i
hiçbir senaryoda birlikte geçmiyor ve bunların 49’u bağlam dışı çift, yani biri
ücretlendirmeye, öteki teslimata ait alan çiftleri. Sınır çizildiğinde çift sayısı 105’ten
56’ya, hiç birlikte geçmeyen çift sayısı 68’den 19’a iniyor; 49 bağlam dışı çift tamamen
ortadan kalkıyor. İki bağlamın ortak tek alanı gonderiNo, yani kimliğin kendisi.
Değişmezlerin Sayılması
Alan sayımı yapının gevşekliğini gösterir; değişmez sayımı daha keskin bir şey gösterir.
Birleşik sınıfın dogrula işlevi on değişmez taşıyor ve bu on değişmezin hepsi her nesne
için çalışıyor. Ücretlendirme kaydının rotası olmadığı, teslimat kaydının beyan değeri
olmadığı için sonuç baştan bellidir, ama sayılması gerekir.
// ihlal-say.mjs — on degismezin alti gonderide kac ihlal verdigi ve ucretin degisip degismedigi import { Gonderi as Birlesik } from "./birlesik/gonderi.mjs"; import { Gonderi as UcretGonderi } from "./ucretlendirme/gonderi.mjs"; import { Gonderi as TeslimatGonderi } from "./teslimat/gonderi.mjs"; import { TARIFE, UCRET_SATIRLARI, TESLIMAT_SATIRLARI } from "./veri.mjs"; const birlesikNesneler = [...UCRET_SATIRLARI, ...TESLIMAT_SATIRLARI].map((s) => new Birlesik(s)); let toplam = 0, gecen = 0; console.log("birlesik sinif (10 degismez)"); for (const g of birlesikNesneler) { const ihlal = g.dogrula(); toplam += ihlal.length; if (ihlal.length === 0) gecen += 1; console.log(` ${g.gonderiNo} ihlal ${ihlal.length} ${ihlal.join(" ") || "-"}`); } console.log(` gecen nesne = ${gecen} / ${birlesikNesneler.length}, toplam ihlal = ${toplam}`); const ayrilmis = [...UCRET_SATIRLARI.map((s) => new UcretGonderi(s)), ...TESLIMAT_SATIRLARI.map((s) => new TeslimatGonderi(s))]; let t2 = 0, g2 = 0; console.log("ayrilmis siniflar (5 + 5 degismez)"); for (const g of ayrilmis) { const ihlal = g.dogrula(); t2 += ihlal.length; if (ihlal.length === 0) g2 += 1; } console.log(` gecen nesne = ${g2} / ${ayrilmis.length}, toplam ihlal = ${t2}`); let ayrilan = 0; for (const s of UCRET_SATIRLARI) { const a = new Birlesik(s).ucret(TARIFE), b = new UcretGonderi(s).ucret(TARIFE); if (a !== b) ayrilan += 1; console.log(` ${s.gonderiNo} birlesik ${a} ayrilmis ${b}`); } console.log(`ayrilan ucret = ${ayrilan} / ${UCRET_SATIRLARI.length}`);
node ihlal-say.mjs
birlesik sinif (10 degismez) G-1041 ihlal 4 T1-rota T2-aktarma T3-durum T4-gecmis G-1042 ihlal 4 T1-rota T2-aktarma T3-durum T4-gecmis G-1043 ihlal 4 T1-rota T2-aktarma T3-durum T4-gecmis G-1041 ihlal 5 U1-agirlik U2-hacim U3-bolge U4-beyan U5-indirim G-1042 ihlal 5 U1-agirlik U2-hacim U3-bolge U4-beyan U5-indirim G-1043 ihlal 5 U1-agirlik U2-hacim U3-bolge U4-beyan U5-indirim gecen nesne = 0 / 6, toplam ihlal = 27 ayrilmis siniflar (5 + 5 degismez) gecen nesne = 6 / 6, toplam ihlal = 0 G-1041 birlesik 5069 ayrilmis 5069 G-1042 birlesik 1260 ayrilmis 1260 G-1043 birlesik 37440 ayrilmis 37440 ayrilan ucret = 0 / 3
Altı gerçek gönderinin hiçbiri birleşik sınıfın değişmezlerini geçmiyor: 0/6, toplam 27 ihlal. Aynı altı gönderi, aynı on değişmez iki sınıfa dağıtıldığında 6/6 geçiyor ve ihlal sayısı sıfır. Değişmezlerin metni değişmedi, yalnız uygulandıkları alan değişti.
Son üç satır ayrımın davranışı bozmadığını gösteriyor: üç gönderinin ücreti iki düzende de aynı. Sınır çizmek modeli iki parçaya böler, hesabı değiştirmez.
Koşullu Değişmezin Gizlediği Şey
Birleşik sınıfı kurtarmanın bilinen bir yolu vardır: değişmezleri koşullu yazmak. Rota denetimi yalnız rota alanı doluysa çalışsın, ağırlık denetimi yalnız ağırlık doluysa çalışsın. Bu, ihlal sayısını sıfıra indirir ama bir şeyi kaybeder: koşul, hangi bağlamın nesnesiyle çalışıldığını sınıfın içine bir veri olarak geri koyar. Değişmez artık “gönderinin rotası en az iki noktadır” demez, “eğer bu nesne teslimat tarafından geldiyse rotası en az iki noktadır” der. İlki alan uzmanının cümlesidir, ikincisi değildir.
Sınırın nereden geçeceğini bulmanın yordamı buradan çıkar: bir sınıfın değişmezlerinden hangilerinin ancak koşul altında yazılabildiğine bakılır. Koşul bir bağlam ayrımını gizliyorsa, sınır o koşulun yerinden geçer.
Özet
- Sınırlı bağlam, bir modelin ve dilinin geçerli olduğu alandır; bileşen sınırı gibi birlikte paketlenen dosyalardan değil, bir adın anlamının değiştiği yerden geçer.
- İki bağlamın sözlüğünde beş sözcükten yalnız biri ortaktı ve o sözcük iki farklı nesneyi
adlandırıyordu; iki sınıfın ortak tek alanı
gonderiNoçıktı. - Birleşik sınıf 15 alan taşıdı, altı senaryonun hiçbiri beşten fazlasını okumadı; 105 alan çiftinin 68’i hiçbir senaryoda birlikte geçmedi ve bunların 49’u bağlam dışı çiftti.
- Sınır çizildiğinde alan çifti 105’ten 56’ya, hiç birlikte geçmeyen çift 68’den 19’a indi; bağlam dışı 49 çift ortadan kalktı.
- On değişmez tek sınıfta altı gerçek gönderinin altısını da reddetti (0/6, 27 ihlal); aynı değişmezler iki sınıfa dağıtıldığında 6/6 geçti ve üç gönderinin ücreti değişmedi.
- Değişmezi koşullu yazmak ihlali sıfırlar ama bağlam ayrımını sınıfın içine veri olarak geri koyar; koşulun yeri sınırın yerini gösterir.
Sonraki Adım
Sınır çizildi ve iki bağlam kendi sözlüğüne kavuştu. Ne var ki iki bağlam birbirinden habersiz çalışamaz: ücretlendirme bir gönderinin fiyatını hesaplarken teslimat o gönderiyi yola çıkarır, teslim edildiğinde ücretlendirmenin bunu bilmesi gerekir. Sınır bir duvar değil, bir geçittir ve geçidin kaç tane olduğu, hangi yöne baktığı, kimin kimin sözlüğüne uyduğu tasarım kararıdır. Sonraki ders bağlamlar arası ilişkileri bir çizge olarak yazar, ilişki türlerini yön ve bağımlılık cinsinden ayırt eder, sonra iki haritada aynı ölçüyü koşturur: bir bağlam değiştiğinde kaç bağlamda dosya düzenlenir ve bu sayı ilişkiler adlandırıldığında nereye iner.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.