İçeriğe geç
academia.sh

Ders 02 / 27

Bildirimsel ve Buyurgan Yaklaşım

Aynı dokuz kalemlik hedef iki biçimde yazılır — dört satırlık istenen durum tanımı ve dokuz satırlık adım listesi — ve üçüncü adımda hata çıkacak biçimde koşturulur. Kısmi başarısızlıktan sonra hedefe ulaşan kalem, yeniden koşumda üretilen boş iş ile hata, ve hedef değiştiğinde yazılan satır sayılır.

İçindekiler

Bir önceki ders kaydı bir kalem listesi olarak yazdı: her satır bir kaynağın ne olması gerektiğini söylüyordu. Aynı ortam bir adım listesi olarak da yazılabilir — önce ağı aç, sonra depoyu aç, sonra makineyi bağla — ve o liste de bir dosyada durur, sürümlenir, gözden geçirilir. İki biçim de kayıttır ve ikisi de aynı ek adımları ister.

Bildirimsel tarif istenen durumu yazar; buyurgan tarif oraya götüren adımları yazar. M22/K01 DevOps Kültürü ve Temelleri kursundaki Altyapı Sağlama Modelleri dersi elle, betikle ve bildirimsel sağlamayı karşılaştırmış, sapmanın neden yalnız bildirimsel modelde sayılabildiğini göstermişti; o karşılaştırma burada tekrarlanmıyor. Bu ders ölçüyü başka bir eksene taşır: kısmi başarısızlık. Aynı hedef iki biçimde yazılır, üçüncü adımda hata çıkacak biçimde koşturulur, ve üç şey sayılır — hatadan sonra hedefe ulaşan kalem, aynı tarif yeniden koşturulunca üretilen iş, ve hedef değiştiğinde yazılan satır.

Ortam yine bölgesel ölçüm ağıdır: sayaç okumalarını toplayan, doğrulayan ve faturaya çeviren kurgu yazılım. Altyapı sağlanmaz; ortam, işlemler ve kota node ile modellenir. Tarif dosyaları gerçekten diske yazılır.

AT7. Hedef dört kaynak ve dokuz kalemdir; kaynağın kendisi de bir kalemdir, nitelikleri de. AT8. Buyurgan tarif dokuz satırlık bir adım listesi, bildirimsel tarif dört satırlık bir tanımdır; ikisi de aynı dokuz kalemi hedefler. AT9. yarat var olan kaynakta hata verir; ayarla aynı değere yazıldığında boş yazma sayılır. Etkisizliğin olup olmadığı bu iki kuralda kararlaşır. AT10. İlk koşumda veri deposu kotası sıfırdır ve üçüncü adım düşer; kota ikinci koşumdan önce bire çıkarılır. Hata önceden yazılıdır, rastgelelik yoktur. AT11. Bildirimsel uygulayıcı kalemleri bağımlılık sırasına göre uygular; bağımlılığı karşılanmayan kalem atlanır, hata sayılmaz. AT12. Buyurgan koşucunun iki modu vardır: ilk hatada durur ya da hatayı atlayarak devam eder.

Aynı Hedef, İki Biçim

#!/usr/bin/env bash
# Ayni hedefin iki bicimi: bildirimsel tanim ve buyurgan adim listesi. Ikinci surumler de yazilir.
set -e

cat > hedef.alt <<'SON'
kaynak ag "olcum-agi" { blok = "10.40.0.0/16" }
kaynak veri_deposu "olcum-deposu" { boyut_gb = 200 }
kaynak makine "toplayici" { sayi = 3  bagli = "veri_deposu.olcum-deposu" }
kaynak kuyruk "okuma-kuyrugu" { bolum = 6 }
SON

cat > adimlar.txt <<'SON'
yarat ag.olcum-agi
ayarla ag.olcum-agi.blok 10.40.0.0/16
yarat veri_deposu.olcum-deposu
ayarla veri_deposu.olcum-deposu.boyut_gb 200
yarat makine.toplayici
ayarla makine.toplayici.sayi 3
yarat kuyruk.okuma-kuyrugu
ayarla kuyruk.okuma-kuyrugu.bolum 6
bagla makine.toplayici veri_deposu.olcum-deposu
SON

# Hedef degisir: toplayici 3 -> 5, kuyruk bolumu 6 -> 12, bir nesne deposu eklenir.
cat > hedef-v2.alt <<'SON'
kaynak ag "olcum-agi" { blok = "10.40.0.0/16" }
kaynak veri_deposu "olcum-deposu" { boyut_gb = 200 }
kaynak makine "toplayici" { sayi = 5  bagli = "veri_deposu.olcum-deposu" }
kaynak kuyruk "okuma-kuyrugu" { bolum = 12 }
kaynak nesne_deposu "arsiv" { sinif = "seyrek" }
SON

cat > adimlar-v2.txt <<'SON'
varsa-atla yarat ag.olcum-agi
ayarla ag.olcum-agi.blok 10.40.0.0/16
varsa-atla yarat veri_deposu.olcum-deposu
ayarla veri_deposu.olcum-deposu.boyut_gb 200
varsa-atla yarat makine.toplayici
ayarla makine.toplayici.sayi 5
varsa-atla yarat kuyruk.okuma-kuyrugu
ayarla kuyruk.okuma-kuyrugu.bolum 12
varsa-atla bagla makine.toplayici veri_deposu.olcum-deposu
varsa-atla yarat nesne_deposu.arsiv
ayarla nesne_deposu.arsiv.sinif seyrek
SON

Bildirimsel tanım dört satır, adım listesi dokuz satırdır ve ikisi de aynı dokuz kalemi taşır. Aradaki asıl fark boyut değil: tanımda bagli bir niteliktir ve makinenin veri deposuna bağlı olduğunu söyler; listede bagla en sondaki bir adımdır ve bağlamanın ne zaman yapılacağını söyler. İlki bir bağımlılık, ikincisi bir sıra bildirir.

// motor.mjs — ortam modeli ve iki kosucu; ikisi de modeldir, gercek altyapi saglanmaz.
import { readFileSync } from "node:fs";

export const yeniDunya = (kota) => ({ kaynak: new Set(), kalem: new Map(), kota,
  denenen: 0, basarili: 0, bos: 0, hata: 0, atlanan: 0 });
export const kopya = (d) => ({ ...d, kaynak: new Set(d.kaynak), kalem: new Map(d.kalem),
  denenen: 0, basarili: 0, bos: 0, hata: 0, atlanan: 0 });

export const islem = (d, ad, hedef, deger) => {           // yarat / ayarla / bagla
  d.denenen++;
  if (ad === "yarat") {
    if (d.kaynak.has(hedef)) return { ok: false, neden: "zaten var" };
    const tur = hedef.split(".")[0];
    const n = [...d.kaynak].filter((k) => k.startsWith(tur + ".")).length;
    if (n >= (d.kota[tur] ?? 9)) return { ok: false, neden: tur + " kotasi dolu" };
    d.kaynak.add(hedef); d.basarili++; return { ok: true };
  }
  const anahtar = ad === "bagla" ? hedef + ".bagli" : hedef;
  const sahip = anahtar.split(".").slice(0, 2).join(".");
  if (!d.kaynak.has(sahip)) return { ok: false, neden: "kaynak yok" };
  if (ad === "bagla" && !d.kaynak.has(deger)) return { ok: false, neden: "hedef kaynak yok" };
  if (d.kalem.get(anahtar) === deger) { d.bos++; return { ok: true, bos: true }; }
  d.kalem.set(anahtar, deger); d.basarili++; return { ok: true };
};

// Buyurgan: adim listesini sirayla kosar. mod "dur" ilk hatada durur, "gecerek" hatayi atlar.
export const buyurgan = (d, satirlar, mod) => {
  const gunluk = [];
  for (const [i, ham] of satirlar.entries()) {
    const koruma = ham.startsWith("varsa-atla ");
    const [ad, hedef, deger] = ham.replace("varsa-atla ", "").split(" ");
    if (koruma && ad === "yarat" && d.kaynak.has(hedef)) { d.atlanan++; continue; }
    if (koruma && ad === "bagla" && d.kalem.get(hedef + ".bagli") === deger) { d.atlanan++; continue; }
    const r = islem(d, ad, hedef, deger);
    if (!r.ok) { d.hata++; gunluk.push(`adim ${i + 1} (${ad} ${hedef}): ${r.neden}`);
      if (mod === "dur") return { gunluk, durulanAdim: i + 1, denenmeyen: satirlar.length - i - 1 }; }
  }
  return { gunluk, durulanAdim: 0, denenmeyen: 0 };
};

const BLOK = /^kaynak\s+(\w+)\s+"([\w-]+)"\s*\{(.*)\}$/;   // 01'deki yapilandirma dilinin ayni
export const hedefiOku = (yol) => {
  const k = new Map();
  for (const s of readFileSync(yol, "utf8").split("\n")) {
    const m = BLOK.exec(s.trim());
    if (!m) continue;
    k.set(`${m[1]}.${m[2]}`, "var");                       // kaynagin kendisi de bir kalemdir
    for (const x of m[3].matchAll(/(\w+)\s*=\s*("[^"]*"|[\w./-]+)/g))
      k.set(`${m[1]}.${m[2]}.${x[1]}`, x[2].replaceAll('"', ""));
  }
  return k;
};

// Bildirimsel: fark hesaplanir, bagimliligi karsilanmayan kalem atlanir, sira derinlige gore.
export const bildirimsel = (d, hedef) => {
  const gunluk = [];
  const fark = [...hedef].filter(([k, v]) => (k.split(".").length === 2
    ? !d.kaynak.has(k) : d.kalem.get(k) !== v));
  for (const [k, v] of fark.sort((a, b) => a[0].split(".").length - b[0].split(".").length)) {
    const gerek = [k.split(".").length > 2 ? k.split(".").slice(0, 2).join(".") : null,
      k.endsWith(".bagli") ? v : null].filter(Boolean);
    if (gerek.some((g) => !d.kaynak.has(g))) { d.atlanan++; gunluk.push(`${k}: bagimlilik yok`); continue; }
    const [ad, hedefAd] = k.split(".").length === 2 ? ["yarat", k]
      : k.endsWith(".bagli") ? ["bagla", k.split(".").slice(0, 2).join(".")] : ["ayarla", k];
    const r = islem(d, ad, hedefAd, v);
    if (!r.ok) { d.hata++; gunluk.push(`${k}: ${r.neden}`); }
  }
  return { gunluk, fark: fark.length };
};

export const rapor = (ad, d, ek = "") =>
  `  ${ad.padEnd(30)} denenen ${d.denenen}  uygulanan ${d.basarili}  bos yazma ${d.bos}` +
  `  hata ${d.hata}  atlanan ${d.atlanan}${ek}`;

Üçüncü Adımda Hata

// senaryo.mjs — ayni hedef iki bicimde: kismi basarisizlik, yeniden kosum, ucuncu kosum.
import { readFileSync } from "node:fs";
import { yeniDunya, kopya, buyurgan, bildirimsel, hedefiOku, rapor } from "./motor.mjs";

const satir = (y) => readFileSync(y, "utf8").trim().split("\n");
const adimlar = satir("adimlar.txt"), hedef = hedefiOku("hedef.alt");
const dar = { veri_deposu: 0 }, genis = { veri_deposu: 1 };   // ucuncu adim dar kotada duser

console.log(`hedef ${hedef.size} kalem   adim listesi ${adimlar.length} satir\n`);
console.log("A. kismi basarisizlik (veri deposu kotasi 0)");
const bA = yeniDunya(dar), sA = buyurgan(bA, adimlar, "dur");
console.log(rapor("buyurgan (ilk hatada durur)", bA,
  `  denenmeyen adim ${sA.denenmeyen}`));
console.log(`     ${sA.gunluk.join("; ")}   nerede kalindigini yazan kayit: 0 dosya`);
const dA = yeniDunya(dar), tA = bildirimsel(dA, hedef);
console.log(rapor("bildirimsel (fark uygular)", dA, `  fark ${tA.fark}`));
console.log(`     ${tA.gunluk.join("; ")}`);
console.log(`  hedefe ulasan kalem   buyurgan ${bA.kaynak.size + bA.kalem.size}/${hedef.size}` +
  `   bildirimsel ${dA.kaynak.size + dA.kalem.size}/${hedef.size}\n`);

console.log("B. ikinci kosum (kota yukseltildi, tarif degismedi)");
const bB = kopya({ ...bA, kota: genis }), sB = buyurgan(bB, adimlar, "dur");
console.log(rapor("buyurgan (ayni liste bastan)", bB, `  denenmeyen adim ${sB.denenmeyen}`));
console.log(`     ${sB.gunluk.join("; ")}`);
const kisa = adimlar.filter((a) => a !== "yarat ag.olcum-agi");   // elle cikarilan 1 satir
const bB2 = kopya({ ...bA, kota: genis });
buyurgan(bB2, kisa, "dur");
console.log(rapor("buyurgan (1 satir cikarildi)", bB2, `  elle duzenleme 1 satir`));
const dB = kopya({ ...dA, kota: genis }), tB = bildirimsel(dB, hedef);
console.log(rapor("bildirimsel (ayni tarif)", dB, `  fark ${tB.fark}, elle duzenleme 0 satir`));
console.log(`  hedefe ulasan kalem   buyurgan ${bB2.kaynak.size + bB2.kalem.size}/${hedef.size}` +
  `   bildirimsel ${dB.kaynak.size + dB.kalem.size}/${hedef.size}\n`);

console.log("C. ucuncu kosum (hedefe ulasildi, tarif yeniden kosuluyor)");
const bC = kopya({ ...bB2, kota: genis });
buyurgan(bC, adimlar, "gecerek");
console.log(rapor("buyurgan (hatayi atlayarak)", bC));
const dC = kopya({ ...dB, kota: genis }), tC = bildirimsel(dC, hedef);
console.log(rapor("bildirimsel", dC, `  fark ${tC.fark}`));

console.log("\nD. hedef degisti (toplayici 3 -> 5, kuyruk bolumu 6 -> 12, nesne deposu eklendi)");
const hedef2 = hedefiOku("hedef-v2.alt"), adimlar2 = satir("adimlar-v2.txt");
const bD = kopya({ ...bC, kota: genis });
buyurgan(bD, adimlar2, "gecerek");
console.log(rapor("buyurgan v2 (korumali liste)", bD, `  liste ${adimlar2.length} satir`));
const dD = kopya({ ...dC, kota: genis }), tD = bildirimsel(dD, hedef2);
console.log(rapor("bildirimsel v2", dD, `  fark ${tD.fark}, tanim ${hedef2.size} kalem`));
hedef 9 kalem   adim listesi 9 satir

A. kismi basarisizlik (veri deposu kotasi 0)
  buyurgan (ilk hatada durur)    denenen 3  uygulanan 2  bos yazma 0  hata 1  atlanan 0  denenmeyen adim 6
     adim 3 (yarat veri_deposu.olcum-deposu): veri_deposu kotasi dolu   nerede kalindigini yazan kayit: 0 dosya
  bildirimsel (fark uygular)     denenen 7  uygulanan 6  bos yazma 0  hata 1  atlanan 2  fark 9
     veri_deposu.olcum-deposu: veri_deposu kotasi dolu; veri_deposu.olcum-deposu.boyut_gb: bagimlilik yok; makine.toplayici.bagli: bagimlilik yok
  hedefe ulasan kalem   buyurgan 2/9   bildirimsel 6/9

B. ikinci kosum (kota yukseltildi, tarif degismedi)
  buyurgan (ayni liste bastan)   denenen 1  uygulanan 0  bos yazma 0  hata 1  atlanan 0  denenmeyen adim 8
     adim 1 (yarat ag.olcum-agi): zaten var
  buyurgan (1 satir cikarildi)   denenen 8  uygulanan 7  bos yazma 1  hata 0  atlanan 0  elle duzenleme 1 satir
  bildirimsel (ayni tarif)       denenen 3  uygulanan 3  bos yazma 0  hata 0  atlanan 0  fark 3, elle duzenleme 0 satir
  hedefe ulasan kalem   buyurgan 9/9   bildirimsel 9/9

C. ucuncu kosum (hedefe ulasildi, tarif yeniden kosuluyor)
  buyurgan (hatayi atlayarak)    denenen 9  uygulanan 0  bos yazma 5  hata 4  atlanan 0
  bildirimsel                    denenen 0  uygulanan 0  bos yazma 0  hata 0  atlanan 0  fark 0

D. hedef degisti (toplayici 3 -> 5, kuyruk bolumu 6 -> 12, nesne deposu eklendi)
  buyurgan v2 (korumali liste)   denenen 6  uygulanan 4  bos yazma 2  hata 0  atlanan 5  liste 11 satir
  bildirimsel v2                 denenen 4  uygulanan 4  bos yazma 0  hata 0  atlanan 0  fark 4, tanim 11 kalem

Üçüncü adımdaki hata iki biçimde çok farklı bir maliyet üretti. Buyurgan liste durduğunda iki kalem yerindeydi ve altı adım hiç denenmemişti — oysa o altı adımın beşinin veri deposuyla hiçbir ilgisi yok. Liste bir sıra bildirdiği için sıradaki hata sonrasını da durdurur; makine ve kuyruk kotadan etkilenmediği hâlde açılmadan kaldı. Bildirimsel uygulayıcı aynı hatayı aldı ve altı kalemi yerine koydu; yalnız veri deposuna gerçekten bağımlı iki kalemi atladı. Aradaki dört kalemlik fark bir uygulayıcı hızından değil, tarifin ne bildirdiğinden geliyor: bağımlılık bildirilirse hata yalnız kendi alt ağacını durdurur, sıra bildirilirse hata kuyruğun tamamını.

Ortada bırakılan durumun okunabilirliği de ayrıdır. Buyurgan koşumun geriye bıraktığı tek bilgi durduğu adım numarasıdır ve ortamın hangi kaleminin yerinde olduğunu ayrıca bakmak gerekir; nerede kalındığını yazan bir kayıt yoktur. Bildirimsel koşum ise bir sonraki koşumda farkı yeniden hesaplar — “nerede kalındı” sorusu sorulmaz, çünkü yanıt her koşumda baştan üretilir.

Yeniden Koşumda Üretilen İş

İkinci koşum aynı ayrımı başka bir yerden gösterir. Kota yükseltildikten sonra buyurgan liste baştan koşturulduğunda birinci adımda düşer: ağ zaten vardır ve yarat var olan kaynakta hata verir. Sekiz adım denenmez. Listeyi ilerletmek için bir satırın elle çıkarılması gerekir; bu çıkarma ortamın o anki durumuna göre yapılır ve başka bir ortamda başka bir satır olur. Bildirimsel tarif ise hiç düzenlenmeden koşar, farkı üç kalem bulur, üçünü uygular.

Üçüncü koşum etkisizliği doğrudan sayar. Hedefe ulaşılmış bir ortamda buyurgan liste hatayı atlayarak koşturulduğunda dokuz adımın dördü hata verir ve beşi boş yazma üretir: aynı değer aynı kaleme yeniden yazılır, ortam değişmez, ama dokuz işlem yine de denenmiştir. Bildirimsel tarif aynı ortamda sıfır fark bulur ve hiçbir işlem denemez. Bu ikisi arasındaki fark bir verimlilik ayrıntısı değil: dokuz denemenin dördü hata olarak görünür ve bir sonraki gerçek hatayı bu dört hatanın arasından ayırmak gerekir.

Hedef Değiştiğinde Yazılan Satır

#!/usr/bin/env bash
# Hedef degistiginde yazilan satir: iki tarifin birinci ve ikinci surumu karsilastirilir.
printf 'tarif buyuklugu (satir)   bildirimsel %s -> %s   buyurgan %s -> %s\n' \
  "$(wc -l < hedef.alt | tr -d ' ')" "$(wc -l < hedef-v2.alt | tr -d ' ')" \
  "$(wc -l < adimlar.txt | tr -d ' ')" "$(wc -l < adimlar-v2.txt | tr -d ' ')"
printf 'hedef degisiminde yazilan satir   bildirimsel %s   buyurgan %s\n' \
  "$(diff hedef.alt hedef-v2.alt | grep -c '^>')" \
  "$(diff adimlar.txt adimlar-v2.txt | grep -c '^>')"
printf 'bunun %s satiri koruma eklemesi, %s satiri deger degisikligi, %s satiri yeni kaynak\n' \
  "$(diff adimlar.txt adimlar-v2.txt | grep '^> varsa-atla' | grep -vc arsiv)" \
  "$(diff adimlar.txt adimlar-v2.txt | grep '^> ayarla' | grep -vc arsiv)" \
  "$(diff adimlar.txt adimlar-v2.txt | grep '^>' | grep -c arsiv)"
tarif buyuklugu (satir)   bildirimsel 4 -> 5   buyurgan 9 -> 11
hedef degisiminde yazilan satir   bildirimsel 3   buyurgan 9
bunun 5 satiri koruma eklemesi, 2 satiri deger degisikligi, 2 satiri yeni kaynak

Aynı hedef değişikliği için bildirimsel tanımda üç, adım listesinde dokuz satır yazıldı. Dokuzun yalnız dördü hedefin kendisiyle ilgili — iki değer değişikliği ve yeni kaynağın iki satırı. Kalan beş satır korumadır: her yarat adımının önüne “varsa atla” yazılır, yoksa liste ikinci kez koşamaz. Bu beş satır D bölümünde işini yapıyor: koşum sıfır hatayla biter, beş adım atlanır, dört kalem uygulanır. Bedeli iki boş yazmadır — koruma kaynağın varlığını okur, niteliğin değerini okumaz.

Burada görülen şey bir dönüşümdür. Buyurgan listeyi yeniden koşulabilir kılmanın yolu her adımın önüne bir durum okuması koymaktır; bu okumalar tamamlandığında liste artık “ne yapılacağını” değil, “ne olması gerektiğini” söylemeye başlar. İki biçim arasındaki sınır bu noktada silinir ve kalan tek fark, o okumaların tarifin içine elle mi yazıldığı yoksa uygulayıcıya mı bırakıldığıdır. Bildirimsel biçimin kazancı bir üstünlük değil, bir tekrar etmemedir: aynı okuma dokuz satırda dokuz kez yazılmaz, uygulayıcıda bir kez yazılır.

Özet

  • Üçüncü adımdaki hatadan sonra hedefe ulaşan kalem buyurgan biçimde 2/9, bildirimsel biçimde 6/9 oldu; buyurgan koşumda denenmeyen 6 adımın 5’i hatayla ilgisizdi.
  • Buyurgan koşum geriye adım numarasından başka kayıt bırakmadı; bildirimsel koşum bir sonraki koşumda farkı baştan hesapladığı için “nerede kalındı” sorusunu sormadı.
  • İkinci koşumda buyurgan liste 1. adımda düştü ve ilerlemesi için 1 satır elle çıkarıldı; bildirimsel tarif 0 satır düzenlemeyle koştu ve 3 kalem uyguladı.
  • Hedefe ulaşılmış ortamda buyurgan liste 9 işlem denedi (4 hata, 5 boş yazma), bildirimsel tarif 0 işlem denedi.
  • Hedef değişince bildirimsel tanımda 3, adım listesinde 9 satır yazıldı; 9’un 5’i yalnız listeyi yeniden koşulabilir kılan koruma satırıydı.

Sonraki Adım

Buraya kadar iki tarif de aynı şeyi yaptı: var olan bir kaynağı yerinde değiştirdi. Kuyruğun bölüm sayısı altıdan on ikiye çıktığında kuyruk aynı kuyruktu; makinenin sayısı üçten beşe çıktığında var olan üç makineye dokunulmadı. Bu, altyapıyı değiştirmenin tek yolu değil.

Sonraki ders aynı güncellemeyi iki yolla yapar — yerinde yama ve yeniden oluşturma — ve dört şeyi sayar: bir dönem sonunda kaç makinenin birbirinden ayrıştığı, güncelleme süresi, geri alma turu, ve yeniden oluşturmanın taşıyamadığı şey. Yeniden oluşturmanın hiçbir şey kazandırmadığı durum da aynı ölçüyle gösterilir.

İ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