Xử lý Tác vụ nền bằng Queue (Hàng đợi)

Trong một hệ thống Backend chuyên nghiệp, có những tác vụ tốn rất nhiều thời gian để xử lý (ví dụ: gửi email xác thực, nén hình ảnh, sinh tệp PDF báo cáo, đồng bộ dữ liệu bên thứ ba). Nếu bạn xử lý các tác vụ này trực tiếp trong luồng nhận HTTP Request, Client sẽ phải đợi phản hồi rất lâu (thậm chí gây treo hệ thống). Queue (Hàng đợi công việc) giúp giải quyết vấn đề này bằng cách đưa các tác vụ nặng vào hàng đợi để xử lý bất đồng bộ (asynchronous) dưới nền, giải phóng luồng chính để phản hồi lập tức cho người dùng. Tài liệu này hướng dẫn cách sử dụng hàng đợi trong NestJS thông qua thư viện Bull (chạy trên nền Redis) và cách ra lệnh cho Claude Code.

1. Cơ chế hoạt động của Hàng đợi (Queue)

Hàng đợi hoạt động theo nguyên tắc FIFO (First In - First Out / Vào trước - Ra trước) và chia làm hai vai trò chính:
  • Producer (Nhà sản xuất): Nơi nhận yêu cầu từ client, đóng gói dữ liệu công việc (Job) và đẩy vào hàng đợi (Queue) rồi trả kết quả “Đã nhận xử lý” ngay cho client.
  • Consumer / Processor (Nhà xử lý): Chạy ngầm dưới nền, liên tục lấy các công việc trong hàng đợi ra để xử lý lần lượt.

2. Thiết lập Queue trong NestJS với Bull & Redis

Yêu cầu bắt buộc: Máy tính của bạn phải đang chạy phần mềm Redis làm kho chứa hàng đợi tạm thời.

Cài đặt thư viện:

npm install @nestjs/bull bull
npm install --save-dev @types/bull

Đăng ký cấu hình kết nối Redis trong app.module.ts:

src/app.module.ts
import { Module } from '@nestjs/common';
import { BullModule } from '@nestjs/bull';
import { EmailsModule } from './emails/emails.module';

@Module({
  imports: [
    // Kết nối đến máy chủ Redis chạy cục bộ
    BullModule.forRoot({
      redis: {
        host: 'localhost',
        port: 6379,
      },
    }),
    EmailsModule,
  ],
})
export class AppModule {}

3. Quy trình Xây dựng Hệ thống Queue gửi Email

Bước A: Đăng ký Queue trong Module tính năng

src/emails/emails.module.ts
import { Module } from '@nestjs/common';
import { BullModule } from '@nestjs/bull';
import { EmailsController } from './emails.controller';
import { EmailsService } from './emails.service';
import { EmailConsumer } from './email.consumer';

@Module({
  imports: [
    // Đăng ký hàng đợi có tên là 'email-sending'
    BullModule.registerQueue({
      name: 'email-sending',
    }),
  ],
  controllers: [EmailsController],
  providers: [EmailsService, EmailConsumer],
})
export class EmailsModule {}

Bước B: Đẩy Job vào hàng đợi (Producer)

Trong EmailsService, ta inject hàng đợi đã đăng ký và đẩy công việc mới vào hàng đợi bằng hàm add():
src/emails/emails.service.ts
import { Injectable } from '@nestjs/common';
import { InjectQueue } from '@nestjs/bull';
import { Queue } from 'bull';

@Injectable()
export class EmailsService {
  constructor(
    @InjectQueue('email-sending') private readonly emailQueue: Queue, // Inject hàng đợi
  ) {}

  async sendWelcomeEmail(email: string, fullName: string) {
    // Đẩy công việc gửi email chào mừng vào hàng đợi
    await this.emailQueue.add('send-welcome', {
      to: email,
      name: fullName,
    });
    
    return { message: 'Yêu cầu gửi email đang được xử lý dưới nền.' };
  }
}

Bước C: Xử lý công việc ngầm (Consumer / Processor)

Tạo tệp email.consumer.ts được trang trí bằng @Processor để xử lý các công việc trong hàng đợi email-sending:
src/emails/email.consumer.ts
import { Processor, Process } from '@nestjs/bull';
import { Job } from 'bull';

@Processor('email-sending') // Lắng nghe hàng đợi 'email-sending'
export class EmailConsumer {
  
  @Process('send-welcome') // Xử lý loại công việc 'send-welcome'
  async handleSendWelcome(job: Job<{ to: string; name: string }>) {
    const { to, name } = job.data;
    
    console.log(`Bắt đầu gửi email chào mừng đến: ${to} (tên: ${name})...`);
    
    // Giả lập thời gian xử lý gửi email thực tế tốn 3 giây
    await new Promise((resolve) => setTimeout(resolve, 3000));
    
    console.log(`Gửi email chào mừng thành công đến: ${to}`);
  }
}

4. Hướng dẫn viết Prompt để Claude Code xây dựng Queue

Hãy mô tả luồng dữ liệu nghiệp vụ cần tách biệt tác vụ nền cho Claude Code:

Prompt mẫu tạo Queue gửi Email ngầm:

Hãy tích hợp hàng đợi 'Bull' chạy trên Redis vào dự án NestJS của tôi:
1. Đăng ký 'BullModule' trong module gốc 'app.module.ts' kết nối tới Redis cục bộ port 6379.
2. Tạo module 'EmailsModule' đăng ký một queue tên là 'notifications'.
3. Viết service 'EmailsService' chứa hàm 'enqueueNotification' nhận vào email và message, rồi đẩy công việc đó vào queue 'notifications' với tên job là 'send-alert'.
4. Tạo file 'notification.consumer.ts' lắng nghe queue 'notifications', lấy job 'send-alert' ra để xử lý (giả lập thời gian gửi tốn 2 giây bằng cách sử dụng setTimeout).