Ders 07 / 12
Şablon ve İskelet Üretimi
Yeni bir servis başlatmak elle kaç dosya, kaç satır ve kaç karar demek; iskelet üretimiyle bunların kaçı kalıyor. Beş servis gerçek dosyalarla üretilir, elle yazılmış bir servisle satır satır karşılaştırılır, sonra şablon güncellenince kaç servisin geride kaldığı ve geride kalanı yakalamanın neye mal olduğu ölçülür.
İçindekiler
Önceki ders ortamı istendiği anda var eden yolu ölçtü: bekleme on saate indi, boşta duran ortam-günü yarıya yakın düştü. Ortam artık hazır, ama içine konacak şey hâlâ boş bir dizinden başlıyor. Ölçüm ağına altıncı bir servis eklemek gerektiğinde ekip kütük biçiminden sağlık ucuna, hat adımlarından yayınlanacak ölçütlere kadar her kararı yeniden veriyor — ve beş ekip bu kararları birbirinden habersiz veriyor.
İskelet üretimi, yeni bir servisin başlangıç dosyalarını bir şablon deposundan üreten akıştır. Şablonun kendi bedeli — değişken sayısı, koşullu dal, kopyalar arasındaki sürüklenme — M22/K03’ün paketleme ve şablonlama dersinde ölçüldü ve burada tekrarlanmaz. Bu dersin ölçtüğü iki şey vardır: başlatma anında neyin yazılmadığı, ve üretimden sonra şablonun ardında ne bıraktığı.
Ölçüm bölgesel ölçüm ağı üzerindedir: su sayacı okumalarını toplayan, doğrulayan ve saha iş emri açan kurgu yazılım. Bu dersteki dosyalar gerçekten diske yazılır ve diskten okunarak sayılır; dosya biçimleri ürün adı taşımaz, ders içinde tanımlanan kendi küçük biçimimizdir.
PT7. Ölçüm ağına eklenen bir servis altı dosyayla başlar: servis tanımı, hat tanımı, ölçüt tanımı, kütük tanımı, duman sınaması ve belge. PT8. Şablon deposu sürüm 1’de üç soru sorar: ad, sahip, kapı. PT9. Sayaç okuma ekibi kendi servisini iskeletten önce elle yazmıştır; o dosyalar modelde birebir durur ve karşılaştırma onların üzerinden yapılır. PT10. Üretim gerçekten koşar; sayılan dosya ve satırlar diskten okunur. PT11. Üretimden sonraki yerel düzenlemeler önceden yazılıdır, rastgelelik yoktur. PT12. Şablon sürüm 2 üç değişiklik getirir ve bir yeni soru ekler. Üretilmiş dosyalar şablona bağlı değildir; şablon değişince kendiliklerinden değişmezler.
Şablon Deposu ve Üç Soru
// sablon.mjs — sablon deposu (surum 1), bes servisin cevaplari, elle yazilmis bir servis. Model. export const SURUM = 1; export const SABLON = { "servis.tanim": `servis {{ad}} sahip {{sahip}} kapi {{kapi}} saglik-ucu /saglik kutuk-bicimi satir-json kutuk-duzeyi ozet`, "hat.tanim": `hat {{ad}} adim derle adim birim-sinama adim paket adim dagit-sinama adim duman-sinama adim dagit-uretim`, "olcut.tanim": `olcut {{ad}} sayac islenen_kayit sayac hatali_kayit olcek istek_suresi_ms`, "kutuk.tanim": `kutuk {{ad}} alan zaman alan duzey alan istek-kimligi`, "duman.tanim": `duman {{ad}} cagri /saglik bekle 200 cagri /olcut bekle 200`, "BELGE.md": `# {{ad}} sahip {{sahip}}, kapi {{kapi}}`, }; export const CEVAP = [ // surum 1 uc soru sorar { ad: "okuma-toplayici", sahip: "sayac-okuma", kapi: 8080 }, { ad: "dogrulama-akisi", sahip: "dogrulama", kapi: 8081 }, { ad: "fatura-hesaplayici", sahip: "faturalama", kapi: 8082 }, { ad: "saha-emir", sahip: "saha", kapi: 8083 }, { ad: "rapor-derleyici", sahip: "raporlama", kapi: 8084 }]; export const uret = (s, c) => s.replace(/\{\{([\w-]+)\}\}/g, (_, a) => String(c[a] ?? `<eksik:${a}>`)); export const satir = (m) => m.split("\n").filter((s) => s.trim()).length; export const ELLE = { // sayac-okuma ekibi iskeletten once elle yazdi "servis.tanim": `servis okuma-toplayici sahip sayac-okuma kapi 8080 saglik-ucu /durum kutuk-bicimi duz-metin`, "hat.tanim": `hat okuma-toplayici adim derle adim birim-sinama adim paket adim elle-onay adim dagit-uretim`, "olcut.tanim": `olcut okuma-toplayici sayac kayit_sayisi olcek istek_suresi_ms`, "kutuk.tanim": `kutuk okuma-toplayici alan zaman alan duzey`, "BELGE.md": `# okuma-toplayici`, };
Şablonun içindeki değerlerin çoğu soru olarak sorulmaz. Sağlık ucunun yolu, kütük biçimi, hattaki altı adımın sırası ve yayınlanacak üç ölçütün adı şablonda sabittir; ekibe sorulan yalnız üç şeydir. Bu ayrım dersin ölçtüğü şeyin ta kendisidir: sorulmayan her alan, bir ekibin vermediği bir karardır — ve aynı zamanda o ekibin artık göremediği bir karardır.
Üretilen ile Elle Yazılan
// uret.mjs — bes servis gercek dosyalarla uretilir, elle yazilanla karsilastirilir. import { mkdirSync, writeFileSync, readFileSync, readdirSync } from "node:fs"; import { SABLON, CEVAP, ELLE, SURUM, uret, satir } from "./sablon.mjs"; const DOSYA = Object.keys(SABLON), baslangic = process.hrtime.bigint(); for (const c of CEVAP) { mkdirSync(`servisler/${c.ad}`, { recursive: true }); for (const d of DOSYA) writeFileSync(`servisler/${c.ad}/${d}`, `# uretici ${SURUM}\n${uret(SABLON[d], c)}\n`); } const gecen = Number(process.hrtime.bigint() - baslangic) / 1e6; const diskte = readdirSync("servisler").flatMap((s) => // gercekten yazildi mi readdirSync(`servisler/${s}`).map((d) => `servisler/${s}/${d}`)); const diskSatir = diskte.reduce((a, y) => a + satir(readFileSync(y, "utf8")), 0); const alan = Object.values(SABLON).join("\n").split("\n").filter((s) => s.startsWith(" ")).length; const degisken = new Set(Object.values(SABLON).join("\n").match(/\{\{([\w-]+)\}\}/g)).size; const bandUst = DOSYA.length * CEVAP.length * 20; // beklenen ust sinir girdiden turer console.log(`sablon ${DOSYA.length} dosya, ${satir(Object.values(SABLON).join("\n"))} satir,` + ` ${alan} alan, ${degisken} degisken`); console.log(`elle baslatma : servis basina ${DOSYA.length} dosya, ` + `${satir(Object.values(SABLON).join("\n"))} satir, ${alan} karar` + ` bes servis icin ${alan * CEVAP.length} karar`); console.log(`iskeletle : servis basina 0 dosya, 0 satir, ${degisken} cevap` + ` bes servis icin ${degisken * CEVAP.length} cevap`); console.log(`diske yazilan ${diskte.length} dosya, ${diskSatir} satir` + ` uretim suresi ${0}-${bandUst} ms bandinda: ${gecen <= bandUst ? "evet" : "hayir"}`); const anahtar = (s) => s.trim().split(" ")[0]; // uretilen ile elle yazilan const tek = (l, k) => l.filter((x) => anahtar(x) === k).length === 1; let ortak = 0, farkli = 0, eksik = 0, fazla = 0; console.log(`\nelle yazilan okuma-toplayici ${Object.keys(ELLE).length} dosya ` + `${satir(Object.values(ELLE).join("\n"))} satir | uretilen ${DOSYA.length} dosya ` + `${satir(Object.values(SABLON).join("\n"))} satir`); for (const d of DOSYA) { const g = uret(SABLON[d], CEVAP[0]).split("\n").filter((s) => s.trim()); const e = (ELLE[d] ?? "").split("\n").filter((s) => s.trim()); const sap = []; let o = 0, f = 0, k = 0; for (const s of g) { if (e.some((x) => x.trim() === s.trim())) o++; else if (tek(g, anahtar(s)) && tek(e, anahtar(s))) { f++; sap.push(`${s.trim()} -> ${e.find((x) => anahtar(x) === anahtar(s)).trim()}`); } else k++; } const z = e.filter((x) => !g.some((s) => s.trim() === x.trim()) && !sap.some((p) => anahtar(p) === anahtar(x))).length; [ortak, farkli, eksik, fazla] = [ortak + o, farkli + f, eksik + k, fazla + z]; console.log(` ${d.padEnd(13)} ortak ${o} farkli ${f} eksik ${k} fazladan ${z}` + `${ELLE[d] === undefined ? " (dosya hic yazilmamis)" : ""}` + `${sap.length ? " [" + sap.join("; ") + "]" : ""}`); } console.log(`toplam: ortak ${ortak} farkli deger ${farkli} eksik ${eksik} fazladan ${fazla}`);
sablon 6 dosya, 26 satir, 19 alan, 3 degisken elle baslatma : servis basina 6 dosya, 26 satir, 19 karar bes servis icin 95 karar iskeletle : servis basina 0 dosya, 0 satir, 3 cevap bes servis icin 15 cevap diske yazilan 30 dosya, 160 satir uretim suresi 0-600 ms bandinda: evet elle yazilan okuma-toplayici 5 dosya 18 satir | uretilen 6 dosya 26 satir servis.tanim ortak 3 farkli 2 eksik 1 fazladan 0 [saglik-ucu /saglik -> saglik-ucu /durum; kutuk-bicimi satir-json -> kutuk-bicimi duz-metin] hat.tanim ortak 5 farkli 0 eksik 2 fazladan 1 olcut.tanim ortak 2 farkli 0 eksik 2 fazladan 1 kutuk.tanim ortak 3 farkli 0 eksik 1 fazladan 0 duman.tanim ortak 0 farkli 0 eksik 3 fazladan 0 (dosya hic yazilmamis) BELGE.md ortak 1 farkli 0 eksik 1 fazladan 0 toplam: ortak 14 farkli deger 2 eksik 10 fazladan 2
Başlatma bedeli üç sayıdadır ve üçü de aynı yöne gitmez. Dosya ve satır sayısı gözle görülür kazançtır: servis başına altı dosya ve yirmi altı satır artık yazılmaz, beş servis için yüz otuz satır düşer. Asıl kalem üçüncüsüdür — on dokuz karar üç cevaba iner. Beş servis için doksan beş karar yerine on beş cevap verilir; geri kalan seksen karar bir kez şablonda verilmiş ve bir daha sorulmamıştır.
Elle yazılan servisle karşılaştırma bu seksen kararın ne demek olduğunu gösterir. Sayaç okuma ekibinin
kendi yazdığı servis on sekiz satırdır ve üretilenin yirmi altı satırıyla yalnız on dört satırda
örtüşür. İki alanda değer farklıdır — sağlık ucu /saglik yerine /durum, kütük biçimi satır-json
yerine düz metin — ve bu iki fark hiçbir hata üretmez, yalnız ortak araçların o servisi tanımamasına yol
açar. On satır eksiktir: hattaki iki adım, ölçüt tanımındaki iki sayaç, kütükteki istek kimliği alanı ve
duman sınaması dosyasının tamamı. İki satır da fazladandır ve şablonda karşılığı yoktur: hattaki elle
onay adımı ve kendi adıyla yazılmış bir sayaç.
Bu ekibin altın yola geçmesi için on iki satır düzeltmesi ve bir dosya eklemesi gerekir; fazladan iki satırın ise şablonda yeri yoktur, yani ya onlardan vazgeçecek ya da dışarıda kalacaktır. Dışına çıkmanın bedeli servis başına on iki satır düzeltme ve iki alışkanlıktan vazgeçmedir.
Şablon Güncellenince Ne Oluyor
// suruklenme.mjs — uretimden sonra sablon guncellenir; geride kalan servisler diskten sayilir. import { readFileSync, writeFileSync, mkdirSync } from "node:fs"; import { SABLON, CEVAP, uret, satir } from "./sablon.mjs"; const YEREL = { // uretimden sonraki yerel duzenleme, onceden yazili "okuma-toplayici": { "hat.tanim": [" adim sema-denetimi", " adim yuk-sinamasi"], "olcut.tanim": [" sayac atlanan_kayit"] }, "dogrulama-akisi": { "kutuk.tanim": [" alan kaynak-sayac"] }, "fatura-hesaplayici": { "hat.tanim": [" adim mutabakat"], "olcut.tanim": [" olcek fatura_tutari"], "duman.tanim": [" cagri /mutabakat bekle 200"] }, "saha-emir": {}, "rapor-derleyici": { "duman.tanim": [" cagri /rapor bekle 200"], "BELGE.md": ["kosum notu gecelik"] } }; let yerel = 0; for (const [s, d] of Object.entries(YEREL)) for (const [dosya, satirlar] of Object.entries(d)) { writeFileSync(`servisler/${s}/${dosya}`, readFileSync(`servisler/${s}/${dosya}`, "utf8") + satirlar.join("\n") + "\n"); yerel += satirlar.length; } const y = `servisler/dogrulama-akisi/servis.tanim`; // dogrulama ekibi duyuruyu gorup elle yamaladi writeFileSync(y, readFileSync(y, "utf8").replace("saglik-ucu /saglik", "saglik-ucu /durum/saglik\n veri-sinifi olcum")); writeFileSync("servisler/dogrulama-akisi/hat.tanim", readFileSync("servisler/dogrulama-akisi/hat.tanim", "utf8") + " adim imzalama\n"); const SABLON2 = { ...SABLON, "servis.tanim": SABLON["servis.tanim"] .replace("saglik-ucu /saglik", "saglik-ucu /durum/saglik\n veri-sinifi {{veri-sinifi}}"), "hat.tanim": SABLON["hat.tanim"] + "\n adim imzalama" }; const KURAL = [["servis.tanim", "saglik-ucu /durum/saglik"], ["servis.tanim", "veri-sinifi "], ["hat.tanim", " adim imzalama"]]; const marker = CEVAP.filter((c) => // birinci tarama: uretici surum satiri readFileSync(`servisler/${c.ad}/servis.tanim`, "utf8").startsWith("# uretici 1")); let eksikSatir = 0, okunan = 0; const icerik = CEVAP.filter((c) => { const eksik = KURAL.filter(([d, k]) => !readFileSync(`servisler/${c.ad}/${d}`, "utf8").includes(k)); okunan += new Set(KURAL.map(([d]) => d)).size; eksikSatir += eksik.length; return eksik.length > 0; }); mkdirSync("deneme", { recursive: true }); // cevapsiz yeniden uretim denemesi let bosluk = 0; for (const c of CEVAP) { const m = uret(SABLON2["servis.tanim"], c); writeFileSync(`deneme/${c.ad}.tanim`, m); bosluk += (m.match(/<eksik:/g) ?? []).length; } const yeniCevap = CEVAP.filter((c) => c["veri-sinifi"] === undefined).length; console.log(`yerel duzenleme ${yerel} satir, ${Object.values(YEREL).filter((d) => Object.keys(d).length).length}/${CEVAP.length} serviste`); console.log(`sablon surum 2: ${KURAL.length} degisiklik, ${satir(SABLON2["servis.tanim"] + "\n" + SABLON2["hat.tanim"]) - satir(SABLON["servis.tanim"] + "\n" + SABLON["hat.tanim"])}` + ` yeni satir, 1 yeni soru; uretilmis servislerin hicbiri kendiliginden degismiyor`); console.log(`marker taramasi : geride ${marker.length}/${CEVAP.length} servis,` + ` okunan dosya ${CEVAP.length}, yanlis pozitif ${marker.length - icerik.length}`); console.log(`icerik taramasi : geride ${icerik.length}/${CEVAP.length} servis,` + ` eksik satir ${eksikSatir}, okunan dosya ${okunan}` + ` (${icerik.map((c) => c.ad).join(", ")})`); console.log(`yeniden uretim : ${CEVAP.length * Object.keys(SABLON).length} dosya yeniden yazilir,` + ` kaybolan yerel satir ${yerel}, once toplanacak yeni cevap ${yeniCevap}` + `; cevapsiz denemede ${bosluk} bosluk`); console.log(`elle yama : ${eksikSatir} dokunus, kaybolan yerel satir 0,` + ` sonraki surumde ayni is yeniden gerekir`);
yerel duzenleme 9 satir, 4/5 serviste sablon surum 2: 3 degisiklik, 2 yeni satir, 1 yeni soru; uretilmis servislerin hicbiri kendiliginden degismiyor marker taramasi : geride 5/5 servis, okunan dosya 5, yanlis pozitif 1 icerik taramasi : geride 4/5 servis, eksik satir 12, okunan dosya 10 (okuma-toplayici, fatura-hesaplayici, saha-emir, rapor-derleyici) yeniden uretim : 30 dosya yeniden yazilir, kaybolan yerel satir 9, once toplanacak yeni cevap 5; cevapsiz denemede 5 bosluk elle yama : 12 dokunus, kaybolan yerel satir 0, sonraki surumde ayni is yeniden gerekir
Şablonun sürüm 2’si sağlık ucunu değiştirir, veri sınıfı alanını zorunlu kılar ve hatta bir imzalama adımı ekler. Diskteki beş servisin hiçbiri bundan haberdar olmaz, çünkü üretilen dosya üreticiye bağlı değildir. Şablon bir kez okundu, çıktı yazıldı ve bağ orada koptu. Bu, önceki dersteki ortam soyutlamasının tam tersi bir sızmadır: orada gizlenen katman geri sızıyordu, burada gizleme hiç sürmüyor.
Geride kalanı bulmanın iki yolu vardır ve ikisi farklı sayı verir. Üretici sürümünü okumak beş dosya okumakla biter ve beş servisin beşini de geride gösterir; oysa doğrulama ekibi duyuruyu görüp üç değişikliği elle yapmıştır, yani bu tarama bir yanlış pozitif üretir. İçeriği denetlemek on dosya okumayı gerektirir, geride kalanı dört servis olarak verir ve toplam on iki eksik satırı adlarıyla gösterir. Doğru sonuç iki kat okuma maliyetindedir.
Yakalamanın da iki yolu vardır. Yeniden üretim otuz dosyayı yeniden yazar ve tek hamlede bitirir, ama iki
bedeli vardır: dokuz yerel satır kaybolur — hat adımları, ölçütler, duman çağrıları — ve yeni sorunun
cevabı hiçbir yerde kayıtlı olmadığı için beş servis için beş yeni cevap toplanması gerekir. Cevapsız
koşulan deneme bunu doğrudan gösterir: beş dosyada <eksik:veri-sinifi> boşluğu kalır. Elle yama ise on
iki dokunuşla biter ve hiçbir yerel satır kaybetmez, ama şablonla bağ yine kurulmadığı için sürüm 3
geldiğinde aynı iş baştan gerekir.
Kazanç ile bedelin tek cümlelik bilançosu şudur: iskelet başlatma anındaki doksan beş kararı on beş cevaba indirdi, başlatmadan sonraki hiçbir kararı devralmadı. Yerel düzenleme yapan dört servis şablonun dışına yerel satır sayısı kadar çıkmış oldu; hiç düzenleme yapmayan tek servis olan saha-emir ise yeniden üretimden hiçbir şey kaybetmez.
Özet
- Yeni servis elle başlatmak servis başına 6 dosya, 26 satır ve 19 karar demektir; beş servis için 95 karar çıkar, iskeletle bunun yerine 15 cevap verilir.
- Beş servis gerçekten üretildi: diske 30 dosya ve 160 satır yazıldı, üretim süresi girdiden türetilen bant içinde kaldı.
- Elle yazılan servis üretilenle 26 satırın yalnız 14’ünde örtüştü: 2 alanda değer farklı, 10 satır eksik, 2 satır fazladan; altın yola geçmenin bedeli 12 satır düzeltme ve 2 alışkanlıktan vazgeçme.
- Şablon sürüm 2’ye çıkınca üretilmiş servislerin hiçbiri kendiliğinden değişmedi; üretici sürümüne bakan tarama 5/5 geride dedi ve 1 yanlış pozitif verdi, içerik taraması 4/5 geride ve 12 eksik satır buldu, karşılığında iki kat dosya okudu.
- Yakalamanın iki yolu da bedelliydi: yeniden üretim 9 yerel satırı sildi ve 5 yeni cevap istedi, elle yama 12 dokunuşla bitti ama bağ kurmadığı için sonraki sürümde aynı iş yeniden gerekti.
Sonraki Adım
Beş servis üretildi, dosyaları diskte duruyor ve her birinin tanımında bir sahip satırı var. O satır yazıldığı gün doğruydu; bugün doğru olup olmadığını kimse bilmiyor. Gece yarısı fatura hesaplayıcı durduğunda arayanın önce şunu bulması gerekiyor: bu servisin sahibi kim, kime bağlı ve kim ona bağlı. Bir sonraki ders katalog kayıtlarından sahiplik ve bağımlılık grafiği kurar; sahipsiz servis, var olmayan bir servise bağlanan öksüz bağımlılık ve doğru kişiye ulaşma süresi katalogla ve katalogsuz sayılır. Kaydın kendi bedeli de ölçülür: kaç kayıt elle güncelleniyor ve kaç kayıt gerçekle uyuşmuyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.