src/DiyanetCleanArchitecture.Infrastructure.Services.Notification projesindedir. Admin panelindeki kullanıcılara gerçek-zamanlı bildirim push etmek için Server-Sent Events (SSE) kullanır. Transport-agnostic bir broadcaster arayüzü etrafında kuruludur.
Broadcaster arayüzü
InMemoryAdminNotificationBroadcaster’dır (singleton, tek API instance için). Her SSE bağlantısı bir Channel<NotificationEnvelope> tutar.
Rol-hedefli yayın
PublishAsync, NotificationEnvelope.TargetRoleId alanına göre filtre uygular: null ise tüm personele, dolu ise yalnızca o role sahip abonelere yazar. Yazım bekletmez — DropOldest ile en eski mesaj düşürülür:
DropOldest ile sınırlıdır — yavaş okuyan client backpressure yaratmasın diye:
Kapasite 50 yeterlidir: sayfa açılışında REST list endpoint’i tam state’i tazelediği için, SSE yalnızca “canlı” delta’ları taşır.
NotificationEnvelope — transport modeli
Broadcaster, Domain entity’si yerine ince bir transport tipi taşır (Infrastructure’ın Application/Domain’e bağımlı kalmaması için):HMAC stream-token
EventSource (tarayıcı SSE API’si) Authorization: Bearer header gönderemez. Bu nedenle frontend, mevcut Keycloak token’ı ile kısa-ömürlü bir capability token alır ve SSE bağlantısını query param ile açar. Token üretici HmacStreamTokenService’tir:
base64url(payload).base64url(hmac) — JWT’ye benzer ama header yok (tek algoritma, HMAC-SHA256). Payload {"sub":"...","roles":[1,2],"exp":...} taşır. Doğrulamada imza FixedTimeEquals ile sabit-zamanlı karşılaştırılır ve exp kontrol edilir.
Bu token yalnızca SSE stream’ini açar; REST endpoint’lerini açamaz — sızıntı riskini azaltır (Keycloak access token kullanılmaz).
Application tarafı — AdminNotification aggregate
Bildirim oluşturmanın tek giriş noktası Application katmanındakiIAdminNotificationService.DispatchAsync’tir. Domain event handler’lar (MediatR INotificationHandler) bunu inject eder:
AdminNotification aggregate’ini aynı UnitOfWork ile DB’ye yazar; SaveEntitiesAsync sonrası NotificationEnvelope üretip broadcaster’a push eder (bağlı client’lar anında alır). Örnek tetikleyiciler: NotifyAdminsUserInvitedDomainEventHandler, NotifyAdminsEtkinlikKontenjanDolduDomainEventHandler, NotifyAdminsCitizenAccountDeactivatedDomainEventHandler.
Heartbeat
SSE bağlantısının canlı kalması için periyodik comment-line gönderilir. FrekansStreamHeartbeatSeconds (default 25 sn) ile ayarlanır ve nginx proxy_read_timeout (default 60 sn) altında kalmalıdır. Controller bunu dar bir INotificationStreamOptions abstraction’ı üzerinden okur.
Multi-instance — henüz desteklenmiyor
InMemoryBroadcaster: false ayarı multi-instance (Redis backplane) senaryosu içindir ama Redis impl’i henüz yoktur. Startup’ta bilinçli olarak NotSupportedException fırlatılır (fail-fast):
Config — Services:Notification
true → in-memory; false → (NYI) Redis backplane, startup’ta exception.SSE heartbeat frekansı (proxy timeout altında kalmalı).
HMAC anahtarı, min 32 karakter. Boş → runtime rastgele (sadece dev).
Stream-token ömrü (saniye).
DI kaydı
Domain Events
Bildirimleri tetikleyen domain event’ler ve handler’lar.
Caching
Multi-instance senaryosunda kullanılan Redis backplane deseni.