Ders 07 / 14
Bağımlılık Ağacı ve Çakışmalar
Kesişmeyen sürüm isteklerinin iç içe kurulumla çözülmesi, yinelenen kopyaların ürettiği kimlik ve durum sorunları, ağacın düzleştirilmesi ve tekillik gerektiren bağımlılıklar.
İçindekiler
Önceki ders tek bir aralığı inceledi. Bir projede onlarca paket bulunur ve bunların çoğu kendi bağımlılıklarını taşır. Aynı pakete iki farklı yerden, iki farklı aralıkla istek gelmesi olağandır.
İstekler kesişiyorsa sorun yoktur: kesişimden bir sürüm seçilir ve herkes onu kullanır. Kesişmediğinde ne olur? Bu dersin konusu, o durumun çözümü ve çözümün bedelidir.
Kesişmeyen İstekler
Ölçüm projesi ortak adlı bir paketin ikinci ana sürümünü ister. Projenin kullandığı
ceviri paketi ise aynı paketin birinci ana sürümüne bağlıdır. ^2.0.0 ile ^1.0.0
kümelerinin kesişimi boştur.
Sistem paket yöneticilerinde bu durum, Linux’a Giriş kursundaki paket yöneticisi kavramı dersinde anlatıldığı gibi bir çakışma bildirimiyle sonuçlanır: bir kütüphanenin sistemde tek bir sürümü kurulabilir. Modül çözümlemesi ise konuma bağlı olduğu için başka bir çıkış yolu sunar — aynı paketin iki sürümü, ağacın iki ayrı düzeyinde durabilir.
İki bildirim, çakışan istekleri yazılı hâlde gösterir:
{ "name": "proje", "version": "1.0.0", "type": "module", "dependencies": { "ceviri": "^3.1.0", "ortak": "^2.0.0" } }
{ "name": "ceviri", "version": "3.1.0", "type": "module", "main": "index.mjs", "dependencies": { "ortak": "^1.0.0" } }
Kurulmuş ağacın biçimi şudur:
$ node agac.mjs [email protected] [email protected] [email protected] yinelenen: ortak -> 1.0.0, 2.0.0
Bu çıktıyı üreten betik, önceki derste anlatılan arama kuralını tersinden uygular: her
node_modules dizinini gezer ve bulduğu bildirimleri yazar.
// dosya: agac.mjs import { readdir, readFile } from 'node:fs/promises'; import { join } from 'node:path'; async function dolas(dizin, derinlik = 0, bulgular = new Map()) { const modulDizini = join(dizin, 'node_modules'); let girdiler; try { girdiler = await readdir(modulDizini); } catch { return bulgular; } for (const ad of girdiler.sort()) { const paketDizini = join(modulDizini, ad); const bildirim = JSON.parse(await readFile(join(paketDizini, 'package.json'), 'utf8')); console.log(`${' '.repeat(derinlik)}${bildirim.name}@${bildirim.version}`); bulgular.set(bildirim.name, (bulgular.get(bildirim.name) ?? new Set()).add(bildirim.version)); await dolas(paketDizini, derinlik + 1, bulgular); } return bulgular; } const bulgular = await dolas('.'); for (const [ad, surumler] of bulgular) { if (surumler.size > 1) console.log(`yinelenen: ${ad} -> ${[...surumler].join(', ')}`); }
Ağaçtaki yerleşim şudur: ortak paketinin ikinci sürümü kök dizinde, birinci sürümü
ceviri paketinin kendi node_modules dizinindedir. Arama kuralı gereği ceviri içinden
yapılan bir içe aktarma önce kendi dizinine bakar ve birinci sürümü bulur; kökten yapılan
bir içe aktarma ikinci sürümü bulur. İki istek de karşılanmıştır.
İki Kopyanın Bedeli
Çakışma çözüldü, ama sonuç iki ayrı paket örneğidir. Modül sistemleri konusunda kurulan tek değerlendirme güvencesi paket başına değil, çözülmüş dosya yolu başına geçerlidir; iki farklı dosya, iki farklı modül demektir.
// dosya: node_modules/ortak/index.mjs — ikinci sürüm export const surum = '2.0.0'; export class Kayit { constructor(ad) { this.ad = ad; } } const kayitlar = []; export function ekle(kayit) { kayitlar.push(kayit); return kayitlar.length; }
// dosya: node_modules/ceviri/index.mjs — kendi dizinindeki birinci sürümü kullanır import { ekle, Kayit, surum } from 'ortak'; export function kullan(ad) { const kayit = new Kayit(ad); return { gorulenSurum: surum, kayitSayisi: ekle(kayit), kayit }; }
// dosya: ana.mjs — yukarıdaki iki paketi kullanır import { ekle, Kayit, surum } from 'ortak'; import { kullan } from 'ceviri'; const bizim = new Kayit('kok'); console.log('kokun gordugu surum:', surum, '| kayit sayisi:', ekle(bizim)); const onunki = kullan('ceviri-ici'); console.log('cevirinin gordugu surum:', onunki.gorulenSurum, '| kayit sayisi:', onunki.kayitSayisi); console.log('cevirinin urettigi kayit bizim sinifimizdan mi:', onunki.kayit instanceof Kayit);
$ node ana.mjs kokun gordugu surum: 2.0.0 | kayit sayisi: 1 cevirinin gordugu surum: 1.0.0 | kayit sayisi: 1 cevirinin urettigi kayit bizim sinifimizdan mi: false
Üç satır, üç ayrı sorunu gösterir.
Sürüm görünürlüğü kullanıcıya bağlıdır. Aynı programda iki farklı ortak sürümü
çalışıyor. Hangisinin çalıştığı, sorunun nereden sorulduğuna göre değişir.
Durum paylaşılmaz. İki kayıt listesi ayrıdır; her ikisi de bir eleman içerir. Paketin tuttuğu kayıt, önbellek veya sayaç türünden her şey bölünmüştür. Bir kütüphane “tüm kayıtları listele” gibi bir işlev sunuyorsa, yalnızca kendi kopyasının kayıtlarını görür.
Kimlik denetimi başarısızdır. ceviri paketinin ürettiği nesne, kökteki sınıfın örneği
değildir. Sınıflar aynı kaynaktan gelse bile iki ayrı değerlendirmenin ürünüdür; nesne
kimliği bunu bilmez. Aynı sorun sembollerde, hata sınıflarında ve marka denetimlerinde
ortaya çıkar.
Bu üç sorun, “programım çalışıyor ama bir yerde tuhaf davranıyor” biçimindeki tanısı zor arızaların sık nedenidir. Bağımlılık ağacında yinelenen sürüm aramak, bu tür bir arızada ilk adımdır.
Düzleştirme
İç içe kurulum her zaman gerekli değildir. İstekler kesişiyorsa tek bir kopya hepsini karşılar ve bu kopya ağacın köküne konur. Bu yerleştirmeye düzleştirme (flattening) denir.
Düzleştirmenin iki gerekçesi vardır. Birincisi yer ve süre: aynı paketin onlarca kopyası yerine tek kopya kurulur. İkincisi az önce sayılan üç sorunun önlenmesidir.
Düzleştirmenin bir yan etkisi, önceki derste değinilen sinsi duruma zemin hazırlamasıdır. Kök dizine çıkarılan bir paket, onu bildiriminde yazmayan modüllerden de görünür hâle gelir. Kod yerelde çalışır; ağaç başka bir biçimde kurulduğunda — örneğin farklı bir kurulum aracı iç içe yerleşimi yeğlediğinde — paket kaybolur. Bu yüzden kural açıktır: kullanılan her paket, kullanan paketin bildiriminde yazılı olmalıdır.
Tekillik Gerektiren Bağımlılıklar
Bazı paketler için iki kopya yalnızca israf değil, doğrudan hatadır. Kayıt tutan, genel bir durum yöneten ya da nesne kimliğine dayanan kütüphaneler tek örnek olmak zorundadır.
Bunu sağlayan mekanizma, ilk derste tanıtılan eş bağımlılık listesidir. Bir eklenti, genişlettiği kütüphaneyi çalışma bağımlılığı olarak yazarsa kendi dizinine kendi kopyasını kurar ve ikilik oluşur. Eş bağımlılık olarak yazdığında ise kütüphaneyi kurmaz; yalnızca “şu aralıktaki bir sürüm ortamda bulunmalı” der. Kurma sorumluluğu uygulamaya geçer ve ağaçta tek kopya kalır.
Eş bağımlılık aralığı bilinçli seçilmelidir. Fazla dar bir aralık, eklentinin kullanılabildiği sürüm kümesini gereksiz daraltır ve kesişmeyen istekler üretir; fazla geniş bir aralık, eklentinin çalışmadığı sürümlerde kurulumun sorunsuz görünmesine yol açar.
Çakışma Ne Zaman Çözülmez
İç içe kurulum her çakışmayı çözmez. Üç durumda tek kopya zorunludur ve karar verilmesi gerekir:
Eş bağımlılık kesişmiyorsa. İki eklenti aynı kütüphanenin uyumsuz ana sürümlerini istiyorsa ikisi bir arada kullanılamaz. Çözüm, eklentilerden birini yükseltmek veya değiştirmektir.
Değerler paketler arasında geçiyorsa. Bir paketin ürettiği nesne başka bir pakete argüman olarak veriliyorsa ve alan taraf kimlik denetimi yapıyorsa, iki kopya çalışmaz.
Çıktı boyutu belirleyiciyse. Tarayıcıya gönderilen bir çıktıda aynı kütüphanenin iki sürümünü taşımak, indirilen baytı iki katına çıkarır.
Bu durumlarda kurulum araçları, geçişli bir bağımlılığın sürümünü dışarıdan sabitlemeye izin veren bir bildirim alanı sunar. Alan işe yarar ama tehlikelidir: bir paketin beklediğinden farklı bir sürümle çalışmaya zorlanması, sürüm sözleşmesinin kasıtlı olarak çiğnenmesidir. Kullanıldığında bir yorumla gerekçelendirilmeli ve geçici sayılmalıdır; kalıcı çözüm, uyumsuz paketin yükseltilmesidir.
Özet
- Kesişmeyen sürüm istekleri, aynı paketin iki kopyasının ağacın farklı düzeylerine kurulmasıyla karşılanır; arama kuralı her tüketiciye kendi kopyasını bulduran şeydir.
- Tek değerlendirme güvencesi paket başına değil, çözülmüş dosya yolu başınadır; iki kopya ayrı durum tutar ve nesne kimliği denetimlerini başarısız kılar.
- Kesişen istekler tek kopyayla karşılanır ve kopya köke çıkarılır; bu düzleştirme, bildirimde yazılmayan paketlerin görünür olmasına da yol açar.
- Tekillik gerektiren kütüphaneler eş bağımlılık olarak bildirilir; kurma sorumluluğu uygulamaya geçer ve ağaçta tek kopya kalır.
- Geçişli bir bağımlılığın sürümünü dışarıdan sabitlemek sözleşmeyi çiğner; geçici çözüm sayılmalıdır.
Sonraki Adım
Buraya kadar tüm paketler dışarıdan geldi. Bir ekibin kendi yazdığı paketler de aynı ağaçta yer alır ve aralarındaki bağımlılıklar her değişiklikte yayımlanmayı bekleyemez. Sonraki ders, birden çok paketin tek bir depoda tutulduğu düzeni, bu düzenin çözümlemeyi nasıl değiştirdiğini ve paketler arası derleme sırasının nasıl belirlendiğini ele alır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.