Blindy

Üniversite öğrencileri için anonim bir tanışma uygulaması. Yüz görmeden yedi dakika konuşuyorsunuz, isimler yalnızca karşılıklı evetle açılıyor.

Rol
Tek geliştirici: backend, Flutter uygulaması, yönetim paneli, sunucu
Durum
Kapalı betada, getblindy.app
Dönem
Ocak 2026'dan bu yana
Teknolojiler
Java 17, Spring WebFlux, PostgreSQL, Redis, Flutter, React
İlk sohbet turu: iki tarafın cevapları kart olarak açılıyor
Karşılıklı evetten sonra açılan isimler
Ana ekrandaki mum ışıklı masa

Özet

Sorun
Telefonlar kararsız ağlarda istekleri yeniden gönderiyor, bağlantıları kopuyor; yine de bir sohbet mesajı ne kaybolmalı ne de iki kez kaydedilmeli. Eşleşme yalnızca akşam penceresinde çalışıyor ve iki işçi aynı kişiyi asla aynı anda eşleştirmemeli.
Benim payım
Tek geliştirici: Java ve Spring WebFlux backend, Flutter uygulaması, React yönetim paneli ve hepsinin çalıştığı sunucu.
Sonuç ve durum
Tekrar gönderilen mesaj bir kez kaydediliyor, yeniden bağlanan telefon kaçırdıklarını istiyor. Kapalı beta, git push ile güncellenen ve her gece yedeklenen tek bir kiralık sunucuda çalışıyor.

Beş ekranda bir akşam

Yanan mumdan karşılıklı evete kadar. Her ekran gerçek uygulama, yalnızca kişiler uydurma.

Ekran 1, toplam 5

  1. Akşam

    Mum yandı
    1

    Mum yandı

    Akşam açık: kullanıcı masayı ve kalan süreyi görür, günün sorularını cevaplar.

  2. Eşleşme

    Eşleşme bulundu
    2

    Eşleşme bulundu

    İki mum yanar, iki rumuz dağıtılır ve ortak ilgi alanları görünür.

  3. Sohbet

    1. tur: Cevaplar
    3

    1. tur: Cevaplar

    İki tarafın cevapları kart olarak yan yana açılır, kullanıcı yorum yazar.

  4. Karar

    Karar
    4

    Karar

    Sohbet biter; kullanıcı 30 saniye içinde evet demek için butona basılı tutar.

  5. Açılış
    5

    Açılış

    İkisi de evet dedi: rumuzun yerini gerçek ad alır, kullanıcı ilk mesajı yazar.

Ürünün tüm akışı31 ekran

Kayıttan hesap silmeye kadar bütün ekranlar, kullanıcının karşılaştığı sırayla.

Ekran 1, toplam 31

  1. Kayıt

    Hoş geldin
    1

    Hoş geldin

    Kullanıcı mumu görür, bir akşamın nasıl işlediğini okur ve Google ile devam eder.

  2. Üniversite e-postası
    2

    Üniversite e-postası

    Kullanıcı öğrenci olduğunu doğrulamak için .edu.tr adresini yazar.

  3. Doğrulama kodu
    3

    Doğrulama kodu

    Kullanıcı üniversite e-postasına gelen 6 haneli kodu girer.

  4. Ad ve doğum tarihi
    4

    Ad ve doğum tarihi

    Kullanıcı adını ve doğum tarihini girer; ad karşılıklı evete kadar gizli kalır.

  5. Fotoğraflar
    5

    Fotoğraflar

    Kullanıcı anonim sohbette kimsenin görmediği en az iki fotoğraf ekler.

  6. İlgi alanları
    6

    İlgi alanları

    Kullanıcı ilgi alanlarını seçer; en fazla üçü anonim sohbette ipucu olur.

  7. Akşam

    İlk akşam
    7

    İlk akşam

    İlk girişte masadaki not bu akşam ne olacağını anlatır.

  8. Mum yandı
    8

    Mum yandı

    Akşam açık: kullanıcı masayı ve kalan süreyi görür, günün sorularını cevaplar.

  9. Bu akşam 21:00'de
    9

    Bu akşam 21:00'de

    Akşamdan önce mum sönüktür; kullanıcı geri sayımı görür, hatırlatma kurabilir.

  10. Özel gün masası
    10

    Özel gün masası

    Bayram ve özel günlerde masa o güne göre süslenir.

  11. Günün soruları
    11

    Günün soruları

    Kullanıcı sohbette kart kart açılacak kısa günlük soruları cevaplar.

  12. Günün destesi
    12

    Günün destesi

    Günün destesi kısa bir törenle açılır, kartlar yelpaze olur.

  13. Eşleşme

    Masa hazırlanıyor
    13

    Masa hazırlanıyor

    Kullanıcı masaya oturur, karşısına oturacak kişi aranırken bekler.

  14. Mum yakılıyor
    14

    Mum yakılıyor

    Kullanıcı sıraya girerken bir kibrit mumu yakar.

  15. Eşleşme bulundu
    15

    Eşleşme bulundu

    İki mum yanar, iki rumuz dağıtılır ve ortak ilgi alanları görünür.

  16. Sohbet

    1. tur: Cevaplar
    16

    1. tur: Cevaplar

    İki tarafın cevapları kart olarak yan yana açılır, kullanıcı yorum yazar.

  17. 2. tur: Seçim
    17

    2. tur: Seçim

    Kullanıcı gizlice bir şık seçer; aynı seçim kıvılcım çıkarır.

  18. 3. tur: Serbest
    18

    3. tur: Serbest

    Serbest sohbet açılır; kullanıcı serbestçe yazar, elindeki kartı oynayabilir.

  19. 3 dakika daha
    19

    3 dakika daha

    Süre biterken karşı taraf 3 dakika daha ister, kullanıcı kabul edebilir.

  20. Karar

    Karar
    20

    Karar

    Sohbet biter; kullanıcı 30 saniye içinde evet demek için butona basılı tutar.

  21. Karşı taraf bekleniyor
    21

    Karşı taraf bekleniyor

    Kullanıcı evet dedi ve bekler; iki karar aynı anda görünür.

  22. Açılış
    22

    Açılış

    İkisi de evet dedi: rumuzun yerini gerçek ad alır, kullanıcı ilk mesajı yazar.

  23. Sonrası

    Açık sohbet
    23

    Açık sohbet

    Açılıştan sonra ikisi adlarıyla, süre sınırı olmadan konuşmaya devam eder.

  24. Bu sefer olmadı
    24

    Bu sefer olmadı

    Karşılıklı evet çıkmazsa kullanıcıya nazikçe söylenir, sonraki akşam yeniden oturabilir.

  25. Akşam zarfı
    25

    Akşam zarfı

    Kullanıcı mührü basılı tutar, mühür çatlayarak açılır.

  26. Zarfın içi
    26

    Zarfın içi

    Zarftan yeni soru kartları ve bir kart arka yüzü çıkar.

  27. Koleksiyon
    27

    Koleksiyon

    Kullanıcı topladığı kartlara, geçmiş akşamlarına ve rozetlerine bakar.

  28. Hesabın

    Profil
    28

    Profil

    Kullanıcı kendi profilini görür: hakkında, ilgi alanları ve masa süsleri.

  29. Ayarlar
    29

    Ayarlar

    Kullanıcı eşleşme tercihlerini, yaş aralığını ve bildirimleri ayarlar.

  30. Gizlilik ve hesap
    30

    Gizlilik ve hesap

    Kullanıcı gizlilik metinlerini okur, rızasını yönetir ve hesap silmeyi bulur.

  31. Hesabı sil
    31

    Hesabı sil

    Kullanıcı hesabını silmeyi onaylar; pencere neyin ne zaman silineceğini anlatır.

Nasıl çalışıyor

Backend yazanlar için okumaya değer kısımlar: sistemin yerleşimi, bir mesajın yolculuğu, iki kişinin nasıl eşleştiği ve kapalı betanın tek sunucuda nasıl çalıştığı.

Sisteme bir bakış

core-api, kimlikten moderasyona on dört modülden oluşan modüler bir monolit. chat-gateway yalnızca WebSocket bağlantılarını tutuyor: bağlanırken Firebase token'ını doğruluyor, Redis'i dinliyor ve veritabanına hiç dokunmuyor. Fotoğraflar imzalı URL'lerle doğrudan telefon ile Cloudflare R2 arasında gidip geliyor.

Sistem metin olarak

Her satır bir kutuyu ve oklarının gösterdiği kutuları sıralıyor.

  • Flutter uygulaması (iOS ve Android) → Cloudflare (DNS, TLS, proxy), Cloudflare R2 (fotoğraf, yedek)
  • Yönetim paneli (React, tarayıcıda) → Cloudflare
  • Cloudflare → Caddy (tek giriş, dört alan adı)
  • Caddy → core-api (modüler monolit, 14 modül), chat-gateway (yalnız soket, veritabanı yok), panel + site (statik dosyalar)
  • core-api → PostgreSQL 15, Redis 7 (kuyruk, pub/sub, kilit), Cloudflare R2, Firebase, FCM, Resend (giriş, bildirim, e-posta)
  • chat-gateway → Redis 7
Sisteme bir bakışOklar: Flutter uygulaması → Cloudflare, Cloudflare R2; Yönetim paneli → Cloudflare; Cloudflare → Caddy; Caddy → core-api, chat-gateway, panel + site; core-api → PostgreSQL 15, Redis 7, Cloudflare R2, Firebase, FCM, Resend; chat-gateway → Redis 7.imzalı URL'lerFlutter uygulamasıiOS ve AndroidYönetim paneliReact, tarayıcıdaCloudflareDNS, TLS, proxyCaddytek giriş, dört alan adıcore-apimodüler monolit, 14 modülchat-gatewayyalnız soket, veritabanı yokpanel + sitestatik dosyalarPostgreSQL 15Redis 7kuyruk, pub/sub, kilitCloudflare R2fotoğraf, yedekFirebase, FCM, Resendgiriş, bildirim, e-posta
  1. Flutter uygulamasıiOS ve AndroidYönetim paneliReact, tarayıcıda
  2. CloudflareDNS, TLS, proxy
  3. Caddytek giriş, dört alan adı
  4. core-apimodüler monolit, 14 modülchat-gatewayyalnız soket, veritabanı yokpanel + sitestatik dosyalar
  5. PostgreSQL 15Redis 7kuyruk, pub/sub, kilitCloudflare R2fotoğraf, yedekFirebase, FCM, Resendgiriş, bildirim, e-posta

Bir mesaj gönderdiğinizde ne oluyor

Adım adım kendiniz ilerlettiğiniz açıklayıcı bir model. Gerçek backend'e istek göndermiyor.

Telefonunuzcore-apiPostgresRedisGatewayKarşı telefonPOST /messagesbir kez kaydetpublish user:{uid}message.createdWebSocket ile iletkaçırılan mesajları GET ile al

Yukarıdan ne olacağını seçin ya da sırayla ilerlemek için Sonraki'ye basın.

  1. Telefonunuz → core-api POST /messages + clientMsgIdMesajı gönderiyorsunuz. Uygulama mesaja kendi kimliğini (clientMsgId) ekliyor, böylece tekrar deneme asla kopya oluşturmuyor.
  2. core-api → Postgres bir kez kaydetcore-api mesajı kaydediyor. Aynı kimlikle gelen ikinci istek, kayıtlı satırdan yanıtlanıyor.
  3. core-api → Redis publish user:{uid}Olay, bir sunucunun değil alıcının adını taşıyan Redis kanalına gidiyor.
  4. Redis → Gateway message.createdKarşı tarafın WebSocket'ini hangi gateway tutuyorsa o kanala abone ve olayı o alıyor.
  5. Gateway → Karşı telefon WebSocket üzerinden iletGateway mesajı karşı telefona iletiyor. Çevrimdışıysa bir Firebase bildirimi telefonu uyandırıyor.
  6. Karşı telefon → core-api kaçırılan mesajları GET ile alYeniden bağlanınca uygulama kaçırdıklarını core-api'den istiyor. Kalıcı kayıt için esas kaynak soket değil, REST.

İki kişi nasıl eşleşiyor

Eşleşme yalnızca akşam penceresinde çalışıyor. Kuyruk Redis'te duruyor; dağıtık bir kilit, iki işçinin aynı anda aynı kişiyi eşleştirememesini sağlıyor.

  • Aynı iki kişi bir daha asla eşleşmiyor.
  • Bir hayır, karşı tarafa hiçbir zaman gösterilmiyor.

Eşleşme

  1. Günde 5 soruherkese aynı sorular
  2. 3'ünü yanıtlakuyruğu açar
  3. Akşam kuyruğuRedis, yalnız pencere içinde
  4. Çift yönlü kontroliki tercih de uymalı
  5. İki kişiyi kilitledağıtık kilit
  6. match.foundiki telefona iletilir

Kapalı betayı çalıştırmak

Kiralık tek bir sunucu her şeyi Docker Compose ile çalıştırıyor. Kod sunucuya git push ile ulaşıyor; veritabanı her gece şifrelenip sunucudan çıkıyor.

  • 80 ve 443 portları yalnızca Cloudflare'in IP aralıklarını kabul ediyor.
  • Veri ağı iç ağ; veritabanına yalnızca API container'ları erişebiliyor.
  • Servisler root olmayan bir kullanıcıyla çalışıyor; uygulamanın veritabanı rolü süper kullanıcı değil.
  • Bir geri yükleme provasında yedek geri açılıp çalışan veritabanıyla karşılaştırıldı.

Dağıtım

  1. git push prodsunucudaki çıplak depoya
  2. post-receive kancasıyeni commit'i açar
  3. docker compose buildimajlar sunucuda derlenir
  4. Yeniden başlatCaddy arkasında

Gece yedeği

  1. pg_dumpher gece 04:30'da
  2. Şifreleage ile
  3. Cloudflare R2sunucu dışında
  4. 14 gün saklasonra dönüşümlü

Rakamlarla

962
backend testi
3.200+
Flutter testi, 256'sı golden görüntü
237
yönetim paneli testi
77
OpenAPI sözleşmesinde API yolu
13
WebSocket olay türü
21
veritabanı migration'ı

Yine aynı şekilde vereceğim kararlar

Sıradaki projeSokak Efsaneleriİstanbul'un sokak hayvanları üzerine bir kaydır-birleştir bulmacası. 1.0 sürümü Ekim 2026'da App Store incelemesine gönderildi.