☰ Mục lục series
Tài liệu NestJS tiếng Việt
  1. Nhập môn (Overview)
    1. Giới thiệu
    2. First steps
    3. Controllers
    4. Providers
    5. Modules
    6. Middleware
    7. Exception filters
    8. Pipes
    9. Guards
    10. Interceptors
    11. Custom decorators
  2. Nền tảng (Fundamentals)
    1. Custom providers
    2. Asynchronous providers
    3. Dynamic modules
    4. Injection scopes
    5. Circular dependency
    6. Module reference
    7. Lazy-loading modules
    8. Execution context
    9. Lifecycle events
    10. Discovery service
    11. Platform agnosticism
    12. Testing
  3. Kỹ thuật (Techniques)
    1. Configuration
    2. Database (SQL)
    3. MongoDB
    4. Validation
    5. Caching
    6. Serialization
    7. Versioning
    8. Task scheduling
    9. Queues
    10. Logging
    11. Cookies
    12. Events
    13. Compression
    14. File upload
    15. Streaming files
    16. HTTP module
    17. Session
    18. MVC
    19. Performance (Fastify)
    20. Server-Sent Events
  4. Bảo mật (Security)
    1. Authentication
    2. Authorization
    3. Encryption & Hashing
    4. Helmet
    5. CORS
    6. CSRF
    7. Rate limiting
    8. Vòng đời request
  5. OpenAPI (Swagger)
    1. Introduction
    2. Types and parameters
    3. Operations
    4. Security
    5. Mapped types
    6. Decorators
    7. CLI plugin
    8. Other features
  6. Recipes
    1. REPL
    2. CRUD generator
    3. SWC (fast compiler)
    4. Passport (auth)
    5. Hot reload
    6. MikroORM
    7. Router module
    8. Health checks (Terminus)
    9. CQRS
    10. Prisma
    11. Serve static
    12. Commander
    13. Async local storage
  7. WebSockets
    1. Gateways
    2. Exception filters
    3. Pipes
    4. Guards
    5. Interceptors
    6. Adapters
  8. Microservices
    1. Overview
    2. Redis
    3. MQTT
    4. NATS
    5. RabbitMQ
    6. Kafka
    7. gRPC
    8. Custom transporters
    9. Exception filters
    10. Pipes
    11. Pre-request hooks
    12. Guards
    13. Interceptors
  9. GraphQL
    1. Quick start
    2. Resolvers
    3. Mutations
    4. Subscriptions
    5. Scalars
    6. Directives
    7. Interfaces
    8. Unions and enums
    9. Field middleware
    10. Mapped types
    11. Plugins
    12. Complexity
    13. Extensions
    14. CLI plugin
    15. Generating SDL
    16. Sharing models
    17. Other features
    18. Federation
  10. CLI & triển khai
    1. CLI overview
    2. Workspaces
    3. Libraries
    4. Usage
    5. Scripts
    6. Deployment
    7. Standalone apps
  11. FAQ
    1. Serverless
    2. HTTP adapter
    3. Keep-Alive connections
    4. Global path prefix
    5. Raw body
    6. Hybrid application
    7. HTTPS & multiple servers
    8. Common errors
  12. Phụ lục
    1. Bảng thuật ngữ
Nền tảng (Fundamentals)

Circular dependency (Phụ thuộc vòng)

Bản dịch từ docs.nestjs.com/fundamentals/circular-dependency

Phụ thuộc vòng (circular dependency) xảy ra khi hai class phụ thuộc lẫn nhau. Ví dụ, class A cần class B, và class B cũng cần class A. Phụ thuộc vòng có thể xuất hiện trong Nest giữa các module với nhau và giữa các provider với nhau.

Mặc dù nên tránh phụ thuộc vòng nếu có thể, nhưng không phải lúc nào cũng tránh được. Trong những trường hợp đó, Nest cho phép bạn giải quyết phụ thuộc vòng giữa các provider theo hai cách. Trong chương này, chúng ta sẽ mô tả cách dùng forward referencing (tham chiếu chuyển tiếp) như một kỹ thuật, và dùng class ModuleRef để lấy instance của provider từ DI container như một kỹ thuật khác.

Chúng ta cũng sẽ mô tả cách giải quyết phụ thuộc vòng giữa các module.

Cảnh báo Phụ thuộc vòng cũng có thể phát sinh khi dùng “barrel file”/file index.ts để gom các import lại. Không nên dùng barrel file cho các class module/provider. Ví dụ, không nên dùng barrel file khi import các file trong cùng thư mục với barrel file đó, tức là cats/cats.controller không nên import cats để lấy file cats/cats.service. Xem thêm chi tiết tại GitHub issue này.

Ghi chú người dịch “Barrel file” là file index.ts chỉ có nhiệm vụ re-export mọi thứ trong thư mục (export * from './cats.service'…). Nó tiện khi import ở nơi khác, nhưng lại dễ tạo vòng lặp import ẩn ngay bên trong chính thư mục đó — TypeScript/bundler phải resolve toàn bộ file trong barrel trước, nên hai file cùng thư mục import qua barrel dễ bị “chờ nhau” dẫn đến circular dependency giả (không phải do logic nghiệp vụ, mà do cách tổ chức import).

Forward reference (Tham chiếu chuyển tiếp)

Forward reference cho phép Nest tham chiếu tới các class chưa được định nghĩa bằng hàm tiện ích forwardRef(). Ví dụ, nếu CatsService và CommonService phụ thuộc lẫn nhau, cả hai phía của mối quan hệ đều có thể dùng @Inject() cùng hàm tiện ích forwardRef() để giải quyết phụ thuộc vòng. Nếu không làm vậy, Nest sẽ không thể khởi tạo chúng vì thiếu metadata cần thiết. Ví dụ:

@Injectable()
export class CatsService {
  constructor(
    @Inject(forwardRef(() => CommonService))
    private commonService: CommonService,
  ) {}
}
@Injectable()
@Dependencies(forwardRef(() => CommonService))
export class CatsService {
  constructor(commonService) {
    this.commonService = commonService;
  }
}

Gợi ý Hàm forwardRef() được import từ package @nestjs/common.

Đó là một phía của mối quan hệ. Giờ hãy làm tương tự với CommonService:

@Injectable()
export class CommonService {
  constructor(
    @Inject(forwardRef(() => CatsService))
    private catsService: CatsService,
  ) {}
}
@Injectable()
@Dependencies(forwardRef(() => CatsService))
export class CommonService {
  constructor(catsService) {
    this.catsService = catsService;
  }
}

Cảnh báo Thứ tự khởi tạo là không xác định (indeterminate). Hãy chắc chắn code của bạn không phụ thuộc vào việc constructor nào được gọi trước. Việc để phụ thuộc vòng phụ thuộc vào các provider có Scope.REQUEST có thể dẫn đến dependency bị undefined. Xem thêm thông tin tại đây

Ghi chú người dịch Tương tự việc hai package Go import lẫn nhau — Go compiler sẽ báo lỗi thẳng (import cycle not allowed), còn ở Nest thì không lỗi biên dịch nhưng runtime sẽ không biết khởi tạo cái nào trước để lấy metadata của cái kia. forwardRef() giống như bạn khai báo “cứ tin tôi, class này sẽ tồn tại, đừng resolve ngay bây giờ” — trì hoãn việc resolve reference sang lúc DI container đã dựng xong toàn bộ graph.

ModuleRef class alternative (Cách thay thế bằng class ModuleRef)

Một cách khác thay cho forwardRef() là tái cấu trúc code và dùng class ModuleRef để lấy một provider ở một phía của mối quan hệ vòng (vốn dĩ nếu không sẽ là circular). Tìm hiểu thêm về class tiện ích ModuleRef tại đây.

Module forward reference (Tham chiếu chuyển tiếp giữa module)

Để giải quyết phụ thuộc vòng giữa các module, hãy dùng cùng hàm tiện ích forwardRef() ở cả hai phía của mối liên kết giữa các module. Ví dụ:

@Module({
  imports: [forwardRef(() => CatsModule)],
})
export class CommonModule {}

Đó là một phía của mối quan hệ. Giờ hãy làm tương tự với CatsModule:

@Module({
  imports: [forwardRef(() => CommonModule)],
})
export class CatsModule {}

Nội dung gốc: NestJS Documentation © Kamil Myśliwiec, giấy phép MIT. Bản dịch và ghi chú: Lê Văn Dũng.