paint-brush
Uygulama Mimarisi Diyagramlarını Otomatikleştirme: Kod Tabanlarını Kaynaktan Eşlemek için Nasıl Bir Araç Geliştirdimile@vladimirf
Yeni tarih

Uygulama Mimarisi Diyagramlarını Otomatikleştirme: Kod Tabanlarını Kaynaktan Eşlemek için Nasıl Bir Araç Geliştirdim

ile Vladimir Filipchenko6m2024/07/30
Read on Terminal Reader

Çok uzun; Okumak

Hiç kodunuzu anında anlaşılır, görsel bir diyagrama dönüştürebilecek bir aracın olmasını dilediniz mi? NoReDraw'ın yaptığı da tam olarak budur! Bir yazılım mühendisinin hayal kırıklığından doğan bu araç, yapılar ve konfigürasyonlar gibi temel bileşenleri tanımlar ve bunları birbirine bağlayarak kapsamlı bir mimari diyagramı oluşturur. Son derece özelleştirilebilir ve kolayca genişletilebilir olacak şekilde tasarlanmıştır; bir şey her değiştiğinde diyagramları yeniden çizme zahmetine girmeden belgelerinizin güncel kalmasını sağlar.
featured image - Uygulama Mimarisi Diyagramlarını Otomatikleştirme: Kod Tabanlarını Kaynaktan Eşlemek için Nasıl Bir Araç Geliştirdim
Vladimir Filipchenko HackerNoon profile picture
0-item

Çünkü hayat diyagramları yeniden çizmek için çok kısa


Yakın zamanda Yazılım Mühendisi olarak yeni bir şirkete katıldım. Her zaman olduğu gibi sıfırdan başlamak zorunda kaldım. Şunun gibi şeyler: Yayınlanan bir uygulamanın kodu nerede? Nasıl konuşlandırılır? Yapılandırmalar nereden geliyor? Neyse ki meslektaşlarım her şeyi 'kod olarak altyapı' haline getirerek harika bir iş çıkardılar. Kendimi şöyle düşünürken yakaladım: Her şey kodun içindeyse neden tüm noktaları birleştirecek bir araç yok?


Bu araç, kod tabanını inceleyecek ve önemli hususları vurgulayan bir uygulama mimarisi diyagramı oluşturacaktır. Yeni bir mühendis şemaya bakıp şöyle diyebilir: "Ah, tamam, bu iş böyle yürüyor."


Her şey sırayla

Ne kadar aradıysam da böyle bir şey bulamadım. Bulduğum en yakın eşleşmeler altyapı şeması çizen hizmetlerdi. Daha yakından bakabilmeniz için bazılarını bu incelemeye koydum. Sonunda Google'da aramayı bıraktım ve yeni harika şeyler geliştirmek için ellerimi denemeye karar verdim.


Öncelikle Gradle, Docker ve Terraform ile örnek bir Java uygulaması geliştirdim. GitHub eylemleri işlem hattı, uygulamayı Amazon Elastic Container Service üzerinde dağıtır. Bu repo, geliştireceğim araç için kaynak olacak (kod burada ).


İkinci olarak, sonuç olarak görmek istediklerimin çok üst düzey bir diyagramını çizdim:



İki tür kaynak olacağına karar verdim:

Kalıntı

Eser terimini çok fazla yüklediğim için Relic'i seçtim. Peki Kalıntı nedir? Görmek istediğiniz her şeyin %90'ı bu. Dahil olmak üzere, ancak bunlarla sınırlı değildir:

  • Yapıtlar (şemadaki mavi kutular, yani Jar'lar, Docker görüntüleri),
  • Terraform kaynaklarını yapılandırır (şemadaki pembe kutular, yani EC2 bulut sunucuları, ECS, SQS kuyrukları),
  • Kubernetes kaynakları,
  • ve çok-çok daha fazlası


Her Kalıntının bir adı (örneğin, my-shiny-app), isteğe bağlı türü (örneğin, Jar) ve bir dizi anahtar → değer çifti (örneğin, yol → /build/libs/my-shiny-app.jar) vardır. Relic'i tam olarak açıklar. Bunlara Tanımlar denir. Relic'in Tanımları ne kadar fazlaysa o kadar iyidir.

Kaynak

İkinci tür Kaynaktır . Kaynaklar, Kalıntıları tanımlar, oluşturur veya tedarik eder (örneğin, yukarıdaki sarı kutular). Bir Kaynak, bir yerdeki bir Kalıntıyı tanımlar ve onun nereden geldiğine dair bir fikir verir. Kaynaklar en çok bilgi aldığımız bileşenler olsa da genellikle diyagram üzerinde ikincil anlamlara sahiptirler. Muhtemelen Terraform veya Gradle'dan diğer Relic'lere giden çok fazla oka ihtiyacınız yoktur.


Kalıntı ve Kaynak'ın çoktan çoğa ilişkisi vardır.


Böl ve fethet

Her kod parçasını kapsamak imkansızdır. Modern uygulamalar birçok çerçeveye, araca veya bulut bileşenine sahip olabilir. AWS tek başına Terraform için yaklaşık 950 kaynak ve veri kaynağına sahiptir! Araç, diğer kişilerin veya şirketlerin katkıda bulunabilmesi için kolayca genişletilebilir ve tasarım açısından ayrıştırılabilir olmalıdır.


İnanılmaz derecede takılabilir Terraform sağlayıcılarının mimarisinin büyük bir hayranı olsam da, basitleştirilmiş de olsa aynısını oluşturmaya karar verdim:

Sağlayıcılar


Sağlayıcının net bir sorumluluğu vardır: İstenilen kaynak dosyalara göre Kalıntılar oluşturmak. Örneğin, GradleProvider *.gradle dosyalarını okur ve Jar , War veya Gz Relics'i döndürür. Her sağlayıcı, bildiği türden Kalıntılar oluşturur. Sağlayıcılar, Kalıntılar arasındaki etkileşimleri umursamazlar. Kalıntıları bildirimsel olarak, birbirlerinden tamamen izole edilmiş şekilde inşa ediyorlar.


Bu yaklaşımla istediğiniz kadar derine inmek kolaydır. Bunun iyi bir örneği GitHub Eylemleridir. Tipik bir iş akışı YAML dosyası, gevşek bağlı bileşenlerin ve hizmetlerin kullanıldığı düzinelerce adımdan oluşur. Bir iş akışı bir JAR, ardından bir Docker görüntüsü oluşturabilir ve bunu ortama dağıtabilir. İş akışındaki her adım sağlayıcı tarafından karşılanabilir. Yani diyelim ki Docker Actions'ın geliştiricileri yalnızca önemsedikleri adımlarla ilgili bir Sağlayıcı oluşturuyor.


Bu yaklaşım, araca daha fazla mantık katarak herhangi sayıda kişinin paralel çalışmasına olanak tanır. Son kullanıcılar ayrıca Sağlayıcılarını (bazı özel teknolojiler durumunda) hızlı bir şekilde uygulayabilir. Aşağıdaki Özelleştirme altında daha fazlasını görün.


Birleşmek ya da birleşmemek

En heyecanlı kısma geçmeden önce bir sonraki tuzağa bakalım. Her biri bir Kalıntı oluşturan iki Sağlayıcı. Bu iyi. Peki ya bu Kalıntılardan ikisi yalnızca iki yerde tanımlanan aynı bileşenin temsilleriyse? İşte bir örnek.


AmazonECSProvider, görev tanımı JSON'u ayrıştırır ve AmazonECSTask türünde bir Kalıntı üretir. GitHub eylem iş akışında ayrıca ECS ile ilgili bir adım bulunur, bu nedenle başka bir sağlayıcı bir AmazonECSTaskDeployment Relic oluşturur. Artık elimizde kopyalar var çünkü her iki sağlayıcı da birbirleri hakkında hiçbir şey bilmiyor. Üstelik herhangi birinin bir başkasının zaten bir Kalıntı yarattığını varsayması yanlıştır. Sonra ne?


Kalıntılar birleşiyor


Her birinin sahip olduğu Tanımlar (nitelikler) nedeniyle kopyalardan hiçbirini bırakamayız. Tek yol onları birleştirmektir. Varsayılan olarak sonraki mantık birleştirme kararını tanımlar:


 relic1.name() == relic2.name() && relic1.source() != relic2.source()


İsimleri eşit ancak farklı Kaynaklarda tanımlıysa iki Kalıntıyı birleştiriyoruz (örneğimizde repodaki JSON ve görev tanımı referansı GithHub Eylemlerinde olduğu gibi).


Birleştiğimizde:

  1. Tek isim seçin
  2. Tüm Tanımları Birleştir (anahtar → değer çiftleri)
  3. Her iki orijinal Kaynağa atıfta bulunan bileşik bir Kaynak oluşturun


Bir çizgi çiz

Bir Kalıntının çok önemli bir yönünü kasıtlı olarak atladım. Bir Matcher'ı olabilir - ve ona sahip olmak daha iyi! Matcher, bir argümanı alan ve onu test eden bir boole fonksiyonudur. Eşleştiriciler bir bağlantı sürecinin önemli parçalarıdır. Bir Kalıntı başka bir Kalıntının herhangi bir tanımıyla eşleşirse birbirine bağlanacaktır.


Sağlayıcıların diğer Sağlayıcılar tarafından oluşturulan Kalıntılar hakkında hiçbir fikrinin olmadığını söylediğimi hatırlıyor musun? Bu hala doğru. Ancak, bir Sağlayıcı bir Kalıntı için Eşleştiriciyi tanımlar. Başka bir deyişle, ortaya çıkan diyagramda iki kutu arasındaki okun bir tarafını temsil eder.


Kalıntılar maçı


Örnek. Dockerfile'ın bir ENTRYPOINT talimatı var.


 ENTRYPOINT java -jar /app/arch-diagram-sample.jar


Kesin olarak Docker'ın ENTRYPOINT altında belirtilen her şeyi kapsayıcıya aldığını söyleyebiliriz. Dolayısıyla, Dockerfile Relic'in basit bir Matcher işlevi vardır: entrypointInstruction.contains(anotherRelicsDefinition) . Büyük olasılıkla, Tanımlarda arch-diagram-sample.jar bulunan bazı Jar Relic'ler bununla eşleşecektir. Evetse Dockerfile ile Jar Relics arasında bir ok görünür.


Matcher tanımlandığında, bağlama işlemi oldukça basit görünüyor. Bağlantı hizmeti tüm Kalıntılar üzerinde yinelenir ve Eşleştiricinin işlevlerini çağırır. Kalıntı A, Kalıntı B tanımlarından herhangi biriyle eşleşiyor mu? Evet? Ortaya çıkan grafikte bu Kalıntılar arasına bir kenar ekleyin. Kenar da adlandırılabilir.


Görselleştirme

Son adım, önceki aşamanın son grafiğini görselleştirmektir. Açık PNG'ye ek olarak araç, Mermaid , Plant UML ve DOT gibi ek formatları da destekler. Bu metin formatları daha az çekici görünebilir, ancak en büyük avantajı bu metinleri hemen hemen her wiki sayfasına gömebilmenizdir ( GitHub , Kavşak , ve daha fazlası).


Örnek reponun son diyagramı şu şekilde görünür:

Son diyagram


Özelleştirme

Özel bileşenlerin eklenmesi veya mevcut mantığın ayarlanması yeteneği, özellikle bir araç başlangıç aşamasındayken çok önemlidir. Kalıntılar ve Kaynaklar varsayılan olarak yeterince esnektir; içlerine dilediğinizi koyabilirsiniz. Diğer tüm bileşenler özelleştirilebilir. Mevcut Sağlayıcılar ihtiyacınız olan kaynakları karşılamıyor mu? Kendi uygulamanızı kolaylıkla uygulayın. Yukarıda açıklanan birleştirme veya bağlama mantığından memnun değil misiniz? Sorun değil; kendi LinkStrategy veya MergeStrategy'nizi ekleyin. Her şeyi bir JAR dosyasına paketleyin ve başlangıçta ekleyin. Daha fazlasını buradan okuyun.


Çıkış

Kaynak koduna dayalı bir diyagram oluşturmak muhtemelen ilgi görecektir. Ve özellikle NoReDraw aracı (evet, bahsettiğim aracın adı bu). Katkıda bulunanları bekliyoruz !


Adından gelen en dikkat çekici faydası, bileşenler değiştiğinde diyagramı yeniden çizmeye gerek olmamasıdır. Mühendisliğin dikkat eksikliği, genel olarak dokümantasyonun (ve özellikle diyagramların) güncelliğini yitirmesinin nedenidir. NoReDraw gibi araçlarla herhangi bir PR/CI hattına kolayca takılabileceği için artık sorun olmayacaktır. Unutmayın, hayat diyagramları yeniden çizmek için çok kısa 😉