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.
147 lines (126 loc) • 4.52 kB
JavaScript
/**
* Financial Services — Payment Transaction Producer
*
* Demonstrates:
* - Idempotent producer (KAFKA_IDEMPOTENT=true) for exactly-once semantics
* - Partitioning by currency for ordered processing per currency pair
* - SASL/SSL config (set env vars — see .env.example)
* - BatchProduceEvent for high-throughput trade confirmation bursts
*
* Regulatory note: every payment event is stamped with a correlationId that
* ties together the originating API request, Kafka message, and downstream
* processing for full audit trail compliance (PCI-DSS, SOX).
*
* Run:
* node examples/financial/paymentProducer.js
*/
require('dotenv').config({ path: require('path').resolve(__dirname, '../../.env') });
const { ProduceEvent, BatchProduceEvent } = require('../../index');
const TOPICS = {
PAYMENT_TRANSACTION: 'payment.transaction',
PAYMENT_FRAUD_ALERT: 'payment.fraud-alert',
PAYMENT_SETTLED: 'payment.settled',
};
const randomAmount = () => parseFloat((Math.random() * 50000).toFixed(2));
const randomCurrency = () => ['USD', 'EUR', 'GBP', 'JPY', 'SGD'][Math.floor(Math.random() * 5)];
const txId = () => `TX-${Date.now()}-${Math.random().toString(36).slice(2, 7).toUpperCase()}`;
// eslint-disable-next-line no-console
const log = (...args) => console.log(`[${new Date().toISOString()}]`, ...args);
/**
* Publishes a single high-value payment transaction.
* Idempotent producer ensures the message is written exactly once
* even if the network retries cause duplicate sends.
*/
async function publishPaymentTransaction() {
const transactionId = txId();
const currency = randomCurrency();
const amount = randomAmount();
const payload = {
transactionId,
type: 'PAYMENT',
amount,
currency,
sender: { accountId: 'ACCT-001', bankCode: 'BOFAUS3N' },
receiver: { accountId: 'ACCT-009', bankCode: 'CHASUS33' },
description: 'Invoice payment INV-2024-00412',
riskScore: Math.random(), // 0.0 (safe) – 1.0 (high risk)
initiatedAt: new Date().toISOString(),
};
await ProduceEvent(
TOPICS.PAYMENT_TRANSACTION,
'PAYMENT_TX',
payload,
{
'x-source': 'payments-api',
'x-tenant': 'finco-global',
'content-type': 'application/json',
},
{
partitionKey: currency, // all USD txns go to the same partition — ordered processing
correlationId: transactionId,
}
);
log(`✓ Payment published | ${transactionId} | ${currency} ${amount}`);
// Simulate fraud detection: high-risk transactions get a separate alert event
if (payload.riskScore > 0.85) {
await ProduceEvent(
TOPICS.PAYMENT_FRAUD_ALERT,
'FRAUD_ALERT',
{
transactionId,
riskScore: payload.riskScore,
reason: 'Risk score exceeds threshold (0.85)',
requiresManualReview: true,
},
{ 'x-source': 'fraud-detection-engine' },
{ correlationId: transactionId }
);
log(`⚠ Fraud alert raised for ${transactionId} (risk=${payload.riskScore.toFixed(3)})`);
}
return payload;
}
/**
* Publishes a batch of end-of-day settlement confirmations.
* Uses BatchProduceEvent for a single broker round-trip.
*/
async function publishSettlementBatch(count = 20) {
log(`Publishing settlement batch (${count} records)...`);
const messages = Array.from({ length: count }, () => {
const id = txId();
return {
topic: TOPICS.PAYMENT_SETTLED,
event: 'PAYMENT_SETTLED',
data: {
transactionId: id,
settledAmount: randomAmount(),
currency: randomCurrency(),
settledAt: new Date().toISOString(),
clearingHouseRef: `CH-${Math.random().toString(36).slice(2, 10).toUpperCase()}`,
},
headers: { 'x-source': 'settlement-engine', 'batch-run': 'EOD-DAILY' },
partitionKey: id,
};
});
await BatchProduceEvent(messages, {
compression: 'gzip',
correlationId: `SETTLE-BATCH-${Date.now()}`,
});
log(`✓ Settlement batch of ${count} records published`);
}
async function main() {
// Publish 5 individual payment transactions
for (let i = 0; i < 5; i++) {
// eslint-disable-next-line no-await-in-loop
await publishPaymentTransaction();
}
// Publish end-of-day settlement batch
await publishSettlementBatch(20);
await ProduceEvent.disconnect();
await BatchProduceEvent.disconnect();
log('Done.');
}
main().catch((err) => {
// eslint-disable-next-line no-console
console.error('Financial producer failed:', err);
process.exit(1);
});