Nestjs Phần 2: Modules, Circular Dependency

4 min read

Trong Phần 2, tôi sẽ thảo luận về Các Module trong Nestjs, Phụ thuộc Tuần hoàn, Guards.

Modules

Phần 1 có đề cập sơ lược về các module. Trong Nestjs, các module không phải là toàn cục mà có tính phân cấp. Tuy nhiên, chúng có thể được chia sẻ giữa các module khác. Mặc dù Nestjs hỗ trợ Module Toàn Cục giống như Angular, nhưng việc giữ các Service/Controller trong module mà chúng được sử dụng chủ yếu là điều được khuyến nghị hơn.

Phần lớn thời gian, các module sẽ được tạo thông qua NestCLI và các provider/controller được tạo trong ngữ cảnh của module đó sẽ tự động được thêm vào bởi CLI. Đây được gọi là các module tính năng (feature modules).

Dưới đây là một ví dụ về module:

////// hi.module.ts //////
import {Module} from "@nestjs/common"
import HiService from "./hi.service";
import HiController from "hi.controller";

@Module({
  providers: [HiService],
  controllers: [HiController],
  exports: [HiService]
})
export class HiModule{
}

////// hello.module.ts//////
import {Module} from "@nestjs/common"
import HelloService from "./hello.service";
import HelloController from "hello.controller";
import HiModule from "../hi/hi.module"

@Module({
    imports: [HiModule],
  providers: [HelloService],
  controllers: [HelloController],
  exports: [HelloService]
})
export class HelloModule{
}

Thuộc tính mảng controllers trong bộ trang trí @Module được sử dụng cho tất cả các controller mà module sử dụng hoặc tất cả các lớp được trang trí với bộ trang trí @Controller. Thuộc tính providers được sử dụng cho các service hoặc các lớp được trang trí với bộ trang trí @Injectable. Hãy nhớ rằng, bất kỳ thứ gì được trang trí bằng @Injectable đều là một provider và bạn phải đưa nó vào trường providers để có thể tiêm (inject)/sử dụng nó.

Thuộc tính exports được sử dụng để xuất/công khai các provider có thể được chia sẻ với các module khác. Đặt bất kỳ provider nào mà bạn muốn tiêm (inject)/sử dụng trong các module khác vào thuộc tính này.

Thuộc tính imports thì ngược lại với exports. Để có thể sử dụng/tiêm (inject) bất kỳ provider bên ngoài nào vào một provider/controller của một module khác, bạn phải thêm module chứa provider được xuất đó vào trường imports của module khác.

Circular Dependency

Thường thì bạn muốn sử dụng một provider trong provider của module khác và ngược lại, sử dụng provider của module khác trong provider/controller của mình. Trong trường hợp này, nó sẽ tạo ra một phụ thuộc tuần hoàn (circular dependency). Phụ thuộc tuần hoàn có thể phát sinh trong Nest giữa các module và giữa các provider. Bạn luôn nên cố gắng tránh Phụ thuộc Tuần hoàn trong Nestjs, nhưng đôi khi điều đó là không thể. Trong trường hợp này, forwardRef và bộ trang trí tham số @Inject rất hữu ích cho các provider nằm trong cùng ngữ cảnh module.

Ví dụ về cách sử dụng forwardRef giữa các provider trong cùng một module để giải quyết phụ thuộc tuần hoàn:

///// bye.service.ts /////
import { forwardRef, Inject, Injectable } from '@nestjs/common';
import { HelloService } from './hello.service';

@Injectable()
export class ByeService {
  constructor(
        // injecting HelloService
    @Inject(forwardRef(() => HelloService))
    private helloService: HelloService,
  ) {}

  getBye(arg: string) {
    return `bye bye, ${arg}`;
  }

    // it uses `helloService` & is within same module
  helloServiceUsingMethod() {
    return this.helloService.getHello('bye');
  }
}

///// hello.service.ts /////
import { forwardRef, Inject, Injectable } from '@nestjs/common';
import { ByeService } from './bye.service';

@Injectable()
export class HelloService {
    // ...other stuff

  constructor(
        // injecting ByeService
    @Inject(forwardRef(() => ByeService))
    private byeService: ByeService,
  ) {}

  getHello(arg: string) {
    return `hello for ${arg}`;
  }

  byeServiceUsingMethod() {
    return this.byeService.getBye('hello');
  }

    // ....other stuff
}

Hãy thêm ByeService mới tạo vào module /hello hoặc trường providers của HelloModule.

////// hello.module.ts //////
// import stuff
import {ByeService} from "./bye.service"

@Module({
  providers: [HelloService, ByeService], // new bye-service added
  controllers: [HelloController],
  exports: [HelloService]
})
export class HelloModule{
}

Bây giờ, còn về các provider từ các module bên ngoài? Đừng lo, chỉ cần làm giống như trên cho các provider và chỉ cần sử dụng forwardRef trong trường imports của cả hai module để nhập các provider của nhau trong ngữ cảnh của chúng.

Ví dụ về việc chuyển tiếp tham chiếu của các provider bên ngoài qua các module:

////// hi.module.ts //////
import { forwardRef, Module } from '@nestjs/common';
import HiService from "./hi.service";
import HiController from "hi.controller";
import HelloModule from "../hello/hello.module";

@Module({
  imports: [forwardRef(() => HelloModule)], // importing HelloMoule using forwardRef
  providers: [HiService],
  controllers: [HiController],
  exports: [HiService] // exporting hi-service for using in hello-service
})
export class HiModule{
}

////// hello.module.ts//////
import {Module, forwardRef} from "@nestjs/common"
import HelloService from "./hello.service";
import HelloController from "hello.controller";
import HiModule from "../hi/hi.module";
import ByeService from "./bye.service";

@Module({
    imports: [forwardRef(() => HiModule)],
  providers: [HelloService, ByeService],
  controllers: [HelloController],
  exports: [HelloService] // exporting hello-service for using in hi-service
})
export class HelloModule{
}

Bây giờ mà các provider của cả hai module đã có sẵn trong phạm vi của nhau, hãy sử dụng forwardRef trong các provider của chúng, HelloService & HiService, để giải quyết sự phụ thuộc tuần hoàn của chúng:

///// hello.service.ts //////
import {Injectable, Inject, forwardRef} from "@nestjs/common"
import HiService from "../hi/hi.service"

@Injectable()
export class HelloService{
  // .... other properties/methods

    constructor(
        // just like provider-scoped circular dependency
        @Inject(forwardRef(()=>HiService))
        private hiService: HiService
     ){
    }

    getHello(arg: string){
        return `hello for ${arg}`
    }

    // a method that uses `hiService`
  hiServiceUsingMethod(){
        return this.hiService.getHi("hello");
  }
  // .... other properties/methods
}

///// hi.service.ts /////
import {Injectable, Inject, forwardRef} from "@nestjs/common"
import HelloService from "../hello/hello.service"

@Injectable()
export class HelloService{
  // .... other properties/methods

    constructor(
        @Inject(forwardRef(()=>HelloService)) private helloService: HelloService
     ){
    }

    getHi(arg: string){
        return `hi for ${arg}`
    }

    // a method that uses `helloService`
  helloServiceUsingMethod(){
        return this.helloService.getHello("hi");
  }
  // .... other properties/methods
}

Hãy đảm bảo rằng mã của bạn không phụ thuộc vào việc constructor nào được gọi trước vì thứ tự khởi tạo của các lớp provider này là không xác định.

Có một phương pháp tiên tiến hơn so với forwardRef. Lớp ModuleRef được cung cấp từ @nestjs/core để khởi tạo động cả các static và scoped provider. Nó có thể được sử dụng chủ yếu để duyệt qua danh sách nội bộ của các provider và nhận một tham chiếu đến bất kỳ provider nào bằng cách sử dụng injection token của nó như một khóa tìm kiếm. ModuleRef có thể được tiêm vào một class theo cách thông thường. Tìm hiểu thêm về ModuleRef

Avatar photo

Leave a Reply

Your email address will not be published. Required fields are marked *