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.

145 lines (123 loc) 4.95 kB
/** * E-Commerce Order Pipeline — Consumers * * Each service listens to its own topic and processes the event. * All consumers run in parallel in this demo; in production each would * be a separate microservice with its own Node process. * * Run: * node examples/ecommerce/consumer.js */ require('dotenv').config({ path: require('path').resolve(__dirname, '../../.env') }); const { ConsumeEvent } = require('../../index'); // eslint-disable-next-line no-console const log = (service, ...args) => console.log(`[${new Date().toISOString()}] [${service}]`, ...args); // ── Inventory Service ───────────────────────────────────────────────────────── async function startInventoryService() { const stop = await ConsumeEvent( 'order.placed', async (msg) => { const { orderId, items } = msg.value.data || msg.value; log('INVENTORY', `Processing reservation for order ${orderId}`); // Simulate stock check per item items.forEach((item) => { log('INVENTORY', ` Reserving ${item.qty}x ${item.sku}`); }); // In production: update inventory DB, emit inventory.reserve event log('INVENTORY', `✓ Reserved stock for order ${orderId}`); }, { groupId: 'inventory-service', fromBeginning: false, retry: 3, dlq: true, } ); return stop; } // ── Payment Service ─────────────────────────────────────────────────────────── async function startPaymentService() { const stop = await ConsumeEvent( 'inventory.reserve', async (msg) => { const payload = msg.value.data || msg.value; log('PAYMENT', `Processing payment for order ${payload.orderId}`); // Simulate payment gateway call await new Promise((r) => setTimeout(r, 50)); // fake latency log('PAYMENT', `✓ Payment authorised for order ${payload.orderId}`); }, { groupId: 'payment-service', fromBeginning: false, retry: 5, // payments get more retries dlq: true, } ); return stop; } // ── Notification Service ────────────────────────────────────────────────────── async function startNotificationService() { const stop = await ConsumeEvent( 'fulfillment.ship', async (msg) => { const payload = msg.value.data || msg.value; log('NOTIFICATION', `Sending shipping confirmation for order ${payload.orderId}`); log('NOTIFICATION', ` Carrier: ${payload.shippingCarrier} | ETA: ${payload.estimatedDelivery}`); // In production: send email/SMS/push via SES, Twilio, FCM log('NOTIFICATION', `✓ Shipping notification sent for order ${payload.orderId}`); }, { groupId: 'notification-service', fromBeginning: false, retry: 2, dlq: true, } ); return stop; } // ── Audit / Observability Service ──────────────────────────────────────────── async function startAuditService() { const topics = ['order.placed', 'payment.charge', 'fulfillment.ship']; const stops = await Promise.all( topics.map((topic) => ConsumeEvent( topic, async (msg) => { log( 'AUDIT', `[${topic}] offset=${msg.offset} key=${msg.key} correlationId=${msg.headers['correlation-id'] || 'n/a'}` ); }, { groupId: 'audit-service', fromBeginning: false } ) ) ); return () => Promise.all(stops.map((s) => s())); } // ── Main ────────────────────────────────────────────────────────────────────── async function main() { log('SYSTEM', 'Starting e-commerce consumers (Ctrl+C to stop)...'); const [stopInventory, stopPayment, stopNotification, stopAudit] = await Promise.all([ startInventoryService(), startPaymentService(), startNotificationService(), startAuditService(), ]); log('SYSTEM', '✓ All consumers running'); // Graceful shutdown — already handled inside ConsumeEvent, // but we can do extra cleanup here too. const shutdown = async () => { log('SYSTEM', 'Shutting down...'); await Promise.all([stopInventory(), stopPayment(), stopNotification(), stopAudit()]); log('SYSTEM', 'Goodbye.'); process.exit(0); }; process.on('SIGTERM', shutdown); process.on('SIGINT', shutdown); } main().catch((err) => { // eslint-disable-next-line no-console console.error('Consumer startup failed:', err); process.exit(1); });