İçeriğe geç
academia.sh

Ders 27 / 30

Kimlik Haritası

Aynı kimliğin tek nesneyle temsil edilmesi: haritasız ve haritalı koşumda üretilen ayrı örnek sayısının, iki kod yolunun aynı kaydı değiştirmesiyle kaybolan alan sayısının ve haritanın bellekte tuttuğu nesne sayısının ölçülmesi, haritanın yaşam süresi uzadığında ortaya çıkan bayat okumanın gösterilmesi.

İçindekiler

İş birimi kirli satırları tablo:kimlik anahtarıyla topladı ve aynı satır üç kez kirlense de bir kez yazıldı. Bu, yazma tarafında çözülmüş bir tekillik sorunudur. Okuma tarafında aynı sorun açık duruyor: aynı gönderi kimliği iki kez arandığında iki ayrı nesne doğar. Bir kod yolu birinci nesnenin indirimini değiştirir, ikinci kod yolu ikinci nesnenin rotasına aktarma ekler; ikisi de kaydedildiğinde son yazan öteki değişikliği siler.

Kimlik haritası (identity map) bu soruna tek cümlelik bir çözüm koyar: bir kimlik için üretilen nesne saklanır, aynı kimlik yeniden arandığında yeni nesne üretilmez, saklanan döndürülür. Kalıp Veri Erişim Katmanı kursunda üretilen sorgu sayısı üzerinden ölçülmüştü. Burada ölçü nesne düzeyindedir: kaç ayrı örnek doğdu, kaç alan kayboldu, harita bellekte kaç nesne tuttu.

Problem: Aynı Kimlik, İki Nesne

Deponun okuma davranışı sorunun kaynağıdır. Bir depo satırın kendisini değil kopyasını verir; aksi hâlde çağıran taraf saklanan satırı doğrudan değiştirebilirdi.

// bellek-depo.mjs — her okumada satirin yeni bir kopyasini veren sade depo
const TABLOLAR = new Map();
const tablo = (ad) => TABLOLAR.get(ad) ?? TABLOLAR.set(ad, new Map()).get(ad);

export const depo = {
  oku: (ad, id) => structuredClone(tablo(ad).get(id) ?? null),
  yaz: (ad, satir) => { tablo(ad).set(satir.id, structuredClone(satir)); },
};
// gonderi.mjs — kimligi olan alan nesnesi
export class Gonderi {
  constructor({ id, agirlik, indirim, aktarma }) {
    Object.assign(this, { id, agirlik, indirim, aktarma });
  }
  net() { return Math.round(3900 * this.agirlik * (1 - this.indirim)); }
}

Kopya vermek doğru karardır ama bir yan sonucu vardır: iki arama iki bağımsız nesne üretir ve ikisi de aynı kaydı temsil ettiğini bilmez.

Çözüm

Harita, üretim işini kendi içine alır. Nesnenin nasıl üretileceğini bilmez; yalnız daha önce üretilip üretilmediğini bilir.

// kimlik-haritasi.mjs — istek boyunca kimlik basina tek nesne
export function kimlikHaritasi() {
  const harita = new Map();
  return {
    al(tur, id, uret) {
      const anahtar = `${tur}:${id}`;
      if (harita.has(anahtar)) return harita.get(anahtar);
      const nesne = uret();
      harita.set(anahtar, nesne);
      return nesne;
    },
    tutulan: () => harita.size,
  };
}

Anahtarın tür adını da içermesi zorunludur: iki ayrı türün aynı kimliği taşıması sıradan bir durumdur. Erişim noktası tek bir yerde kalır.

// kaynak.mjs — ayni esleyici; harita verilirse ayni kimlik ayni nesneyi dondurur
import { depo } from "./bellek-depo.mjs";
import { Gonderi } from "./gonderi.mjs";

export const kaynak = (harita) => ({
  bul(id) {
    const uret = () => {
      const satir = depo.oku("gonderi", id);
      return satir === null ? null : new Gonderi(satir);
    };
    return harita === null ? uret() : harita.al("gonderi", id, uret);
  },
  kaydet: (g) => depo.yaz("gonderi", { id: g.id, agirlik: g.agirlik, indirim: g.indirim, aktarma: g.aktarma }),
});

Tekilliğin Sayılması

Ölçüm iki koşum yapar. Beş arama üç ayrı kimliğe gider; ardından aynı kimlik iki kez aranır, iki kod yolu ayrı alanları değiştirir ve ikisi de kaydedilir.

// tekillik-say.mjs — haritasiz ve haritali kosumda ayri ornek, kayip alan ve tutulan nesne
import { depo } from "./bellek-depo.mjs";
import { kaynak } from "./kaynak.mjs";
import { kimlikHaritasi } from "./kimlik-haritasi.mjs";

const KIMLIKLER = ["G1", "G2", "G3"];
const ARAMALAR = ["G1", "G2", "G3", "G2", "G1"];

function kosu(ad, harita) {
  for (const id of KIMLIKLER) depo.yaz("gonderi", { id, agirlik: 3, indirim: 0, aktarma: ["34"] });
  const k = kaynak(harita);
  const ornekler = new Set(ARAMALAR.map((id) => k.bul(id)));

  const ucret = k.bul("G2"), yon = k.bul("G2");
  ucret.indirim = 0.15;
  yon.aktarma.push("06");
  k.kaydet(ucret);
  k.kaydet(yon);

  const son = depo.oku("gonderi", "G2");
  const beklenen = { indirim: 0.15, aktarma: 2 };
  const kayip = (son.indirim === beklenen.indirim ? 0 : 1) + (son.aktarma.length === beklenen.aktarma ? 0 : 1);
  console.log(`${ad}`);
  console.log(`  ${ARAMALAR.length} arama -> ayri ornek = ${ornekler.size}   ayni kimlik ayni nesne mi = ${ucret === yon}`);
  console.log(`  yazma sonrasi indirim = ${son.indirim}, aktarma = ${son.aktarma.length}, kayip alan = ${kayip}`);
  console.log(`  haritada tutulan nesne = ${harita === null ? 0 : harita.tutulan()}`);
}

kosu("haritasiz", null);
kosu("kimlik haritasi ile", kimlikHaritasi());
node tekillik-say.mjs
haritasiz
  5 arama -> ayri ornek = 5   ayni kimlik ayni nesne mi = false
  yazma sonrasi indirim = 0, aktarma = 2, kayip alan = 1
  haritada tutulan nesne = 0
kimlik haritasi ile
  5 arama -> ayri ornek = 3   ayni kimlik ayni nesne mi = true
  yazma sonrasi indirim = 0.15, aktarma = 2, kayip alan = 0
  haritada tutulan nesne = 3

Beş arama haritasız düzende 5 ayrı nesne üretti, haritalı düzende 3 — kimlik sayısı kadar. Aynı kimliği iki kez arayan iki kod yolu haritasız düzende iki farklı nesne aldı ve === karşılaştırması yanlış sonuç verdi; harita ile aynı nesneyi aldılar.

Kayıp alan sayısı farkın en somut biçimidir: haritasız koşumda indirim değişikliği kayboldu, kaydedilen satırda indirim 0 kaldı. Haritalı koşumda iki değişiklik de aynı nesnenin üzerinde birikti ve ikisi birlikte yazıldı.

Karşılıkta bir sayı daha var: harita 3 nesneyi bellekte tutuyor. Haritasız düzende tutulan nesne 0’dır, üretilen nesneler kullanıldıktan sonra bırakılır. Kalıbın bedeli tam olarak budur ve ikinci bir sonuç doğurur.

Haritanın Yaşam Süresi

Harita bir nesneyi tuttuğu sürece o nesne deponun sonraki hâlini görmez.

// bayat.mjs — harita yasarken depo degisirse okunan deger hangisi olur
import { depo } from "./bellek-depo.mjs";
import { kaynak } from "./kaynak.mjs";
import { kimlikHaritasi } from "./kimlik-haritasi.mjs";

for (const [ad, harita] of [["haritasiz", null], ["kimlik haritasi ile", kimlikHaritasi()]]) {
  depo.yaz("gonderi", { id: "G9", agirlik: 3, indirim: 0.1, aktarma: ["34"] });
  const k = kaynak(harita);
  const ilk = k.bul("G9").indirim;
  depo.yaz("gonderi", { id: "G9", agirlik: 3, indirim: 0.3, aktarma: ["34"] });
  const ikinci = k.bul("G9").indirim;
  console.log(`${ad.padEnd(20)} ilk okuma = ${ilk}, depo 0.3 oldu, ikinci okuma = ${ikinci}, bayat = ${ikinci !== 0.3}`);
}
node bayat.mjs
haritasiz            ilk okuma = 0.1, depo 0.3 oldu, ikinci okuma = 0.3, bayat = false
kimlik haritasi ile  ilk okuma = 0.1, depo 0.3 oldu, ikinci okuma = 0.1, bayat = true

İkinci okuma haritasız düzende yeni değeri, haritalı düzende ilk değeri verdi. Bu bir kusur değil, kalıbın tanımının gereğidir: harita bir nesnenin tekilliğini garanti eder, tazeliğini garanti etmez. Buradan kalıbın uygulanma sınırı çıkar. Harita bir işin — tek bir isteğin ya da tek bir iş biriminin — sınırında yaşar ve o sınır bittiğinde bırakılır. Süreç boyunca yaşayan bir harita bir önbelleğe dönüşür ve tazelik sorusu sorulmaya başlar; o soru kimlik haritasının cevapladığı soru değildir.

Ne Zaman Uygulanmaz

Kalıbın kazancı iki koşuldan doğar: nesnenin bir kimliği olması ve değiştirilebilir olması. İkisinden biri yoksa kazanç sıfırlanır.

Değer nesnelerinde kimlik yoktur; iki bölge katsayısı aynı değeri taşıyorsa birbirinin yerine geçer ve eşitlik değere göre kurulur. Değiştirilemez bir değer nesnesinde iki ayrı örneğin birbirini ezmesi de olanaksızdır, dolayısıyla ölçümdeki kayıp alan sayısı harita olmadan da 0’dır. Kazanç 0, bedel ise haritanın tuttuğu nesne sayısı kadar.

Tek okuma yapan senaryolarda da kazanç yoktur: ayrı örnek sayısı zaten arama sayısına eşittir ve arama sayısı 1’dir. Son sınır ölçekle ilgilidir. Harita süreç yereldir; iki ayrı süreç aynı kaydı yüklerse her birinin haritası kendi nesnesini tutar ve tekillik süreçler arasında korunmaz. Süreçler arası aynı anda yazma sorunu kilitleme konusuna aittir ve Veri Erişim Katmanı kursunda ölçülmüştür.

Özet

  • Kimlik haritası bir kimlik için üretilen nesneyi saklar ve aynı kimlik yeniden arandığında yeni nesne üretmek yerine saklananı döndürür.
  • Beş arama haritasız düzende 5 ayrı örnek, haritalı düzende kimlik sayısı kadar 3 örnek üretti; aynı kimliği arayan iki kod yolu haritasız düzende === karşılaştırmasını geçemedi.
  • İki kod yolu aynı kaydın ayrı alanlarını değiştirdiğinde haritasız koşumda 1 alan kayboldu; haritalı koşumda iki değişiklik aynı nesnede birikti ve kayıp 0 oldu.
  • Bedel bellekte tutulan nesne sayısıdır (3’e karşı 0) ve ikinci bedel bayat okumadır: depo değiştiğinde haritalı düzen ilk değeri vermeye devam etti.
  • Harita tek bir işin sınırında yaşar; değer nesnelerinde ve tek okumalı senaryolarda kazanç sıfırlanır, süreçler arasında tekillik korunmaz.

Sonraki Adım

Buraya kadarki dört kalıp iş mantığı ile kalıcılık arasındaki sınırı düzenledi. Alan nesnesi artık kuralı taşıyor, kalıcılığı bilmiyor ve bellekte tekil. Ama bu nesne kitaplığın dışına da çıkacak: bir arayüz onu listeleyecek, bir taşıyıcı sistemi ona rota soracak, bir fatura dökümü ondan üretilecek. Alan nesnesinin dışarıya olduğu gibi verilmesi iç yapıyı dış sözleşme hâline getirir ve iç yapı her değiştiğinde dış sözleşme de değişir. Sonraki ders katmanlar arası veri taşımayı veri aktarım nesneleri ve eşleyicilerle kurar, iç modelde bir alan yeniden adlandırıldığında dokunulan dosya sayısını ve eşlemenin satır başına bedelini sayar.

İ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