Bağımlılık Yönetimi

Sorun bildirme Kaynağı görüntüleme Nightly · 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

Önceki sayfalara göz atarken bir temanın tekrar tekrar ortaya çıktığını göreceksiniz: Kendi kodunuzu yönetmek oldukça kolaydır ancak bağımlılıkları yönetmek çok daha zordur. Çeşitli bağımlılık türleri vardır: Bazen bir göreve (ör. "Sürümün tamamlandığını işaretlemeden önce dokümanları gönderin") bazen de bir yapıya (ör. "Kodu derlemek için bilgisayar görüşü kitaplığının en son sürümüne sahip olmam gerekiyor") bağımlılık vardır. Bazen kod tabanınızın başka bir bölümüne, bazen de başka bir ekibin (kuruluşunuzda veya üçüncü taraf) sahip olduğu koda ya da verilere harici bağımlılıklarınız vardır. Ancak her durumda, "Buna sahip olmak için önce şuna ihtiyacım var" fikri, derleme sistemlerinin tasarımında tekrar tekrar ortaya çıkan bir şeydir ve bağımlılık yönetimi, belki de bir derleme sisteminin en temel işlevidir.

Modüller ve Bağımlılıklarla Başa Çıkma

Bazel gibi yapıya dayalı derleme sistemleri kullanan projeler, bir dizi modüle ayrılır. Bu modüller, BUILD dosyaları aracılığıyla birbirlerine olan bağımlılıkları ifade eder. Bu modüllerin ve bağımlılıkların doğru şekilde düzenlenmesi, hem derleme sisteminin performansı hem de sürdürülmesi için gereken iş yükü üzerinde büyük bir etkiye sahip olabilir.

Ayrıntılı Modülleri ve 1:1:1 Kuralını Kullanma

Yapıya dayalı bir derleme oluştururken ilk soru, tek bir modülün ne kadar işlev içermesi gerektiğine karar vermektir. Bazel'de modüller, java_library veya go_binary gibi derlenebilir bir birimi belirten bir hedefle temsil edilir. Bir uçta, köke bir BUILD dosyası yerleştirilerek projenin tüm kaynak dosyaları yinelemeli bir şekilde bir araya getirilerek tüm proje tek bir modülde yer alabilir. Diğer uçta, neredeyse her kaynak dosya kendi modülünde yapılabilir. Bu da etkin bir şekilde, her bir dosyanın bağlı olduğu diğer tüm dosyaların BUILD dosyasında listelenmesini gerektirir.

Çoğu proje bu iki uç nokta arasında bir yerdedir ve seçim, performans ile sürdürülebilirlik arasında bir denge kurmayı gerektirir. Projenin tamamı için tek bir modül kullanmak, harici bağımlılık ekleme dışında BUILD dosyasına hiç dokunmanız gerekmediği anlamına gelebilir ancak bu durumda derleme sisteminin her zaman projenin tamamını tek seferde oluşturması gerekir. Bu, derlemenin bazı bölümlerini paralelleştiremeyeceği veya dağıtamayacağı, ayrıca daha önce derlenmiş bölümleri önbelleğe alamayacak anlamına gelir. Dosya başına bir modül ise bunun tam tersi: Derleme sistemi, derlemenin önbelleğe alma ve planlama adımlarında maksimum esnekliğe sahiptir ancak mühendisler, referans verilen dosyaları her değiştirdiklerinde bağımlılık listelerini tutmak için daha fazla çaba harcamalıdır.

Tam ayrıntı düzeyi dile göre (ve genellikle dil içinde bile) değişiklik gösterse de Google, normalde görev tabanlı bir derleme sisteminde yazabileceğinizden çok daha küçük modülleri tercih eder. Google'daki tipik bir üretim ikili dosyası genellikle on binlerce hedefe bağlıdır ve orta büyüklükte bir ekip bile kod tabanında birkaç yüz hedefe sahip olabilir. Güçlü bir yerleşik paketleme kavramına sahip Java gibi diller için her dizin genellikle tek bir paket, hedef ve BUILD dosyası içerir (Bazel'i temel alan başka bir derleme sistemi olan Pantolonlar için buna 1:1:1 kuralı denir). Paketleme kuralları daha zayıf olan diller genellikle BUILD dosyası başına birden fazla hedef tanımlar.

Daha küçük derleme hedeflerinin avantajları, daha hızlı dağıtılan derlemelere ve hedefleri yeniden oluşturma ihtiyacının daha seyrek olmasına yol açtığından ölçekte gerçekten kendini göstermeye başlar. Test süreci başladıktan sonra bu avantajlar daha da cazip hale gelir. Çünkü daha ayrıntılı hedefler, derleme sisteminin herhangi bir değişiklikten etkilenebilecek sınırlı bir test alt kümesini çalıştırma konusunda çok daha akıllı olabileceği anlamına gelir. Google, daha küçük hedefler kullanmanın sistematik faydalarına inandığı için geliştiricilerin yükünü azaltmak için BUILD dosyalarını otomatik olarak yönetecek araçlara yatırım yaparak olumsuz tarafları azaltma konusunda bazı adımlar attık.

buildifier ve buildozer gibi bu araçlardan bazıları, Bazel'de buildtools dizininde kullanılabilir.

Modül Görünürlüğünü En Aza İndirme

Bazel ve diğer derleme sistemleri, her hedefin bir görünürlük belirtmesine olanak tanır. Bu görünürlük, diğer hangi hedeflerin bu hedefe bağlı olabileceğini belirleyen bir özelliktir. Gizli bir hedefe yalnızca kendi BUILD dosyası içinde referans verilebilir. Bir hedef, açıkça tanımlanmış bir BUILD dosyası listesinin hedeflerine veya herkese açık görünürlük durumunda, çalışma alanındaki her hedefe daha geniş görünürlük verebilir.

Çoğu programlama dilinde olduğu gibi, görünürlüğü olabildiğince en aza indirmek genellikle en iyi seçenektir. Genel olarak Google'daki ekipler, hedefleri yalnızca Google'daki tüm ekiplerin kullanabileceği yaygın olarak kullanılan kitaplıkları temsil ediyorsa herkese açık hale getirir. Kodlarını kullanmadan önce diğer kullanıcıların kendileriyle koordine olmasını gerektiren ekiplerin, hedeflerinin görünürlüğü olarak müşteri hedeflerinin izin verilenler listesi bulunur. Her ekibin dahili uygulama hedefleri yalnızca ekibin sahip olduğu dizinlerle sınırlı olacak ve çoğu BUILD dosyasının gizli olmayan yalnızca bir hedefi olacaktır.

Bağımlılıkları yönetme

Modüllerin birbirlerine referans verebilmesi gerekir. Kod tabanını ayrıntılı modüllere ayırmanın dezavantajı, bu modüller arasındaki bağımlılıkları yönetmeniz gerekmesidir (ancak araçlar bu işlemi otomatikleştirmenize yardımcı olabilir). Bu bağımlılıkların ifade edilmesi, genellikle bir BUILD dosyasındaki içeriğin büyük kısmını oluşturur.

İç bağımlılıklar

İnce ayrıntılı modüllere ayrılmış büyük bir projede, bağımlılıkların çoğunun dahili olması muhtemeldir. Yani aynı kaynak depoda tanımlanan ve oluşturulan başka bir hedeftedir. İç bağımlılıklar, derleme işlemi sırasında önceden derlenmiş bir yapı olarak indirilmek yerine kaynaktan derlendikleri için harici bağımlılıklardan farklıdır. Bu aynı zamanda, dahili bağımlılar için "sürüm" kavramının olmadığı anlamına da gelir. Bir hedef ve tüm dahili bağımlılıkları, depodaki aynı taahhüt/düzeltme ile her zaman derlenir. Dahili bağımlılıklarla ilgili dikkatle ele alınması gereken bir konu, geçişli bağımlılıkların nasıl ele alınacağıdır (Şekil 1). A hedefinin, ortak bir kitaplık hedefi C'ye bağlı olan B hedefine bağlı olduğunu varsayalım. A hedefi, C hedefinde tanımlanan sınıfları kullanabilmeli mi?

Geçişli bağımlılıklar

Şekil 1. Geçiş bağımlılıkları

Temel araçlar açısından bu konuda bir sorun yoktur; hem B hem de C, derlendiğinde hedef A'ya bağlanır. Bu nedenle, C'de tanımlanan tüm semboller A tarafından bilinir. Bazel bu duruma yıllarca izin verdi ancak Google büyüdükçe sorunlar yaşamaya başladık. B'nin, artık C'ye bağımlı olması gerekmeyecek şekilde yeniden yapılandırıldığını varsayalım. B'nin C'ye bağımlılığı kaldırılırsa A ve B'ye bağımlılık aracılığıyla C kullanan diğer hedefler bozulur. Bu nedenle, bir hedefin bağımlılıkları, herkese açık sözleşmesinin bir parçası haline geldi ve hiçbir zaman güvenli bir şekilde değiştirilemedi. Bu durum, zaman içinde bağımlılıkların birikmesine ve Google'daki derlemelerin yavaşlamaya başlamasına neden oldu.

Google, sonunda Bazel'de "katı geçişli bağımlılık modu"nu tanıtarak bu sorunu çözdü. Bu modda Bazel, bir hedefin bir sembole doğrudan bağlı olmadan referansta bulunmaya çalışıp çalışmadığını algılar. Bu durumda, bir hata mesajı ve bağımlılığı otomatik olarak eklemek için kullanılabilecek bir kabuk komutuyla başarısız olur. Bu değişikliği Google'ın tüm kod tabanında kullanıma sunmak ve milyonlarca derleme hedefimizin her birini bağımlılıkları açıkça listeleyecek şekilde yeniden yapılandırmak yıllar süren bir çalışmaydı ancak buna değdi. Hedeflerin gereksiz bağımlılıkları daha az olduğu için derlemelerimiz artık çok daha hızlı. Ayrıca mühendisler, kendilerine bağlı hedeflerin bozulmasından endişe etmeden ihtiyaç duymadıkları bağımlılıkları kaldırabiliyor.

Her zaman olduğu gibi, katı geçişli bağımlılıkları zorunlu kılmak da bir denge gerektiriyordu. Sık kullanılan kitaplıkların artık tesadüfen dahil edilmek yerine birçok yerde açıkça listelenmesinin gerekmesi ve mühendislerin BUILD dosyalarına bağımlılıklar eklemek için daha fazla çaba harcaması gerektiğinden, derleme dosyaları daha ayrıntılı hale geldi. O zamandan beri, eksik bağımlılıkların çoğunu otomatik olarak tespit edip geliştirici müdahalesi olmadan BUILD dosyalarına ekleyerek bu zahmeti azaltan araçlar geliştirdik. Ancak bu tür araçlar olmadan bile, kod tabanı ölçeklendikçe bu dengenin buna değer olduğunu tespit ettik: BUILD dosyasına açıkça bağımlılık eklemek tek seferlik bir maliyettir ancak dolaylı geçişli bağımlılıklarla uğraşmak, derleme hedefi var olduğu sürece sürekli sorunlara neden olabilir. Bazel varsayılan olarak Java kodunda katı geçişli bağımlılıkları zorunlu kılar.

Harici bağımlılıklar

Bir bağımlılık dahili değilse harici olmalıdır. Dış bağımlılıklar, derleme sistemi dışında oluşturulup depolanan yapılardır. Bağımlılık doğrudan bir yapı deposundan (genellikle internet üzerinden erişilir) içe aktarılır ve kaynaktan derlenmek yerine olduğu gibi kullanılır. Dış ve iç bağımlılıklar arasındaki en büyük farklardan biri, dış bağımlılıkların sürümlerinin olması ve bu sürümlerin projenin kaynak kodundan bağımsız olarak var olmasıdır.

Otomatik ve manuel bağımlılık yönetimi

Derleme sistemleri, harici bağımlılıkların sürümlerinin manuel veya otomatik olarak yönetilmesine izin verebilir. Manuel olarak yönetildiğinde derleme dosyası, yapı deposundan indirmek istediği sürümü açıkça listeler. Bunun için genellikle 1.1.4 gibi bir anlamsal sürüm dizesi kullanılır. Otomatik olarak yönetildiğinde kaynak dosya, kabul edilebilir sürümlerin bir aralığını belirtir ve derleme sistemi her zaman en son sürümü indirir. Örneğin Gradle, ana sürüm 1 olduğu sürece bir bağımlının herhangi bir alt veya yama sürümünün kabul edilebilir olduğunu belirtmek için bağımlılık sürümünün "1.+" olarak tanımlanmasına izin verir.

Otomatik olarak yönetilen bağımlılıklar küçük projeler için uygun olabilir ancak genellikle önemli boyutlarda veya birden fazla mühendisin üzerinde çalıştığı projelerde felaketle sonuçlanır. Otomatik olarak yönetilen bağımlılıklarla ilgili sorun, sürümün ne zaman güncelleneceği üzerinde hiçbir kontrole sahip olmamanızdır. Harici tarafların, anlamsal sürüm kontrolünü kullandıklarını iddia etseler bile çalışmayı durduran güncellemeler yapmayacaklarını garanti etmek mümkün değildir. Bu nedenle, bir gün çalışan bir derleme, ertesi gün çalışmayı durdurabilir ve neyin değiştiğini tespit etmenin veya derlemeyi çalışır duruma geri döndürmenin kolay bir yolu yoktur. Derleme bozulmasa bile, tespit edilmesi imkansız olan küçük davranış veya performans değişiklikleri olabilir.

Buna karşılık, manuel olarak yönetilen bağımlılıklar kaynak denetiminde bir değişiklik gerektirdiğinden kolayca bulunabilir ve geri alınabilir. Ayrıca, eski bağımlılıklarla derleme yapmak için kod deposunun eski bir sürümüne göz atabilirsiniz. Bazel, tüm bağımlılıkların sürümlerinin manuel olarak belirtilmesini gerektirir. Orta düzeyli ölçeklerde bile manuel sürüm yönetiminin ek yükü, sağladığı kararlılık açısından buna değer.

Tek Sürüm Kuralı

Bir kitaplığın farklı sürümleri genellikle farklı yapılarla temsil edilir. Bu nedenle, teoride derleme sisteminde aynı dış bağımlılığın farklı sürümlerinin her ikisinin de farklı adlarla tanımlanamamasının bir nedeni yoktur. Böylece her hedef, kullanmak istediği bağımlılık sürümünü seçebilir. Bu durum pratikte pek çok soruna yol açtığından Google, kod tabanımızdaki tüm üçüncü taraf bağımlılıkları için katı bir Tek Sürüm Kuralı uygulamaktadır.

Birden fazla sürüme izin vermenin en büyük sorunu, elmas bağımlılığı sorunudur. A hedefinin B hedefine ve harici bir kitaplığın 1. sürümüne bağlı olduğunu varsayalım. B hedefi daha sonra aynı harici kitaplığın v2 sürümüne bağımlılık eklemek için yeniden yapılandırılırsa A hedefi artık aynı kitaplığın iki farklı sürümüne dolaylı olarak bağımlı olduğu için çalışmaz. Bir hedeften, birden fazla sürümü olan herhangi bir üçüncü taraf kitaplığına yeni bir bağımlılık eklemek hiçbir zaman güvenli değildir, çünkü söz konusu hedefin kullanıcıları zaten farklı bir sürüme bağlı olabilir. Tek Sürüm Kuralını uygulamak bu çakışmayı imkansız kılar. Bir hedef üçüncü taraf kitaplığına bağımlılık eklerse mevcut bağımlılıklar aynı sürümde olur. Böylece birlikte kullanılabilirler.

Geçişli harici bağımlılıklar

Bir dış bağımlığın geçişli bağımlılıklarıyla başa çıkmak özellikle zor olabilir. Maven Central gibi birçok yapı deposu, yapıların depodaki diğer yapıların belirli sürümlerine olan bağımlılıkları belirtmesine olanak tanır. Maven veya Gradle gibi araçlar genellikle varsayılan olarak her geçişli bağımlılığı tekrarlı bir şekilde indirir. Yani projenize tek bir bağımlılık eklemek onlarca yapının toplamda indirilmesine neden olabilir.

Bu çok kullanışlıdır: Yeni bir kitaplığa bağımlılık eklerken, söz konusu kitaplığın geçişli bağımlılıklarının her birini bulup hepsini manuel olarak eklemek çok zor olur. Ancak aynı zamanda büyük bir dezavantaj vardır: Farklı kitaplıklar aynı üçüncü taraf kitaplığının farklı sürümlerine bağlı olabileceğinden, bu strateji, Tek Sürüm Kuralı'nı mutlaka ihlal eder ve elmas bağımlılık sorununa yol açar. Hedefiniz, aynı bağımlılığın farklı sürümlerini kullanan iki harici kitaplığa bağlıysa hangi sürümün kullanılacağı belli olmaz. Bu durum, harici bir bağımlılığın güncellenmesi durumunda yeni sürüm, bağımlılığının bazılarının çakışan sürümlerini çekmeye başlarsa kod tabanında birbiriyle alakalı olmayan hatalar oluşabileceği anlamına da gelir.

Bu nedenle Bazel, geçişli bağımlılıkları otomatik olarak indirmez. Maalesef bu sorunu çözmek için tek bir çözüm yok. Bazel'in alternatifi, deponun harici bağımlılıklarının her birini ve depo genelinde bu bağımlılık için kullanılan açık bir sürümü listeleyen bir genel dosya gerektirmektir. Neyse ki Bazel, bir Maven yapı grubuna ait geçişli bağımlılıkları içeren bu tür bir dosyayı otomatik olarak oluşturabilen araçlar sağlar. Bu araç, bir projenin ilk WORKSPACE dosyasını oluşturmak için bir kez çalıştırılabilir. Ardından, her bağımlılığın sürümlerini ayarlamak için bu dosya manuel olarak güncellenebilir.

Burada da seçim, kolaylık ve ölçeklenebilirlik arasındadır. Küçük projelerde, geçişli bağımlılıkları yönetmek zorunda kalmamak tercih edilebilir ve otomatik geçişli bağımlılıklar kullanılabilir. Kuruluş ve kod tabanı büyüdükçe ve çakışmalar ile beklenmedik sonuçlar giderek daha sık ortaya çıktıkça bu stratejinin cazibesi azalır. Bağımlılıkları manuel olarak yönetmenin maliyeti, daha büyük ölçeklerde otomatik bağımlılık yönetiminin neden olduğu sorunlarla uğraşmanın maliyetinden çok daha düşüktür.

Harici bağımlılıkları kullanarak derleme sonuçlarını önbelleğe alma

Dış bağımlılıklar çoğu zaman, belki kaynak kodu sağlamadan kitaplıkların kararlı sürümlerini yayınlayan üçüncü taraflarca sağlanır. Bazı kuruluşlar, kendi kodlarının bir kısmını yapı olarak sunmayı da seçebilir. Bu sayede diğer kod parçaları, şirket içi bağımlılıklar yerine üçüncü taraf olarak bu yapılara bağımlı olabilir. Yapıların derlenmesi yavaş ancak indirilmesi hızlıysa bu, teorik olarak derlemeleri hızlandırabilir.

Ancak bu, çok fazla ek yük ve karmaşıklık da getirir: Bu yapıların her birini oluşturmaktan ve yapı deposuna yüklemekten birinin sorumlu olması gerekir. Ayrıca istemcilerin en son sürümle güncel kalmasını sağlaması gerekir. Sistemin farklı bölümleri, depoda farklı noktalardan derleneceği ve artık kaynak ağacın tutarlı bir görünümü olmadığı için hata ayıklama da çok daha zor hale gelir.

Yapımlarının uzun sürmesi sorununu çözmenin daha iyi bir yolu, daha önce açıklandığı gibi uzaktan önbelleğe almayı destekleyen bir derleme sistemi kullanmaktır. Böyle bir derleme sistemi, her derlemeden ortaya çıkan yapıları mühendisler arasında paylaşılan bir konuma kaydeder. Böylece, bir geliştirici, yakın zamanda başkası tarafından oluşturulmuş bir yapıya bağımlıysa derleme sistemi onu oluşturmak yerine otomatik olarak indirir. Bu, doğrudan yapı ürünlerine bağlı olmanın tüm performans avantajlarını sağlarken derlemelerin her zaman aynı kaynaktan derlenmiş gibi tutarlı olmasını sağlar. Bu, Google tarafından şirket içinde kullanılan stratejidir ve Bazel, uzak önbelleği kullanacak şekilde yapılandırılabilir.

Harici bağımlılıkların güvenliği ve güvenilirliği

Üçüncü taraf kaynaklarından gelen yapıların kullanılması doğal olarak risklidir. Üçüncü taraf kaynağı (ör. yapı deposu) devre dışı kalırsa kullanılabilirlik riski vardır. Çünkü harici bir bağımlılık indiremezse derlemenizin tamamı durabilir. Ayrıca güvenlik riski de vardır: Üçüncü taraf sisteminin güvenliği bir saldırgan tarafından ihlal edilirse saldırgan, referans verilen yapıyı kendi tasarımlarından biriyle değiştirebilir ve derlemenize keyfi kodlar yerleştirebilir. Bağımlı olduğunuz yapıları sizin kontrol ettiğiniz sunuculara yansıtarak ve derleme sisteminizin Maven Central gibi üçüncü taraf yapı depolarına erişmesini engelleyerek her iki sorun da azaltılabilir. Ancak bu yansıtıcıların bakımı için çaba ve kaynak gerekir. Bu nedenle, bunları kullanıp kullanmayacağınıza karar vermek genellikle projenin ölçeğine bağlıdır. Güvenlik sorunu, her üçüncü taraf yapının karmasının kaynak depoda belirtilmesini zorunlu kılarak da çok az ek yük ile tamamen önlenebilir. Bu durumda, yapıda değişiklik yapılması derlemenin başarısız olmasına neden olur. Sorunu tamamen atlatan bir diğer alternatif de projenizin bağımlılıklarını tedarikçiye aktarmaktır. Bir proje, bağımlılıkları tedarikçiye gönderirken bunları projenin kaynak koduyla birlikte kaynak veya ikili kod olarak kaynak denetimine gönderir. Bu, projenin tüm harici bağımlılıklarının dahili bağımlılıklara dönüştürüldüğü anlamına gelir. Google, bu yaklaşımı dahili olarak kullanır ve Google'da referans verilen her üçüncü taraf kitaplığını Google'ın kaynak ağacının kökündeki bir third_party dizininde kontrol eder. Ancak bu, yalnızca Google'ın kaynak denetimi sisteminin son derece büyük bir monorepoyu işleyebilecek şekilde özel olarak tasarlanmış olması nedeniyle Google'da işe yarar. Bu nedenle, tedarikçi firmalardan hizmet almak tüm kuruluşlar için uygun olmayabilir.