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.