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 {}
複製程式碼
基於名稱的匯出
上文中DATABASE
和SEQUELIZE
這種服務提供者都是自定義的,而且指定的識別符號。
@Module({
providers: [sequelizeProvider],exports: ['SEQUELIZE'],// 其他模組的元件直接使用@Inject('SEQUELIZE')即可
})
複製程式碼
結尾
服務提供者是NestJs的精華之一,提供了幾種方式方便我們在各種環境下的服務提供者建立。
如果您覺得有所收穫,分享給更多需要的朋友,謝謝!
如果您想交流關於NestJs更多的知識,歡迎加群討論!