<

Mobil Uygulamada Offline-First: Şebeke Yokken de Çalışan Uygulama

İki yıl önce bir tarım kooperatifi için saha denetim uygulaması teslim ettik. Pilot bölge Ege'ydi, testler kusursuz geçti. Uygulama İç Anadolu'ya yayıldığında ise destek kutusu doldu: denetçiler köylerde form dolduramıyor, fotoğraf yükleyemiyor, gün sonunda kahvehane wifi'sine bağlanıp her şeyi baştan giriyordu. Saha ölçümü yaptığımızda gerçek ortaya çıktı: denetçilerin çalışma noktalarının yaklaşık üçte birinde ya hiç şebeke yoktu ya da bağlantı, tek bir API çağrısını tamamlayamayacak kadar kesikliydi. Uygulamayı "internet var" varsayımıyla tasarlamıştık; kullanıcının gerçeğinde internet, garanti değil ihtimaldi. O proje bize offline-first mimariyi seçenek değil, zorunluluk olarak öğretti.

Çevrimdışı desteği eklenmez, baştan kurulur

İlk refleksimiz yamamaktı: istek atılamazsa yerel bir kuyruğa yaz, şebeke gelince gönder. İki sprint sonra bu yaklaşımın neden yetmediğini gördük. Formun bir kısmı gönderilmiş, kalanı kuyruktayken kullanıcı kaydı düzenlerse ne olacak? Kuyruktaki istek, sunucuda bu arada değişmiş bir kayda mı uygulanacak? Liste ekranı, sunucudan gelen eski veriyle mi yerel kuyruktar taze veriyle mi dolacak? Her soruyu ayrı bir if ile yamadıkça kod, kimsenin akıl yürütemediği bir duruma geldi.

Doğru model, zihinsel bir tersine çevirme gerektiriyor: uygulamanın gerçeği yerel veritabanıdır; sunucu, o gerçeğin eninde sonunda eşitlenen bir kopyasıdır. Ekranlar asla ağdan okumaz, her zaman yerel veritabanından okur. Kullanıcı işlemleri asla API'ye değil, yerel veritabanına yazar. Senkronizasyon ise arka planda, kullanıcı arayüzünden tamamen bağımsız çalışan ayrı bir motordur. Bu modeli kurduğunuzda "çevrimdışı mod" diye bir mod kalmıyor — uygulama her zaman aynı şekilde çalışıyor, şebekenin varlığı yalnızca senkron motorunun temposunu etkiliyor.

Bu tersine çevirmenin hoş bir yan etkisi de var: uygulama şebekeli ortamda bile hızlanıyor. Ekranlar ağ gecikmesini hiç beklemediği için her liste, her detay sayfası anında açılıyor; senkron motoru veriyi arkada tazeliyor. Yani offline-first, adının aksine sadece çevrimdışı senaryonun değil, çevrimiçi deneyimin de yatırımı. Müşteriye maliyeti anlatırken bu kartı açmak işe yarıyor — "köydeki denetçi için" diye başlayan konuşma, "İstanbul'daki kullanıcı da ekranların açılışını beklemesin" diye bitiyor.

Kooperatif uygulamasını bu mimariyle yeniden yapılandırdık: Flutter tarafında yerel veritabanı, üstünde reaktif sorgular (veri değişince ekran kendiliğinden güncelleniyor), yanda kendi hâlinde çalışan senkron servisi. Denetçi artık şebekesi olup olmadığını düşünmüyor; biz de destek kutusunu düşünmüyoruz.

Senkronizasyonun ilk yasası: kayıt silinmez, değişiklik kaybolmaz

Senkron motoru tasarlarken en pahalı dersimiz kimliklerle ilgiliydi. Sunucunun ürettiği otomatik artan ID'lere dayanan bir modelde, çevrimdışı oluşturulan kayıtların kimliği belirsiz kalıyor ve ilişkili kayıtlar (denetim → fotoğraf → not) senkron sırasında birbirini kaybediyor. Çözüm baştan basitti aslında: tüm kimlikler istemcide üretilen UUID'ler. Kayıt, doğduğu anda nihai kimliğine sahip; sunucu yalnızca kabul ediyor.

İkinci yasa: silme dahil hiçbir işlem yıkıcı olamaz. Fiziksel silme yerine tombstone (silindi işareti), her değişiklikte artan versiyon numarası ve cihaz başına bir işlem günlüğü. Senkron motoru aslında bu günlüğü karşı tarafa taşıyan bir postacı; günlük yaklaşımının güzelliği, kesintiye dayanıklılığında — postacı yolda kalırsa kaldığı satırdan devam ediyor, hiçbir işlem iki kez uygulanmıyor çünkü her satırın kimliği var.

Çakışmalara gelince: literatür CRDT'lere, vektör saatlere uzanan derin bir kuyu ama saha uygulamalarında çatışmaların büyük çoğunluğu iki mütevazı kuralla çözülüyor. Alan bazında "son yazan kazanır" (kayıt bazında değil — iki kullanıcı aynı kaydın farklı alanlarını değiştirdiyse ikisi de kazanmalı) ve gerçekten kritik az sayıda alan için işi insana bırakan bir çatışma ekranı. Kooperatif projesinde iki yılda insana düşen çatışma sayısı: 11. Bu 11 vaka için CRDT altyapısı kurmak, kelimenin tam anlamıyla mühendislik israfı olurdu.

Kullanıcıya yalan söylemeyen arayüz

Offline-first'ün mimari kadar önemli bir yüzü de dürüstlük. Kullanıcı kaydettiği verinin telefonda mı sunucuda mı olduğunu bilmeli — ama bu bilgi onu boğmamalı. Bizim dengemiz şöyle: her kayıt işlemi anında "kaydedildi" olarak onaylanıyor (çünkü gerçekten kaydedildi, yerel gerçeğe), senkron durumu ise kayıt bazında değil ekran köşesinde tek bir sade göstergeyle özetleniyor: bekleyen işlem sayısı ve son eşitleme zamanı. Denetçi köyde formu doldururken hiçbir uyarıyla karşılaşmıyor; kasabaya inip şebeke bulduğunda köşedeki sayacın eridiğini görüyor.

Bir kural daha, bunu pazarlama ekipleri pek sevmiyor ama yazacağım: çevrimdışıyken yapılamayan işlemleri (ödeme onayı gibi, sunucusuz gerçekleşmesi mümkün olmayanları) gizlemeyin, devre dışı da bırakmayın — kuyruğa alın ve ne zaman gerçekleşeceğini söyleyin. "Bu işlem bağlantı geldiğinde tamamlanacak" cümlesi, gri bir butonun yarattığı çaresizlikten çok daha az destek çağrısı üretiyor.

Test edilmeyen çevrimdışılık, çevrimdışı çalışmıyor demektir

Sunucu tarafını da es geçmeyeyim: offline-first istemci, API tasarımını da değiştiriyor. Kayıt bazlı klasik REST uçları yerine, "şu andan sonra değişen her şeyi ver" diyebileceğiniz delta uçlarına ihtiyacınız var; yoksa her senkron, tüm veri kümesini yeniden indirir ve köydeki o cılız bağlantıyı ilk seferde boğarsınız. Bizim delta uçlarımız sayfalı ve sıkıştırılmış; iki haftalık birikimi bile 3G'nin tek çizgisinde eritebiliyor.

Son ders süreçle ilgili. Çevrimdışı senaryolar, uçak modunu açıp kapatarak elle test edilemeyecek kadar çeşitli: senkronun tam ortasında kopan bağlantı, saati yanlış cihazlar, iki hafta çevrimdışı kalıp 400 işlem biriktirmiş bir tablet, aynı hesabın iki cihazda çevrimdışı düzenlediği aynı kayıt. Bunların her biri bizim entegrasyon test setinde senaryo olarak yaşıyor; senkron motoruna dokunan hiçbir PR bu set geçmeden merge edilmiyor. Ağı programatik olarak kesip bağlayan bir test altyapısı kurmak iki haftamızı aldı; o iki hafta, muhtemelen kariyerimde geri dönüşü en yüksek test yatırımı.

Fotoğraflar ve büyük dosyalar için bir dipnot düşeyim, çünkü bu konu her offline projede ayrı pazarlık konusu oluyor. Denetim fotoğraflarını işlem günlüğüyle aynı kanaldan senkronize etmiyoruz; günlük küçük ve kritik, medya büyük ve sabırlı. Fotoğraflar ayrı bir yükleme kuyruğunda, yalnızca wifi'da veya kullanıcı açıkça isterse hücresel ağda akıyor ve kayıt, fotoğrafı "yüklenecek" durumuyla referanslıyor. Bu ayrım olmadan tek bir 40 megabaytlık video, arkasındaki iki yüz kritik form kaydını saatlerce rehin alabiliyor.

Kooperatif uygulaması bugün 1.800 aktif denetçiyle, şebekenin uğramadığı köylerde gündelik iş görüyor; son bir yılın veri kaybı raporu boş. Sahada çalışan, depoda çalışan, bodrum katta çalışan bir uygulama fikriniz varsa mimariyi en başta konuşalım — bize ulaşmanız yeterli. Çünkü bu işin tek gerçekten pahalı hâli, "internet var" varsayımıyla yazılmış bir uygulamayı sonradan çevrimdışına döndürmek; onu da yaptık, kimseye tavsiye etmiyoruz.

📅 Yayınlanma:  ·  Yakup Zengin