Event-driven microservices
Decouple services with pub/sub, fan-out, transactional publish, and saga orchestration
Microservices need a reliable way to communicate without tight coupling. npayload provides the messaging backbone: publish events once, deliver them to every service that needs them, with guaranteed delivery and automatic retries.
The challenge
Direct service-to-service calls create tight coupling, cascading failures, and complex retry logic. When Service A calls Service B, Service C, and Service D synchronously, a failure in any one blocks the entire chain.
How npayload solves it
Services publish events to channels. Other services subscribe independently. npayload handles fan-out, retries, and failure recovery.
Basic event publishing
// Order service publishes an event
await npayload.messages.publish({
channel: 'orders',
routingKey: 'order.created',
payload: {
event: 'order.created',
orderId: 'ord_123',
customerId: 'cust_456',
items: [{ sku: 'WIDGET-001', quantity: 2, price: 29.99 }],
total: 59.98,
},
});Multiple services subscribe independently
// Fulfillment service
await npayload.subscriptions.create({
channel: 'orders',
name: 'fulfillment',
type: 'webhook',
filter: { routingKey: 'order.created' },
endpoint: { url: 'https://fulfillment.internal/webhooks/orders' },
});
// Billing service
await npayload.subscriptions.create({
channel: 'orders',
name: 'billing',
type: 'webhook',
filter: { routingKey: 'order.*' },
endpoint: { url: 'https://billing.internal/webhooks/orders' },
});
// Analytics service
await npayload.subscriptions.create({
channel: 'orders',
name: 'analytics',
type: 'webhook',
filter: { routingKey: 'order.*' },
endpoint: { url: 'https://analytics.internal/webhooks/orders' },
});Each service receives events independently. If billing is down, fulfillment and analytics still receive their events. Billing receives its events when it recovers (npayload retries automatically).
Transactional publish
When a business operation spans multiple channels, use transactional publish to guarantee all events are published atomically:
await npayload.messages.publishTransactional([
{
channel: 'orders',
payload: { event: 'order.created', orderId: 'ord_123' },
},
{
channel: 'inventory',
payload: { event: 'stock.reserved', sku: 'WIDGET-001', quantity: 2 },
},
{
channel: 'notifications',
payload: { event: 'order.confirmation', customerId: 'cust_456' },
},
]);
// All three messages are published atomically, or none areFan-out at scale
One published message can trigger dozens of subscribers. npayload handles fan-out automatically:
// Each subscriber gets its own delivery with independent retries
// If 1 of 10 subscribers fails, only that subscriber retries
// The other 9 are unaffectedArchitecture patterns
Saga orchestration
Coordinate multi-step business transactions with compensating actions:
// Step 1: Reserve inventory
await npayload.messages.publish({
channel: 'inventory',
routingKey: 'inventory.reserve',
groupKey: orderId, // FIFO ordering per order
payload: { orderId, items, action: 'reserve' },
});
// Step 2: Charge payment (after inventory confirmation)
await npayload.messages.publish({
channel: 'payments',
routingKey: 'payment.charge',
groupKey: orderId,
payload: { orderId, amount: 59.98, action: 'charge' },
});
// If payment fails, compensate by releasing inventory
await npayload.messages.publish({
channel: 'inventory',
routingKey: 'inventory.release',
groupKey: orderId,
payload: { orderId, items, action: 'release', reason: 'payment-failed' },
});CQRS with streams
Use streams for building read-optimized projections:
// Write side: publish commands
await npayload.messages.publish({
channel: 'user-events',
payload: { event: 'profile.updated', userId: 'usr_1', changes: { name: 'Alice' } },
});
// Read side: stream consumer builds a projection
const consumer = await npayload.streams.createConsumer({
channel: 'user-events',
name: 'user-profile-projection',
startFrom: 'earliest',
});
const messages = await npayload.streams.pull(consumer.gid, { limit: 100 });
for (const msg of messages) {
await updateProjection(msg.payload);
}Priority-based processing
Handle urgent events before routine ones:
// High priority: payment failures
await npayload.messages.publish({
channel: 'alerts',
priority: 10,
payload: { event: 'payment.failed', severity: 'critical' },
});
// Normal priority: daily reports
await npayload.messages.publish({
channel: 'alerts',
priority: 1,
payload: { event: 'report.ready', severity: 'info' },
});Why npayload for microservices
| Feature | Benefit |
|---|---|
| Fan-out | One event, unlimited subscribers |
| Guaranteed delivery | Retries + DLQ, messages never lost |
| Transactional publish | Atomic multi-channel events |
| Message groups | FIFO ordering per entity |
| Consumer groups | Horizontal scaling of consumers |
| Circuit breaker | Protect failing services automatically |
| Routing keys | Filter events per subscriber |
| Connectors | Bridge to Kafka, SQS, EventBridge |
Next steps
- Channels for channel types and configuration
- Subscriptions for delivery patterns
- Saga orchestration pattern for detailed workflow patterns
Was this page helpful?