Architecting for Resilience and Scale
Why Your Users Can't Wait
Imagine a user places an order on your e-commerce site. In a simple, synchronous system, your API must complete ALL of these tasks before it can respond:
The user is left staring at a loading spinner for 5-10 seconds. This is a terrible user experience and will lose you customers.
A professional architect divides work into two categories:
The question is: how do we reliably hand off these background jobs?
The To-Do List for Your System
A Message Queue is an intermediary service that allows different parts of your system to communicate asynchronously.
Analogy: The restaurant order queue. The waiter (Producer) writes an order ticket and places it on a rotating wheel (the Message Broker). The chef (Consumer) picks up tickets from the wheel at their own pace and cooks the food.
The waiter is not coupled to the chef. They can take many orders even if the chef is busy.
Message Queues are a cornerstone of modern distributed systems.
RabbitMQ vs. Kafka
RabbitMQ is a mature, feature-rich message broker that implements the AMQP protocol.
It excels at complex routing. The producer sends a message to an Exchange, and the exchange decides which Queue(s) to send it to based on routing rules.
Apache Kafka is a different kind of beast. It is a distributed, persistent, and ordered commit log.
Producers append messages to the end of a Topic. Consumers are responsible for keeping track of which messages they have read (their "offset").
Kafka is designed for extremely high throughput and is the standard for data streaming, analytics, and event sourcing.
A senior architect knows the trade-offs.
From Monolith to Distributed System
NestJS provides a powerful module for building microservices that communicate over different "transporters," including message queues.
npm install @nestjs/microservices amqplib amqp-connection-manager
This installs the core library and the necessary drivers for RabbitMQ.
In our main API, we register a "Client" that knows how to connect to the message broker.
`app.module.ts`
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
@Module({
imports: [
ClientsModule.register([
{
name: 'ORDER_SERVICE', // An injection token
transport: Transport.RMQ,
options: {
urls: ['amqp://localhost:5672'],
queue: 'orders_queue',
},
},
]),
],
controllers: [AppController],
})
export class AppModule {}
We inject the client and use `.emit()` to send a fire-and-forget message.
// app.controller.ts
import { Controller, Post, Inject, Body } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
@Controller('orders')
export class AppController {
constructor(@Inject('ORDER_SERVICE') private client: ClientProxy) {}
@Post()
placeOrder(@Body() orderData: any) {
// This sends the message to the 'orders_queue' and returns immediately.
this.client.emit('order_created', orderData);
return { message: 'Order received! You will receive a confirmation email shortly.' };
}
}
The consumer is a separate NestJS application that is configured to listen to a queue instead of HTTP.
`main.ts` (of the worker application)
import { NestFactory } from '@nestjs/core';
import { Transport } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.createMicroservice(AppModule, {
transport: Transport.RMQ,
options: {
urls: ['amqp://localhost:5672'],
queue: 'orders_queue',
queueOptions: {
durable: false
},
},
});
await app.listen();
}
bootstrap();
In the consumer's controller, we use the `@MessagePattern()` decorator to define a handler for a specific message type.
import { Controller } from '@nestjs/common';
import { MessagePattern, Payload } from '@nestjs/microservices';
@Controller()
export class AppController {
@MessagePattern('order_created')
handleOrderCreated(@Payload() data: any) {
console.log('Received a new order! Processing...');
console.log(data);
// TODO: Send email, update inventory, etc.
// This is the background job.
}
}
You will be given a working NestJS CRUD API for users. Your task is to decouple the "welcome email" process by using a message queue.