UNPKG

kafka-pub-sub

Version:

Enterprise-grade Kafka publish/subscribe library for Node.js — producer pool, batch sending, DLQ, SSL/SASL, multi-broker, and real-world industry examples.

154 lines (133 loc) 5.77 kB
/** * Financial Services — Payment Consumer * * Demonstrates: * - Consuming payment transactions with retry + DLQ * - Consuming fraud alerts separately with dedicated consumer group * - Reading from DLQ for manual review / replay * - Consumer lag monitoring via KafkaAdmin * * DLQ behaviour: * If the handler throws after all retries, the message is automatically * routed to `payment.transaction.dlq`. A separate consumer listens there * for manual reconciliation or automated replay. * * Run: * node examples/financial/paymentConsumer.js */ require('dotenv').config({ path: require('path').resolve(__dirname, '../../.env') }); const { ConsumeEvent, KafkaAdmin } = require('../../index'); // eslint-disable-next-line no-console const log = (service, ...args) => console.log(`[${new Date().toISOString()}] [${service}]`, ...args); // ── Payment processor ───────────────────────────────────────────────────────── async function startPaymentProcessor() { return ConsumeEvent( 'payment.transaction', async (msg) => { const tx = msg.value.data || msg.value; log('PAYMENT-PROC', `Processing TX ${tx.transactionId} | ${tx.currency} ${tx.amount}`); // Simulate occasional processing failure (for DLQ demo) if (tx.riskScore > 0.95) { throw new Error(`Blocked high-risk transaction ${tx.transactionId} (score=${tx.riskScore?.toFixed(3)})`); } // In production: write to ledger DB, call clearing APIs, etc. await new Promise((r) => setTimeout(r, 20)); log('PAYMENT-PROC', `✓ TX ${tx.transactionId} processed successfully`); }, { groupId: 'payment-processor', fromBeginning: false, retry: 3, dlq: true, // failed messages go to payment.transaction.dlq } ); } // ── Fraud alert handler ─────────────────────────────────────────────────────── async function startFraudAlertHandler() { return ConsumeEvent( 'payment.fraud-alert', async (msg) => { const alert = msg.value.data || msg.value; log( 'FRAUD-HANDLER', `ALERT: TX ${alert.transactionId} flagged | score=${alert.riskScore?.toFixed(3)} | manual=${alert.requiresManualReview}` ); // In production: create case in fraud review system, notify compliance team }, { groupId: 'fraud-review', fromBeginning: false, retry: 5, dlq: false, // fraud alerts must never be silently dropped } ); } // ── DLQ consumer — dead-letter review ──────────────────────────────────────── async function startDlqReviewer() { return ConsumeEvent( 'payment.transaction.dlq', async (msg) => { const originalTopic = msg.headers['dlq-original-topic'] || 'unknown'; const failedAt = msg.headers['dlq-failed-at'] || 'unknown'; const error = msg.headers['dlq-error'] || 'unknown'; log('DLQ-REVIEWER', `Dead-lettered message from [${originalTopic}]`); log('DLQ-REVIEWER', ` Failed at: ${failedAt}`); log('DLQ-REVIEWER', ` Reason: ${error}`); log('DLQ-REVIEWER', ` Payload: ${JSON.stringify(msg.value).slice(0, 120)}…`); // In production: store to reconciliation table, alert on-call, attempt replay }, { groupId: 'dlq-reviewer', fromBeginning: true, // always read from beginning to catch all failed messages } ); } // ── Settlement consumer ─────────────────────────────────────────────────────── async function startSettlementConsumer() { return ConsumeEvent( 'payment.settled', async (msg) => { const s = msg.value.data || msg.value; log('SETTLEMENT', `Confirmed settlement ${s.transactionId} | ref=${s.clearingHouseRef}`); }, { groupId: 'settlement-recorder', fromBeginning: false } ); } // ── Consumer lag monitor ────────────────────────────────────────────────────── async function logConsumerLag() { const admin = KafkaAdmin(); try { const lag = await admin.getConsumerGroupOffsets('payment-processor', 'payment.transaction'); log('LAG-MONITOR', 'payment-processor lag:', JSON.stringify(lag)); } catch (err) { log('LAG-MONITOR', 'Could not fetch lag (topic may not exist yet):', err.message); } finally { await admin.disconnect(); } } // ── Main ────────────────────────────────────────────────────────────────────── async function main() { log('SYSTEM', 'Starting financial consumers...'); const stops = await Promise.all([ startPaymentProcessor(), startFraudAlertHandler(), startDlqReviewer(), startSettlementConsumer(), ]); log('SYSTEM', '✓ All consumers running (Ctrl+C to stop)'); // Log consumer lag every 30 seconds const lagInterval = setInterval(logConsumerLag, 30000); const shutdown = async () => { clearInterval(lagInterval); await Promise.all(stops.map((s) => s())); process.exit(0); }; process.on('SIGTERM', shutdown); process.on('SIGINT', shutdown); } main().catch((err) => { // eslint-disable-next-line no-console console.error('Financial consumer startup failed:', err); process.exit(1); });