NestJS - Providers

Providers là thành phần cơ bản và cực kỳ quan trọng trong Nest để thực hiện Dependency Injection.
NestJS - Providers
NestJS - Providers

Providers

Trong bài viết trước, mình có nhắc đến khái niệm Dependency Injection (DI), đi kèm đó là 3 khái niệm class

  • Client
  • Service
  • Injector

Trong Nest, providers đại diện cho các class Service. Điều đó có nghĩa là các providers sẽ được inject vào những nơi cần sử dụng thông qua DI. Một số class cơ bản trong Nest được xem là provider như

  • Service
  • Factory
  • Repository
  • Helper
  • ...

 

Dependency Injection

Nhờ Typescript, chúng ta có thể dễ dàng quản lý các dependencies trong Nest thông qua kiến trúc type-mapping. 

Bạn có thể tham khảo ví dụ sau:

//app.service.ts

import { Injectable } from "@nestjs/common";

@Injectable()
export class AppService {
  getHello(): string {
    return "Hello World!";
  }
}
//app.controller.ts

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

Trong AppService, chúng ta định nghĩa nó là một dependency bằng cách sử dụng decorator @Injectable().

Ngoài ra, AppService được inject vào AppController thông qua constructor.

constructor(private readonly appService: AppService) {}

Việc sử dụng Access Modifier (public - protected - private) trong constructor là một cách viết tắt giúp chúng ta có thể khai báo và khởi tạo property trong class cùng một lúc.

Đoạn code trên tương đương với cách viết như sau:

//app.controller.ts

@Controller()
export class AppController {
  private readonly appService: AppService
  
  constructor(appService: AppService) {
    this.appService = appService
  }

  ...
}

Một điều quan trọng nữa là các bạn cần register AppService trong Nest module để nó resolve và thực hiện inject AppService đúng cách.

Quá trình cũng rất đơn giản. Chúng ta chỉ cần chỉnh sửa file Nest module, sau đó thêm AppService vào mảng providers của decorator @Module().

//app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

 

Nest cung cấp sẵn một hệ thống IoC Container riêng giúp thực hiện DI một cách dễ dàng. Mình sẽ tóm tắt cơ chế hoạt động của nó như sau

  • Trong app.service.ts, decorator @Injectable() khai báo cho IoC Container biết AppService là một dependency được quản lý bởi IoC Container.
  • Trong app.controller.ts, controller AppController khai báo nó cần dependency AppService thông qua constructor.
  • Trong app.module.ts, Nest tạo ra một token liên kết với AppService từ tập tin app.service.ts. Việc đăng ký được thực hiện thông qua mảng providers của decorator @Module().
  • Khi Nest khởi tạo AppController, nó sẽ tìm kiếm tất cả các dependencies mà AppController yêu cầu - ở đây là AppService.
  • IoC Container lúc này sẽ kiểm tra tất cả providers đã được register trong Nest module và tìm AppService thông qua AppService token đã được đăng ký trước đó.
  • Sau khi tìm thấy, nó sẽ khởi tạo (hoặc lấy về nếu đã tồn tại) instance của AppService, sau đó khởi tạo class AppController, đồng thời inject AppService vào AppController thông qua constructor.
  • Nếu không tìm thấy, Nest sẽ báo lỗi cho bạn biết. 
Nest can't resolve dependencies of the AppController (?). Please make sure that the argument AppService at index [0] is available in the AppModule context.

Ngoài ra, IoC Container còn thực hiện analysis các dependencies (Nest gọi nó là tạo Dependency Graph). Dependency Graph bảo đảm các dependencies được resolve theo thứ tự - về cơ bản là "từ dưới lên". 

Cơ chế này của IoC Container giúp chúng ta giải quyết vấn đề gặp phải ở bài viết trước - khi bạn có nhiều lớp Service phụ thuộc nhau.

 

Providers cơ bản

Đây là cách khai báo đơn giản nhất, có thể sử dụng trong hầu hết các trường hợp.

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})

Bạn có thể truyền trực tiếp các class name vào trong mảng providers. Cú pháp này là cách viết tắt của cách viết đầy đủ như sau:

providers: [
  {
    provide: AppService,
    useClass: AppService,
  }
]

Nest hỗ trợ một số phương thức để thực hiện resolve dependencies:

  • useClass
  • useValue
  • useFactory
  • useExisting

useClass

Bạn chỉ cần khai báo tên class bạn muốn sử dụng làm instance. Hãy nhớ, chỉ có các class đăng ký @Injectable() mới có thể sử dụng làm instance theo cách này.

 

useValue

Nest sẽ resolve dependency bằng chính instance hoặc value mà bạn khai báo. Cách này thường được dùng để viết mock test.

import { CatsService } from './cats.service';

const mockCatsService = {
  /* mock implementation
  ...
  */
};

@Module({
  imports: [CatsModule],
  providers: [
    {
      provide: CatsService,
      useValue: mockCatsService,
    },
  ],
})
export class AppModule {}

 

useFactory

Cú pháp này giúp chúng ta các provider một cách dynamic. Dependency sẽ được resolve bằng giá trị của hàm useFactory(). Bạn cũng có thể inject dependencies vào các factory phức tạp.

  • Bạn có thể truyền các optional params vào factory function
  • Nếu truyền params vào factory function, bạn cần khai báo thêm các dependencies cho factory thông qua property inject.
const connectionFactory = {
  provide: 'CONNECTION',
  useFactory: (optionsProvider: OptionsProvider) => {
    const options = optionsProvider.get();
    return new DatabaseConnection(options);
  },
  inject: [OptionsProvider],
};

@Module({
  providers: [connectionFactory],
})
export class AppModule {}

 

useExisting

Phương thức này giúp chúng ta alias các providers đã tồn tại.

@Injectable()
class LoggerService {
  /* implementation details */
}

const loggerAliasProvider = {
  provide: 'AliasedLoggerService',
  useExisting: LoggerService,
};

@Module({
  providers: [LoggerService, loggerAliasProvider],
})
export class AppModule {}

Ở ví dụ trên, string token `AliasedLoggerService` là một alias của LoggerService. Nếu scope của cả 2 provider này đều là SINGLETON, thì chúng sẽ trả về cùng một instance.

 

Non-class provider tokens

Như đã đề cập ở ví dụ trước đó, Nest cũng cho phép bạn register DI tokens bằng string hoặc Symbol

@Module({
  providers: [
    {
      provide: 'CAT_REPOSITORY',
      useValue: new CatRepository(),
    },
  ],
})
export class AppModule {}

Sau đó sử dụng decorator Inject() ở nơi cần sử dụng.

@Controller()
export class AppController {
  constructor(@Inject('MY_REPOSITORY') private readonly repository: CatRepository {}
  ...
}

 

Export Provider

Giả sử bạn có 2 module A và B. Nếu bạn muốn một provider khai báo trong module A có thể được dùng bởi module B, đây là lúc bạn cần export provider đó từ module A, sau đó import module A vào trong module B để sử dụng.

const connectionFactory = {
  provide: 'CONNECTION',
  useFactory: (optionsProvider: OptionsProvider) => {
    const options = optionsProvider.get();
    return new DatabaseConnection(options);
  },
  inject: [OptionsProvider],
};

@Module({
  providers: [connectionFactory],
  exports: ['CONNECTION'],
})
export class AppModule {}

Thực ra thì export luôn cái provider object cũng được, chả sao cả.

@Module({
  providers: [connectionFactory],
  exports: [connectionFactory],
})
export class AppModule {}

 

Kết

Mình xin phép kết thúc bài viết ở đây. Hẹn gặp lại trong phần tiếp theo nhé.

Comments

Bài viết nổi bật

Dạo gần đây đi đâu cũng nghe nói về microservices, người người nhà nhà rục rịch chuyển dịch hệ thống sang microservices. Trước khi đưa ra sự so sánh, mình sẽ khái quát một chút về Monolith Application và MicroServices một chút cho các bạn chưa biết nắm rõ hơn nhé.
PHP là ngôn ngữ được sử dụng rộng rãi nhất trên thế giới trong lập trình web. Nó cũng bị ghét nhất. Nhưng tại sao nhiều developer lại ghét nó đến vậy? Hôm nay chúng ta hãy cùng tìm hiểu lý do xem chúng có thuyết phục không nhé ^_^
Lúc trước mình hay sử dụng cách này trên laptop phụ của mình, giờ mua license luôn rồi. Hôm nay mình xin chia sẻ cho bạn nào cần nhé.
JWT Tokens là một cách thức lưu trữ thông tin xác thực hiệu quả, nhưng làm cách nào để chúng ta có thể giúp chúng an toàn hơn? Có 2 cách thường dùng để lưu trữ JWT Tokens là LocalStorage và Cookies. Bây giờ chúng ta sẽ bắt đầu "mổ xẻ" các ưu - nhược điểm của mỗi loại nhé.
Có khá nhiều bạn đã yêu cầu mình một bài viết về Repository Design Pattern. Vậy mục đích của nó là gì? Nó có thực sự cần thiết cho ứng dụng của bạn hay không? Những điểm mạnh, điểm yếu của nó là gì? Chúng ta cùng đi sâu tìm hiểu qua bài viết này nhé.

Mục lục

Related posts

Triển khai Saga Pattern trong microservices với NodeJS và Choreography-Based Saga
Mô hình Saga đưa ra một giải pháp có cấu trúc để giải quyết thách thức này. Nó cung cấp một phương pháp có hệ thống để quản lý transaction qua nhiều microservices. Điều này giải quyết những phức tạp của các transaction phân tán và hoàn toàn tương thích với các nguyên tắc của kiến trúc microservices, được đặc trưng bởi sự kết nối lỏng lẻo và khả năng triển khai độc lập của các service.
Một API cho phép giao tiếp hai chiều giữa các ứng dụng phần mềm thông qua các requests. Một Webhook là một API nhẹ, hỗ trợ chia sẻ dữ liệu một chiều được kích hoạt bởi các events.
Một trong những câu hỏi được đặt thường xuyên nhất về TypeScript là liệu chúng ta nên sử dụng interface hay type. Câu trả lời cho câu hỏi này, giống như nhiều câu hỏi lập trình khác, là nó phụ thuộc vào tình hình cụ thể. Trong một số trường hợp, một cái có lợi thế rõ rệt hơn cái kia, nhưng trong nhiều trường hợp, chúng có thể thay thế cho nhau.
Đây là các types cơ bản nhưng cũng phổ biến nhất trong Typescript. Một số types khác phức tạp hơn cũng được xây dựng dựa trên những types cơ bản này.
Trong thế giới lập trình, trách nhiệm lớn nhất của chúng ta không phải chỉ làm cho code chạy được, mà còn phải đảm bảo rằng các đoạn code mà chúng ta viết có thể dễ dàng kiểm tra và bảo trì trong một khoảng thời gian dài.
Phân trang - một thành phần không thể thiếu trong các ứng dụng có lượng dữ liệu lớn. Tuy nhiên, bạn hiểu được bao nhiêu về nó?
Javascript là một thành phần không thể thiếu đối với frontend developers. Tuy nhiên, ngay từ lúc ra đời, nó đã tồn tại khá nhiều vấn đề cần khắc phục. Đó là lý do tại sao từ 2015 (ES6) tới 2021 (ES12) ra đời nhằm giúp Javascript trở nên tốt hơn.
Dạo này mình làm việc với mấy bạn trên github, thấy hay xài mấy từ viết tắt mà mình không hiểu lắm. Thôi thì tổng hợp lại một list các từ viết tắt hay dùng trong github luôn cho ai cần :D
Dạo gần đây đi đâu cũng nghe nói về microservices, người người nhà nhà rục rịch chuyển dịch hệ thống sang microservices. Trước khi đưa ra sự so sánh, mình sẽ khái quát một chút về Monolith Application và MicroServices một chút cho các bạn chưa biết nắm rõ hơn nhé.
Cách bỏ qua câu lệnh --set-upstream quen thuộc cho các con lười
Mình sẽ giới thiệu 2 cách để xóa một property trong Javascript Object. Một cách sử dụng mutable - toán tử delete, một cách còn lại là immutable - tính năng Object Restructuring.

Tin mới nhất

Triển khai Saga Pattern trong microservices với NodeJS và Choreography-Based Saga
Mô hình Saga đưa ra một giải pháp có cấu trúc để giải quyết thách thức này. Nó cung cấp một phương pháp có hệ thống để quản lý transaction qua nhiều microservices. Điều này giải quyết những phức tạp của các transaction phân tán và hoàn toàn tương thích với các nguyên tắc của kiến trúc microservices, được đặc trưng bởi sự kết nối lỏng lẻo và khả năng triển khai độc lập của các service.
Một API cho phép giao tiếp hai chiều giữa các ứng dụng phần mềm thông qua các requests. Một Webhook là một API nhẹ, hỗ trợ chia sẻ dữ liệu một chiều được kích hoạt bởi các events.
Một trong những câu hỏi được đặt thường xuyên nhất về TypeScript là liệu chúng ta nên sử dụng interface hay type. Câu trả lời cho câu hỏi này, giống như nhiều câu hỏi lập trình khác, là nó phụ thuộc vào tình hình cụ thể. Trong một số trường hợp, một cái có lợi thế rõ rệt hơn cái kia, nhưng trong nhiều trường hợp, chúng có thể thay thế cho nhau.
Trong phần này, chúng ta sẽ tìm hiểu một số khái niệm cơ bản nhất về AWS là gì và một số lợi ích khi sử dụng AWS.
Trở thành một software developer hiệu suất cao không phải là điều dễ dàng. Điều này đòi hỏi bạn phải có kỹ năng và kiến thức về lập trình, cũng như cách tiếp cận và giải quyết các vấn đề phức tạp. Tuy nhiên, nếu bạn có chút kiên nhẫn và sự nỗ lực, bạn hoàn toàn có thể trở thành một developer tài năng và thành công.
Đây là các types cơ bản nhưng cũng phổ biến nhất trong Typescript. Một số types khác phức tạp hơn cũng được xây dựng dựa trên những types cơ bản này.
Trong thế giới lập trình, trách nhiệm lớn nhất của chúng ta không phải chỉ làm cho code chạy được, mà còn phải đảm bảo rằng các đoạn code mà chúng ta viết có thể dễ dàng kiểm tra và bảo trì trong một khoảng thời gian dài.
Thông tin được định nghĩa dưới dạng dữ liệu, kiến thức về thông tin, và trí tuệ về tri thức.
Phân trang - một thành phần không thể thiếu trong các ứng dụng có lượng dữ liệu lớn. Tuy nhiên, bạn hiểu được bao nhiêu về nó?
Javascript là một thành phần không thể thiếu đối với frontend developers. Tuy nhiên, ngay từ lúc ra đời, nó đã tồn tại khá nhiều vấn đề cần khắc phục. Đó là lý do tại sao từ 2015 (ES6) tới 2021 (ES12) ra đời nhằm giúp Javascript trở nên tốt hơn.
Dạo này mình làm việc với mấy bạn trên github, thấy hay xài mấy từ viết tắt mà mình không hiểu lắm. Thôi thì tổng hợp lại một list các từ viết tắt hay dùng trong github luôn cho ai cần :D
Triển khai Saga Pattern trong microservices với NodeJS và Choreography-Based Saga
Một trong những câu hỏi được đặt thường xuyên nhất về TypeScript là liệu chúng ta nên sử dụng interface hay type. Câu trả lời cho câu hỏi này, giống như nhiều câu hỏi lập trình khác, là nó phụ thuộc vào tình hình cụ thể. Trong một số trường hợp, một cái có lợi thế rõ rệt hơn cái kia, nhưng trong nhiều trường hợp, chúng có thể thay thế cho nhau.
Đây là các types cơ bản nhưng cũng phổ biến nhất trong Typescript. Một số types khác phức tạp hơn cũng được xây dựng dựa trên những types cơ bản này.
Trong thế giới lập trình, trách nhiệm lớn nhất của chúng ta không phải chỉ làm cho code chạy được, mà còn phải đảm bảo rằng các đoạn code mà chúng ta viết có thể dễ dàng kiểm tra và bảo trì trong một khoảng thời gian dài.
Javascript là một thành phần không thể thiếu đối với frontend developers. Tuy nhiên, ngay từ lúc ra đời, nó đã tồn tại khá nhiều vấn đề cần khắc phục. Đó là lý do tại sao từ 2015 (ES6) tới 2021 (ES12) ra đời nhằm giúp Javascript trở nên tốt hơn.