别再只会用@Injectable了!NestJS Providers的四种高级玩法(含useFactory异步工厂实战)
别再只会用Injectable了NestJS Providers的四种高级玩法含useFactory异步工厂实战在NestJS开发中Injectable()可能是你最熟悉的装饰器但Providers的能力远不止于此。当你需要处理动态配置、异步初始化或复杂依赖关系时基础用法往往捉襟见肘。本文将带你突破常规探索四种高阶Providers用法特别是如何用useFactory解决异步服务初始化的难题。1. 动态配置useValue的实战技巧useValue常被简单理解为静态值注入但在实际项目中它能实现更灵活的配置管理。假设我们需要根据环境变量动态配置数据库连接// config/database.config.ts export const DatabaseConfig { host: process.env.DB_HOST || localhost, port: parseInt(process.env.DB_PORT) || 5432, username: process.env.DB_USER, password: process.env.DB_PASS }; // database.module.ts Module({ providers: [ { provide: DATABASE_CONFIG, useValue: DatabaseConfig }, DatabaseService ] }) export class DatabaseModule {}进阶技巧通过useValue注入函数可以实现动态计算{ provide: DYNAMIC_CONFIG, useValue: (key: string) process.env[key] || config.get(key) }注意当注入的值需要复杂计算时应考虑改用useFactory2. 自定义Token突破类名限制的依赖注入默认情况下NestJS使用类名作为注入标识符但在以下场景需要自定义Token接口实现多态多个服务实现同一接口避免命名冲突第三方库与自有服务同名动态依赖解析运行时决定具体实现// 定义抽象接口 export abstract class CacheService { abstract get(key: string): Promisestring; } // Redis实现 Injectable() export class RedisCacheService implements CacheService { // ...实现细节 } // 模块配置 Module({ providers: [ { provide: CACHE_SERVICE, useClass: RedisCacheService } ] }) export class CacheModule {} // 使用时通过Inject注入 Injectable() export class UserService { constructor( Inject(CACHE_SERVICE) private readonly cache: CacheService ) {} }典型应用场景不同环境使用不同的存储实现开发用Mock生产用RedisA/B测试时动态切换服务版本插件系统的基础设施抽象3. 工厂模式useFactory处理复杂依赖当服务初始化需要依赖其他服务或复杂逻辑时useFactory是最佳选择。以下是一个电商系统中价格计算服务的案例Module({ providers: [ DiscountService, TaxCalculatorService, { provide: PRICE_ENGINE, inject: [DiscountService, TaxCalculatorService], useFactory: ( discount: DiscountService, tax: TaxCalculatorService ) { return new PriceEngine(discount, tax, { currency: USD, rounding: 2 }); } } ] }) export class PricingModule {}工厂模式的优势延迟创建只在需要时实例化对象依赖解耦工厂函数处理所有依赖关系灵活配置可基于运行时条件返回不同实现4. 异步工厂useFactory处理异步初始化现代应用常需要异步加载配置或初始化连接这正是异步工厂的用武之地。下面演示如何异步初始化一个需要从远程加载配置的邮件服务Module({ providers: [ ConfigService, { provide: MAIL_SERVICE, inject: [ConfigService], useFactory: async (config: ConfigService) { const mailConfig await config.load(mail); const transporter nodemailer.createTransport({ host: mailConfig.host, port: mailConfig.port, auth: { user: mailConfig.user, pass: mailConfig.pass } }); await transporter.verify(); return transporter; } } ], exports: [MAIL_SERVICE] }) export class MailModule {}关键实践要点错误处理工厂函数应该妥善处理异步错误健康检查关键服务应验证连接状态超时控制设置合理的初始化超时时间// 增强版的错误处理 useFactory: async (config: ConfigService) { try { const mailConfig await Promise.race([ config.load(mail), new Promise((_, reject) setTimeout(() reject(new Error(Config load timeout)), 5000) ) ]); // ...其余初始化逻辑 } catch (err) { logger.error(Mail service init failed, err); throw new ServiceUnavailableException(Mail service initialization failed); } }5. 综合实战数据库连接池的动态配置结合上述所有技术我们实现一个完整的数据库连接池管理方案Module({ providers: [ { provide: DB_CONFIG_LOADER, useFactory: async () { // 模拟从远程配置中心加载 return new PromiseDbConfig((resolve) { setTimeout(() resolve({ host: cluster.prod.db, poolSize: 20, timeout: 3000 }), 1000); }); } }, { provide: DATABASE_POOL, inject: [DB_CONFIG_LOADER, MonitoringService], useFactory: async ( config: DbConfig, monitor: MonitoringService ) { const pool new Pool(config); pool.on(error, (err) { monitor.recordDatabaseError(err); }); await pool.connect(); // 测试连接 return pool; } } ] }) export class DatabaseModule {}性能优化技巧使用APP_INITIALIZER提前初始化关键服务实现健康检查接口监控服务状态考虑连接池的预热策略// 应用启动时初始化 { provide: APP_INITIALIZER, useFactory: (pool) () pool.warmup(), inject: [DATABASE_POOL], multi: true }在大型NestJS应用中合理组合这四种Providers用法可以优雅解决以下工程难题配置信息的集中管理服务依赖的动态解析异步资源的初始化顺序控制实现可测试的松耦合架构掌握这些高阶技巧后你会发现NestJS的依赖注入系统远比表面看到的强大。最近在一个微服务项目中我们通过自定义Token工厂模式成功实现了在不修改核心业务代码的情况下动态切换消息队列实现从RabbitMQ迁移到Kafka只花了不到半天时间。