
Bir yazılım projesinin en büyük karmaşıklığı çoğu zaman kod miktarından değil, zamanla biriken iş kurallarından (business rules) kaynaklanır. Başlangıçta oldukça basit görünen ürünler; kullanıcı rolleri, abonelik planları, şirket bazlı ayarlar, onay mekanizmaları ve onlarca istisna ile birlikte fark edilmeden bir "Rule Engine"e dönüşür. Bu yazıda gerçek ürünlerin neden bu noktaya geldiğini, bunun neden kaçınılmaz olduğunu ve mühendislerin bu karmaşıklığı yönetebilmek için nelere dikkat etmesi gerektiğini konuşacağız.
Yazılım geliştirmeye ilk başladığım yıllarda bir projenin karmaşıklığını satır sayısıyla ölçüyordum.
Kod arttıkça proje zorlaşıyor, dosya sayısı büyüdükçe bakım maliyeti artıyor diye düşünüyordum. Bir noktaya kadar da bu doğruydu. Daha fazla kod, daha fazla sorumluluk demekti.
Fakat gerçek ürünlerde çalışmaya başladıktan sonra fark ettim ki, zamanla projeleri zorlaştıran şey aslında kodun kendisi değilmiş.
Kurallar.
Ürünün zaman içinde biriktirdiği iş kuralları.
Çünkü bir yazılım ürünü hiçbir zaman ilk gün tasarlandığı gibi kalmıyor. İlk versiyonlar neredeyse her zaman oldukça sade oluyor. Kullanıcı ihtiyacını doğrulamaya odaklanıyorsunuz. Mimariden çok ürünün gerçekten işe yarayıp yaramadığını test etmek istiyorsunuz.
Örneğin bir Learning Management System (LMS) geliştirdiğinizi düşünelim.
İlk sprintte ihtiyaç oldukça basit olabilir.
Ardından dersler eklenir.
Öğrenci kursu görüntüler.
İlerleme kaydedilir.
Belki küçük bir sınav eklenir.
Ve ürün çalışır.
İlk bakışta sistem oldukça temiz görünür. Çünkü aslında ortada yönetilmesi gereken çok fazla karar yoktur.
Fakat ürün kullanılmaya başladığında gerçek dünya yavaş yavaş sisteme girmeye başlar.
İlk müşteri gelir ve şöyle der:
"Bu kurs sadece yöneticiler tarafından görüntülenebilsin."
İkinci müşteri farklı bir ihtiyaçla gelir.
"Bu eğitim sadece Enterprise paketinde aktif olsun."
Üçüncü müşteri ise bambaşka bir istekle gelir.
"Kullanıcı sınavı geçmeden sonraki modüle geçemesin."
İlk başta bunların her biri oldukça küçük geliştirmeler gibi görünür.
Birkaç satır kod.
Bir iki yeni kontrol.
Belki küçük bir migration.
Hepsi bu.
Ama ürün geliştirmede ilginç olan şey, karmaşıklığın tek tek özelliklerden değil, özelliklerin birbirleriyle kurduğu ilişkiden doğmasıdır.
İlk kural hiçbir zaman son kural değildir.
Çünkü bir süre sonra yeni sorular gelmeye başlar.
Sertifika hangi başarı oranında oluşturulacak?
Sınav başarısız olursa öğrenci tekrar deneyebilecek mi?
Bazı şirketlerde bu kural aktif olacak, bazılarında olmayacak mı?
Kullanıcının bulunduğu ülkeye göre farklı içerikler gösterilecek mi?
Kurumsal müşteriler kendi eğitim akışlarını oluşturabilecek mi?
Eğitmen yayınlanan kursu sonradan değiştirebilecek mi?
Her yeni soru aslında yeni bir özellik istemez.
Yeni bir karar ister.
Ve ürün, fark ettirmeden karar vermeye başlayan bir sisteme dönüşmeye başlar.
İşte bu noktada birçok geliştirici hâlâ aynı hatayı yapmaya devam ediyor.
Yeni gelen her ihtiyacı yeni bir özellik olarak görüyor.
Oysa çoğu zaman geliştirilen şey yeni bir özellik değildir.
Yeni bir iş kuralıdır.
Bu ayrım küçük gibi görünse de uzun vadede bütün mimariyi değiştirebilir.
Bir sertifika sistemi tek başına karmaşık değildir.
Bir yetkilendirme sistemi de değildir.
Abonelik yönetimi de değildir.
Hatta ilerleme takibi, rozet sistemi veya şirket bazlı ayarlar da tek başlarına yönetilebilir yapılardır.
Asıl problem bunların birbirleriyle konuşmaya başladığı anda ortaya çıkar.
Örneğin kullanıcı bir sertifika almak istiyor diyelim.
Bu karar artık yalnızca sınav sonucuna bağlı olmayabilir.
Belki kullanıcının doğru abonelik planında olması gerekiyor.
Belki kursun yayınlanmış olması gerekiyor.
Belki şirket yöneticisinin onayı bekleniyor.
Belki eğitimin belirli bir tarihten önce tamamlanmış olması gerekiyor.
Artık tek bir karar, sistemdeki birçok farklı modüle bağımlı hale gelir.
Kod da doğal olarak şu şekle evrilmeye başlar.
Burada dikkat edilmesi gereken nokta kodun uzunluğu değildir.
Sorun, bu kararın artık beş farklı modülden veri alıyor olmasıdır.
Yarın bu kurallardan biri değiştiğinde yalnızca bu fonksiyon değişmeyecek.
Belki raporlama ekranı değişecek.
Belki dashboard değişecek.
Belki mobil uygulama değişecek.
Belki API değişecek.
İşte gerçek ürünlerin karmaşıklığı tam olarak burada başlıyor.
Framework'ler değişebilir.
Kütüphaneler değişebilir.
Ama ürün büyüdükçe iş kurallarının birbirine bağlanması neredeyse kaçınılmazdır.
Ve çoğu başarılı ürün, farkında olmadan yavaş yavaş bir Rule Engine olmaya başlar.
İşin ilginç tarafı şu; hiçbir ekip masaya oturup "Bugün bir Rule Engine geliştirelim." demez.
Hatta çoğu zaman ekipler bunun farkına bile varmaz.
Her sprintte gelen yeni ihtiyaçlar oldukça makul görünür.
"Şuraya küçük bir kontrol ekleyelim."
"Bu özellik sadece Premium kullanıcılar için çalışsın."
"Bu şirket bu özelliği kullanamasın."
"Bu ülke için farklı davransın."
Her biri kendi başına değerlendirildiğinde son derece mantıklıdır.
Problem, bu kararların yıllar boyunca birikmesidir.
İlk yazılan kontrol ikinciyi doğurur.
İkinci üçüncüyü.
Üçüncü ise dördüncüyü.
Bir gün kodu açıp baktığınızda aslında artık ürün geliştirmediğinizi fark edersiniz.
Siz artık kuralları yönetiyorsunuz.
Mesela bir kursu yayınlama işlemini ele alalım.
İlk versiyon muhtemelen şöyle görünür.
Oldukça temiz.
Oldukça anlaşılır.
Sonra ilk müşteri gelir.
"Yalnız eğitmen onaylamadan yayınlanmasın."
Kod biraz değişir.
Sonra ürün büyür.
"Enterprise müşterilerinde yöneticinin de onayı gerekiyor."
Ardından başka bir istek gelir.
"Aboneliği biten şirketlerde yayınlama kapatılsın."
Sonra bir tane daha.
"Feature Flag açıksa yeni yayın akışı kullanılsın."
Ve birkaç ay sonra kod aşağı yukarı şu hale gelir.
Buraya kadar hâlâ çok büyük bir problem görünmeyebilir.
Asıl problem bundan sonra başlıyor.
Çünkü aynı kurallar yalnızca burada yaşamıyor.
Gerçek ürünlerde business logic'in en büyük problemi tekrar etmesidir.
Örneğin yukarıdaki yayınlama kuralını düşünelim.
Backend bu kontrolü yapıyor.
Ama frontend de butonu gösterebilmek için aynı kontrolü yapmak zorunda kalıyor.
Dashboard tarafı "Yayınlanabilir kurslar" listesini oluştururken yine aynı kuralları kullanıyor.
Mobil uygulama aynı davranışı tekrar ediyor.
Admin paneli yine aynı mantığı uyguluyor.
Bir süre sonra aynı iş kuralının dört farklı implementasyonu oluşuyor.
İşte teknik borcun sessizce büyüdüğü yer tam da burası.
Çünkü bir gün Product Owner geliyor ve yalnızca tek bir cümle söylüyor.
"Artık Enterprise müşterilerinde yönetici onayı gerekmesin."
Bu cümle aslında tek bir requirement gibi görünür.
Ama geliştirici için anlamı şudur:
Backend değişecek.
Frontend değişecek.
Dashboard değişecek.
Mobil uygulama değişecek.
Test senaryoları değişecek.
Dokümantasyon güncellenecek.
Requirement değişmedi.
Business Rule değişti.
Ve business rule değiştiğinde bütün sistem etkilenmeye başladı.
Birçok projede teknik borç büyük mimari hatalarla başlamıyor.
Küçük if bloklarıyla başlıyor.
Çünkü yeni bir ihtiyaç geldiğinde en hızlı çözüm genellikle yeni bir kontrol eklemek oluyor.
Sonraki sprintte.
Birkaç hafta sonra.
Altı ay sonra ise kimse bu koşulun neden oluştuğunu hatırlamıyor.
Kod çalışıyor.
Testler geçiyor.
Kimse dokunmak istemiyor.
Çünkü herkes biliyor ki buradaki küçücük bir değişiklik sistemin başka bir yerinde beklenmedik sonuçlar doğurabilir.
Bence büyük projelerin en korkutucu noktası tam olarak burası.
Kod okunabiliyor olabilir.
Ama kararların neden alındığını kimse artık bilmiyordur.
İnsanlar büyük projelere baktığında genellikle dosya sayısından veya satır sayısından etkileniyor.
Ben artık farklı bir şeye bakıyorum.
Bir değişiklik yapmak için kaç farklı modülü düşünmek zorundayım?
Eğer bir özelliği değiştirirken;
yetkilendirme sistemini,
abonelik sistemini,
bildirim sistemini,
raporlama tarafını,
audit loglarını,
mobil uygulamayı,
API sözleşmelerini
aynı anda düşünmek zorundaysam...
Sorun kodun uzunluğu değildir.
Sorun, kuralların birbirine çok sıkı bağlanmış olmasıdır.
Ve işte tam bu yüzden başarılı ürünler zamanla bir "Rule Engine"e dönüşür.
Çünkü ürün artık ekran göstermiyor.
Karar veriyor.
Kimin neyi göreceğine...
Neyi yapabileceğine...
Hangi koşulda hangi aksiyonun çalışacağına...
Ve mühendislik, bu noktadan sonra kod yazmaktan çok bu kararları yönetme problemine dönüşüyor.
Buraya kadar anlattıklarım biraz karamsar gelebilir.
"Demek ki her ürün eninde sonunda karmaşıklaşıyor."
Evet.
Bence bunun önüne tamamen geçmek mümkün değil.
Bir ürün büyüyorsa, müşteri kazanıyorsa ve yeni problemlere çözüm üretiyorsa; doğal olarak yeni kurallar da ortaya çıkacaktır.
Asıl hedef karmaşıklığı tamamen ortadan kaldırmak değil.
Onu yönetilebilir hale getirmek.
Çünkü mühendislikte iyi mimari, karmaşıklığın olmadığı sistem değildir.
Karmaşıklığın doğru yerde yaşadığı sistemdir.
İşte burada en sık yapılan hatalardan biri, business logic'in zamanla uygulamanın her köşesine yayılmasıdır.
Bir kısmı frontend'de olur.
Bir kısmı backend'de.
Bir kısmı API Gateway'de.
Bir kısmı SQL sorgularının içine gömülür.
Bir kısmı da cron job'larda yaşamaya başlar.
Sonra aynı kural beş farklı yerde farklı şekillerde uygulanır.
Asıl problem de tam olarak burada başlar.
Birçok ekip business logic'i yazılmış kod olarak görüyor.
Ben artık öyle düşünmüyorum.
Business logic aslında ürünün bilgisidir.
Kod ise onun yalnızca ifade biçimidir.
Örneğin şu karar:
"Sertifika yalnızca kurs tamamlandıysa ve sınav başarıyla geçildiyse oluşturulsun."
Bu bir kod değildir.
Bu ürünün bilgisidir.
Yarın React yerine başka bir frontend framework'üne geçebilirsiniz.
Backend'i Node.js'ten Go'ya taşıyabilirsiniz.
Veritabanını değiştirebilirsiniz.
Ama bu kural büyük ihtimalle aynı kalacaktır.
İşte bu yüzden business logic, teknolojilerden daha uzun ömürlüdür.
Ve tam da bu nedenle en çok korunması gereken katmandır.
Kod değişebilir.
Framework değişebilir.
Ama ürünün kuralları yıllarca yaşamaya devam eder.
Frontend tarafında bunu çok görüyorum.
Özellikle React projelerinde zamanla component'ler yalnızca UI üretmez.
Karar da vermeye başlar.
İlk bakışta bunda yanlış bir şey yok gibi görünüyor.
Ama birkaç ay sonra aynı kontrol başka bir component'te tekrar yazılıyor.
Sonra başka bir sayfada.
Sonra mobil uygulamada.
Ve bir gün Product Owner geliyor.
"Artık yönetici olmasına gerek yok."
İşte o gün aynı değişikliği dört farklı yerde yapmaya başlıyoruz.
Problem React değil.
Problem business logic'in UI'ın içine taşınmış olması.
UI değişebilir.
Ama karar mekanizmasının tek bir kaynağı olmalı.
Ben artık büyük projelerde kendime sürekli şu soruyu soruyorum:
"Şu an yazdığım kod gerçekten bir ekran mı oluşturuyor, yoksa ürün adına karar mı veriyor?"
Eğer cevap ikinci seçenekse, o kodun yeri büyük ihtimalle component değildir.
Bence yazılım dünyasında "ölçeklenebilirlik" kavramı biraz yanlış anlaşılıyor.
Çoğu kişi ölçeklenebilirliği milyonlarca kullanıcıyla ilişkilendiriyor.
Benim gördüğüm en büyük ölçek problemi kullanıcı sayısı değil.
Kural sayısı.
Çünkü kullanıcı sayısı arttığında sunucu eklersiniz.
Cache eklersiniz.
CDN kullanırsınız.
Ama ürünün iş kuralları arttığında donanım satın alamazsınız.
O karmaşıklığı yalnızca doğru tasarımla yönetebilirsiniz.
Aslında iyi mimari, bugünkü problemi çözmek için kurulmaz.
Henüz gelmemiş kuralları karşılayabilecek kadar esnek olabilmek için kurulur.
Bu yüzden artık yeni bir özellik geliştirirken kendime farklı bir soru soruyorum.
"Bu kod bugün çalışıyor mu?"
yerine,
"Altı ay sonra bu kurala yeni bir istisna geldiğinde ne olacak?"
Çünkü gerçek hayatta neredeyse her kurala yeni bir istisna geliyor.
Yazılım geliştirmeye ilk başladığımızda ürünleri; ekranlardan, component'lerden, API'lardan ve veritabanlarından oluşan yapılar olarak görüyoruz.
Bir süre sonra fark ediyoruz ki bunların hiçbiri asıl problem değil.
Asıl problem, ürünün zaman içinde biriktirdiği kararlar.
Kim görebilir?
Kim değiştirebilir?
Hangi durumda çalışır?
Ne zaman çalışmaz?
Hangi müşteride farklı davranır?
İşte bu sorular çoğaldıkça ürün de sessizce bir Rule Engine'e dönüşmeye başlıyor.
Bence bunu engellemek mümkün değil.
Hatta başarılı ürünlerde bunun olması kaçınılmaz.
Önemli olan, bu dönüşümün farkında olmak.
Çünkü farkında olmadığınız her yeni iş kuralı, sistemin farklı bir köşesine gizlice yerleşiyor.
Bir süre sonra ürününüzü geliştirmiyorsunuz.
Ürünün geçmişte aldığı kararlarla mücadele ediyorsunuz.
Ve belki de yazılım mühendisliğinin en zor tarafı tam olarak burada başlıyor.
Kod yazmak çoğu zaman işin kolay kısmı.
Asıl mühendislik, yıllar boyunca değişecek kuralları; sade, anlaşılır ve sürdürülebilir bir yapının içinde yönetebilmek.
Framework'ler değişiyor.
Diller değişiyor.
AI kod yazıyor.
Yeni kütüphaneler her gün ortaya çıkıyor.
Ama yirmi yıl önce de yazılımcılar business rule yönetiyordu, bugün de yönetiyor, muhtemelen yirmi yıl sonra da yönetmeye devam edecek.
Çünkü kullanıcılar aslında yeni ekranlar istemiyor.
Kendi iş süreçlerinin doğru çalışmasını istiyor.
Ben artık büyük bir projeye baktığımda component sayısından çok, ürünün kaç farklı karar vermek zorunda olduğuna bakıyorum.
Çünkü bence bir yazılım ürününün gerçek karmaşıklığı, sahip olduğu kod miktarıyla değil; yönetmek zorunda olduğu iş kurallarının sayısıyla ölçülür.