Bağımlı Yönetim

Sorun bildirin Kaynağı göster

Önceki sayfalara baktığımızda, bir temanın tekrar tekrar ortaya çıktığını görüyoruz: Kendi kodunuzu yönetmek oldukça basit olsa da, bağımlılıkları yönetmek çok daha zordur. Her türlü bağımlılık söz konusudur: Bazen bir görevin bağımlılığı olur ("bir sürümü "tamamlandı olarak işaretlemeden önce belgeleri aktar" gibi) ve bazen bir yapıya (kodumu oluşturmak için bilgisayar görüş kitaplığının en son sürümüne sahip olmam gerekir) bağlı olur. Bazen, kod tabanınızın başka bir bölümüne, veya bir başkasına ait olan başka bir bölümü dahili olarak bağımlı olur. Yine de her durumda, “Eğer olamam gerekir” fikri, derleme sistemlerinin tasarımında tekrar tekrar ortaya çıkan bir şeydir ve bağımlılıkları yönetmek bir derleme sisteminin en temel işi olabilir.

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

Bazel gibi yapı tabanlı derleme sistemleri kullanan projeler, BUILD dosyaları aracılığıyla birbirine bağımlı olan modülleri ifade eden bir dizi modüle ayrılmıştır. Bu modüllerin ve bağımlıların uygun şekilde organize edilmesi, hem derleme sisteminin performansı hem de bakım için gereken iş üzerinde büyük bir etkiye sahip olabilir.

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

Yapı tabanlı bir yapı yapılandırırken ortaya çıkan ilk soru, tek bir modülün ne kadar işlev içermesi gerektiğine karar vermektir. Bazel'da Modül, java_library veya go_binary gibi yapılandırılabilir bir birim belirten bir hedefle temsil edilir. Uç noktada, köke bir BUILD dosyası yerleştirilerek ve ilgili projenin tüm kaynak dosyaları yinelenen şekilde bir araya getirilerek proje tümüyle tek bir modülde bulunabilir. Diğerinde, hemen hemen her kaynak dosya kendi modülüne dönüştürülerek bağımlı olduğu diğer tüm dosyalarda bir BUILD dosyasının listelenmesi etkin bir şekilde zorunlu kılındı.

Çoğu proje, bu uç noktaların arasında bir yere denk gelir ve seçim, performans ile sürdürülebilirlik arasında bir denge sağlar. Projenin tamamı için tek bir modül kullanmak, harici bir bağımlılık ekleme işlemi dışında hiçbir zaman BUILD dosyasına dokunmanız gerekmediği anlamına gelebilir. Ancak bu, derleme sisteminin tüm projeyi tek seferde oluşturması gerektiği anlamına gelir. Bu, derlemenin bazı bölümlerinin paralel genişletilemediği veya dağıtılamayacağı ya da derlemenin önceden oluşturulmuş bölümlerinin önbelleğe alınamayacağı anlamına gelir. Dosya başına bir modül zıttır: Derleme sistemi, derlemenin önbelleğe alınması ve planlanması için maksimum esnekliğe sahiptir, ancak mühendislerin hangi dosyalarda hangi referansa başvurduğunu her değiştirdiğinde bağımlıların listelerini koruma konusunda daha fazla çaba harcamaları gerekir.

Tam ayrıntı düzeyi dile göre (ve hatta dil içinde) değişse de Google genellikle göreve dayalı bir derleme sisteminde yazabileceğinden çok daha küçük modülleri tercih eder. Google'da tipik bir ikili ikili program genellikle on binlerce hedefe bağlıdır. Orta ölçekli bir ekibin bile kod tabanında birkaç yüz hedefi olabilir. Güçlü paketleme kavramına sahip Java gibi her dizinde genellikle tek bir paket, hedef ve BUILD dosyası (Bazel tabanlı başka bir derleme sistemi olan Pantolonlara da 1:1:1 kural denir) denir. Daha zayıf paketleme kurallarına sahip diller genellikle her BUILD dosyasında birden fazla hedef tanımlar.

Daha küçük derleme hedeflerine sahip olan daha hızlı derlemeler ve daha az sıklıkta hedefleri yeniden oluşturma ihtiyacı ile karşılaşıldığından bu hedefler, küçük ölçekli hedeflerin gerçekten geniş ölçekte gösterilmesine başlar. Teste girildikten sonra avantajlar daha da zorlaşmaktadır. Daha ayrıntılı hedefler, derleme sisteminin herhangi bir değişiklikten etkilenebilecek testlerin yalnızca sınırlı bir alt kümesini çalıştırma konusunda çok daha akıllı olabileceği anlamına gelir. Google, daha küçük hedefleri kullanmanın sistematik faydalarına inandığından, geliştiricilerin yük kurmasını önlemek için BUILD dosyalarını otomatik olarak yönetecek araçlara yatırım yaparak dezavantajı azaltma konusunda bazı adımlar attık.

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

Modül Görünürlüğünü En aza indirme

Bazel ve diğer derleme sistemleri, her bir hedefin bir görünürlük belirtmesine olanak tanır. Bu özellik, diğer hedeflerin bunlara bağlı olabileceğini belirler. Gizli hedeflere yalnızca kendi BUILD dosyalarında referans verilebilir. Hedef, açık bir şekilde tanımlanmış BUILD dosya listesinin hedeflerine veya herkese açık olması durumunda çalışma alanındaki her hedefe görünürlük kazandırabilir.

Çoğu programlama dilinde olduğu gibi, görünürlüğü en aza indirmek için genellikle en iyi seçenektir. Google'daki ekipler, yalnızca bu hedefler Google'daki herhangi bir ekip tarafından kullanılabilen yaygın şekilde kullanılan kitaplıkları temsil ediyorsa hedefleri herkese açık yapar. Kodlarını kullanmadan önce başkalarının kendileriyle koordine edilmesini gerektiren ekipler, hedeflerinin görünürlüğü olarak müşteri hedeflerinin izin verilenler listesini saklar. Her ekibin dahili uygulama hedefleri yalnızca ekibin sahip olduğu dizinlerle kısıtlanır ve çoğu BUILD dosyasında gizli olmayan yalnızca bir hedef olur.

Bağımlıları Yönetme

Modüller birbirini anlayabilmelidir. Kod tabanını ayrıntılı modüllere bölmenin dezavantajı, bu modüller arasındaki bağımlılıkları yönetmenizdir (araçlar bunu otomatik hale getirmeye yardımcı olabilir). Bu bağımlılıkları açıklamak genellikle bir BUILD dosyasındaki içeriğin toplu içeriği olur.

Dahili bağımlılıklar

Ayrıntılı modüllere bölünmüş büyük bir projede, çoğu bağımlılık büyük olasılıkla dahili niteliktedir; yani aynı kaynak deposunda tanımlanıp oluşturulan başka bir hedeftedir. Dahili bağımlılıklar, derleme çalıştırılırken önceden oluşturulmuş bir yapı olarak indirilmek yerine kaynaktan oluşturulmaları açısından harici bağımlılıklardan farklıdır. Bu, dahili bağımlılıklar için "sürüm" kavramının bulunmadığı anlamına da gelir. Hedef ve tüm dahili bağımlılıkları her zaman depodaki aynı kaydetme/düzeltme altında oluşturulur. Dahili bağımlılıklarla ilgili dikkatli bir şekilde ele alınması gereken sorunlardan biri, geçişli bağımlılıkların nasıl ele alınacağıdır (Şekil 1). A hedefinin, ortak kitaplık hedefi C'ye bağlı olan B hedefine bağlı olduğunu varsayalım. A hedefi, hedef C'de tanımlanan sınıfları kullanabilir mi?

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

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

Temel araçlar söz konusu olduğunda bu konuda bir sorun yoktur. Oluşturulduğunda hem B hem de C, hedef A'ya bağlanacağı için C'de tanımlanan simgeler A olarak bilinir. Bazel buna yıllarca izin verdi ama Google büyüdükçe sorunlar görmeye başladık. B'nin, artık C'ye bağımlı olmayacak şekilde yeniden düzenlendiğini varsayalım. B'nin C'ye olan bağımlılığı kaldırılırsa A ve B'ye bağımlılığıyla C'yi kullanan diğer tüm hedefler bozulur. Bir hedefin bağımlıları, etkili bir şekilde herkese açık sözleşmesinin bir parçası haline gelmiş ve hiçbir zaman güvenli bir şekilde değiştirilemez. Bu nedenle, zaman içinde bağımlılıklar birikti ve Google'daki derlemeler yavaşlamaya başladı.

Google sonunda Bazel'de "katı bir geçişli bağımlı modu" sunarak bu sorunu çözdü. Bu moddayken Bazel, bir hedefin doğrudan ona bağlı olmadan bir sembole referans vermeye çalışıp çalışmadığını tespit eder. Bu bağımsız değişken bir hata içeriyorsa ve bağımsız değişkeni otomatik olarak eklemek için kullanılabilen bir kabuk komutuyla da başarısız olur. Bu değişikliği Google'ın tüm kod tabanına yaymak ve milyonlarca oluşturma hedefimizin her birini bağımlılarını açıkça listelemek için yeniden düzenlemek çok yıllı bir çalışmaydı, ancak buna değdi. Hedeflerimiz gereksiz bağımlılıklara sahip olduğundan yapılarımız artık çok daha hızlı. Mühendisler ihtiyaç duymadıkları bağımlılıkları kaldırma konusunda endişelenmeden ortadan kaldırmaları için ihtiyaç duyacakları gücü sağlıyor.

Her zaman olduğu gibi katı geçişli bağımlıların zorunlu kılınması bir ödün verilmesini de beraberinde getirdi. Sık kullanılan kitaplıkların artık arızi bir şekilde almak yerine birçok yerde açıkça listelenmesi gerektiğinden ve mühendislerin BUILD dosyalarına bağımlılık eklemek için daha fazla çaba harcamaları gerektiğinden bu durum daha ayrıntılı bir yapıya dönüştü. O zamandan beri, birçok eksik bağımlıyı otomatik olarak tespit edip herhangi bir geliştirici müdahalesi olmadan bunları bir BUILD dosyasına ekleyerek bu çabayı azaltan araçlar geliştirdik. Ancak bu tür araçlar olmadan bile, kod tabanı ölçeklendirilirken buna değdiğini gördük: BUILD dosyasına açık bir şekilde bağımlılık eklemek tek seferlik bir maliyettir ancak örtülü geçişli bağımlılıklarla ilgilenmek derleme hedefi olduğu sürece süregelen sorunlara yol açabilir. Bazel, Java kodunda varsayılan olarak 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. Harici bağımlılıklar, derleme sisteminin dışında oluşturulan ve depolanan yapılara bağımlıdır. Bağımlılık doğrudan bir yapı deposundan (genellikle internet üzerinden erişilebilir) içe aktarılır ve kaynaktan oluşturulmak yerine olduğu gibi kullanılır. Harici ve dahili bağımlılar arasındaki en büyük farklardan biri, harici bağımlıların sürümlerinin olması ve bu sürümlerin projenin kaynak kodundan bağımsız olarak mevcut olmasıdır.

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

Derleme sistemleri, harici bağımlılık sürümlerinin manuel veya otomatik olarak yönetilmesine olanak tanır. Derleme dosyası manuel olarak yönetildiğinde, yapı deposundan indirmek istediği sürümü genellikle listeler (1.1.4 gibi bir semantik sürüm dizesi kullanarak). Kaynak dosyası otomatik olarak yönetildiğinde bir dizi kabul edilebilir sürümü belirtir ve derleme sistemi her zaman en son sürümü indirir. Örneğin Gradle, ana sürümün 1 olduğu sürece bir bağımlının alt veya yama sürümünün kabul edilebilir olduğunu belirtmek için bir bağımlılık sürümünün "1.+" olarak bildirilmesine olanak tanır.

Otomatik olarak yönetilen bağımlılıklar küçük projeler için uygun olsa da genellikle önemsiz boyutta olan veya birden fazla mühendisin üzerinde çalıştığı projelerde afet tarifi olarak kullanılır. Otomatik olarak yönetilen bağımlılıklarla ilgili sorun, sürümün ne zaman güncelleneceği üzerinde kontrolünüzün olmamasıdır. Harici tarafların zarar verici güncellemeler yapmayacaklarını (anlamsal sürüm oluşturmayı kullandıklarını iddia etseler bile) garanti etmek mümkün değildir. Bu nedenle, bir gün işe yarayan bir derleme bir sonraki gün bozulabilir. Bunun nedeni, neyin değiştiğini tespit etmek veya çalışma durumuna geri döndürmektir. Derleme bozulmazsa bile fark 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 kontrolünde bir değişiklik gerektirdiğinden kolayca keşfedilip geri alınabilir ve eski bağımlılarla derleme yapmak için deponun eski bir sürümüne göz atmak mümkündür. Bazel, tüm bağımlıların sürümlerinin manuel olarak belirtilmesini gerektiriyor. Orta düzeylerdeki ölçeklerde bile manuel sürüm yönetiminin sağladığı ek yük, sağladığı kararlılık açısından buna değecektir.

Tek Sürüm Kuralı

Bir kitaplığın farklı sürümleri genellikle farklı yapılarla temsil edilir. Bu nedenle teoride, aynı harici bağımlılığın farklı sürümlerinin derleme sisteminde farklı adlar kullanılarak beyan edilememesinin bir nedeni yoktur. Bu şekilde, her bir hedef kullanmak istediği bağımlı sürümünü seçebilir. Bu durum, pratikte çok sayıda soruna neden olduğundan Google, kod tabanımızdaki tüm üçüncü taraf bağımlılıkları için katı bir Tek Sürüm Kuralı uygular.

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 v1 sürümüne bağlı olduğunu varsayalım. B hedefi daha sonra aynı harici kitaplığın v2'ye bağımlılığını ekleyecek şekilde yeniden düzenlenirse, artık aynı kitaplığın iki farklı sürümüne dolaylı olarak bağlı olduğu için A hedefi bozulur. Bir hedefin kullanıcıları farklı bir sürüme bağlı olabileceğinden, etkin bir şekilde, birden fazla sürümü olan herhangi bir üçüncü taraf kitaplığına yeni bir bağımlılık eklemek güvenli değildir. Tek Sürüm Kuralı'na uyulması bu çatışmayı imkansız hale getirir. Bir hedef, üçüncü taraf kitaplığına bağımlılık eklerse mevcut bağımlılıklar zaten aynı sürümde olur, böylece mutlulukla bir arada bulunabilirler.

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

Harici bir bağımlılığın geçişli bağımlılıklarıyla başa çıkmak özellikle zor olabilir. Maven Central gibi pek çok yapı deposu, yapılara depodaki diğer yapıların belirli sürümlerindeki bağımlılıkları belirtme olanağı tanır. Maven veya Gradle gibi araçlar genellikle her geçişli bağımlılığı varsayılan olarak yinelenen bir şekilde indirir. Bu da projenize tek bir bağımlılık eklemenin, onlarca yapının toplamda indirilmesine neden olabileceği anlamına gelir.

Bu çok kolaydır: Yeni bir kitaplıka bağımlılık eklerken, kitaplığın geçişli bağımlılarını ayrı ayrı izlemek ve bunları manuel olarak eklemek çok büyük bir sorun olacaktır. Ancak bunun büyük bir dezavantajı var: Farklı kitaplıklar aynı üçüncü taraf kitaplığının farklı sürümlerine bağlı olabileceğinden, bu strateji yalnızca Tek Sürüm Kuralını ihlal eder ve elmas bağımlılığa yol açar. Hedefiniz aynı bağımlılığın farklı sürümlerini kullanan iki harici kitaplıktan etkileniyorsa hangisinin kullanılacağını öğrenmeniz gerekmez. Bu aynı zamanda, yeni bir sürüm bağımlılarından bazılarının çakışan sürümlerini almaya başlaması durumunda, harici bir bağımlıyı güncellemek kod tabanında alakasız görünen hatalara neden olabilir.

Bu nedenle, Bazel geçişli bağımlılıkları otomatik olarak indirmez. Ne yazık ki sihirli bir değnek yok. Bazel'ın alternatifi, kod deposunun harici bağımlılarının her birini listeleyen global bir dosyanın ve kod deposunun bu bağımlılığı için kullanılan açık bir sürümün kullanılmasını gerektiriyor. Neyse ki Bazel, bir grup Maven yapısının geçişli bağımlılıklarını içeren böyle bir dosyayı otomatik olarak oluşturabilen araçlar sağlıyor. Bu araç, bir projenin ilk WORKSPACE dosyasını oluşturmak için bir kez çalıştırılabilir. Daha sonra bu dosya, her bir bağımlının sürümlerini ayarlamak için manuel olarak güncellenebilir.

Burada da seçim rahatlık ve ölçeklenebilirlik arasında seçim yapıyor. Küçük projeler, geçişli bağımlılıkları kendileri yönetme konusunda endişelenmek istemez ve otomatik geçişli bağımlıları kullanarak ortadan kalkabilir. Kuruluş ve kod tabanı büyüdükçe bu strateji giderek daha az ilgi çekici hale gelir ve çatışmalar ile beklenmedik sonuçlar giderek daha sık hale gelir. Daha geniş ölçekte, bağımlılıkları manuel olarak yönetmenin maliyeti, otomatik bağımlılık yönetiminden kaynaklanan sorunların maliyetinden çok daha düşüktür.

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

Harici bağımlılar, genellikle kaynak kod sağlamadan kitaplıkların kararlı sürümlerini yayınlayan üçüncü taraflarca sağlanır. Bazı kuruluşlar, kendi kodlarından bazılarını yapı olarak sunmayı da seçebilir. Böylece diğer kod parçaları kendi dahili bağımlılıkları yerine üçüncü taraf olarak çalışabilir. Bu, yapıların yavaş ancak indirmesi hızlıysa derlemeleri teorik olarak hızlandırabilir.

Ancak bu yöntem önemli bir ek yük ve karmaşıklık da beraberinde getiriyor: Birisi bu yapıların her birini oluşturmak ve yapı deposuna yüklemekten sorumlu olmalı ve müşteriler en son sürümdeki güncellemeleri takip edebilmelidir. Sistemin farklı bölümleri depodaki farklı noktalardan oluşturulacağı ve kaynak ağacının tutarlı bir görünümü olmayacağı için hata ayıklama işlemi de çok daha zor hale gelir.

Yapıyı geliştirmenin 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 kaynaklanan yapıları mühendisler arasında paylaşılan bir konuma kaydeder. Böylece bir geliştirici, yakın zamanda başka biri tarafından oluşturulan bir yapıya bağımlıysa derleme sistemi, derlemek yerine otomatik olarak söz konusu binayı indirir. Bu özellik, doğrudan yapılara bağımlı olan tüm performans avantajlarını sunarken aynı zamanda derlemeler de her zaman aynı kaynaktan oluşturulmuş gibi tutarlı olmalarını sağlar. Bu, Google tarafından dahili olarak kullanılan bir stratejidir ve Bazel uzak önbellek kullanacak şekilde yapılandırılabilir.

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

Üçüncü taraf kaynaklardan gelen yapılara bağlı olarak doğası risklidir. Üçüncü taraf kaynak (ör. yapı deposu) bozulduğunda kullanılabilirlik riski vardır. Çünkü harici bir bağımlılık indirememesi halinde tüm derlemeniz durdurulabilir. Ayrıca güvenlik riski de vardır: Üçüncü taraf sistemin 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 böylece derlemenize rastgele kod yerleştirebilir. Her iki sorun da ihtiyacınız olan yapıları, kontrol ettiğiniz sunuculara yansıtarak ve derleme sisteminizin Maven Central gibi üçüncü taraf yapı depolarına erişimini engelleyerek hafifletilebilir. Bu işlevin dezavantajı, bu aynaların bakımı için gereken çaba ve kaynak olduğudur. Böylece, bunların kullanılıp kullanılmayacağı seçimi projenin ölçeğine bağlı olur. Ayrıca, her bir üçüncü taraf yapısının karmasının kaynak kod deposunda belirtilmesini gerektirerek güvenlik sorunu da tamamen çözülebilir ve bu yapıyla oynanması halinde derleme başarısız olabilir. Sorunu tamamen ortadan kaldıran diğer bir alternatif de projenizin bağımlılıklarını tedarik etmektir. Bir proje bağımlılarını sağladığında, projenin kaynak kodu (kaynak veya ikili olarak) ile birlikte kaynak kontrolünü kontrol eder. Bu, projenin tüm harici bağımlılıklarının dahili bağımlılığa dönüştürüleceği anlamına gelir. Google bu yaklaşımı dahili olarak kullanır ve Google genelinde başvurulan her üçüncü taraf kitaplığını, Google'ın kaynak ağacının kökündeki bir third_party dizinine kontrol eder. Ancak Google'ın kaynak denetim sistemi son derece büyük bir monorepo'yu işlemek için özel olarak tasarlandığından bu özellik, tüm kuruluşlar için bir seçenek olarak sunulmaz.