Ders 08 / 16
Yapılandırma Yönetimi
Ortam değişkenlerinden okunan yapılandırma bir şemayla tanımlanır; tip, aralık ve seçenek doğrulaması açılışta yapılır, bütün sorunlar tek seferde bildirilir ve geçersiz ortamda uygulamanın sysexits çıkış koduyla düşmesi ölçülür.
İçindekiler
Önceki ders iskeletin katmanlarını ayırdı, ama bütün değerler kodun içinde yazılı kaldı: bağlantı noktası, ödünç günü, üye ödünç sınırı, deponun yeri. Aynı uygulama yerelde, sınama ortamında ve yayında farklı değerlerle çalışmalıdır; bu değerler kodda durduğu sürece her ortam için ayrı bir yapı üretmek gerekir.
Bu ders değerleri koddan çıkarıp ortam değişkenlerine taşır. Asıl konu değerlerin nereden okunduğu değil, ne zaman ve nasıl doğrulandığıdır: eksik ya da yanlış tipte bir değişkenle başlatılan uygulama ne yapmalıdır?
Neyin Yapılandırma Olduğu
Kodda duran her sabit yapılandırma değildir. Ayrımı veren ölçüt tektir: ortamdan ortama değişen değer yapılandırmadır. Ödünç süresinin on dört gün olması kütüphanenin kuralıdır ve yayında da yerelde de aynıdır; bağlantı noktası ise makineden makineye değişir.
Bu ölçüt uygulandığında üç öbek oluşur:
- Ortama bağlı değerler. Bağlantı noktası, deponun adresi, kütük düzeyi, bakım kipi. Bunlar ortam değişkenidir.
- İş kuralı sabitleri. Ödünç süresi, üye ödünç sınırı. Bunlar alan katmanına aittir ve koddadır — ama ortamlar arasında ayarlanabilmeleri isteniyorsa yapılandırmaya geçerler.
- Gizli değerler. Veritabanı parolası, imzalama anahtarı. Bunlar da ortamdan gelir, ama ayrı bir sınıfı vardır ve sonraki dersin konusudur.
Aşağıdaki şema ilk iki öbeği bir arada tanımlar; ödünç süresi ve üye sınırı, ortamlar arası denemeye açık olsunlar diye yapılandırmaya alınmıştır ve varsayılanları iş kuralının kendisidir.
Şema, Varsayılan ve Doğrulama
Bir yapılandırma okuyucusunun üç işi vardır: değeri okumak, tipini doğrulamak, eksikse varsayılanı koymak. Aşağıdaki modül bunları bir şema üzerinden yapar. Ortam değişkenleri tek bir önek taşır; bu önek, uygulamaya ait olmayan değişkenlerin şemaya karışmasını önler.
// kaynak/kurulum/yapilandirma.mjs — ortamdan okur, tumunu dogrular, degistirilemez nesne dondurur const ONEK = "KUTUPHANE_"; export const SEMA = { BAGLANTI_NOKTASI: { tip: "sayi", varsayilan: 8435, alt: 1024, ust: 65535 }, ODUNC_GUN: { tip: "sayi", varsayilan: 14, alt: 1, ust: 90 }, UYE_ODUNC_SINIRI: { tip: "sayi", varsayilan: 5, alt: 1, ust: 50 }, DEPO_ADRESI: { tip: "metin", zorunlu: true }, KUTUK_DUZEYI: { tip: "secenek", secenekler: ["hata", "uyari", "bilgi", "ayrintili"], varsayilan: "bilgi" }, BAKIM_KIPI: { tip: "mantiksal", varsayilan: false }, }; const cevir = (ad, kural, ham) => { if (kural.tip === "sayi") { const sayi = Number(ham); if (!Number.isInteger(sayi)) return { hata: `tam sayi bekleniyor, "${ham}" verildi` }; if (sayi < kural.alt || sayi > kural.ust) { return { hata: `${kural.alt}-${kural.ust} araliginda olmali, ${sayi} verildi` }; } return { deger: sayi }; } if (kural.tip === "mantiksal") { if (ham !== "true" && ham !== "false") { return { hata: `"true" ya da "false" bekleniyor, "${ham}" verildi` }; } return { deger: ham === "true" }; } if (kural.tip === "secenek") { if (!kural.secenekler.includes(ham)) { return { hata: `secenekler [${kural.secenekler.join(", ")}], "${ham}" verildi` }; } return { deger: ham }; } return ham.length > 0 ? { deger: ham } : { hata: "bos olamaz" }; }; export const yapilandirmayiOku = (ortam) => { const sorunlar = []; const ayarlar = {}; for (const [ad, kural] of Object.entries(SEMA)) { const anahtar = ONEK + ad; const ham = ortam[anahtar]; if (ham === undefined) { if (kural.zorunlu) sorunlar.push(`${anahtar}: zorunlu, tanimli degil`); else ayarlar[ad] = kural.varsayilan; continue; } const sonuc = cevir(ad, kural, ham); if (sonuc.hata) sorunlar.push(`${anahtar}: ${sonuc.hata}`); else ayarlar[ad] = sonuc.deger; } for (const anahtar of Object.keys(ortam)) { // yazim hatalari sessizce gecmesin if (anahtar.startsWith(ONEK) && !(anahtar.slice(ONEK.length) in SEMA)) { sorunlar.push(`${anahtar}: bilinmeyen anahtar`); } } if (sorunlar.length > 0) { const hata = new Error("yapilandirma gecersiz"); hata.sorunlar = sorunlar.sort(); throw hata; } return Object.freeze(ayarlar); };
Üç tasarım kararı burada gizlidir ve üçü de ölçümde görünecektir.
Bütün sorunlar toplanır. Okuyucu ilk hatada durmaz; şemanın tamamını gezer, sorunları bir listede biriktirir ve sonunda tek seferde bildirir. Sekiz değişkeni yanlış olan bir ortamı sekiz kez başlatmak zorunda kalmamanın yolu budur.
Bilinmeyen anahtar hatadır. Önekli ama şemada karşılığı olmayan bir değişken, neredeyse her zaman bir yazım hatasıdır. Sessizce yok sayıldığında uygulama açılır, varsayılanla çalışır ve yanlış davranışın nedeni ortam dosyasında saatlerce aranır.
Sonuç dondurulur. Object.freeze çağrısı yapılandırmayı değiştirilemez hâle getirir.
Çalışma sırasında bir ayarı değiştiren kod, sürecin hangi anda hangi değerle çalıştığını
belirsizleştirir; dondurma bu yolu kapatır.
Açılışta Düşmek
Doğrulamanın değerini belirleyen, ne zaman yapıldığıdır. Yapılandırma isteği karşılarken okunursa hata ilk istekte, belki de yayından saatler sonra ortaya çıkar. Açılışta okunursa hatalı ortam hiç hizmet vermez.
Aşağıdaki açılış dosyası bunu uygular: yapılandırma sunucu kurulmadan önce okunur, geçersizse sunucu hiç oluşturulmaz.
// kaynak/kurulum/ac.mjs — acilis: yapilandirma once okunur; gecersizse sunucu hic kurulmaz import { createServer } from "node:http"; import { yapilandirmayiOku } from "./yapilandirma.mjs"; const EX_CONFIG = 78; // sysexits.h: yapilandirma hatasi let ayar; try { ayar = yapilandirmayiOku(process.env); } catch (hata) { console.error(hata.message + ":"); for (const sorun of hata.sorunlar ?? []) console.error(" - " + sorun); process.exit(EX_CONFIG); } console.log("yapilandirma: " + JSON.stringify(ayar)); try { ayar.ODUNC_GUN = 30; // degistirilemez nesne: yazma denemesi } catch (hata) { console.log("ayar degistirme denemesi: " + hata.constructor.name); } createServer((istek, yanit) => { yanit.sendDate = false; yanit.writeHead(200, { "content-type": "application/json; charset=utf-8" }); yanit.end(JSON.stringify({ oduncGun: ayar.ODUNC_GUN, bakim: ayar.BAKIM_KIPI })); }).listen(ayar.BAGLANTI_NOKTASI, "127.0.0.1", () => console.log(`acildi 127.0.0.1:${ayar.BAGLANTI_NOKTASI}`));
Çıkış kodu rastgele seçilmemiştir. sysexits.h başlığında tanımlı EX_CONFIG değeri
78’dir ve “yapılandırma hatası” anlamına gelir. Uygulamayı gözeten bir denetçi bu kodu
gördüğünde süreci yeniden başlatmanın işe yaramayacağını bilir; aynı ortam aynı hatayı
üretecektir.
Ölçüm dört ortamla açılışı dener. env -i çağrısı ortamı boşaltır, böylece kabuktaki var
olan değişkenler sonuca karışmaz.
#!/usr/bin/env bash # Dort ayri ortamla acilisi dener; her denemede iletiyi ve cikis kodunu basar. dene() { # $1 = aciklama, kalan argumanlar ANAHTAR=deger ciftleri aciklama="$1"; shift echo "=== $aciklama ===" env -i PATH="$PATH" "$@" node kaynak/kurulum/ac.mjs & pid=$! sleep 0.8 if kill -0 "$pid" 2>/dev/null; then curl -sS "http://127.0.0.1:8435/"; echo kill "$pid"; echo "surec ayakta, sonlandirildi" else wait "$pid"; echo "cikis kodu=$?" fi } dene "1. zorunlu degisken eksik" dene "2. tip ve secenek hatasi bir arada" \ KUTUPHANE_DEPO_ADRESI=file:./veri/kutuphane.db \ KUTUPHANE_BAGLANTI_NOKTASI=seksen \ KUTUPHANE_KUTUK_DUZEYI=detayli \ KUTUPHANE_ODUC_GUN=14 dene "3. aralik disi deger" \ KUTUPHANE_DEPO_ADRESI=file:./veri/kutuphane.db \ KUTUPHANE_BAGLANTI_NOKTASI=80 dene "4. gecerli ortam (varsayilanlar tamamliyor)" \ KUTUPHANE_DEPO_ADRESI=file:./veri/kutuphane.db \ KUTUPHANE_BAKIM_KIPI=true
=== 1. zorunlu degisken eksik ===
yapilandirma gecersiz:
- KUTUPHANE_DEPO_ADRESI: zorunlu, tanimli degil
cikis kodu=78
=== 2. tip ve secenek hatasi bir arada ===
yapilandirma gecersiz:
- KUTUPHANE_BAGLANTI_NOKTASI: tam sayi bekleniyor, "seksen" verildi
- KUTUPHANE_KUTUK_DUZEYI: secenekler [hata, uyari, bilgi, ayrintili], "detayli" verildi
- KUTUPHANE_ODUC_GUN: bilinmeyen anahtar
cikis kodu=78
=== 3. aralik disi deger ===
yapilandirma gecersiz:
- KUTUPHANE_BAGLANTI_NOKTASI: 1024-65535 araliginda olmali, 80 verildi
cikis kodu=78
=== 4. gecerli ortam (varsayilanlar tamamliyor) ===
yapilandirma: {"BAGLANTI_NOKTASI":8435,"ODUNC_GUN":14,"UYE_ODUNC_SINIRI":5,"DEPO_ADRESI":"file:./veri/kutuphane.db","KUTUK_DUZEYI":"bilgi","BAKIM_KIPI":true}
ayar degistirme denemesi: TypeError
acildi 127.0.0.1:8435
{"oduncGun":14,"bakim":true}
surec ayakta, sonlandirildi
Ölçümün Söyledikleri
Birinci deneme zorunlu bir değişkenin eksikliğini gösterir. Uygulama açılmamış, bağlantı
noktasını dinlememiş ve 78 ile çıkmıştır. Eksik olan değerin ne olduğu adıyla yazılmıştır;
“bir şeyler ters gitti” biçiminde bir ileti, ortam dosyasını düzeltecek kişiye hiçbir şey
söylemez.
İkinci deneme üç ayrı sorunu bir arada bildirir. BAGLANTI_NOKTASI sayıya çevrilememiş,
KUTUK_DUZEYI seçenek listesinde bulunmamış ve KUTUPHANE_ODUC_GUN bilinmeyen anahtar
olarak işaretlenmiştir. Üçüncüsü, ODUNC yerine ODUC yazılmış bir değişkendir; şema bunu
yakalamasa uygulama açılır ve ödünç süresi sessizce varsayılanda kalırdı. İletide seçenek
listesinin de yazılmış olması, düzeltmeyi belgeye bakmadan yapılabilir kılar.
Üçüncü deneme tip doğru, değer yanlış durumunu ayırır. 80 geçerli bir tam sayıdır ama
ayrıcalıklı bağlantı noktası aralığındadır. Tip denetimi tek başına yetmez; anlamlı aralık da
şemanın parçasıdır.
Dördüncü deneme geçerli ortamı gösterir. Yalnız iki değişken verilmiştir; kalan dördü varsayılanlarıyla tamamlanmış ve tam yapılandırma tek satırda yazdırılmıştır. Açılışta yapılandırmanın kütüğe yazılması, bir sürecin hangi ayarlarla çalıştığını sonradan sormaya gerek bırakmaz.
Aynı çıktıda ayar degistirme denemesi: TypeError satırı, dondurulmuş nesneye yazma
girişiminin sessizce yutulmadığını doğrular. Modül kodu katı kipte çalıştığı için atama bir
hata fırlatır; katı kip dışında aynı atama sessizce yok sayılırdı.
Öncelik Sırası ve Ortam Dosyası
Ortam değişkeni tek kaynak değildir. Node.js Çalışma Zamanı kursunun Yapılandırma Yönetimi dersinde kurulan katmanlı yapılandırma düzeni burada da geçerlidir: bir değer birden çok yerde tanımlanabilir ve hangisinin kazandığı önceden belirlenmiş olmalıdır. Yaygın sıra, soldan sağa artan öncelikle şudur: şemadaki varsayılan, ortam dosyası, gerçek ortam değişkeni, komut satırı seçeneği.
Bu okuyucu yalnız iki katman kullanır — varsayılan ve ortam değişkeni — ama sıra genişletmeye
açıktır: yapilandirmayiOku fonksiyonu ortamı parametre olarak aldığı için, çağıran taraf
katmanları birleştirip tek bir nesne verebilir. Fonksiyonun process.env değerini kendi
içinden okumaması bilinçli bir karardır; bu sayede sınamalarda gerçek ortam kirletilmeden
farklı ortamlar denenebilir.
Ortam dosyasının kendisi bir kolaylıktır ve sınırı vardır: yerel geliştirmede rahatlık sağlar, yayında ise değerlerin nereden geldiğini gizler. Dosyanın sürüm denetimine girmemesinin nedeni de budur ve bu, sonraki dersin sınırını çizer.
Özet
- Ortamdan ortama değişen değer yapılandırmadır; değişmeyen kural koda, gizli değerler ise ayrı bir sınıfa aittir.
- Şema her değişkenin tipini, aralığını, seçeneklerini ve varsayılanını tek yerde tanımlar; okuyucu ilk hatada durmaz, bütün sorunları toplayıp tek seferde bildirir.
- Önekli ama şemada bulunmayan bir anahtar hata sayılır; ölçümde
KUTUPHANE_ODUC_GUNyazım hatası bu kuralla yakalanmıştır. - Doğrulama açılışta yapılır: geçersiz ortamda sunucu hiç kurulmaz ve süreç
sysexits.hbaşlığındakiEX_CONFIGdeğeriyle, yani78ile çıkar. - Okunan yapılandırma dondurulur; katı kipte çalışan modül kodunda değiştirme girişimi
TypeErrorüretir. Eksik bırakılan ayarlar varsayılanlarla tamamlanır ve açılışta kütüğe yazılır.
Sonraki Adım
Bu dersin şemasında bir değişken kasten yoktu: deponun parolası. Bağlantı noktası ile parola aynı yerden, ortamdan okunur; buna karşılık biri kütüğe yazılabilir, diğeri yazılamaz. Sonraki ders bu sınırı çizer: yapılandırma ile gizli değer arasındaki fark nedir, gizli bir değerin kütüğe, hata yanıtına ve istemciye ulaşmasını hangi kurallar engeller ve maskelemenin gerçekten uygulandığı nasıl denetlenir?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.