⚡ Domain Events (CQRS Pattern)
FVT'nin {entity}:{action} naming pattern'i ile event-driven mimari
🎯 4 Domain Event
| Event | Tetikleyici | Subscribers |
|---|---|---|
dividend:changed | Temettü dağıtım günü (cron job) | Wallet (gelir kayıt), Portföy (dividends update), Bildirim |
portfolio:changed | Pozisyon değişti (trade, fiyat, dividend) | Portföy UI, Sidebar widget, Cüzdan, Genel Varlık |
target:changed | Hedef dağılım güncellendi veya rebalance | Portföy Analiz, Hedef Dağılım sekmesi |
trade:changed | Yeni al/sat işlemi | İşlem geçmişi, Wallet, Analiz |
📐 Pattern
{entity}:{action}
CQRS / Event Sourcing benzeri pattern. Namespaced events.
🔄 Event Flow Detayı
1. trade:changed → Multi-cascade
[POST /api/portfolio/trade]
│
▼
[Backend DB transaction]
│
▼
[Event: trade:changed]
│
▼
[Subscribers:]
├─ Portfolio service → portfolio:changed emit
├─ Wallet service → wallet:transaction emit
├─ Balance service → balance:changed emit
└─ Analytics → PostHog event capture
2. portfolio:changed → Frontend Sync
[Backend emit: portfolio:changed]
│
▼
[WS Gateway broadcast]
│
├─→ Tab 1 → React Query invalidate ['portfolio', id]
├─→ Tab 2 → React Query invalidate (BroadcastChannel)
└─→ Tab 3 → Same
│
▼
UI eş zamanlı refresh
3. dividend:changed → Auto Income
[Temettü cron job (örn THYAO ₺3.4420/lot)]
│
▼
[Portföydeki kullanıcılar query: WHERE sembol='THYAO' AND adet > 0]
│
▼
[Net hesap: Brüt × (1 - 0.15)]
│
▼
[INSERT wallet_transactions (gelir kayıt)]
│
▼
[Event: dividend:changed + wallet:transaction]
│
▼
[Bildirim: "₺X temettü tahsil edildi"]
│
▼
[Frontend WS event receive → UI refresh]
4. target:changed → Rebalance Suggest
[Kullanıcı: Hedef Dağılım > "Hisse %50, Tahvil %30, Döviz %20" set]
│
▼
[PUT /api/portfolio/{id}/target-allocation]
│
▼
[Event: target:changed]
│
▼
[Backend: Mevcut dağılım vs Hedef karşılaştır]
│
▼
[Frontend: Rebalance modal göster]
│
▼
[Kullanıcı onaylar → POST /rebalance]
📡 Backend Implementation (Tahmini)
// NestJS örneği
@Injectable()
export class PortfolioService {
constructor(private eventBus: EventBus) {}
async addTrade(dto: TradeDto) {
await this.db.transaction(async (tx) => {
// 1. Trade insert
const trade = await tx.trades.create(dto);
// 2. Position update (avg cost recalc)
await tx.positions.upsert({ avgCost: newAvg, adet: newAdet });
// 3. Wallet transaction
await tx.walletTransactions.create({ user, amount: -dto.adet * dto.fiyat });
});
// 4. Domain events emit
this.eventBus.emit('trade:changed', { tradeId, portfolioId });
this.eventBus.emit('portfolio:changed', { portfolioId });
this.eventBus.emit('wallet:transaction', { userId, amount });
}
}
// Event handlers
@EventsHandler('portfolio:changed')
class PortfolioChangedHandler {
handle(event) {
this.wsGateway.broadcast(event.userId, 'portfolio:changed', event);
}
}
🌐 Event Bus Mimari
[NestJS app]
│
▼
[Event Bus (in-memory)]
│
▼
[Redis Pub/Sub] ← Multi-server için
│
├─→ WS Gateway → tarayıcıya push
├─→ Notification Service → DB INSERT
├─→ Analytics → PostHog
└─→ Audit Log → DB
🎁 Faydaları
- Decoupling: Modüller birbirini direkt çağırmaz, event üzerinden
- Scalability: Redis pub/sub ile multi-server sync
- Audit Trail: Her event log'lanabilir
- Real-time: WS broadcast ile anlık UI update
- Eventual Consistency: Cüzdan bakiye trade sonrası anında değil sürdürülebilir delay ile sync
🚨 Failure Senaryoları
| Senaryo | Risk | Mitigation |
|---|---|---|
| Event bus down | WS update gelmez | Page reload manuel refetch |
| Redis cluster fail | Multi-server sync bozuk | Single-server failover |
| WS disconnect | Real-time kayıp | Reconnect_attempt + polling fallback |
| Race condition | İki tab eş zamanlı trade | DB unique constraint + optimistic lock |