Ders 07 / 19
Fabrikalar ve Depolar
Yaratım ve erişimin alan modeline uydurulması: geçersiz bir gönderinin dört yaratım yolundan kaçında üretilebildiğinin sayılması, fabrikadan geçen modelde bu sayının sıfıra inmesi, depo arayüzü sorgu diliyle yazıldığında çağrı yerine sızan sütun adı, işleç ve eşik sayısının ölçülmesi ve iki deponun aynı sonucu verdiğinin gösterilmesi.
İçindekiler
Önceki dersin uygulama servisi gönderiyi bir depodan okudu ve tarifeleri ondan istedi. O deponun arayüzünde hangi sözcüklerin yazılı olduğu sorulmadı. Aynı soru yaratım için de geçerlidir: gönderi doğrudan yapıcıyla mı kuruluyor, yoksa değişmezini kuruluş anında güvenceye alan bir yerden mi geçiyor?
Fabrika (factory) ve depo (repository) kalıpları Tasarım Kalıpları kursunda kuruldu; biri yaratım sorumluluğunun nereye konduğunu, öteki toplamaya erişimin nereden geçtiğini söylüyordu. Bu ders kalıpları yeniden anlatmaz. Tek soruyu sorar: ikisi de alan diliyle mi yazılmış? Fabrika için bu, değişmezin kuruluş anında sağlanması demektir; depo için, arayüzde alan sözcüklerinin bulunması ve sütun adlarının bulunmaması demektir.
Geçerlilik Kuralı
İki model de aynı kurala tabi tutulur: gönderi numarası boş olmayan bir katardır, ağırlık pozitif bir tam sayıdır, bölge kodu iki basamaklıdır.
mkdir -p ortak sorgu alan
// ortak/kural.mjs — iki modelin de tabi oldugu gecerlilik kurali export const gecerliMi = (g) => typeof g.gonderiNo === "string" && g.gonderiNo.length > 0 && Number.isInteger(g.agirlikGram) && g.agirlikGram > 0 && /^\d{2}$/.test(g.bolgeKodu);
Yaratım: Kural Nerede Uygulanıyor
Birinci sürümde yapıcı dışa açıktır, alanlar yazılabilir ve kural hiçbir yerde uygulanmaz.
// sorgu/gonderi.mjs — yapici disa acik, alanlar yazilabilir, kural kurulus aninda yok export class Gonderi { constructor(gonderiNo, agirlikGram, bolgeKodu, sozlesmeNo = null) { this.gonderiNo = gonderiNo; this.agirlikGram = agirlikGram; this.bolgeKodu = bolgeKodu; this.sozlesmeNo = sozlesmeNo; } } export const kayittanKur = (satir) => new Gonderi(satir.gonderi_no, satir.agirlik_gram, satir.bolge_kodu, satir.sozlesme_no);
İkinci sürümde yapıcıya yalnız fabrikanın bildiği bir belirteçle girilir. İki fabrika yöntemi vardır — biri alan çağrıları için, biri ham kayıttan kurmak için — ve ikisi de aynı kuralı uygular. Nesne kuruluşta dondurulur.
// alan/gonderi.mjs — fabrika: gecerlilik kurulus aninda saglanir, nesne dondurulur import { gecerliMi } from "../ortak/kural.mjs"; const FABRIKA = Symbol("fabrika"); export class Gonderi { constructor(anahtar, gonderiNo, agirlikGram, bolgeKodu, sozlesmeNo) { if (anahtar !== FABRIKA) throw new TypeError("Gonderi yalnizca fabrikadan kurulur"); this.gonderiNo = gonderiNo; this.agirlikGram = agirlikGram; this.bolgeKodu = bolgeKodu; this.sozlesmeNo = sozlesmeNo; Object.freeze(this); } static olustur(gonderiNo, agirlikGram, bolgeKodu, sozlesmeNo = null) { const aday = { gonderiNo, agirlikGram, bolgeKodu, sozlesmeNo }; if (!gecerliMi(aday)) throw new RangeError(`gecersiz gonderi: ${gonderiNo}`); return new Gonderi(FABRIKA, gonderiNo, agirlikGram, bolgeKodu, sozlesmeNo); } static kayittanKur(satir) { return Gonderi.olustur(satir.gonderi_no, satir.agirlik_gram, satir.bolge_kodu, satir.sozlesme_no); } }
Ölçü, önceki dersteki ulaşılabilirlik ölçüsünün yaratım tarafındaki karşılığıdır: geçersiz bir gönderi dört yoldan kaçında üretilebiliyor?
// fabrika-olc.mjs — gecersiz bir gonderi kac yoldan yaratilabiliyor import { gecerliMi } from "./ortak/kural.mjs"; import { Gonderi as SorguGonderi, kayittanKur } from "./sorgu/gonderi.mjs"; import { Gonderi as AlanGonderi } from "./alan/gonderi.mjs"; const BOZUK_SATIR = { gonderi_no: "G-4099", agirlik_gram: -50, bolge_kodu: "3", sozlesme_no: null }; const SORGU_YOLLARI = { "yapicidan dogrudan": () => new SorguGonderi("", -3, "3"), "alanin sonradan atanmasi": () => { const g = new SorguGonderi("G-4098", 2400, "34"); g.agirlikGram = -3; return g; }, "toplu atama": () => Object.assign(new SorguGonderi("G-4098", 2400, "34"), { bolgeKodu: "abc" }), "ham kayittan kurma": () => kayittanKur(BOZUK_SATIR), }; const ALAN_YOLLARI = { "yapicidan dogrudan": () => new AlanGonderi("", -3, "3"), "alanin sonradan atanmasi": () => { const g = AlanGonderi.olustur("G-4098", 2400, "34"); g.agirlikGram = -3; return g; }, "toplu atama": () => Object.assign(AlanGonderi.olustur("G-4098", 2400, "34"), { bolgeKodu: "abc" }), "ham kayittan kurma": () => AlanGonderi.kayittanKur(BOZUK_SATIR), }; for (const [ad, yollar] of Object.entries({ sorgu: SORGU_YOLLARI, alan: ALAN_YOLLARI })) { let gecersiz = 0; let hata = 0; for (const [yol, dene] of Object.entries(yollar)) { let sonuc; try { const g = dene(); sonuc = gecerliMi(g) ? "gecerli" : "GECERSIZ NESNE"; if (sonuc !== "gecerli") gecersiz += 1; } catch { sonuc = "hata"; hata += 1; } console.log(` ${ad.padEnd(6)} ${yol.padEnd(26)} ${sonuc}`); } console.log(`${ad}: 4 yoldan ${gecersiz} tanesi gecersiz nesne uretti, ${hata} tanesi hata verdi`); }
node fabrika-olc.mjs
sorgu yapicidan dogrudan GECERSIZ NESNE sorgu alanin sonradan atanmasi GECERSIZ NESNE sorgu toplu atama GECERSIZ NESNE sorgu ham kayittan kurma GECERSIZ NESNE sorgu: 4 yoldan 4 tanesi gecersiz nesne uretti, 0 tanesi hata verdi alan yapicidan dogrudan hata alan alanin sonradan atanmasi hata alan toplu atama hata alan ham kayittan kurma hata alan: 4 yoldan 0 tanesi gecersiz nesne uretti, 4 tanesi hata verdi
Dört yolun dördü birinci modelde geçersiz bir gönderi üretiyor ve hiçbiri uyarı vermiyor. Fabrikadan geçen modelde dördü de hata veriyor. Kazanç kuralın kaç kez yazıldığı değildir — kural iki modelde de bir kez yazılabilirdi — kuralı atlayan bir yolun kalmamasıdır.
Erişim: Depo Hangi Dili Konuşuyor
Kalıcılık tarafından gelen ham satırlar sütun adlarını taşır.
// ortak/satirlar.mjs — kalicilik tarafindan gelen ham satirlar export const SATIRLAR = [ { gonderi_no: "G-4001", agirlik_gram: 2400, bolge_kodu: "34", sozlesme_no: "S-77" }, { gonderi_no: "G-4002", agirlik_gram: 7200, bolge_kodu: "06", sozlesme_no: null }, { gonderi_no: "G-4003", agirlik_gram: 400, bolge_kodu: "65", sozlesme_no: "S-77" }, { gonderi_no: "G-4004", agirlik_gram: 12000, bolge_kodu: "34", sozlesme_no: null }, { gonderi_no: "G-4005", agirlik_gram: 5600, bolge_kodu: "06", sozlesme_no: "S-77" }, ];
Birinci depo genel bir sorgu arayüzü verir: koşulu çağıran taraf kurar ve ham satır alır.
// sorgu/depo.mjs — sorgu diliyle yazilmis depo: kosulu cagiran taraf kurar const KARS = { "=": (a, b) => a === b, ">": (a, b) => a > b, "<": (a, b) => a < b, }; export const depo = (satirlar) => ({ bul: (kosul) => satirlar.filter((s) => kosul.every(([alan, isl, d]) => KARS[isl](s[alan], d))), });
// sorgu/cagrilar.mjs — sutun adlari, isleciler ve esikler cagri yerinde yazili export const agirGonderiler = (d) => d.bul([["agirlik_gram", ">", 5000]]); export const bolgedekiler = (d, bolge) => d.bul([["bolge_kodu", "=", bolge]]); export const sozlesmesizAgirlar = (d) => d.bul([["agirlik_gram", ">", 5000], ["sozlesme_no", "=", null]]); export const bolgeAgirliklari = (d, bolge) => d.bul([["bolge_kodu", "=", bolge]]).map((s) => s.agirlik_gram); export const hafifSozlesmeliler = (d) => d.bul([["agirlik_gram", "<", 1000], ["sozlesme_no", "=", "S-77"]]);
İkinci depo alan sözcüklerini yöntem adı olarak verir, ham satırı fabrikadan geçirir ve alan nesnesi döndürür. “Ağır” ile “hafif” eşiklerinin ne olduğu da burada durur.
// alan/depo.mjs — alan diliyle yazilmis depo: sutun adi, esik ve islec burada kalir import { Gonderi } from "./gonderi.mjs"; const AGIR_SINIR_GRAM = 5000; const HAFIF_SINIR_GRAM = 1000; const kur = (satirlar) => satirlar.map((s) => Gonderi.kayittanKur(s)); export const depo = (satirlar) => ({ agirGonderiler: () => kur(satirlar.filter((s) => s.agirlik_gram > AGIR_SINIR_GRAM)), bolgedekiler: (bolge) => kur(satirlar.filter((s) => s.bolge_kodu === bolge)), sozlesmesizAgirlar: () => kur(satirlar.filter((s) => s.agirlik_gram > AGIR_SINIR_GRAM && s.sozlesme_no === null)), hafifSozlesmeliler: (sozlesmeNo) => kur(satirlar.filter((s) => s.agirlik_gram < HAFIF_SINIR_GRAM && s.sozlesme_no === sozlesmeNo)), });
// alan/cagrilar.mjs — cagri yerinde yalniz alan sozcukleri var export const agirGonderiler = (d) => d.agirGonderiler(); export const bolgedekiler = (d, bolge) => d.bolgedekiler(bolge); export const sozlesmesizAgirlar = (d) => d.sozlesmesizAgirlar(); export const bolgeAgirliklari = (d, bolge) => d.bolgedekiler(bolge).map((g) => g.agirlikGram); export const hafifSozlesmeliler = (d) => d.hafifSozlesmeliler("S-77");
// depo-olc.mjs — sutun adi ve islec cagri yerine siziyor mu, iki depo ayni sonucu veriyor mu import { readFileSync } from "node:fs"; import { SATIRLAR } from "./ortak/satirlar.mjs"; import { depo as sorguDepo } from "./sorgu/depo.mjs"; import { depo as alanDepo } from "./alan/depo.mjs"; import * as sorguCagri from "./sorgu/cagrilar.mjs"; import * as alanCagri from "./alan/cagrilar.mjs"; const SUTUN = /agirlik_gram|bolge_kodu|sozlesme_no/g; const ISLEC = /"[=<>]"/g; const ESIK = /5000|1000/g; const MODEL = { sorgu: { cagri: "sorgu/cagrilar.mjs", sinir: ["sorgu/depo.mjs"] }, alan: { cagri: "alan/cagrilar.mjs", sinir: ["alan/depo.mjs", "alan/gonderi.mjs"] }, }; for (const [ad, { cagri, sinir }] of Object.entries(MODEL)) { const metin = readFileSync(cagri, "utf8"); const say = (r) => (metin.match(r) ?? []).length; const sinirda = sinir.map((d) => (readFileSync(d, "utf8").match(SUTUN) ?? []).length) .reduce((a, b) => a + b, 0); console.log(`${ad}: cagri yerinde sutun adi ${say(SUTUN)}, islec ${say(ISLEC)}, ` + `esik ${say(ESIK)}`); console.log(` sinir dosyalarinda sutun adi ${sinirda} [${sinir.join(" ")}]`); } const s = sorguDepo(SATIRLAR); const a = alanDepo(SATIRLAR); const ad = (liste) => liste.map((x) => x.gonderi_no ?? x.gonderiNo ?? x).join(","); let ayrilan = 0; for (const [islem, x, y] of [ ["agirGonderiler", sorguCagri.agirGonderiler(s), alanCagri.agirGonderiler(a)], ["bolgedekiler", sorguCagri.bolgedekiler(s, "34"), alanCagri.bolgedekiler(a, "34")], ["sozlesmesizAgirlar", sorguCagri.sozlesmesizAgirlar(s), alanCagri.sozlesmesizAgirlar(a)], ["bolgeAgirliklari", sorguCagri.bolgeAgirliklari(s, "06"), alanCagri.bolgeAgirliklari(a, "06")], ["hafifSozlesmeliler", sorguCagri.hafifSozlesmeliler(s), alanCagri.hafifSozlesmeliler(a)], ]) { if (ad(x) !== ad(y)) ayrilan += 1; console.log(` ${islem.padEnd(20)} ${ad(x)}`); } console.log(`ayrilan sonuc = ${ayrilan} / 5`);
node depo-olc.mjs
sorgu: cagri yerinde sutun adi 8, islec 7, esik 3 sinir dosyalarinda sutun adi 0 [sorgu/depo.mjs] alan: cagri yerinde sutun adi 0, islec 0, esik 0 sinir dosyalarinda sutun adi 9 [alan/depo.mjs alan/gonderi.mjs] agirGonderiler G-4002,G-4004,G-4005 bolgedekiler G-4001,G-4004 sozlesmesizAgirlar G-4002,G-4004 bolgeAgirliklari 7200,5600 hafifSozlesmeliler G-4003 ayrilan sonuc = 0 / 5
Sorgu diliyle yazılmış depoda çağrı yeri sekiz sütun adı, yedi karşılaştırma işleci ve üç eşik sayısı taşıyor. Alan diliyle yazılmış depoda üçü de sıfır. Sütun adları yok olmadı; dokuz geçişle iki sınır dosyasına toplandı.
Toplanmanın karşılığı üç ayrı karar cinsindedir. Sütun adı bir kalıcılık kararıdır; işleç bir
sorgu kararıdır; “ağır” eşiği bir alan kararıdır. Sorgu modelinde üçü de aynı satırda,
uygulama katmanının içinde duruyor. Eşiğin 5000 gramdan 4000 grama inmesi orada iki çağrı
yerine dokunmayı gerektirir ve o iki yer alan sözlüğünde “ağır” diye bir kavram bulunmadığı
için aranamaz. Alan modelinde aynı değişiklik AGIR_SINIR_GRAM sabitine dokunur.
Son satır ölçünün ne olmadığını yine söylüyor: beş sorgunun beşinde iki depo aynı gönderileri döndürüyor.
Deponun Alan Diliyle Yazılmasının Sınırı
Alan diliyle yazılmış deponun bedeli yöntem sayısıdır. Her yeni alan sorusu yeni bir yöntem açar ve arayüz büyür; genel sorgu arayüzünde arayüz tek yöntemde kalır, büyüyen şey çağrı yerlerindeki koşullardır.
Ölçü bu yüzden soruların tekrarına bakar. Aynı soru birden çok yerden soruluyorsa yöntemleşmesi kazanç üretir. Yalnız bir yerden sorulan, bir daha sorulmayacak sorularda — bir kereye mahsus bir mutabakat raporu gibi — genel arayüz daha ucuzdur; sorgu nesneleri ve belirtimler kalıbı Veri Erişim Katmanı ve İş Mantığı kursunda tam bu durum için kurulmuştu.
İkinci sınır fabrikadadır. Fabrikanın kazancı, atlanacak yol bırakmamasından gelir; bu ise yapıcıya erişimi kapatmayı ve nesneyi dondurmayı gerektirir. Değişmezi olmayan bir nesnede bu bedelin karşılığı yoktur: dondurmak her güncellemede yeni nesne üretmek demektir ve kuralı olmayan bir nesnede korunacak bir şey de yoktur.
Özet
- Fabrika ve depo bu derste kalıp olarak değil, alan diline uyup uymadıklarıyla ele alındı.
- Yapıcının dışa açık olduğu modelde geçersiz bir gönderi dört yaratım yolunun 4’ünde üretildi ve hiçbiri uyarı vermedi; fabrikadan geçen modelde 0’ında üretildi, dördü hata verdi.
- Fabrikanın kazancı kuralın kaç kez yazıldığı değil, kuralı atlayan yolun kalmamasıdır.
- Sorgu diliyle yazılmış deponun çağrı yeri 8 sütun adı, 7 işleç ve 3 eşik taşıyordu; alan diliyle yazılmış deponun çağrı yerinde üçü de 0, sütun adları iki sınır dosyasında toplandı.
- Sorgu modelinde kalıcılık, sorgu ve alan kararları aynı satırda karışıyor; “ağır” eşiği bir alan kararı olduğu hâlde alan sözlüğünde adı bulunmuyor.
- Beş sorgunun beşinde iki depo aynı sonucu verdi; alan diliyle yazılmış deponun bedeli arayüzün soru sayısıyla büyümesidir.
Sonraki Adım
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 yeniden fiyatlandırıldı, sözleşme indirimi kaldırıldı, tarife değiştiği için tutar yeniden hesaplandı. Bu olguların kodda bir adı olabilir ya da yalnız “kayıt güncellendi” diye geçebilirler. Sonraki ders bir olay kütüğü üzerinde alan uzmanının sorularını yanıtlamayı dener: olay adları alan dilinden geldiğinde kaç soru tek süzmeyle yanıtlanıyor, geldiği yerde kaç olayın alanları karşılaştırılmak zorunda kalıyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.