---
title: 'Şablon ve İskelet Üretimi'
source: 'https://academia.sh/tr/kurslar/platform-muhendisligi/sablon-ve-iskelet-uretimi'
course: 'Platform Mühendisliği'
language: tr
updated: '2026-08-17T18:09:10+00:00'
license: 'CC BY-SA 4.0'
---

# Ş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.

Ö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

```js
// 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

```js
// 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

```js
// 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.
