Akışlar

Canlı Okuma & PSU

Kullanıcı gerçekten ekranın başındayken (PSU=E) "şu anki" banka 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 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 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 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 hiç gidilmez — bankayı 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 üst üste sorgulanmaz.
Senkron RPC kurulur
ILiveQueryClient.QueryAsync
Veri eskiyse API, ilgili her banka 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 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 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 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 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 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 giden her sorgu, kaynağını belirten bir psuInitiated kodu taşır. Banka 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 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 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

api-yos bankaya hâlâ DOĞRUDAN bağlanmaz. Banka 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 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.