ÇalışmalarBildirim ve uygulama mimarisi
Genişletilebilir Bildirim Yönlendirme Altyapısı
Büyüyen switch-case bloklarını bağımsız bildirim aksiyonlarına dönüştüren; push bildirimlerini uygulama durumuna göre güvenli ve ölçeklenebilir biçimde yöneten Flutter altyapısı.
Problem
Uygulamanın ilk dönemlerinde az sayıda bildirim tipi vardı. Gelen payload içindeki tür veya rota
değeri okunuyor, merkezi bir switch-case bloğunda ilgili ekran açılıyordu. Birkaç senaryo için bu
yaklaşım sade ve anlaşılırdı. Ancak sağlık uygulamasına randevu, reçete, sohbet, cihaz, hatırlatıcı,
sağlık raporu, görüntülü görüşme ve benzeri yeni alanlar eklendikçe aynı blok giderek büyüdü.
Yeni bir bildirim tanımı geldiğinde merkezi yapı değiştirilmek zorundaydı. Yeni bir case eklemek
yalnızca rota tanımlamak anlamına gelmiyordu; payload ayrıştırma, mevcut ekranı kontrol etme, doğru
navigasyon yığınını bulma ve bazı durumlarda sayfayı yenileme gibi kararlar da aynı yerde
toplanıyordu.
Bunun sonucunda:
- Bir bildirim için yapılan değişiklik diğer bildirim akışlarını etkileyebiliyordu.
- Merkezi dosya çok sayıda modüle bağımlı hale geliyordu.
- Farklı yönlendirme kuralları iç içe koşullarla yönetiliyordu.
- Kod incelemelerinde küçük bir bildirim eklemesi, büyük ve riskli bir değişiklik gibi görünüyordu.
- Bir bildirimi bağımsız test etmek zorlaşıyordu.
- Aynı payload kontrolü farklı case’lerde tekrar edebiliyordu.
Asıl sorun switch-case sözdizimi değildi. Sorun, sistem büyürken bütün bildirim davranışlarının tek
bir karar merkezine bağlanmasıydı.
Hedef
Amaç, her yeni bildirim türünde mevcut yönlendirme kodunu yeniden açıp değiştirme ihtiyacını ortadan kaldırmaktı. Bildirim altyapısının gelen mesajı alması; ancak mesajın hangi koşulda çalışacağına ve ne yapacağına ilgili bildirim sınıfının karar vermesi hedeflendi.
Yeni yapı şu prensipler üzerine kuruldu:
- Her bildirim davranışı tek bir sınıfta izole edilmeli,
- Sınıf hem desteklediği payload’ı tanımalı hem de kendi aksiyonunu çalıştırmalı,
- Merkezi yönetici bildirim tiplerini bilmek yerine uygun aksiyonu bulmalı,
- Yeni bir tür eklemek mevcut aksiyonların kodunu değiştirmemeli,
- Uygulamanın kapalı, arka planda veya ön planda olması aynı giriş noktasında birleşmeli,
- Hatalı ya da bilinmeyen payload uygulamanın çalışmasını bozmamalı.
Eski yapıdan yeni yapıya
Önceki yaklaşımda akış merkezi bir karar bloğuna bağlıydı:
Bildirim → türü oku → switch-case → rota ve özel davranış
Yeni yaklaşımda bildirim, ortak sözleşmeyi uygulayan aksiyonlar arasında değerlendirilir:
FCM veya yerel bildirim
↓
Ortak payload işleyici
↓
Notification Navigator
↓
Eşleşen Notification Action
↓
Payload doğrulama + güvenli yönlendirme
Navigator’ın görevi artık uzun bir koşul listesi çalıştırmak değildir. Kayıtlı aksiyonlara sırayla “Bu bildirimi sen yönetiyor musun?” diye sorar ve ilk eşleşen sınıfı çalıştırır. Bu düzen Strategy ile Chain of Responsibility yaklaşımlarının sade bir birleşimi olarak kullanıldı.
Ortak bildirim sözleşmesi
Bütün bildirim aksiyonları aynı küçük sözleşmeye uyar. Bir aksiyon iki soruya cevap verir:
- Gelen veri bu aksiyonun sorumluluğunda mı?
- Eşleşiyorsa hangi işlem gerçekleştirilmeli?
Örneğin sohbet bildirimi kendi tür ve rota eşleşmesini kontrol eder; kullanıcı zaten sohbet ekranındaysa içeriği yeniler, başka bir ekrandaysa doğru navigasyon yığınını hazırlayıp sohbet sayfasını açar. Cihaz bildirimi ise aynı sözleşmeyi kullanır fakat tamamen farklı bir modül ve rota davranışına sahiptir.
Bu sayede bildirim yöneticisi sohbetin, reçetenin veya randevunun iş kuralını bilmez. Her sınıfın değişme nedeni kendi bildirim senaryosuyla sınırlı kalır.
Bildirim yaşam döngülerini tek noktada birleştirmek
Mobil bildirim yalnızca kullanıcı uygulama dışındayken gelmez. Mesaj uygulama kapalıyken, arka planda açıkken veya aktif olarak kullanılırken alınabilir. Android’de ön plandaki mesajın yerel bildirim olarak gösterilmesi, kapalı uygulamayı açan ilk mesajın işlenmesi ve kullanıcının bildirim seçiminin dinlenmesi farklı platform olaylarıdır.
Firebase Messaging istemcisi bu yaşam döngülerini yönetiyor ve elde ettiği payload’ı aynı bildirim navigator’ına gönderiyor. Böylece her action sınıfının uygulamanın hangi sistem olayından açıldığını ayrı ayrı bilmesine gerek kalmıyor. Platform entegrasyonu ile ürün yönlendirme davranışı birbirinden ayrılıyor.
Bu ayrım ayrıca token alma, token yenileme, bildirim izni ve kullanıcı çıkışında abonelikleri kapatma gibi altyapı sorumluluklarının rota kararlarıyla karışmasını engelliyor.
Payload eşleştirme ve doğrulama
Bildirim payload’ı dış sistemden gelen dinamik bir veridir. Beklenen alan eksik, değer eski veya veri biçimi hatalı olabilir. Bu nedenle bir bildirimin yalnızca rota adına bakarak koşulsuz çalıştırılması yeterli değildir.
Her action önce genel bildirim türünü, ardından sorumlu olduğu rota değerini kontrol eder. Eşleşme sağlandıktan sonra yalnızca ihtiyaç duyduğu alanları okur. Örneğin detay ekranına giden bir bildirim kayıt kimliğine ihtiyaç duyarken liste ekranına giden başka bir bildirim ek veriye ihtiyaç duymayabilir.
Payload ayrıştırma sırasında oluşan hata navigator sınırında yakalanır ve teknik kayda dönüştürülür. Bilinmeyen bir bildirim türü için eşleşme bulunmazsa kullanıcı yanlış bir ekrana gönderilmez ve uygulama çökmeye zorlanmaz.
Navigasyon durumunu korumak
Bir push bildiriminin hedef ekranı açması tek başına yeterli değildir. Kullanıcı zaten ilgili ekranda olabilir, hedef modül navigasyon yığınında bulunabilir veya uygulama tamamen farklı bir akışta olabilir. Her durumda doğrudan yeni sayfa açmak yinelenen ekranlar ve bozuk geri navigasyonu oluşturabilir.
Action sınıfları kendi bağlamına göre şu kararlardan birini verebilir:
- Hedef ekran zaten açıksa yeni ekran eklemek yerine içeriği yenilemek,
- Hedef modül yığında bulunuyorsa o yığına dönüp gerekli sayfayı açmak,
- Modül aktif değilse önce güvenli başlangıç noktasına dönmek,
- Gerekli veriyi payload’dan çıkarıp detay rotasına aktarmak,
- Kullanıcı zaten aktif bir görüşmedeyse ikinci bir görüşme ekranı açmamak.
Bu kararların her biri ilgili bildirimin sınıfında kaldığı için navigator büyümez. Karmaşık bir bildirim yalnızca kendi dosyasını karmaşıklaştırır; diğer bildirimlerin okunabilirliğini etkilemez.
Modüler mimariyle uyum
Uygulama; randevu, sohbet, cihazlar, reçeteler, hatırlatıcılar, dosyalar ve sağlık raporları gibi
bağımsız modüllerden oluşuyor. Tek bir merkezi switch-case, zamanla bütün modüllerin rota ve iş
detaylarını bilen bir bağımlılık merkezine dönüşüyordu.
Sınıf tabanlı yapıda her bildirim action’ı ilgili modülün yönlendirme ihtiyacını kapsüllüyor. Ortak çekirdek katman yalnızca bildirim sözleşmesini tanımlıyor; uygulama katmanı somut aksiyonları bir araya getiriyor. Böylece temel mimari modüllerin ekran ayrıntılarını bilmeden çalışabiliyor.
Event Bus kullanılan akışlarda action doğrudan ekran bileşenine erişmek yerine ilgili modüle niyet bildiren bir olay gönderiyor. Bu yaklaşım özellikle mevcut sayfayı yenileme veya dashboard yüklendikten sonra modüller arası geçiş yapma senaryolarında bağımlılığı azaltıyor.
Değişime açık, bozulmaya kapalı tasarım
Bu dönüşüm Açık/Kapalı Prensibi’nin pratik bir karşılığı oldu. Sistem yeni bildirim davranışlarına açık; ancak mevcut davranışları taşıyan sınıfların sürekli değiştirilmesine kapalıdır.
Yeni bir tanım geldiğinde geliştirici merkezi koşul bloğunda doğru yeri aramaz. Yeni action sınıfı:
- Kendi eşleşme kuralını tanımlar,
- İhtiyaç duyduğu payload alanlarını doğrular,
- Kendi navigasyon veya event davranışını uygular,
- Navigator’ın aksiyon listesine kaydedilir.
Kayıt listesi yalnızca hangi yeteneklerin sisteme dahil olduğunu gösteren composition noktasıdır; bildirimlerin iş kurallarını içermez. Bu nedenle yeni sınıfın eklenmesi mevcut sınıfların davranışını değiştirmez ve regresyon alanı daralır.
Test edilebilirlik
Önceki merkezi yapıda tek bir bildirimi test etmek için büyük yönlendirme fonksiyonunun farklı koşullarını hazırlamak gerekiyordu. Ayrıştırılmış yapıda her action bağımsız olarak sınanabilir:
- Doğru tür ve rota için eşleşiyor mu?
- Başka bir bildirimi yanlışlıkla sahipleniyor mu?
- Eksik payload durumunda güvenli davranıyor mu?
- Hedef ekran açıksa yenileme davranışını çalıştırıyor mu?
- Hedef ekran açık değilse doğru rotayı oluşturuyor mu?
Navigator içinse daha küçük bir sözleşme testi yeterlidir: kayıtlı aksiyonlar içinde ilk eşleşeni bulmak, onu bir kez çalıştırmak ve bilinmeyen payload’da güvenli biçimde tamamlanmak.
Sonuç
Bildirim altyapısı, büyüyen bir switch-case bloğundan bağımsız sorumluluklara sahip action
sınıflarına dönüştürüldü. Randevu, sohbet, reçete, cihaz, sağlık raporu ve diğer bildirim türleri aynı
giriş noktasını kullanırken kendi yönlendirme kararlarını ayrı ayrı taşıyor.
Yeni bir bildirim eklemek artık mevcut koşul ağacını değiştiren riskli bir işlem değil; ortak sözleşmeye uyan yeni bir davranış ekleme işlemidir. Kod inceleme kapsamı küçüldü, olası regresyonlar daha dar bir alana çekildi ve ekipler kendi modüllerine ait bildirim akışlarını diğer akışları bozmadan geliştirebilir hale geldi.
Öğrendiklerim
- Sorun switch-case kullanmak değil, değişim nedenlerini tek yerde toplamaktır. Az sayıda sabit durum için uygun olan yapı, iş kuralları büyüdüğünde sorumluluk merkezine dönüşebilir.
- Her bildirim kendi davranışını sahiplenmelidir. Eşleşme, veri doğrulama ve yönlendirme kararı birlikte kapsüllendiğinde değişiklik sınırı netleşir.
- Merkezi yönetici iş detaylarını bilmemelidir. Görevi doğru aksiyonu bulmak ve çalıştırmaktır.
- Composition noktası ile iş mantığı aynı şey değildir. Yeni sınıfı listeye kaydetmek, merkezi karar ağacını büyütmeden sisteme yeni yetenek ekler.
- Uygulama yaşam döngüsü tek giriş noktasında normalize edilmelidir. Kapalı, arka plan ve ön plan bildirimleri aynı yönlendirme sözleşmesine ulaştığında davranış tutarlı olur.
- Navigasyon mevcut durumu dikkate almalıdır. Bildirim tıklaması her zaman yeni ekran açmak anlamına gelmez; bazen doğru davranış açık ekranı yenilemektir.
- Bilinmeyen payload kontrollü biçimde yok sayılmalıdır. Harici verinin uygulamayı kararsız hale getirmesine izin verilmemelidir.