--- title: Use Message and Event Patterns Correctly impact: MEDIUM impactDescription: Proper patterns ensure reliable microservice communication tags: microservices, message-pattern, event-pattern, communication --- ## Use Message and Event Patterns Correctly NestJS microservices support two communication patterns: request-response (MessagePattern) and event-based (EventPattern). Use MessagePattern when you need a response, and EventPattern for fire-and-forget notifications. Understanding the difference prevents communication bugs. **Incorrect (using wrong pattern for use case):** ```typescript // Use @MessagePattern for fire-and-forget @Controller() export class NotificationsController { @MessagePattern('user.created') async handleUserCreated(data: UserCreatedEvent) { // This WAITS for response, blocking the sender await this.emailService.sendWelcome(data.email); // If email fails, sender gets an error (coupling!) } } // Use @EventPattern expecting a response @Controller() export class OrdersController { @EventPattern('inventory.check') async checkInventory(data: CheckInventoryDto) { const available = await this.inventory.check(data); return available; // This return value is IGNORED with @EventPattern! } } // Tight coupling in client @Injectable() export class UsersService { async createUser(dto: CreateUserDto): Promise { const user = await this.repo.save(dto); // Blocks until notification service responds await this.client.send('user.created', user).toPromise(); // If notification service is down, user creation fails! return user; } } ``` **Correct (use MessagePattern for request-response, EventPattern for fire-and-forget):** ```typescript // MessagePattern: Request-Response (when you NEED a response) @Controller() export class InventoryController { @MessagePattern({ cmd: 'check_inventory' }) async checkInventory(data: CheckInventoryDto): Promise { const result = await this.inventoryService.check(data.productId, data.quantity); return result; // Response sent back to caller } } // Client expects response @Injectable() export class OrdersService { async createOrder(dto: CreateOrderDto): Promise { // Check inventory - we NEED this response to proceed const inventory = await firstValueFrom( this.inventoryClient.send( { cmd: 'check_inventory' }, { productId: dto.productId, quantity: dto.quantity }, ), ); if (!inventory.available) { throw new BadRequestException('Insufficient inventory'); } return this.repo.save(dto); } } // EventPattern: Fire-and-Forget (for notifications, side effects) @Controller() export class NotificationsController { @EventPattern('user.created') async handleUserCreated(data: UserCreatedEvent): Promise { // No return value needed - just process the event await this.emailService.sendWelcome(data.email); await this.analyticsService.track('user_signup', data); // If this fails, it doesn't affect the sender } } // Client emits event without waiting @Injectable() export class UsersService { async createUser(dto: CreateUserDto): Promise { const user = await this.repo.save(dto); // Fire and forget - doesn't block, doesn't wait this.eventClient.emit('user.created', { userId: user.id, email: user.email, timestamp: new Date(), }); return user; // User creation succeeds regardless of event handling } } // Hybrid pattern for critical events @Injectable() export class OrdersService { async createOrder(dto: CreateOrderDto): Promise { const order = await this.repo.save(dto); // Critical: inventory reservation (use MessagePattern) const reserved = await firstValueFrom( this.inventoryClient.send({ cmd: 'reserve_inventory' }, { orderId: order.id, items: dto.items, }), ); if (!reserved.success) { await this.repo.delete(order.id); throw new BadRequestException('Could not reserve inventory'); } // Non-critical: notifications (use EventPattern) this.eventClient.emit('order.created', { orderId: order.id, userId: dto.userId, total: dto.total, }); return order; } } // Error handling patterns // MessagePattern errors propagate to caller @MessagePattern({ cmd: 'get_user' }) async getUser(userId: string): Promise { const user = await this.repo.findOne({ where: { id: userId } }); if (!user) { throw new RpcException('User not found'); // Received by caller } return user; } // EventPattern errors should be handled locally @EventPattern('order.created') async handleOrderCreated(data: OrderCreatedEvent): Promise { try { await this.processOrder(data); } catch (error) { // Log and potentially retry - don't throw this.logger.error('Failed to process order event', error); await this.deadLetterQueue.add(data); } } ``` Reference: [NestJS Microservices](https://docs.nestjs.com/microservices/basics)