Kafayı yiyecektim. Meğerse sorun, iki ay önce masumane eklediğim bir alandaymış. "Şu `User` objesine bir tane `preferred_language` alanı ekleyeyim, ne olacak ki?" demiştim. Aman tanrım, ne olacakmış...
Patlayan Domino Taşları
Değişikliği yaptım, lokalimde testler geçti, "merge" butonuna bastım. Sonraki 20 dakika, CI/CD pipeline'ımın bir dizi test hatasıyla kıpkırmızı olduğunu izlemekle geçti. E-posta gönderen servis çöktü, çünkü dil bilgisine ihtiyaç duyuyordu. Raporlama modülü hata verdi, çünkü `User` nesnesini serialize eden eski bir metodu vardı. Hatta şaka gibi, kullanıcı avatarı oluşturan bir background job, `NoneType` hatasıyla patladı. StackOverflow'da bile bulamazsın böyle bir zincirleme reaksiyonu.
Meğerse Her Yere Sızmışım
Sorunu debug etmeye başlayınca gördüğüm manzara içler acısıydı. `User` sınıfım, bir God Object'e dönüşmüştü farkında olmadan. Onlarca modül doğrudan bu sınıfın iç yapısına (implementation details) sıkı sıkıya bağlanmıştı. Buna Tight Coupling deniyormuş meğer. Herkes `user.preferred_language` diye erişiyor, kimse araya bir abstraction, bir interface koymamıştı. Veri yapısı ile iş mantığı (business logic) öyle birbirine geçmişti ki, birini değiştirmek diğerini kesin bozuyordu.
Çözüm: Araya Duvarlar Örmek
Çözüm, bağımlılıkları gevşetmekte (Loose Coupling) ve soyutlamalara (Abstraction) yönelmekte yatıyormuş. İlk yaptığım şey, dil tercihi gibi özelliklere erişimi bir servis katmanı üzerinden sağlamak oldu. Modüller artık `user.preferred_language` yerine `user_service.get_preferred_language(user_id)` çağırıyor. Böylece içerdeki veri yapısı değişse bile, servis arayüzü sabit kalıyor ve her şey çalışmaya devam ediyor.
Bir diğer silahım ise DTO'lar (Data Transfer Objects) oldu. Modüller arası veri geçişlerinde, tüm domain objemi değil, sadece gerekli alanları içeren hafif objeler kullanmaya başladım. Değişim maliyeti inanılmaz düştü.
Öğrenilen Acı Ders
Bu olay bana şunu öğretti: Bir değişiklik yapacağın zaman, "Bu değişiklik kaç yeri etkiler?" sorusunun cevabı "Sadece burası" değilse, orada bir coupling kokusu var demektir. Sistemleri, birbirine minimum bağımlılıkla konuşan, bağımsız modüller olarak tasarlamak, gelecekteki seni kurtaracak en büyük yatırım.
Siz de böyle "küçük bir değişiklik" ile tüm sistemi ateşe verdiğiniz oldu mu? Tight Coupling'den kurtulmak için favori pattern'ınız veya taktiğiniz nedir? Gelin yorumlarda konuşalım, birbirimizin kodlarından ders alalım.
Değişikliği yaptım, lokalimde testler geçti, "merge" butonuna bastım. Sonraki 20 dakika, CI/CD pipeline'ımın bir dizi test hatasıyla kıpkırmızı olduğunu izlemekle geçti. E-posta gönderen servis çöktü, çünkü dil bilgisine ihtiyaç duyuyordu. Raporlama modülü hata verdi, çünkü `User` nesnesini serialize eden eski bir metodu vardı. Hatta şaka gibi, kullanıcı avatarı oluşturan bir background job, `NoneType` hatasıyla patladı. StackOverflow'da bile bulamazsın böyle bir zincirleme reaksiyonu.
Sorunu debug etmeye başlayınca gördüğüm manzara içler acısıydı. `User` sınıfım, bir God Object'e dönüşmüştü farkında olmadan. Onlarca modül doğrudan bu sınıfın iç yapısına (implementation details) sıkı sıkıya bağlanmıştı. Buna Tight Coupling deniyormuş meğer. Herkes `user.preferred_language` diye erişiyor, kimse araya bir abstraction, bir interface koymamıştı. Veri yapısı ile iş mantığı (business logic) öyle birbirine geçmişti ki, birini değiştirmek diğerini kesin bozuyordu.
Python:
# Kötü örnek - Doğrudan bağımlılık
def send_welcome_email(user):
subject = get_translation("welcome", user.preferred_language) # Direkt alana erişim!
# ... kod devam eder
Çözüm, bağımlılıkları gevşetmekte (Loose Coupling) ve soyutlamalara (Abstraction) yönelmekte yatıyormuş. İlk yaptığım şey, dil tercihi gibi özelliklere erişimi bir servis katmanı üzerinden sağlamak oldu. Modüller artık `user.preferred_language` yerine `user_service.get_preferred_language(user_id)` çağırıyor. Böylece içerdeki veri yapısı değişse bile, servis arayüzü sabit kalıyor ve her şey çalışmaya devam ediyor.
Bir diğer silahım ise DTO'lar (Data Transfer Objects) oldu. Modüller arası veri geçişlerinde, tüm domain objemi değil, sadece gerekli alanları içeren hafif objeler kullanmaya başladım. Değişim maliyeti inanılmaz düştü.
Python:
# Daha iyi örnek - Soyutlama üzerinden
def send_welcome_email(user_id):
user_lang = user_service.get_preferred_language(user_id) # Soyut bir metod
# ... kod devam eder
Bu olay bana şunu öğretti: Bir değişiklik yapacağın zaman, "Bu değişiklik kaç yeri etkiler?" sorusunun cevabı "Sadece burası" değilse, orada bir coupling kokusu var demektir. Sistemleri, birbirine minimum bağımlılıkla konuşan, bağımsız modüller olarak tasarlamak, gelecekteki seni kurtaracak en büyük yatırım.
Siz de böyle "küçük bir değişiklik" ile tüm sistemi ateşe verdiğiniz oldu mu? Tight Coupling'den kurtulmak için favori pattern'ınız veya taktiğiniz nedir? Gelin yorumlarda konuşalım, birbirimizin kodlarından ders alalım.