TB
← Tüm yazılar

Offline-first mobil mimari

Flutter'da yerel veri katmanı, senkronizasyon kuyrugu ve catisma çözümü ile bağlantı kesintilerine dayanikli offline-first mimari kurulumunu aciklar.

Mobil uygulamalarda internet baglantisi her zaman güvenilir degildir. Metro tunelleri, ucak modu, zayif LTE sinyali veya sunucu kesintileri kullanıcının is akisini aniden keser. Offline-first mimari, verinin once cihazda güvenli şekilde saklanip sonra arka planda senkronize edildigi bir yaklasimdir. Flutter bu modeli destekler; ancak başarı, doğru yerel depolama seçimi, tutarlı domain modeli ve açık catisma çözümü kurallariyla gelir.

Offline-first nedir, online-first'ten farki nedir?

Online-first uygulamalar her okuma ve yazma islemini ag uzerinden yapar; bağlantı yoksa ekran hata verir veya bos kalir. Offline-first uygulamada ise source of truth cihazdaki yerel veritabanidir. Ag baglantisi geldiginde değişiklikler sunucuya iletilir, gelen guncellemeler yerel kayitlarla birlestirilir. Kullanıcı perspektifinden uygulama "her zaman çalışır"; senkronizasyon ise gorunmez bir arka plan gorevidir.

Bu mimari özellikle saha ekipleri, lojistik, sağlık ve finans uygulamalarinda kritiktir. Flutter tarafinda tek kod tabani ile iOS ve Android'de ayni offline stratejiyi uygulayabilirsiniz; platform farklari daha cok arka plan gorevleri ve dosya sistemi izinlerinde ortaya cikar.

Yerel depolama secenekleri

Flutter ekosisteminde offline-first icin yaygın seçenekler:

  • drift (SQLite): iliskisel veri, güçlü sorgu, migration destegi
  • isar: yüksek performansli NoSQL benzeri yerel store
  • hive: hafif key-value ve type adapter modeli
  • sqflite: düşük seviye SQLite erisimi

Karmaşık filtreleme, join ve raporlama gerektiren uygulamalarda drift genellikle en dengeli secimdir. Basit cache ve ayar saklama icin hive yeterli olabilir. Seçim yaparken sadece okuma hizina degil, migration kolayligina, transaction destegine ve test edilebilirlige de bakin.

Domain model ve DTO ayrimi

API'den gelen JSON modeli ile uygulama icinde kullanilan domain entity'yi ayirmak offline-first projelerde zorunludur. DTO katmanı ag sozlesmesine bağımlıdır; domain katmanı ise kullanıcının gordugu tutarlı durumu temsil eder. Yerel tabloda genellikle su alanlar bulunur:

  • localId: cihaz uretimi birincil anahtar
  • remoteId: sunucu tarafindaki kimlik (nullable)
  • syncStatus: pending, synced, conflict, failed
  • updatedAt ve deletedAt: soft delete ve birlestirme icin

Senkronizasyon kuyrugu tasarımı

Kullanıcı offline iken yaptigi her yazma işlemi aninda yerel DB'ye yazilir ve bir outbox kuyruguna eklenir. Bağlantı gelince worker bu kuyrugu sirayla veya oncelikle isler. Kuyruk kaydi tipik olarak su bilgileri tasir: entity tipi, işlem turu (create/update/delete), payload hash, deneme sayisi, son hata mesaji.

class SyncJob {
  final String id;
  final String entityType;
  final SyncOperation operation;
  final Map<String, dynamic> payload;
  final DateTime queuedAt;
  int retryCount;
  String? lastError;
}

enum SyncOperation { create, update, delete }

Exponential backoff ile tekrar deneme, geçici ag hatalarinda servisi yormadan dayaniklilik sağlar. Kalıcı hatalarda (400 Bad Request) kaydi dead-letter benzeri bir tabloya tasiyip kullanıcıya duzeltme imkani sunmak gerekir.

Connectivity ve arka plan çalışma

connectivity_plus paketi ag durumunu izler; ancak "wifi bağlı" her zaman internet erisimi anlamina gelmez. Gerçek kontrol icin periyodik health check endpoint'i veya başarısız istek sonrasi fallback stratejisi kullanın. Arka planda senkron icin workmanager (Android) ve background_fetch gibi çözümler degerlendirilir. iOS tarafinda arka plan kisitlari daha sert olduğu icin uygulama on plana geldiginde delta sync calistirmak pratik bir tamamlayicidir.

Okuma yolu: stale-while-revalidate

Offline-first okuma modelinde ekran once yerel veriyi gösterir; arka planda güncel veri cekilir ve fark varsa UI güncellenir. Bu pattern kullanıcıya aninda geri bildirim verir. Riverpod, Bloc veya benzeri state katmaninda "local stream + remote refresh" ikilisini net ayirmak bakimi kolaylastirir.

Stream<List<TaskEntity>> watchTasks() =>
    localDb.watchTasks().map((rows) => rows.map(TaskMapper.fromRow).toList());

Future<void> refreshTasks() async {
  final remote = await api.fetchTasks();
  await localDb.upsertAll(remote.map(TaskMapper.toRow));
}

Catisma çözümü stratejileri

Ayni kaydin iki farklı cihazda guncellenmesi kacinilmazdir. Catisma cozumunde uc temel yaklaşım vardir:

  1. Last write wins: en son timestamp kazanir; basit ama veri kaybi riski yüksek
  2. Field-level merge: alan bazinda birlestirme; form tabanli kayitlarda etkili
  3. Operational transform / CRDT: metin ve işbirliği senaryolarinda gelismis çözüm

Çoğu kurumsal mobil uygulama, domain kurallariyla desteklenmis field-level merge veya kullanıcıya catisma ekrani gosteren manuel çözümü tercih eder. Önemli olan, catisma aninda sessiz veri kaybi yapmamaktir.

Soft delete ve tombstone kayitlari

Silinen kayitlari yerel DB'den aninda kaldirmak, sync sirasinda tekrar geri gelmesine neden olabilir. Tombstone yaklasiminda deletedAt isaretlenir ve bir sonraki başarılı sync'te kalıcı silme yapilir. Sunucu tarafinda da silme olaylari versiyonlanmis event olarak tutulursa cihazlar arasi tutarlılık artar.

Güvenlik ve sifreleme

Offline veri cihazda kalıcı olduğu icin hassas içerikler icin flutter_secure_storage veya SQLCipher tabanli şifreli veritabanı dusunulmelidir. Token'lar keychain/keystore uzerinde saklanmali, yerel DB dump'i anlamli veri tasimamali. Root/jailbreak tespiti gereksinime gore ek katman olabilir; ancak asil savunma hatti doğru anahtar yönetimi ve minimum veri prensibidir.

UI durumlari: sync feedback

Kullanıcıya senkron durumunu göstermek guven oluşturur:

  • Bekleyen değişiklik sayisi
  • Son başarılı sync zamani
  • Catisma veya hata durumunda açık mesaj
  • Manuel "şimdi senkronize et" aksiyonu

Bu bilgiler agresif popup'lar yerine status bar veya ayarlar ekraninda sunulursa akis bozulmaz. Offline modda düzenleme yapilabildigi net şekilde belirtilmelidir.

Test stratejisi

Offline-first kod yolu unit testlerle kuyruk ve merge mantigini, integration testlerle ise gerçek DB uzerinde senaryolari dogrular. Test ortaminda sahte connectivity katmanı ile "offline -> yaz -> online -> sync" akisini otomatiklestirmek regresyonlari erken yakalar. Fake API ile gecikme ve hata enjekte etmek de dayaniklilik icin kritiktir.

Olceklenme ve veri boyutu

Yerel DB sinirsiz degildir. Büyük medya dosyalarini DB'de tutmak yerine dosya sisteminde saklayip metadata'yi DB'de tutun. Pagination ve arsivleme politikasi belirleyin; eski kayitlari cold storage'a tasiyin. Full sync yerine delta sync (since token veya change feed) ag ve pil tuketimini dusurur.

Mimari katman onerisi

Flutter offline-first projelerinde okunabilir bir katmanlasma:

  1. Presentation: widget + state management
  2. Application: use case'ler (CreateTask, SyncNow)
  3. Domain: entity, repository sozlesmesi
  4. Data: local datasource, remote datasource, repository impl

Repository tek giriş noktasi olarak "once local don, sonra remote dene" kuralini uygular. Boylece UI katmanı ag detayini bilmez.

Gerçek dünya dersleri

Offline-first'e geciste en sik yapılan hatalar: sunucuyu source of truth kabul etmeye devam etmek, catisma stratejisini sona birakmak, sync hatalarini loglayip kullanıcıya gostermemek ve migration planini ihmal etmek. Erken asamada veri modeline syncStatus alanlari eklemek, ileride büyük refaktor maliyetini onler. Flutter'in tek kod tabani avantaji, tutarlı offline davranisini iki platformda ayni anda sunmanizi sağlar; ancak arka plan çalışma kurallarini platform bazinda ayri test etmeniz gerekir.

Sunucu tarafinda idempotent API tasarımı, ayni outbox kaydinin tekrar gonderilmesinde cift kayıt olusmasini engeller. Client-generated UUID ile create islemlerinde deterministik kimlik kullanmak bu sorunu pratikte cozer.

Büyük dosya senkronizasyonunda chunked upload ve resume token kullanın. Flutter tarafinda dio ile arka plan upload ve ilerleme bildirimi, kullanıcıya uzun islemlerde şeffaflık sağlar.