Chào mừng các bạn quay lại với series "Mỗi ngày một chút với NestJs", bài hôm nay chúng ta cùng đi tìm hiểu Scope trong NestJs.
Trong NestJS, Scope là cách bạn kiểm soát vòng đời của các provider. Khi dự án của bạn càng lớn, việc kiểm soát vòng đời (lifecycle) của các service càng trở nên quan trọng. Trong NestJS, bạn có thể kiểm soát điều này bằng cách khai báo Scope cho từng provider. Việc hiểu đúng scope sẽ giúp:
-
Tránh leak dữ liệu giữa người dùng.
-
Tối ưu hiệu năng.
-
Dễ dàng debug và mở rộng hệ thống.
Việc hiểu rõ scope giúp bạn tránh những lỗi khó lường trong hệ thống lớn: như chia sẻ sai instance, rò rỉ trạng thái, hoặc sử dụng sai context (ví dụ như trong xử lý HTTP request, WebSocket, v.v.).
NestJS cung cấp 3 scope chính:
Scope |
Tạo Instance khi nào? |
Áp dụng khi nào? |
---|---|---|
SINGLETON |
Khi app khởi động |
Dùng chung toàn hệ thống |
REQUEST |
Mỗi HTTP request mới |
Cần dữ liệu gắn với từng request |
TRANSIENT |
Mỗi lần được inject |
Cần nhiều bản sao độc lập của provider |
1. SINGLETON – Mặc định, chỉ tạo 1 instance duy nhất
Đặc điểm:
-
Chỉ tạo một instance duy nhất cho toàn bộ ứng dụng.
-
Dùng chung ở mọi nơi, inject ở đâu cũng là cùng một object.
Cách hoạt động:
Nest chỉ tạo 1 instance của provider khi ứng dụng khởi động, và dùng lại trong toàn bộ lifecycle.
@Injectable()
export class ConfigService {
getDatabaseUrl() {
return process.env.DATABASE_URL;
}
}
Inject vào nhiều nơi:
@Injectable()
export class UserService {
constructor(private readonly configService: ConfigService) {}
}
Khi nào dùng:
-
Service chứa config
-
Logging
-
Database connection (singleton DB client)
-
Email sender (1 pool duy nhất)
2. REQUEST – Tạo mới mỗi request
Đặc điểm:
-
Tạo mỗi khi có request mới.
-
Phù hợp khi bạn cần thông tin gắn với request (ví dụ: currentUser, ip, headers, language,…)
Ví dụ:
import { Scope, Injectable, Inject } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
@Injectable({ scope: Scope.REQUEST })
export class CurrentUserService {
constructor(@Inject(REQUEST) private readonly request: Request) {}
getUser() {
return this.request.user;
}
}
Khi service A dùng REQUEST scope → các service phụ thuộc vào nó cũng sẽ bị buộc chuyển sang REQUEST scope theo “scope cascade”.
Khi nào dùng?
-
Service chứa thông tin người dùng hiện tại
-
Truy cập request.body, request.headers, request.ip
-
Xác định locale (ngôn ngữ, múi giờ theo request)
3. TRANSIENT – Mỗi lần inject tạo mới
Đặc điểm:
-
Mỗi lần inject là một instance hoàn toàn mới.
-
Tách biệt 100% giữa các nơi sử dụng, không chia sẻ state.
Cách dùng:
@Injectable({ scope: Scope.TRANSIENT })
export class UUIDGenerator {
private readonly id = Math.random().toString(36).slice(2);
getId() {
return this.id;
}
}
@Injectable()
export class AService {
constructor(private readonly uuid: UUIDGenerator) {
console.log('A:', uuid.getId());
}
}
@Injectable()
export class BService {
constructor(private readonly uuid: UUIDGenerator) {
console.log('B:', uuid.getId());
}
}
Khi nào dùng:
-
Dịch vụ dùng trong batch hoặc xử lý song song
-
Tạo mới logic phụ thuộc tham số đầu vào
-
Sử dụng trong các factory pattern
Tổng kết
Tiêu chí |
SINGLETON |
REQUEST |
TRANSIENT |
---|---|---|---|
Vòng đời |
Toàn app |
Mỗi request |
Mỗi lần inject |
Hiệu năng |
Tốt nhất |
Trung bình |
Thấp nhất |
Dùng khi nào |
Cấu hình, DB |
Request data |
Factory, xử lý độc lập |
Chia sẻ state |
Có |
Không |
Không |
Kết luận
Dùng SINGLETON nếu không có lý do đặc biệt.
Dùng REQUEST nếu bạn cần data thay đổi theo từng người dùng.
Dùng TRANSIENT nếu bạn cần sự độc lập hoàn toàn cho mỗi lần xử lý.
Đồng Nguyễn
Software Engineer
BÌNH LUẬN
Địa chỉ email của bạn sẽ không được công khai.