---
title: 'On İki Etmen Uygulama'
source: 'https://academia.sh/tr/kurslar/devops-temelleri/on-iki-etmen-uygulama'
course: 'DevOps Kültürü ve Temelleri'
language: tr
updated: '2026-08-17T18:09:05+00:00'
license: 'CC BY-SA 4.0'
---

# On İki Etmen Uygulama

On iki ilkenin sekizi diskteki dosyalara bakıp karar veren koşturulabilir bir denetime çevrilir ve kurgu bir uygulamanın gerçek dosyaları üzerinde koşturulur; yerleştirilmiş sekiz kusurdan kaçının yakalandığı, kaçının kaçtığı ve kaç yanlış alarm verildiği sayılır, çevrilemeyen dört ilkenin yerine ne konduğu yazılır.

On iki etmen uygulama ilkeleri, uygulamayı yazan ile onu çalıştıran arasındaki sözleşmeyi
adlandırır. İlkelerin ne söylediği **Sunucu Tarafı Temelleri** kursunda kurulmuştu ve burada
tekrarlanmaz. Bu dersin sorusu başkadır: bir ilke, diskteki dosyalara bakıp evet ya da hayır
diyen **koşturulabilir bir denetime** çevrilebiliyor mu? Çevrilebiliyorsa denetim neyi yakalıyor,
neyi kaçırıyor, neye boş yere alarm veriyor? Çevrilemiyorsa yerine ne konuyor?

Bir ilkenin karar taşıması, cümlesinin doğru görünmesine değil, ihlal edildiğinde birinin bunu
göreceğine bağlıdır. Denetime çevrilemeyen ilke ancak biri bakmayı akıl ettiğinde görünür ve akıl
edilmesi gereken bakış, ölçülmemiş bakıştır. Ölçüm yine **bölgesel ölçüm ağı** üzerinde yapılır:
su sayacı okumalarını toplayan, doğrulayan, faturaya çeviren ve saha iş emri açan **kurgu** bir
yazılım. Aşağıdaki dosya ağacı gerçekten diske yazılır, denetleyici gerçekten koşar; dosyaların
ve bağımlılık adlarının tamamı kurgudur.

**DK23.** Denetleyici yalnız diskteki dosya ağacını görür; depo geçmişini, işletim adımlarını ve
ekip alışkanlıklarını görmez.
**DK24.** Bir ilke, tek bir ağaç üzerinde tek bir koşumda ikili karar üretebiliyorsa çevrilebilir
sayılır; karar için ikinci bir ortam, ikinci bir süreç ya da insan yorumu isteyen ilke çevrilemez
sayılır.
**DK25.** Kusurlar taramadan önce bilerek yerleştirilir ve sayıları bilinir; kaçanın sayılabilmesi
buna bağlıdır.
**DK26.** Denetimlerin tamamı dizgi ve dosya düzeyindedir; sözdizim ağacı çözümlemesi yoktur.
Kaçan kusurların sınıfı doğrudan bu seçimden doğar.

## Denetime Çevrilen Sekiz İlke

Aşağıdaki tablo ilkenin ne söylediğini değil, ilkenin **diskte neye baktığını** yazar.

| İlke | Denetimin baktığı şey | Kararı veren |
|---|---|---|
| Bağımlılıklar | bildirim dosyasının yanında kilit dosyası var mı | dosya varlığı |
| Yapılandırma | kaynakta gömülü varsayılan ve gömülü sunucu adresi | `??` kalıbı, adres kalıbı |
| Bağlantı noktası bağlama | kaynak kendi soketini açıyor mu | `.listen(` çağrısı |
| Kütükler | kütük standart çıktı yerine dosyaya mı yazılıyor | dosya yazma çağrısı |
| Tek kullanımlık olma | bildirilen her giriş noktası kapanma sinyalini işliyor mu | `SIGTERM` dizgesi |
| Süreçler | süreç belleğinde kalıcı durum tutuluyor mu | üst düzey değişebilir kap |
| Derle, yayımla, çalıştır | yapı çıktısına ortam değeri gömülmüş mü | adres kalıbı |
| Yönetim süreçleri | tek seferlik iş uygulamanın kodunu kullanıyor mu | içe aktarım yolu |

Sekizinin ortak yanı, kararın tek bir ağaçtan okunabilmesidir. Kalan dört ilke bu tabloya
girmedi; nedenleri ölçümden sonra sayılır.

Üçüncü sütun, tablonun asıl bilgisini taşır. İlke bir niyet cümlesidir; denetim ise bir metin
kalıbıdır. Aradaki çeviri her satırda bir daraltmadır: "kütükler bir olay akışıdır" cümlesi
"kaynak dizininde dosya yazma çağrısı bulunmasın" kuralına indirgenir, "süreçler durumsuzdur"
cümlesi "üst düzeyde değişebilir kap bildirilmesin" kuralına. Daraltma iki yönde de kayıp
üretir. Kural ilkeden dar kaldığı yerde kusur kaçar; ilkenin kapsamadığı bir dosyaya uzandığı
yerde yanlış alarm doğar. Aşağıdaki sayılar bu iki kaybın ikisini de ayrı ayrı ölçer, çünkü
tek bir "geçti / kaldı" toplamı hangi yönde kaybedildiğini söylemez.

## Kurgu Uygulamanın Dosyaları

```bash
#!/usr/bin/env bash
# Kurgu olcum agi uygulamasinin dosya agacini diske yazar. Bagimlilik adlari da kurgudur.
set -e
mkdir -p kaynak sinama betik yapi

cat > paket.json <<'SON'
{ "ad": "olcum-agi", "tur": "modul",
  "girisler": ["kaynak/sunucu.mjs", "betik/gecelik-duzeltme.mjs"],
  "bagimlilik": { "olcum-cozumleyici": "2.4.0", "fatura-bicimleyici": "1.1.3" } }
SON

cat > kaynak/ayar.mjs <<'SON'
export const ayar = {
  ortam: process.env.ORTAM_ETIKETI,
  veriAdresi: process.env.OLCUM_VERI_ADRESI,
  baglantiNoktasi: Number(process.env.OLCUM_BAGLANTI_NOKTASI),
  yenidenOkumaGun: Number(process.env.YENIDEN_OKUMA_GUN ?? 30),
};
SON

cat > kaynak/sunucu.mjs <<'SON'
import { createServer } from "node:http";
import { ayar } from "./ayar.mjs";
export const sunucu = createServer((istek, yanit) => yanit.end(ayar.ortam ?? "yok"));
sunucu.listen(ayar.baglantiNoktasi);
SON

cat > kaynak/kutuk.mjs <<'SON'
import { appendFileSync } from "node:fs";
export const kutuk = (k) => appendFileSync("/var/kutuk/olcum.log", JSON.stringify(k) + "\n");
SON

cat > kaynak/rapor.mjs <<'SON'
import { writeFileSync } from "node:fs";
export const faturayiYaz = (f) => writeFileSync(`cikti/fatura-${f.no}.json`, JSON.stringify(f));
SON

cat > kaynak/toplu-is.mjs <<'SON'
const IS_EMRI_ADRESI = "is-emri-uretim.ic:8081";
export const kuyruk = (() => {
  const bekleyen = new Map();
  return { ekle: (id, v) => bekleyen.set(id, v), sayi: () => bekleyen.size, IS_EMRI_ADRESI };
})();
SON

cat > sinama/sahte-olcum.mjs <<'SON'
// Surec ici sahte olcum deposu; adres gercek bir hedefe gitmez.
export const sahteAdres = "127.0.0.1:5432";
SON

cat > betik/gecelik-duzeltme.mjs <<'SON'
const ADRES = process.env.OLCUM_VERI_ADRESI;
console.log(`duzeltme calisti -> ${ADRES}`);
SON

cat > yapi/paket.mjs <<'SON'
// Yapi cikti dosyasi (kurgu): uretim adresi derleme aninda gomuldu.
export const HEDEF = "olcum-uretim.ic:" + 5432;
SON
```

Bu ağaca sekiz kusur yerleştirilmiştir. Sayıları denetleyicinin içinde yazılı olduğu için kaçan
da sayılabilir; denetimlerin hiçbiri o listeyi okumaz.

## Koşum

```js
// denetim.mjs — cevrilebilen ilkeleri kurgu uygulamanin gercek dosyalari uzerinde kosturur.
import { readFileSync, existsSync } from "node:fs";

const DOSYA = ["paket.json", "kaynak/ayar.mjs", "kaynak/sunucu.mjs", "kaynak/kutuk.mjs",
  "kaynak/rapor.mjs", "kaynak/toplu-is.mjs", "sinama/sahte-olcum.mjs",
  "betik/gecelik-duzeltme.mjs", "yapi/paket.mjs"];
const metin = Object.fromEntries(DOSYA.map((y) => [y, readFileSync(y, "utf8")]));
const alt = (on) => DOSYA.filter((y) => y.startsWith(on));
const ara = (kapsam, kalip) => kapsam
  .flatMap((y) => metin[y].split("\n").map((s, i) => ({ yol: y, satirNo: i + 1, satir: s })))
  .filter((b) => kalip.test(b.satir));
const not = (yol, satir) => ({ yol, satirNo: 0, satir });
const ADRES = /["'][\w.-]+:\d{4,5}["']/;

const DENETIM = [
  ["bagimliliklar", () => existsSync("paket.kilit") ? [] : [not("paket.json", "paket.kilit yok")]],
  ["yapilandirma", () => [...ara(alt("kaynak/"), /process\.env\.\w+\s*\?\?/),
    ...ara([...alt("kaynak/"), ...alt("sinama/"), ...alt("betik/")], ADRES)]],
  ["baglanti-noktasi", () => ara(alt("kaynak/"), /\.listen\(/).length
    ? [] : [not("kaynak/", "listen cagrisi yok")]],
  ["kutukler", () => ara(alt("kaynak/"), /(appendFileSync|writeFileSync|createWriteStream)\(/)],
  ["tek-kullanimlik", () => JSON.parse(metin["paket.json"]).girisler
    .filter((y) => !metin[y].includes("SIGTERM")).map((y) => not(y, "SIGTERM isleyicisi yok"))],
  ["surecler", () => ara(alt("kaynak/"), /^(const|let|var)\s+\w+\s*=\s*new (Map|Set)\(/)],
  ["derle-yayimla-calistir", () => ara(alt("yapi/"), ADRES)],
  ["yonetim-surecleri", () => alt("betik/").filter((y) => !metin[y].includes("kaynak/"))
    .map((y) => not(y, "kaynak/ altindan ice aktarim yok"))],
];

const YERLESIK = [                                   // taramadan once yerlestirilen kusurlar
  ["paket.json", "paket.kilit"], ["kaynak/ayar.mjs", "?? 30"],
  ["kaynak/toplu-is.mjs", "is-emri-uretim.ic"], ["kaynak/kutuk.mjs", "appendFileSync"],
  ["kaynak/sunucu.mjs", "SIGTERM"], ["kaynak/toplu-is.mjs", "new Map("],
  ["yapi/paket.mjs", "olcum-uretim.ic:"], ["betik/gecelik-duzeltme.mjs", "kaynak/"],
];

const bulgu = DENETIM.flatMap(([ad, f]) => f().map((b) => ({ ...b, denetim: ad })));
const gercek = (b) => YERLESIK.some(([y, iz]) => y === b.yol && b.satir.includes(iz));
const kacan = YERLESIK.filter(([y, iz]) => !bulgu.some((b) => b.yol === y && b.satir.includes(iz)));
const yanlis = bulgu.filter((b) => !gercek(b));

const satir = (a, b, c, d) => a.padEnd(24) + b.padStart(6) + c.padStart(8) + d.padStart(8);
console.log(satir("denetim", "bulgu", "gercek", "yanlis"));
for (const [ad, f] of DENETIM) {
  const b = f();
  console.log(satir(ad, `${b.length}`, `${b.filter(gercek).length}`,
    `${b.filter((x) => !gercek(x)).length}`));
}
console.log(`cevrilebilen ilke: ${DENETIM.length}   cevrilemeyen: ${12 - DENETIM.length}`);
console.log(`yerlestirilen kusur: ${YERLESIK.length}   bildirilen bulgu: ${bulgu.length}`);
console.log(`yakalanan: ${YERLESIK.length - kacan.length}   kacan: ${kacan.length}   ` +
  `yanlis alarm: ${yanlis.length}`);
for (const [y, iz] of kacan) console.log(`  [KACAN ] ${y}  iz=${iz}`);
for (const b of yanlis) console.log(`  [YANLIS] ${b.yol}:${b.satirNo}  denetim=${b.denetim}`);
```

```
denetim                  bulgu  gercek  yanlis
bagimliliklar                1       1       0
yapilandirma                 3       2       1
baglanti-noktasi             0       0       0
kutukler                     2       1       1
tek-kullanimlik              2       1       1
surecler                     0       0       0
derle-yayimla-calistir       0       0       0
yonetim-surecleri            1       1       0
cevrilebilen ilke: 8   cevrilemeyen: 4
yerlestirilen kusur: 8   bildirilen bulgu: 9
yakalanan: 6   kacan: 2   yanlis alarm: 3
  [KACAN ] kaynak/toplu-is.mjs  iz=new Map(
  [KACAN ] yapi/paket.mjs  iz=olcum-uretim.ic:
  [YANLIS] sinama/sahte-olcum.mjs:2  denetim=yapilandirma
  [YANLIS] kaynak/rapor.mjs:2  denetim=kutukler
  [YANLIS] betik/gecelik-duzeltme.mjs:0  denetim=tek-kullanimlik
```

## Yakalanan, Kaçan, Yanlış Alarm

Sekiz kusurdan altısı yakalandı, ikisi kaçtı ve denetimler üç yanlış alarm verdi. Tabloda okunması
gereken asıl satırlar sıfır bildirenlerdir. `baglanti-noktasi` sıfır bulgu bildirdi ve haklıydı:
kaynak kendi soketini açıyor. `surecler` ile `derle-yayimla-calistir` de sıfır bildirdi ve
**ikisi de yanılıyordu** — kusur her ikisinde de yerindeydi. **Sıfır bulgu kusursuzluğu değil,
kuralın kapsamını ölçer** ve tablo bu iki durumu aynı biçimde yazar.

Kaçan iki kusurun sınıfı ortaktır. `toplu-is.mjs` süreç belleğinde bir eşleme tutuyor, ama eşleme
bir kapanışın içinde: kural üst düzey bir bildirim arıyor, kap ise iki boşluk içeride. Yapı
çıktısındaki üretim adresi ise derleme anında birleştirilmiş (`"olcum-uretim.ic:" + 5432`), yani
adres kalıbının aradığı biçimde hiç durmuyor. İkisi de dizgi düzeyinde çalışan bir kuralın yapı
düzeyinde duran bir olguyu göremediği örneklerdir; bir önceki dersin sır tarayıcısı da tam bu
sınıfı kaçırmıştı. Bu, denetimlerin dikkatsizliği değil, DK26'nın doğrudan sonucudur — kural
dizgiye bakar, kusur ağaca yerleşir.

Üç yanlış alarmın üçü de **doğru davranışı** işaretledi. Sınama düzeneğindeki sabit adres, süreç
içinde kalan sahte bir depoya işaret ediyor; orada yapılandırmadan okumak anlamsızdır. Fatura
dosyası yazan çağrı bir kütük değil, ürünün kendisidir. Gecelik düzeltme betiği tek seferlik bir
iştir ve kapanma sinyali işlemesi gerekmez. Üçü de kuralın **kapsamı** ile ilkenin **konusu**
arasındaki açıklıktan doğuyor: kural dosyaya bakıyor, ilke ise o dosyanın ne işe yaradığını
soruyor.

Yanlış alarmın bedeli, kaçanın bedeliyle aynı yöne çalışır. Dokuz satırlık bir raporun üçü boşsa,
raporu ikinci kez okuyan kişi satırları tek tek açmaz; kaçan iki kusurun bulunma olasılığı da bu
noktada düşer. Bir denetim kümesini değerlendiren tek sayı yoktur — yakalanan, kaçan ve yanlış
alarm ayrı ayrı yazılmadıkça hangisinin arttığı görünmez.

## Kaçanın Koşumdaki Karşılığı

Kaçan iki kusur denetim tablosunda yalnız birer boşluktu. Boşluğun neye karşılık geldiğini görmek
için ikisi de aynı süreçte çalıştırılır.

```js
// kacan.mjs — denetimin goremedigi iki kusuru kosumda gosterir.
import { kuyruk } from "./kaynak/toplu-is.mjs";
import { HEDEF } from "./yapi/paket.mjs";

kuyruk.ekle("sayac-1041", 3120);                    // birinci okuma paketi
kuyruk.ekle("sayac-2277", 8840);                    // ikinci paket, ayni surec
console.log(`ORTAM_ETIKETI          : ${process.env.ORTAM_ETIKETI}`);
console.log(`kuyrukta kalan kayit   : ${kuyruk.sayi()}`);
console.log(`yapi ciktisinin hedefi : ${HEDEF}`);
```

```bash
#!/usr/bin/env bash
# Ayni yapi ciktisini iki ortam etiketiyle kosturur.
for o in sinama uretim; do ORTAM_ETIKETI="$o" node kacan.mjs; done
```

```
ORTAM_ETIKETI          : sinama
kuyrukta kalan kayit   : 2
yapi ciktisinin hedefi : olcum-uretim.ic:5432
ORTAM_ETIKETI          : uretim
kuyrukta kalan kayit   : 2
yapi ciktisinin hedefi : olcum-uretim.ic:5432
```

Kuyrukta iki kayıt kaldı. İkinci okuma paketi, birincinin süreç belleğinde bıraktığını buldu.
Süreç yeniden başladığında bu kayıtlar yok olur; ölçek değiştiğinde ikinci sürecin kuyruğu boş
başlar ve iki süreç aynı gecelik işi iki farklı durumla koşar. Denetimin göremediği kusur,
koşumda bir sayı olarak görünüyor.

İkinci satır daha keskindir. Ortam etiketi sınamayken de üretimken de yapı çıktısının hedefi
aynı kalıyor: üretim veri deposunun adresi. **Yapı çıktısı ortamı taşıyor, ortam yapı çıktısını
değil.** Sınama ortamında koşan bir gecelik toplu iş üretim verisine bakar ve bunu hiçbir
yapılandırma dosyası bildirmez, çünkü fark yapılandırmada değil, derleme anında alınmış bir
kararda saklıdır. Bir önceki dersin ortam farkı sayımı bu değeri hiç göremezdi; sayılan şey
yapılandırma dosyalarıydı ve bu adres onların hiçbirinde geçmiyor.

Bu, kursun kuralının en dar biçimidir. Aynı yazılım iki ortamda başka davranmadı — **aynı
davrandı**, ve sorun tam olarak budur. Ortam etiketi değişti, davranış değişmedi; fark, ortam
değişkeninin erişemeyeceği bir yere gömüldüğü için ortamın onu değiştirme yetkisi kalmadı.
Denetim tablosundaki `derle-yayimla-calistir` satırının sıfırı, bu farkın üstünü örten şeydi.

İki kusuru da yakalamanın yolu bellidir: kural dizgiye değil, çözümlenmiş bir sözdizim ağacına
bakar; kapanış içindeki bildirimi ve derleme anında birleştirilen değeri ancak o düzeyde görür.
Bedeli DK26'nın kaldırılmasıdır — denetim artık dosyayı okumakla kalmaz, dili de çözümlemek
zorundadır. Sekiz denetimin ucuzluğu bu seçimden geliyordu; ucuzluk kaybedilince denetimin sık
koşma özelliği de tartışmaya açılır.

## Çevrilemeyen Dört İlkenin Yerine

| İlke | Tek ağaçta neden ölçülemez | Yerine konan |
|---|---|---|
| Kod tabanı | tek kod tabanından kaç dağıtım çıktığı depo düzeyinde bir olgudur; ağaç tek bir dağıtımı gösterir | yapı çıktısının taşıdığı kaynak kimliğinin dağıtım kayıtlarında eşleşmesi |
| Destekleyici servisler | adresin yapılandırmadan geldiği denetlenir, ama kaynağın değiştirilebilir olduğu ancak değiştirilerek görülür | adres değiştirilip koşumun yinelenmesi |
| Eşzamanlılık | süreç modeliyle ölçeklendiği tek süreçlik bir ağaçtan okunamaz | iki süreçle yük altında koşum ve süreç sayısıyla değişmeyen çıktı |
| Geliştirme/üretim eşliği | iki ortamın karşılaştırılmasını ister; ağaç tek ortamdır | bir önceki dersin ortam farkı sayımı |

Dördünün ortak yanı, karar için **ikinci bir gözlem** istemeleridir: ikinci bir dağıtım, ikinci
bir adres, ikinci bir süreç, ikinci bir ortam. Sınır tam buradan geçiyor ve adı **tek gözlemle
karar verilebilirlik**tir. Sekiz denetim tek ağaçta karar verdi; dördü karşılaştırma istedi.

Bu ayrım maliyeti de belirler. Sekiz denetim her değişiklikte saniyeler içinde koşar; kalan dördü
ayrı bir ortam, ayrı bir koşum ya da yük üreten bir düzenek ister. Ucuz olan sık koşulur, pahalı
olan seyrek — ve seyrek koşan ölçümün yakaladığı kusur yakalandığında çoktan üretime gitmiştir.
Bir önceki dersin ortam farkı sayımı, dört pahalı ilkeden birinin yerine konan şeydi ve bu
dersteki hiçbir denetim onun yerini tutmuyor.

Maliyet farkı sahiplik farkını da getirir. Sekiz ucuz denetim yazan ekibin elindedir; kod
değiştiği anda koşar ve sonucunu değiştiren kişi görür. Dört pahalı ölçüm ise ikinci ortamı,
ikinci süreci ya da yük düzeneğini elinde tutan işleten ekibin alanına düşer. Aynı ilke kümesinin
yarısı bir ekipte, yarısı ötekinde ölçülüyor; ihlalin hangi ekibe göründüğü, ihlalin türüne göre
değişiyor. Ölçüm ağının iki ekibi arasındaki sinyal farkı bu bölünmeden doğar.

## Özet

- On iki ilkenin sekizi tek bir dosya ağacında karar veren denetime çevrildi, dördü çevrilemedi;
  ayıran ölçüt tek gözlemle karar verilebilirliktir.
- Yerleştirilen sekiz kusurdan altısı yakalandı, ikisi kaçtı; denetimler dokuz bulgu bildirdi ve
  üçü yanlış alarmdı.
- Sıfır bulgu bildiren üç denetimden biri haklıydı, ikisi yanılıyordu. Sıfır bulgu kuralın
  kapsamını ölçer, kusursuzluğu değil.
- Kaçan iki kusur da aynı sınıftan: kural dizgiye bakıyor, kusur yapıya yerleşiyor — kapanış
  içindeki durum ve derleme anında birleştirilen adres. Koşumda ikisi de göründü: süreç belleğinde
  iki kayıt kaldı, yapı çıktısının hedefi iki ortam etiketinde de üretim adresi çıktı.
- Üç yanlış alarmın üçü de doğru davranışı işaretledi; kuralın kapsamı ile ilkenin konusu
  arasındaki açıklıktan doğdular.
- Çevrilemeyen dört ilkenin yerine konan şeylerin hepsi ikinci bir gözlem ister; bu yüzden seyrek
  koşarlar ve yakaladıkları kusur geç yakalanır.

## Sonraki Adım

Çevrilemeyen dört ilkenin dördü de ikinci bir gözlem istedi ve ikisi bu gözlemin adını doğrudan
koydu: ikinci bir ortam. Buraya kadar ortamların var olduğu varsayıldı — geliştirme, sınama ve
üretim vardı, aralarındaki fark sayıldı, aynı ağaç üzerinde denetimler koşturuldu. Ortamın
kendisinin **nasıl var olduğu** ise hiç sorulmadı. Elle mi kuruluyor, bir betik mi kuruyor, yoksa
istenen son durumu bildiren bir tanım mı? Sonraki ders bu üç biçimi karşılaştırır ve aynı soruyu
sorar: aynı tanımdan iki kez kurulan iki ortam arasında kaç fark kalıyor ve o fark nerede
saklanıyor?
