İçeriğe geç
academia.sh

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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat