欢迎继续关注NestJs之旅系列文章介绍Serviceprovider是NestJs中一个非常重要的概念。一般来说,被装饰器@Injectable()装饰的类都可以认为是一个服务提供者。服务提供者一般包括以下几种类型:Services(业务逻辑)Factory(用于创建提供者)Repository(用于数据库访问)Utils(实用功能)Use下面将使用Services来说明服务提供者的具体使用。典型的MVC架构其实有个问题,业务逻辑放在哪里?如果放在controller中,代码复用就成了问题。不可能创建一个新的控制器然后调用方法。controller方法根据路由地址进行绑定,放在Model中,导致Model层臃肿。模型应该直接与数据库打交道。是的,业务逻辑和数据库的关系不是强制的。只有当业务逻辑涉及数据查询/存储时,才会用到Model层。现阶段比较流行的架构是多加一个Services层写业务逻辑,把Model层分离出来。不该做的事。//业务类user.service.ts@Injectable()exportclassUserServices{privatereadonlyusers:User[]=[];创建(用户:用户){this.users.push(用户);}findAll():User[]{returnthis.users;}}//用户控制器@Controller('users')exportclassUserController{constructor(privatereadonlyuserService:UserService){}//注入UserService@Post()asynccreate(@Body()createUserDTO:CreateUserDTO){this.userService.create(createUserDTO);}@Get()asyncfindAll(){returnthis.userService.findAll();}}服务提供者的ScopeSpringBoot提供了Scope注解来表示bean的Scope,NestJs也提供了类似的@Scope()装饰器:作用域名称描述SINGLETON单例模式,整个应用只有一个实例。REQUEST为每个请求初始化一次。TRANSIENT为每次注入实例化。@Injectable({scope:Scope.REQUEST})exportclassUserService{}可选依赖默认情况下,如果依赖注入的对象不存在,会提示错误并中断应用。这时候可以使用@Optional()来表示选择性注入,但是当依赖注入的对象不存在时不会报错。@Controller('users')exportclassUserController{constructor(@Optional()privatereadonlyuserService:UserService){}}基于属性的注入上面的注入都是基于构造函数的,它有一个缺陷,如果涉及到继承的情况下,子类必须显式调用super来实例化父类。如果父类的构造函数参数过多,就会成为子类的负担。为了解决这个问题,NestJs建议的方式是基于属性注入。@Controller('users')exportclassUserController{@Inject()privatereadonlyuserService:UserService;}服务提供者注册只有注册的服务提供者才能被NestJs自动注入。@Module({controllers:[UserController],//registercontrollerproviders:[UserServices],//注册服务提供者,可以是服务,工厂等})exportclassUserModule{}自定义服务提供者使用值所提供的服务本文中一般都是用来写业务逻辑的,结构基本固定。如果需要集成其他库作为注入对象,则需要使用自定义服务提供者。比如我们使用sequelize创建一个数据库连接,想注入到我们的Services中进行数据库操作。这可以使用以下方法处理://sequelize.tsdatabaseaccessexportconstsequelize=newSequelize({///});//sequelize.provider.tsimport{sequelize}from'./sequelize';exportconstsequelizeProvider={provide:'SEQUELIZE',//服务提供者IDuseValue:sequelize,//直接使用值}//user.module.ts@Module({providers:[UserService,sequelizeProvider]})exportclassUserModule{}//user.service.ts@Injectable()exportclassUserService{constructor(@Inject('SEQUELIZE')privatereadonlysequelize:Sequelize){}}使用类OOP的一个重要思想是面向接口的设计。比如我们开发了一个日志接口,有写入本地文件的实现,也有写入syslog的实现。说到依赖注入,我们希望使用接口进行注入,而不是具体的实现。//logger.tsexportinterfaceLogger{log(log:string);}//file.logger.tsexportclassFileLoggerimplementsLogger{log(log:string){//写入本地文件}}//syslog.logger.tsexportclassSyslogLoggerimplementsLogger{log(log:string){//写入Syslog}}//logger.provider.tsexportconstloggerProvider={provide:Logger,//使用接口标志useClass:process.env.NODE_ENV==='development'?FileLogger:SyslogLogger,//开发日志写入本地,生产日志写入syslog}//user.module.ts@Module({providers:[UserService,loggerProvider]})exportclassUserModule{}//user.service.ts@Injectable()exportclassUserService{constructor(@Inject(Logger)privatereadonlylogger:Logger){}}使用factory工厂模式相信大家都很熟悉了。工厂模式本质上就是一个函数或者方法,返回我们需要的产品。传统的第三方库以回调或者事件的形式提供连接,比如redis。如果需要使用这种类型的注入对象,工厂模式是最好的方式。下面是使用工厂模式创建数据库连接的例子://database.provider.tsexportconstdatabaseProvider={provide:'DATABASE',useFactory:async(optionsProvider:OptionsProvider){//使用依赖,注入顺序与下面定义的顺序一致returnnewPromise((resolve,reject)=>{constconnection=createConnection(optionsProvider.getDatabaseConfig())connection.on('ready',()=>resolve(connection));连接。on('错误',(e)=>reject(e));});},inject:[OptionsProvider],//注入依赖}//user.module.ts@Module({providers:[OptionsProvider,databaseProvider]})exportclassUserModule{}//user.service.ts@Injectable()exportclassUserService{constructor(@Inject('DATABASE')privatereadonlyconnection:Connection){}}可以基于现有提供者创建别名。constloggerAliasProvider={provide:'AliasedLoggerService',useExisting:Logger,};将服务提供者导出到其他模块的详细知识后面会讲到,但是有一点需要提前知道,只有本模块导出的服务提供者才能被其他模块导入基于类型的导出上面的UserService是基于类型注入的而不是传入名称。@Module({providers:[UserService],exports:[UserService],//important})exportclassUserModule{}基于名称的export上面的DATABASE、SEQUELIZE等服务提供者都是自定义的,指定标识符号。@Module({providers:[sequelizeProvider],exports:['SEQUELIZE'],//其他模块的组件可以直接使用@Inject('SEQUELIZE')})端服务提供者是NestJs的精髓之一,提供有几种方法可以促进在各种环境中创建我们的服务提供者。如果觉得自己有所收获,请分享给更多有需要的朋友,谢谢!如果你想交流更多关于NestJs的知识,欢迎加入群讨论!
