260223:1415 20260223 nextJS & nestJS Best pratices
All checks were successful
Build and Deploy / deploy (push) Successful in 4m44s
All checks were successful
Build and Deploy / deploy (push) Successful in 4m44s
This commit is contained in:
167
.agents/skills/nestjs-best-practices/rules/micro-use-patterns.md
Normal file
167
.agents/skills/nestjs-best-practices/rules/micro-use-patterns.md
Normal file
@@ -0,0 +1,167 @@
|
||||
---
|
||||
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<User> {
|
||||
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<InventoryResult> {
|
||||
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<Order> {
|
||||
// Check inventory - we NEED this response to proceed
|
||||
const inventory = await firstValueFrom(
|
||||
this.inventoryClient.send<InventoryResult>(
|
||||
{ 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<void> {
|
||||
// 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<User> {
|
||||
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<Order> {
|
||||
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<User> {
|
||||
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<void> {
|
||||
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)
|
||||
Reference in New Issue
Block a user