1. 程式人生 > 程式設計 >NestJs學習之旅(3)——服務提供者

NestJs學習之旅(3)——服務提供者

歡迎持續關注NestJs之旅系列文章

二維碼

簡介

服務提供者是NestJs一個非常重要的概念,一般來說,被裝飾器@Injectable()修飾的類都可以視為服務提供者。服務提供者一般包含以下幾種:

  • Services(業務邏輯)
  • Factory(用來建立提供者)
  • Repository(資料庫訪問使用)
  • Utils(工具函式)

使用

下文中將以Services來說明服務提供者的具體使用。

典型的MVC架構中其實有一個問題,業務邏輯到底放哪裡?

  • 放在控制器,程式碼複用成了問題,不可能去New一個控制器然後呼叫方法,控制器方法都是根據路由地址繫結的
  • 放在Model,導致Model層臃腫,Model應該是直接和資料庫打交道的,業務邏輯跟資料庫的關係並不是強制繫結的,只有業務邏輯涉及到資料查詢/儲存才會使用到Model層

現階段比較流行的架構是多新增一個Services層來寫業務邏輯,分離Model層不應該做的事情。

// 業務類 user.service.ts
@Injectable()
export class UserServices {
  private readonly users: User[] = [];

  create(user: User) {
    this.users.push(user);
  }

  findAll(): User[] {
    return this.users;
  }
}
複製程式碼
// 使用者控制器
@Controller('users')
export
class UserController { constructor(private readonly userService: UserService) {} // 注入UserService @Post() async create(@Body() createUserDTO:CreateUserDTO) { this.userService.create(createUserDTO); } @Get() async findAll() { return this.userService.findAll(); } } 複製程式碼

服務提供者的Scope

SpringBoot中提供了Scope註解來指明Bean的作用域,NestJs也提供了類似的@Scope()裝飾器:

scope名稱 說明
SINGLETON 單例模式,整個應用內只存在一份例項
REQUEST 每個請求初始化一次
TRANSIENT 每次注入都會例項化
@Injectable({scope: Scope.REQUEST})
export class UserService {

}
複製程式碼

可選的依賴項

預設情況下,如果依賴注入的物件不存在會提示錯誤,中斷應用執行,此時可以使用@Optional()來指明選擇性注入,但依賴注入的物件不存在時不會發生錯誤。

@Controller('users')
export class UserController {
    constructor(@Optional() private readonly userService:UserService){}
}
複製程式碼

基於屬性的注入

上文中的注入都是基於建構函式的,這樣做有一個缺陷,如果涉及到繼承的話,子類必須顯示呼叫super來例項化父類。如果父類的建構函式引數過多的話反而成了子類的負擔。

針對這個問題,NestJs建議的方式是基於屬性進行注入。

@Controller('users')
export class UserController {
    @Inject()
    private readonly userService:UserService;
}
複製程式碼

服務提供者註冊

只有被註冊過的服務提供者才能被NestJs進行自動注入。

@Module({
    controllers:[UserController],// 註冊控制器
    providers: [UserServices],// 註冊服務提供者,可以是services,factory等等
})
export class UserModule {

}
複製程式碼

自定義服務提供者

使用值

上文中提供的Services一般用在編寫業務邏輯,結構基本是固定的,如果需要整合其他庫作為注入物件的話,需要使用的自定義的服務提供者。

比如我們使用sequelize建立了資料庫連線,想把他注入到我們的Services中進行資料庫操作。可以使用以下方式進行處理:

// sequelize.ts 資料庫訪問
export const sequelize = new Sequelize({
    ///
});
複製程式碼
// sequelize.provider.ts
import {sequelize} from './sequelize';

export const sequelizeProvider = {
    provide: 'SEQUELIZE',// 服務提供者標識
    useValue: sequelize,// 直接使用值
}
複製程式碼
// user.module.ts
@Module({
    providers:[UserService,sequelizeProvider]
})
export class UserModule {}
複製程式碼
// user.service.ts
@Injectable()
export class UserService {
    constructor(@Inject('SEQUELIZE') private readonly sequelize: Sequelize) {}
}
複製程式碼

使用類

OOP的一個重要思想就是面向介面化設計,比如我們開發了一個日誌介面,有寫入本地檔案的實現,也有寫入syslog的實現。依賴注入到時候我們希望使用介面進行注入,而不是具體的實現。

// logger.ts
export interface Logger {
    log(log:string);
}
複製程式碼
// file.logger.ts
export class FileLogger implements Logger {
    log(log:string) {
        // 寫入本地檔案
    }
}
複製程式碼
// syslog.logger.ts
export class SyslogLogger implements Logger {
    log(log:string) {
        // 寫入Syslog
    }
}
複製程式碼
// logger.provider.ts
export const loggerProvider = {
    provide: Logger,// 使用介面標識
    useClass: process.env.NODE_ENV==='development'?FileLogger:SyslogLogger,// 開發日誌寫入本地,生產日誌寫入syslog
}
複製程式碼
// user.module.ts
@Module({
    providers:[UserService,loggerProvider]
})
export class UserModule {

}
複製程式碼
// user.service.ts
@Injectable()
export class UserService {
    constructor(@Inject(Logger) private readonly logger: Logger) {}
}
複製程式碼

使用工廠

工廠模式相信大家都不陌生,工廠模式本質上是一個函式或者方法,返回我們需要的產品。

傳統的第三方庫都是提供callback形式或者事件形式的進行連線,比如redis,如果需要使用該型別的注入物件,工廠模式是最佳方式。

以下是使用工廠模式建立資料庫連線的例子:

// database.provider.ts
export const databaseProvider = {
    provide:'DATABASE',useFactory: async(optionsProvider: OptionsProvider) { // 使用依賴,注入順序和下面定義的順序一致
        return new Promise((resolve,reject) => {
            const connection = createConnection(optionsProvider.getDatabaseConfig())
            connection.on('ready',()=>resolve(connection));
            connection.on('error',(e)=>reject(e));
        });
    },inject:[OptionsProvider],// 注入依賴
}
複製程式碼
// user.module.ts
@Module({
    providers:[OptionsProvider,databaseProvider]
})
export class UserModule {

}
複製程式碼
// user.service.ts
@Injectable()
export class UserService {
    constructor(@Inject('DATABASE') private readonly connection: Connection) {}
}
複製程式碼

別名方式

別名方式可以基於現有的提供者進行建立。

const loggerAliasProvider = {
  provide: 'AliasedLoggerService',useExisting: Logger,};
複製程式碼

匯出服務提供者到其他模組

模組的詳細知識將在後文提到,但是有一點需要提前知道,只有被模組匯出的服務提供者才能被其他模組匯入

基於型別的匯出

上文中的UserService是基於型別而不是進入名稱進行注入的。

@Module({
    providers: [UserService],exports: [UserService],// 重要
})
export class UserModule {}
複製程式碼

基於名稱的匯出

上文中DATABASESEQUELIZE這種服務提供者都是自定義的,而且指定的識別符號。

@Module({
    providers: [sequelizeProvider],exports: ['SEQUELIZE'],// 其他模組的元件直接使用@Inject('SEQUELIZE')即可
})
複製程式碼

結尾

服務提供者是NestJs的精華之一,提供了幾種方式方便我們在各種環境下的服務提供者建立。

如果您覺得有所收穫,分享給更多需要的朋友,謝謝!

如果您想交流關於NestJs更多的知識,歡迎加群討論!

image