QuitQOS
IQOS'u bırakmak isteyenler için geliştirdiğim bir iOS uygulaması. Bıraktığın andan itibaren çalışan bir sayaç tutuyor, vücudun toparlanmasındaki sağlık eşiklerini sırayla işaretliyor. Backend'inden mobiline, tasarımından deploy'una kadar tüm geliştirmeler bana ait.
Problem
Sigarayı bırakan biri ilk günlerde motivedir, sonra o his hızla söner. İnsanı asıl orada tutacak şey, ilerlemesini görmesi. Ama kimse ilerlemesini görmek için uygulamayı her gün açmaz. Yani uygulama kapalıyken de çalışmak zorunda: bir sağlık eşiğine ulaşıldığında bunu kendisi fark edip kullanıcıya bildirim göndermesi gerekiyor. Bir şart daha vardı: bunların hiçbiri hesap açmayı gerektirmemeli. Kayıt sadece verisini başka cihaza taşımak ya da leaderboard'a girmek isteyenler için.
Teknik detaylar
Firebase Auth'u nasıl kullandım
Firebase'e tek bir iş yaptırıyorum: bu kişi gerçekten bu Google ya da Apple hesabının sahibi mi? Dönen ID token'ı bir kez doğruluyorum, sonra kendi access JWT'mi ve refresh token'ımı üretiyorum. Firebase bir daha hiç çağrılmıyor. Refresh tek kullanımlık; token'ın kendisi veritabanında durmuyor, sadece SHA-256 hash'i saklanıyor.
İş kurallarını veritabanı garanti ediyor
Bir kullanıcının aynı anda tek bir ACTIVE denemesi olabilir. Bunu servis katmanında if ile kontrol etmiyorum, çünkü araya race condition girebilir; kural PostgreSQL'de partial unique index olarak duruyor, yani veritabanı zaten izin vermiyor. Guest verilerini kayıtlı hesaba aktarırken de aynı kural geçerli: en erken başlayan deneme aktif kalıyor, diğerleri relapsed olarak kapanıyor.
Aynı ödülü iki kez veremeyen scheduler
Bir @Scheduled job 60 saniyede bir aktif denemeleri tarıyor: önce achievement kaydını oluşturuyor, badge'i veriyor, push bildirimini ancak bundan sonra gönderiyor. Bu sıra bilinçli. Eğer önce push gönderilse ve FCM geçici bir hata verse, bir sonraki turda aynı badge ikinci kez dağıtılırdı. Kullanıcı bildirimleri kapatmışsa ya da kayıtlı bir device token yoksa push adımı tamamen atlanıyor, ama achievement gene de kaydediliyor.
Şemayı migration'lar yönetiyor, ORM değil
Yedi ayrı Flyway migration'ı şemayı ilk beş tablodan bugünkü haline taşıyor: kullanıcı bazlı locale desteği ve çevrilmiş milestone içerikleri dahil. Hibernate ddl-auto=validate ile çalışıyor, yani mevcut şemayı sadece doğrulayabiliyor, üzerinde değişiklik yapamıyor. Her ortam aynı migration dosyalarını aynı sırayla çalıştırdığı için hepsi birebir aynı şemaya sahip oluyor.
Kimseyi ifşa etmeyen public endpoint
Kayıt olmayan kullanıcılar leaderboard isteğinde 403 alıyordu, dolayısıyla sıralama ekranı onlar için tamamen boş kalıyordu. Bunun için küçük bir public endpoint yazdım: topluluğun genel toplamlarını ve ilk üçü dönüyor. Döndüğü DTO'da userId alanı bilerek yok. Yani giriş yapmamış biri genel tabloyu görebiliyor ama kimin kim olduğunu göremiyor.
Yazmakla bitmiyor, yayına almak da var
Uygulama App Store'da yayında. Backend Hetzner'da, ortak bir edge proxy arkasında çalışıyor; backend'e dokunan her push'ta GitHub Actions otomatik deploy ediyor. Buraya gelene kadar feature yazmakla hiç ilgisi olmayan bir sürü iş çıktı: Apple zorunlu tuttuğu için uçtan uca hesap silme akışı, gizlilik ve destek sayfaları, Firebase pod'ları yüzünden iOS tarafında static framework linkleme derdi ve App Store incelemesinde silme butonuna ulaşamadıkları için baştan scroll'lanabilir hale getirdiğim ayarlar ekranı.