Microservices architecture has become the backbone of modern, scalable, and resilient applications. In this article, weโ€™ll explore powerful design patterns that make microservices shine, specifically in the context of NestJS, a progressive Node.js framework.

1. ๐Ÿ›ก๏ธ Gateway Pattern

The Gateway Pattern acts as the single entry point for all microservice calls. It routes requests to the appropriate service, handles authentication, and logging, and can even aggregate responses.

import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { GatewayController } from './gateway.controller';

@Module({
imports: [
ClientsModule.register([
{ name: 'USER_SERVICE', transport: Transport.TCP },
{ name: 'ORDER_SERVICE', transport: Transport.TCP },
]),
],
controllers: [GatewayController],
})
export class GatewayModule {}
import { Controller, Get } from '@nestjs/common';
import { ClientProxy, ClientProxyFactory, Transport } from '@nestjs/microservices';

@Controller('gateway')
export class GatewayController {
private userServiceClient: ClientProxy;
private orderServiceClient: ClientProxy;

constructor() {
this.userServiceClient = ClientProxyFactory.create({ transport: Transport.TCP, options: { port: 3001 } });
this.orderServiceClient = ClientProxyFactory.create({ transport: Transport.TCP, options: { port: 3002 } });
}

@Get('user')
getUser() {
return this.userServiceClient.send({ cmd: 'get-user' }, {});
}

@Get('order')
getOrder() {
return this.orderServiceClient.send({ cmd: 'get-order' }, {});
}
}

2. ๐Ÿ“ก Service Registry Pattern

The Service Registry Pattern allows microservices to discover each other without hardcoding their locations. Itโ€™s essential in dynamic environments where services may change IPs or ports.

import { Injectable, OnModuleInit } from '@nestjs/common';
import { Consul } from 'consul';

@Injectable()
export class ServiceRegistry implements OnModuleInit {
private consul: Consul;

constructor() {
this.consul = new Consul();
}

onModuleInit() {
this.consul.agent.service.register({
name: 'user-service',
address: '127.0.0.1',
port: 3001,
});
}
}

3. โšก Circuit Breaker Pattern

The Circuit Breaker Pattern prevents cascading failures in microservices by breaking the circuit and returning a fallback response when a service fails or is slow to respond.

import { Injectable, HttpService } from '@nestjs/common';
import { catchError } from 'rxjs/operators';
import { of } from 'rxjs';

@Injectable()
export class CircuitBreakerService {
constructor(private httpService: HttpService) {}

getUserData() {
return this.httpService.get('http://user-service/user')
.pipe(
catchError(err => {
console.log('Service unavailable, returning fallback data');
return of({ id: 'fallback', name: 'Fallback User' });
})
);
}
}

4. ๐Ÿ”„ SAGA Pattern

The SAGA Pattern manages complex transactions across multiple services by breaking them into smaller steps. Each step in the SAGA can either be completed successfully or trigger compensating transactions to undo the previous steps if something goes wrong.

import { Injectable } from '@nestjs/common';
import { EventPattern } from '@nestjs/microservices';

@Injectable()
export class SagaService {
@EventPattern('order-created')
async handleOrderCreated(data: Record<string, unknown>) {
// Reserve inventory
// If inventory reservation fails, trigger a compensating transaction
}

@EventPattern('payment-processed')
async handlePaymentProcessed(data: Record<string, unknown>) {
// Confirm order
// If payment fails, trigger a compensating transaction to release inventory
}
}

5. ๐Ÿ”ง CQRS (Command Query Responsibility Segregation)

CQRS separates read and write operations, improving performance by optimizing each operation type independently. Itโ€™s particularly useful in systems with complex querying requirements.

import { QueryHandler, IQueryHandler } from '@nestjs/cqrs';

export class GetUserQuery {
constructor(public readonly userId: string) {}
}

@QueryHandler(GetUserQuery)
export class GetUserHandler implements IQueryHandler<GetUserQuery> {
async execute(query: GetUserQuery) {
// Handle the query, e.g., return user data
return { id: query.userId, name: 'John Doe' };
}
}

6. ๐Ÿงฑ Bulkhead Pattern

The Bulkhead Pattern isolates components within a service to prevent failures from spreading, ensuring that one failing service doesnโ€™t bring down others.

import { Injectable } from '@nestjs/common';
import { Queue } from 'bull';

@Injectable()
export class BulkheadService {
private readonly taskQueue: Queue;

constructor() {
this.taskQueue = new Queue('tasks');
}

async handleTask(taskData: any) {
await this.taskQueue.add(taskData);
// Process task without affecting other components
}
}

7. ๐Ÿš— Sidecar Pattern

The Sidecar Pattern adds extra functionalities (like monitoring, logging, or proxying) to a service without altering the core service logic.

import { Injectable } from '@nestjs/common';
import { createProxyMiddleware } from 'http-proxy-middleware';

@Injectable()
export class SidecarService {
configure(app: any) {
app.use('/user', createProxyMiddleware({ target: 'http://localhost:3001', changeOrigin: true }));
}
}

8. ๐Ÿ”— API Composition Pattern

API Composition orchestrates multiple microservices into a single API response, which is useful when building APIs that aggregate data from different sources.

import { Controller, Get } from '@nestjs/common';
import { HttpService } from '@nestjs/common';

@Controller('orders')
export class OrdersController {
constructor(private httpService: HttpService) {}

@Get()
async getOrders() {
const user = await this.httpService.get('http://user-service/user').toPromise();
const order = await this.httpService.get('http://order-service/order').toPromise();
return { ...user.data, ...order.data };
}
}

9. โš™๏ธ Event-Driven Architecture

In Event-Driven Architecture, services react to events asynchronously, making the system more responsive and decoupled.

import { Controller } from '@nestjs/common';
import { EventPattern } from '@nestjs/microservices';

@Controller()
export class EventController {
@EventPattern('user_created')
handleUserCreated(data: Record<string, unknown>) {
console.log('User created event received:', data);
// React to the event
}
}

10. ๐Ÿ“Š Database per Service

Each microservice owns its data, stored in its database, ensuring data isolation and independence. This pattern allows microservices to evolve independently.

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';

@Injectable()
export class UserService {
constructor(@InjectRepository(User) private userRepository: Repository<User>) {}

findAll() {
return this.userRepository.find();
}
}

11. ๐Ÿ” Retry Pattern

The Retry Pattern handles transient failures by retrying failed operations, often with an exponential backoff strategy.

import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/common';
import { catchError, retryWhen, delay, take } from 'rxjs/operators';
import { of } from 'rxjs';

@Injectable()
export class RetryService {
constructor(private httpService: HttpService) {}

fetchData() {
return this.httpService.get('http://unreliable-service/data')
.pipe(
retryWhen(errors => errors.pipe(delay(1000), take(3))),
catchError(err => {
console.log('Failed after retries');
return of({ fallback: true });
})
);
}
}

12. ๐Ÿ“‚ Configuration Externalization

Configuration Externalization centralizes configuration management, making it easier to change configuration without redeploying services.

import { ConfigModule } from '@nestjs/config';

@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: '.env',
}),
],
})
export class AppModule {}

Conclusion

Microservices architecture, combined with design patterns like those mentioned above, empowers applications to be flexible, resilient, and scalable. NestJS, with its modular approach and powerful abstractions, is an excellent framework for implementing these patterns, ensuring that your application can handle the complexities of modern distributed systems.