27 puan yazan GN⁺ 2025-04-24 | 4 yorum | WhatsApp'ta paylaş
  • Go dili, paketler arasında döngüsel başvuruyu kesin biçimde yasakladığı için, bu durum doğal olarak katmanlı tasarımı (layered design) teşvik eder
  • Bu yazı, Go projelerinin zorunlu olarak sahip olduğu katmanlı yapıyı açıklar ve bunun üzerine ayrıca bir mimari dayatılmasa da yeterince geçerli olduğunu savunur
  • Döngüsel bağımlılık ortaya çıktığında, bunu çözmek için somut ve pratik refaktöring stratejilerini adım adım sunar
  • Her paket, kendi başına anlamlı bir işlev birimine sahip olacak şekilde tasarlanır; bu da test, bakım ve mikroservislere ayırma açısından avantaj sağlar
  • Sonuç olarak bu yaklaşım, gerçek kod tasarımında sık görülen "muz istemişken tüm ormanı getirmek" sorununu önler

Go'da Katmanlı Tasarım Yaklaşımı

Temel ilkeler

  • Go, paketler arasında döngüsel başvuruya izin vermez
  • Tüm Go programlarının import ilişkileri yönlü çevrimsiz grafik (DAG) oluşturmalıdır
  • Bu yapı bir tercih değil, dil seviyesinde zorunlu kılınan bir tasarım kuralıdır

Paket katmanlarının kendiliğinden oluşması

  • Harici paketler hariç, proje içindeki paketler başvuru derinliğine göre kendiliğinden katmanlanabilir
  • Aşağıdaki şekilde olduğu gibi en altta metrics, logging, ortak veri yapıları gibi temel yardımcı paketler yer alır
  • Sonrasında üst paketler, giderek işlevleri birleştirerek yukarı doğru yığılan bir yapı oluşturur

Bu tasarım yaklaşımının özellikleri

  • Katmanlar, hiyerarşik soyutlamaya değil başvuru yönüne dayanır
  • Bir paket, birden fazla alt seviye pakete başvurabilir
  • MVC, hexagonal architecture gibi mevcut tasarım yaklaşımları da bu yapının üzerine "uygulanabilir"
    → Ancak Go'nun yapısal kısıtları mutlaka dikkate alınmalıdır

Döngüsel başvuruyu çözme stratejileri

Döngüsel başvuru oluştuğunda, aşağıdaki sırayla refaktöring denenebilir:

1. İşlevi taşıma

  • En çok önerilen yöntemdir
  • Döngüye neden olan işlev tam olarak analiz edilip mantıksal olarak uygun yere taşınır
  • Sık uygulanmasa da kavramsal açıklığı en çok artıran yöntemdir

2. Ortak işlevi ayrı bir pakete ayırma

  • Her iki tarafta ortak kullanılan tip veya fonksiyonlar (Username gibi) üçüncü bir pakete taşınır
  • Paket küçük görünse bile çekinmeden ayırın
    → Zaman içinde bu paketin büyüme ihtimali yüksektir

3. Üst düzey bir birleştirici paket oluşturma

  • Döngü oluşturan iki paketi birleştiren üçüncü bir paket oluşturulur
  • Örnek: Category ile BlogPost arasındaki çift yönlü bağımlılık üst pakete taşınır
    → Alt paketler dumb struct olarak kalır, gerçek işlevler üst pakette birleştirilir

4. Arayüz ekleme

  • Struct ya da fonksiyon bağımlılığı, yalnızca gereken metodları içeren bir arayüzle değiştirilir
  • Gereksiz bağımlılıklar kaldırılır ve test kolaylığı sağlanır
  • Ancak aşırı kullanılırsa tasarımı tersine daha karmaşık hale getirebilir

5. Kopyalama (Copy)

  • Bağımlı olunan hedef çok küçükse, basitçe kopyalanıp kullanılabilir
  • DRY ihlali gibi görünebilir ama pratikte tasarımın netleşmesine yardımcı olduğu çok olur

6. Tek pakette birleştirme

  • Yukarıdaki yöntemlerin hiçbiri mümkün değilse iki paket birleştirilir
  • Fazla büyük bir paket oluşmayacaksa bu kabul edilebilir
    → Yine de koşulsuz birleştirmeden kaçınılmalı, dikkatle karar verilmelidir

Bu tasarım yaklaşımının pratik avantajları

  • Her paket, kendi başına anlamlı bir işlev birimi taşır ve bağımsız olarak test edilebilir
  • Paket içi başvurular sınırlı olduğu için, tüm kodu anlamadan da tek tek paketleri anlamak mümkündür
  • İstenmeden tüm bağımlılıkların birbirine bağlanması (=orman sorunu) engellenir ve yalnızca gerekeni kullanan kod yazımı teşvik edilir
  • Mikroservislere ayırırken de kolayca dışarı çıkarılabilir
    → Çünkü bağımlılıkların çoğu net biçimde tanımlanmıştır

Sonuç

  • Go'nun paket tasarımı kısıtları can sıkıcı bir engel değil, iyi tasarımı teşvik eden bir mekanizmadır
  • Özel bir mimari olmadan da yalnızca paketler arası başvuru yapısıyla sağlam bir tasarım kurulabilir
  • Döngüsel başvuruya yönelik ince analiz ve refaktöring stratejileri, yalnızca Go için değil diğer diller için de geçerlidir

4 yorum

 
bus710 2025-04-25

İlk başta alelacele yazıp çalıştırınca eğlenceli geliyor
ama test eklemeye başlayınca
o zaman ben bunu neden böyle yaptım diye düşünmeye başlıyorsunuz.

 
bungker 2025-04-24

"Muz istemiştim ama ormanı getirmiş" sözü gerçekten çok komik.

 
iwanhae 2025-04-24

Spring geliştirirken en zor şeylerden birinin bağımlılık döngüsü olduğunu düşünüyorum..
Sonsuzca birbirlerini başlatıp bellek sızıntısıyla çöküp gitmelerinin yarattığı o bunaltıcı his...

 
GN⁺ 2025-04-24
Hacker News görüşleri
  • Döngüsel bağımlılıklara izin verilmemesi, büyük ölçekli programlar inşa ederken harika bir tasarım tercihidir

    • Bu, ilgi alanlarının uygun şekilde ayrılmasını zorunlu kılar
    • Döngüsel bağımlılık oluşuyorsa tasarımda bir sorun vardır ve yazı bunun nasıl çözüleceğini iyi açıklıyor
    • Bazen, başka bir paketin yeniden tanımladığı function pointer'lar kullanılarak döngüsel bağımlılık çözülür
    • Keşke Go derleyicisi döngüsel bağımlılık oluşturulduğunda daha faydalı çıktı verse
    • Şu anda döngüye dahil olan tüm paketlerin listesini veriyor; bu liste oldukça uzun olabiliyor ve genelde soruna yol açan şey en son değiştirdiğiniz şey oluyor
  • Harika bir blog yazısı

    • Bu sitede müthiş yazılar var; fonksiyonel programlama hakkında bir şeyler öğrenmeyi seviyorsanız göz atmanızı öneririm
    • bağlantı
  • "Üçüncü bir pakete taşı" tavsiyesiyle ilgili ek bir teknik

    • Çok sayıda model yapısı (SQL, Protobuf, GraphQL vb.) ürettiğinizde, üretilen katmanlar arasında net bir yön oluşturabilirsiniz
    • Tüm üretilmiş kodu uygulama koduna bir "temel paket" olarak sunup her şeyi birlikte compose edebilirsiniz
    • Bu tekniği kullanmadan önce "modellerin modelleri döngüsel olarak import etmesi" sorunu vardı; ancak yapısal bir ek katman getirilince bu sorun tamamen ortadan kalktı
  • Sanki Yourdon'un yapısal yöntemleri hakkında bir kitap okuyormuş gibi

  • Paketler birbirini döngüsel olarak referans edemez

    • Aslında Go'da go:linkname kullanarak bu mümkün
  • Randomizer'ın somut kavramını hatırlatıyor

  • Golang'in ilginç bir özelliği, paket seviyesinde döngüsel bağımlılık olamaması ama go.mod içinde olabilmesi

    • Kısacası, bunu da yapmamalısınız
  • Jerf'in paketleri nasıl düşündüğüne ve döngüsel bağımlılıkları nasıl ele aldığına dair güzel bir açıklama