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
JavaScript
/**
* 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);
});