Akışlar

Canlı Okuma & PSU

Kullanıcı gerçekten ekranın başındayken (PSU=E) "şu anki" banka/kurum verisini istediğinde, sistem niyet kutusunu atlar ve batch'i senkron çağırıp taze veriyi anında döndürür. Bu yol, aynı anda çok sayıda kullanıcının canlı veri istediği durumlar için tasarlanmıştır — hızlı ve hafif çalışır.

Neden ayrı bir yol? Niyet kutusu (outbox), yazma işleri için gereklidir (rıza/ödeme — bu işlerin kaybolmaması, yani dayanıklılık şarttır). Ama okuma başarısız olursa büyük bir sorun yoktur: kullanıcı bir daha dener, veri zaten bankada/kurumda durur. Bu yüzden kullanıcı-present okuma, outbox'a yazıp işçinin sırasını beklemek yerine doğrudan senkron RPC ile yürür. Sonuç: hem daha hızlı, hem sunucuda boşuna bekleyen thread (iş parçacığı) tutmaz.

Adım adım

Bir ?fresh=true isteğinin, kullanıcıdan bankaya/kuruma ve geri dönüşe kadar geçtiği yol aşağıdadır:

Kullanıcı canlı ister
GET /accounts?fresh=true
Gerçek kullanıcı ekranın başındayken 'şu anki' veriyi ister ve isteğe ?fresh=true parametresini ekler. Bu, PSU=E (kullanıcı-present, yani kullanıcı şu an burada) bir sorgudur. Örneğin kullanıcı hesap ekranında 'bakiyeyi tazele' düğmesine bastığında bu yol devreye girer. fresh eklenmezse en son kaydedilmiş veri (yerel önbellek) döner, banka/kurum hiç aranmaz.
Koordinatör devreye girer
LiveRefreshCoordinator.ResolveAsync
Önce verinin tazeliği kontrol edilir. Veri çok yeniyse (kısa bir 'tazelik penceresi' içinde alınmışsa) doğrudan veritabanından döner ve bankaya/kuruma hiç gidilmez — bankayı/kurumu gereksiz yormamak için. Ayrıca aynı anda aynı kaynağa gelen birden çok tazeleme isteği tek bir çağrıda birleştirilir (tekrarları emer), böylece banka/kurum üst üste sorgulanmaz.
Senkron RPC kurulur
ILiveQueryClient.QueryAsync
Veri eskiyse API, ilgili her banka/kurum bağlantısı için bir LiveQueryRequest (canlı sorgu isteği) hazırlar: neyin istendiği (kind — hesap/bakiye/işlem/kart), hangi bağlantı (connectionId) ve gerekiyorsa hesap/kart referansı. Bu istek batch'e POST edilir. RPC, bir programın diğerini doğrudan çağırıp yanıtını beklemesidir — yani API burada batch'in dönmesini bekler.
İç uç doğrular
POST /internal/live-query · X-Internal-Secret
Batch'in bu ucu iç uçtur: dışarıya açık değildir, yalnız iç ağdan çağrılır. Gelen istekteki gizli başlık (X-Internal-Secret), sabit-zamanlı bir karşılaştırmayla (zamanlama sızıntısına kapalı) doğrulanır. Anahtar eşleşmezse istek 401 (yetkisiz) ile reddedilir — yani bu uç dışarıdan kötü niyetle çağrılamaz (fail-closed: şüphede kapalı davranır).
Batch anlık BKM sorgusu
LiveQueryService → ConnectionSyncService
Batch, bankaya/kuruma PSU-Initiated=E bilgisiyle hemen sorar; bağlantı yine mTLS (çift taraflı sertifikalı şifreli kanal) + JWS (dijital imza) ile korunur. Kaynağı kullanıcı (E) olduğu için istek-adedi KOTASI bu sorguya uygulanmaz — kullanıcı beklerken 'sınıra takıldın' denmez. Batch, zaten var olan periyodik yenileme mantığını yeniden kullanır, yani ayrı bir kod yolu yazılmamıştır (kod tekrarı yok).
DB güncellenir
Upsert + SaveChanges
Bankadan/Kurumdan gelen taze veri veritabanına yazılır (upsert — varsa günceller, yoksa ekler). Bu sayede yerel önbellek de sıcak kalır. Faydası şu: bundan sonraki fresh=false (canlı istemeyen) okumalar da bu taze veriyi anında görür, tekrar bankaya/kuruma gitmeye gerek kalmaz.
Taze veri döner
DetachAll + re-read → Freshness=Live
Batch dönünce API, veritabanından yeni yazılan taze veriyi baştan okur ve yanıtı Freshness=Live (canlı) etiketiyle döndürür. Böylece kullanıcı, aynı HTTP isteği içinde o anki gerçek banka/kurum verisini görür — ayrı bir bekleme veya ikinci istek gerekmez.
Hata olursa: cache'e düşer
Outcome=unreachable/hhs_error
Batch'e ulaşılamaz ya da banka/kurum hata verirse istek çökmez. API, önbellekteki son veriyi Freshness=Stale (bayat) ve Refreshing (yenileniyor) etiketiyle döndürür ve arka planda bir yenileme başlatır. Yani kullanıcı asla hata ekranı görmez; en kötü ihtimalle biraz eski bir veriyle karşılaşır, o da kısa süre sonra tazelenir.

psuInitiated — üç kod, tek kural

Bankaya/Kuruma giden her sorgu, kaynağını belirten bir psuInitiated kodu taşır. Banka/Kurum bu kodla, isteğin ardında canlı bir kullanıcı mı yoksa arka plan botu mu olduğunu anlar. BKM, oran-sınırını (kotayı — 'saatte şu kadar sorabilirsin' sınırı) yalnız otonom (H) sorgulara uygular:

KodKaynakNe zaman üretilirKota
EUserPresentKullanıcı-tetikli canlı okuma (fresh=true) → senkron RPCMuaf
HAutonomousKullanıcı yokken çalışan periyodik arka plan yenileme job'larıSayılır
OHhsInitiatedBanka/Kurum bir olay bildirdiğinde (KaynakGuncellendi) buna verilen tepkiMuaf

Kod tarafında şöyle eşlenir: RefreshReason (UserRequested / Autonomous / EventTriggered) → PsuInitiator (UserPresent=E / Autonomous=H / HhsInitiated=O). Kota sayacı (HhsQueryQuota) yalnız H'yi sayar; TryReserveAsyncçağrısı E (kullanıcı) ve O (banka/kurum olayı) sorgularını saymadan geçirir. Mantık basit: gecikmenin doğrudan bir kullanıcıyı beklettiği durumlarda kota uygulanmaz.

Güvenlik korunur mu?

Bu senkron yol hız için eklendi, ama mimarinin güvenlik kuralları hiç gevşetilmedi:

API yine sertifika taşımaz

FinevoConnect YÖS bankaya/kuruma hâlâ DOĞRUDAN bağlanmaz. Banka/Kurum ile konuşmak için gereken sertifikalar ve JWS imza anahtarları yalnız batch'te durur. API sadece batch'in iç ucunu çağırır (iç ağ HTTP'si) — bu ayrım bir mimari kuraldır ve otomatik testle (NetArchTest) zorlanır.

İç uç gizli-anahtarlı

/internal/live-query ucuna yalnız iç ağdan ve doğru X-Internal-Secret başlığıyla erişilir. Anahtar sabit-zamanlı karşılaştırılır (zamanlama sızıntısına kapalı) ve şüphede istek reddedilir (fail-closed). İleride ek bir güvenlik katmanı olarak mTLS de eklenebilir.

Yazma yolu değişmedi

Rıza ve ödeme gibi yazma işleri ile periyodik/olay senkronları hâlâ niyet kutusu + arka plan job'ları üzerinden yürür. Değişen tek şey kullanıcı-present okumanın senkronlaşmasıdır; kalanı aynı güvenli hattadır.

Ölçek: 10 bin eşzamanlı canlı okuma?

E (kullanıcı) sorguları kotaya takılmadığı için tavan, yapay bir limit değil; bankanın/kurumun ham kapasitesi ile batch'in aynı anda kaç istek işleyebileceğidir. Batch yetişemezse tek tek istekler önbelleğe düşer (bu bir hata değil, sadece biraz bayat veri) — yani sistem zarifçe yavaşlar, kırılmaz. Çok yüksek okuma hacminde doğru desen şudur: tazelik penceresi + periyodik yenileme ile veritabanını sürekli sıcak tutup okumaların çoğunu fresh=false (saf veritabanı, sınırsız ölçek) ile karşılamak; fresh=true'yu ise gerçekten o anki canlı verinin şart olduğu az sayıda noktaya saklamak.