Go'nun Katmanlı Tasarım Yaklaşımı
(jerf.org)- 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 (
Usernamegibi) üçü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:
CategoryileBlogPostarası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
İ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.
"Muz istemiştim ama ormanı getirmiş" sözü gerçekten çok komik.
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...
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
Harika bir blog yazısı
"Üçüncü bir pakete taşı" tavsiyesiyle ilgili ek bir teknik
Sanki Yourdon'un yapısal yöntemleri hakkında bir kitap okuyormuş gibi
Paketler birbirini döngüsel olarak referans edemez
go:linknamekullanarak bu mümkünRandomizer'ı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.modiçinde olabilmesiJerf'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