1. 理解NestJS提供者的核心概念第一次接触NestJS的提供者(Providers)时我一度以为它就是个简单的依赖注入容器。直到在实际项目中踩了几个坑才发现这玩意儿远比想象中强大。简单来说提供者就是NestJS用来封装和共享业务逻辑的基本单元可以是服务(Service)、仓库(Repository)、工厂(Factory)或者值(Value)等。举个例子假设我们正在开发一个电商系统的用户订单模块。最基本的提供者可能就是OrderServiceInjectable() export class OrderService { private readonly orders: Order[] []; create(order: Order) { this.orders.push(order); return order; } findAll() { return this.orders; } }这个Injectable()装饰器就是告诉Nest嘿我这个类是可以被注入的。然后在控制器里就可以直接使用Controller(orders) export class OrderController { constructor(private readonly orderService: OrderService) {} // ... }NestJS会自动处理依赖关系这就是依赖注入(DI)的魅力。但提供者远不止这么简单它实际上有七种不同的类型每种都有特定的使用场景。2. 基础注入从Service到Repository在实际项目中我们很少会把所有逻辑都塞在一个Service里。更常见的做法是分层设计比如把数据访问逻辑抽到Repository层。让我们扩展下电商系统的例子Injectable() export class OrderRepository { private readonly orders: Order[] []; save(order: Order) { this.orders.push(order); return order; } findAll() { return this.orders; } } Injectable() export class OrderService { constructor(private readonly orderRepository: OrderRepository) {} createOrder(orderDto: CreateOrderDto) { const order this.mapToOrder(orderDto); return this.orderRepository.save(order); } private mapToOrder(dto: CreateOrderDto): Order { // 映射逻辑 } }这里有个关键点要注意我们不需要手动实例化OrderRepositoryNestJS的IoC容器会自动处理。只需要在模块中注册这两个提供者Module({ providers: [OrderRepository, OrderService], controllers: [OrderController] }) export class OrderModule {}这种分层设计不仅使代码更清晰也更容易测试。我曾在项目中遇到过测试困难的问题后来发现就是因为没有合理分层导致Service里混杂了太多职责。3. 自定义令牌与值提供者有时候我们不想直接用类作为令牌(token)或者需要提供简单的值而不是类实例。这时候就可以使用自定义令牌和值提供者。假设我们的订单服务需要支持多语言错误消息可以这样配置const ERROR_MESSAGES { ORDER_NOT_FOUND: Order not found, INVALID_STATUS: Invalid order status }; Module({ providers: [ { provide: ERROR_MESSAGES, useValue: ERROR_MESSAGES }, OrderService ] }) export class OrderModule {}然后在Service中通过Inject装饰器使用Injectable() export class OrderService { constructor( Inject(ERROR_MESSAGES) private readonly errorMessages, private readonly orderRepository: OrderRepository ) {} getOrder(id: string) { const order this.orderRepository.findById(id); if (!order) { throw new Error(this.errorMessages.ORDER_NOT_FOUND); } return order; } }这种模式特别适合配置项、常量或者第三方库的实例。我在一个国际化项目中就用它来管理多语言资源比直接硬编码在代码里灵活多了。4. 动态工厂模式实战当我们需要根据运行时的条件动态创建提供者时工厂模式就派上用场了。比如电商系统中我们可能要根据用户所在地区选择不同的运费计算策略interface ShippingCalculator { calculate: (order: Order) number; } class DomesticShippingCalculator implements ShippingCalculator { calculate(order: Order) { // 国内运费计算逻辑 } } class InternationalShippingCalculator implements ShippingCalculator { calculate(order: Order) { // 国际运费计算逻辑 } } Injectable() export class ShippingCalculatorFactory { create(userCountry: string): ShippingCalculator { return userCountry CN ? new DomesticShippingCalculator() : new InternationalShippingCalculator(); } }然后在模块中注册Module({ providers: [ { provide: SHIPPING_CALCULATOR, useFactory: (factory: ShippingCalculatorFactory, req: Request) { const country req.headers[x-user-country] || CN; return factory.create(country); }, inject: [ShippingCalculatorFactory, REQUEST] }, ShippingCalculatorFactory ] }) export class OrderModule {}这里有几个关键点使用了useFactory来定义工厂函数通过inject指定工厂函数的依赖项注入了REQUEST对象来获取请求头信息这种模式在需要根据运行时条件创建对象的场景特别有用。我在处理多租户系统时就用类似的方式实现了租户特定的服务实例化。5. 异步提供者与初始化有些情况下我们需要等待异步操作完成才能使用提供者。比如数据库连接、配置文件加载等。NestJS支持异步工厂模式Module({ providers: [ { provide: CONFIG, useFactory: async () { const config await loadConfigFromFile(config.json); return config; } }, { provide: DATABASE_CONNECTION, useFactory: async (config) { const connection await createConnection(config.database); return connection; }, inject: [CONFIG] } ] }) export class AppModule {}在这个例子中DATABASE_CONNECTION提供者会等待CONFIG提供者初始化完成后才会创建。NestJS会确保所有异步提供者都准备就绪后才开始处理请求。我在实际项目中使用这个特性来确保服务启动前完成所有必要的初始化工作比如加载敏感配置(需要解密)建立数据库连接池预加载缓存数据验证第三方服务可用性6. 提供者的生命周期与作用域默认情况下NestJS中的提供者是单例的但有时候我们需要更细粒度的控制。NestJS支持三种作用域DEFAULT单例整个应用生命周期内只有一个实例REQUEST每个请求都会创建一个新实例TRANSIENT每次注入都会创建一个新实例修改作用域很简单Injectable({ scope: Scope.REQUEST }) export class OrderService { constructor(Inject(REQUEST) private readonly req: Request) {} getCurrentUser() { return this.req.user; } }需要注意的是REQUEST作用域的提供者不能注入到DEFAULT作用域的提供者中否则会导致作用域不匹配。我在处理用户会话时就遇到过这个问题后来通过提取用户信息到中间件解决了。7. 高级技巧动态模块与提供者当我们需要根据配置动态注册提供者时可以结合动态模块来实现。比如电商系统可能需要支持多种支付网关Module({}) export class PaymentModule { static register(options: PaymentOptions): DynamicModule { return { module: PaymentModule, providers: [ { provide: PAYMENT_OPTIONS, useValue: options }, { provide: PAYMENT_GATEWAY, useFactory: (options: PaymentOptions) { return options.type alipay ? new AlipayGateway(options) : new WechatPayGateway(options); }, inject: [PAYMENT_OPTIONS] } ], exports: [PAYMENT_GATEWAY] }; } }使用时Module({ imports: [PaymentModule.register({ type: alipay, appId: ... })] }) export class AppModule {}这种模式在开发可复用模块时特别有用。我在开发一个多平台通知模块时就采用了类似的设计可以根据配置动态选择邮件、短信或Webhook通知方式。8. 实战电商订单处理系统让我们把这些概念整合到一个完整的电商订单处理场景中。假设我们需要处理以下需求下单时需要验证库存根据用户等级应用不同折扣记录操作日志发送订单确认通知首先定义核心服务Injectable() export class InventoryService { async checkStock(productId: string, quantity: number) { // 检查库存逻辑 } } Injectable() export class DiscountService { async getDiscount(userId: string) { // 获取用户折扣 } } Injectable({ scope: Scope.REQUEST }) export class OrderLogger { constructor(Inject(REQUEST) private readonly req: Request) {} log(action: string, data: any) { // 记录带用户信息的日志 } } Injectable() export class NotificationService { async send(userId: string, message: string) { // 发送通知 } }然后实现订单服务Injectable() export class OrderService { constructor( private readonly inventoryService: InventoryService, private readonly discountService: DiscountService, private readonly orderLogger: OrderLogger, private readonly notificationService: NotificationService, private readonly orderRepository: OrderRepository ) {} async placeOrder(userId: string, items: OrderItem[]) { // 验证库存 await Promise.all( items.map(item this.inventoryService.checkStock(item.productId, item.quantity) ) ); // 计算折扣 const discount await this.discountService.getDiscount(userId); // 创建订单 const order await this.orderRepository.create({ userId, items, discount }); // 记录日志 this.orderLogger.log(ORDER_CREATED, { orderId: order.id }); // 发送通知 await this.notificationService.send( userId, Your order #${order.id} has been placed ); return order; } }最后在模块中注册所有提供者Module({ imports: [ConfigModule.forFeature(orderConfig)], providers: [ InventoryService, DiscountService, OrderLogger, NotificationService, OrderRepository, OrderService, { provide: ORDER_CONFIG, useFactory: (config: ConfigService) config.get(order), inject: [ConfigService] } ], controllers: [OrderController] }) export class OrderModule {}这个例子展示了如何合理组合不同类型的提供者来构建复杂的业务逻辑。我在实际项目中采用这种架构后代码的可维护性和可测试性都得到了显著提升。
小满nestjs(第十章 提供者:从基础注入到动态工厂的实战指南)
1. 理解NestJS提供者的核心概念第一次接触NestJS的提供者(Providers)时我一度以为它就是个简单的依赖注入容器。直到在实际项目中踩了几个坑才发现这玩意儿远比想象中强大。简单来说提供者就是NestJS用来封装和共享业务逻辑的基本单元可以是服务(Service)、仓库(Repository)、工厂(Factory)或者值(Value)等。举个例子假设我们正在开发一个电商系统的用户订单模块。最基本的提供者可能就是OrderServiceInjectable() export class OrderService { private readonly orders: Order[] []; create(order: Order) { this.orders.push(order); return order; } findAll() { return this.orders; } }这个Injectable()装饰器就是告诉Nest嘿我这个类是可以被注入的。然后在控制器里就可以直接使用Controller(orders) export class OrderController { constructor(private readonly orderService: OrderService) {} // ... }NestJS会自动处理依赖关系这就是依赖注入(DI)的魅力。但提供者远不止这么简单它实际上有七种不同的类型每种都有特定的使用场景。2. 基础注入从Service到Repository在实际项目中我们很少会把所有逻辑都塞在一个Service里。更常见的做法是分层设计比如把数据访问逻辑抽到Repository层。让我们扩展下电商系统的例子Injectable() export class OrderRepository { private readonly orders: Order[] []; save(order: Order) { this.orders.push(order); return order; } findAll() { return this.orders; } } Injectable() export class OrderService { constructor(private readonly orderRepository: OrderRepository) {} createOrder(orderDto: CreateOrderDto) { const order this.mapToOrder(orderDto); return this.orderRepository.save(order); } private mapToOrder(dto: CreateOrderDto): Order { // 映射逻辑 } }这里有个关键点要注意我们不需要手动实例化OrderRepositoryNestJS的IoC容器会自动处理。只需要在模块中注册这两个提供者Module({ providers: [OrderRepository, OrderService], controllers: [OrderController] }) export class OrderModule {}这种分层设计不仅使代码更清晰也更容易测试。我曾在项目中遇到过测试困难的问题后来发现就是因为没有合理分层导致Service里混杂了太多职责。3. 自定义令牌与值提供者有时候我们不想直接用类作为令牌(token)或者需要提供简单的值而不是类实例。这时候就可以使用自定义令牌和值提供者。假设我们的订单服务需要支持多语言错误消息可以这样配置const ERROR_MESSAGES { ORDER_NOT_FOUND: Order not found, INVALID_STATUS: Invalid order status }; Module({ providers: [ { provide: ERROR_MESSAGES, useValue: ERROR_MESSAGES }, OrderService ] }) export class OrderModule {}然后在Service中通过Inject装饰器使用Injectable() export class OrderService { constructor( Inject(ERROR_MESSAGES) private readonly errorMessages, private readonly orderRepository: OrderRepository ) {} getOrder(id: string) { const order this.orderRepository.findById(id); if (!order) { throw new Error(this.errorMessages.ORDER_NOT_FOUND); } return order; } }这种模式特别适合配置项、常量或者第三方库的实例。我在一个国际化项目中就用它来管理多语言资源比直接硬编码在代码里灵活多了。4. 动态工厂模式实战当我们需要根据运行时的条件动态创建提供者时工厂模式就派上用场了。比如电商系统中我们可能要根据用户所在地区选择不同的运费计算策略interface ShippingCalculator { calculate: (order: Order) number; } class DomesticShippingCalculator implements ShippingCalculator { calculate(order: Order) { // 国内运费计算逻辑 } } class InternationalShippingCalculator implements ShippingCalculator { calculate(order: Order) { // 国际运费计算逻辑 } } Injectable() export class ShippingCalculatorFactory { create(userCountry: string): ShippingCalculator { return userCountry CN ? new DomesticShippingCalculator() : new InternationalShippingCalculator(); } }然后在模块中注册Module({ providers: [ { provide: SHIPPING_CALCULATOR, useFactory: (factory: ShippingCalculatorFactory, req: Request) { const country req.headers[x-user-country] || CN; return factory.create(country); }, inject: [ShippingCalculatorFactory, REQUEST] }, ShippingCalculatorFactory ] }) export class OrderModule {}这里有几个关键点使用了useFactory来定义工厂函数通过inject指定工厂函数的依赖项注入了REQUEST对象来获取请求头信息这种模式在需要根据运行时条件创建对象的场景特别有用。我在处理多租户系统时就用类似的方式实现了租户特定的服务实例化。5. 异步提供者与初始化有些情况下我们需要等待异步操作完成才能使用提供者。比如数据库连接、配置文件加载等。NestJS支持异步工厂模式Module({ providers: [ { provide: CONFIG, useFactory: async () { const config await loadConfigFromFile(config.json); return config; } }, { provide: DATABASE_CONNECTION, useFactory: async (config) { const connection await createConnection(config.database); return connection; }, inject: [CONFIG] } ] }) export class AppModule {}在这个例子中DATABASE_CONNECTION提供者会等待CONFIG提供者初始化完成后才会创建。NestJS会确保所有异步提供者都准备就绪后才开始处理请求。我在实际项目中使用这个特性来确保服务启动前完成所有必要的初始化工作比如加载敏感配置(需要解密)建立数据库连接池预加载缓存数据验证第三方服务可用性6. 提供者的生命周期与作用域默认情况下NestJS中的提供者是单例的但有时候我们需要更细粒度的控制。NestJS支持三种作用域DEFAULT单例整个应用生命周期内只有一个实例REQUEST每个请求都会创建一个新实例TRANSIENT每次注入都会创建一个新实例修改作用域很简单Injectable({ scope: Scope.REQUEST }) export class OrderService { constructor(Inject(REQUEST) private readonly req: Request) {} getCurrentUser() { return this.req.user; } }需要注意的是REQUEST作用域的提供者不能注入到DEFAULT作用域的提供者中否则会导致作用域不匹配。我在处理用户会话时就遇到过这个问题后来通过提取用户信息到中间件解决了。7. 高级技巧动态模块与提供者当我们需要根据配置动态注册提供者时可以结合动态模块来实现。比如电商系统可能需要支持多种支付网关Module({}) export class PaymentModule { static register(options: PaymentOptions): DynamicModule { return { module: PaymentModule, providers: [ { provide: PAYMENT_OPTIONS, useValue: options }, { provide: PAYMENT_GATEWAY, useFactory: (options: PaymentOptions) { return options.type alipay ? new AlipayGateway(options) : new WechatPayGateway(options); }, inject: [PAYMENT_OPTIONS] } ], exports: [PAYMENT_GATEWAY] }; } }使用时Module({ imports: [PaymentModule.register({ type: alipay, appId: ... })] }) export class AppModule {}这种模式在开发可复用模块时特别有用。我在开发一个多平台通知模块时就采用了类似的设计可以根据配置动态选择邮件、短信或Webhook通知方式。8. 实战电商订单处理系统让我们把这些概念整合到一个完整的电商订单处理场景中。假设我们需要处理以下需求下单时需要验证库存根据用户等级应用不同折扣记录操作日志发送订单确认通知首先定义核心服务Injectable() export class InventoryService { async checkStock(productId: string, quantity: number) { // 检查库存逻辑 } } Injectable() export class DiscountService { async getDiscount(userId: string) { // 获取用户折扣 } } Injectable({ scope: Scope.REQUEST }) export class OrderLogger { constructor(Inject(REQUEST) private readonly req: Request) {} log(action: string, data: any) { // 记录带用户信息的日志 } } Injectable() export class NotificationService { async send(userId: string, message: string) { // 发送通知 } }然后实现订单服务Injectable() export class OrderService { constructor( private readonly inventoryService: InventoryService, private readonly discountService: DiscountService, private readonly orderLogger: OrderLogger, private readonly notificationService: NotificationService, private readonly orderRepository: OrderRepository ) {} async placeOrder(userId: string, items: OrderItem[]) { // 验证库存 await Promise.all( items.map(item this.inventoryService.checkStock(item.productId, item.quantity) ) ); // 计算折扣 const discount await this.discountService.getDiscount(userId); // 创建订单 const order await this.orderRepository.create({ userId, items, discount }); // 记录日志 this.orderLogger.log(ORDER_CREATED, { orderId: order.id }); // 发送通知 await this.notificationService.send( userId, Your order #${order.id} has been placed ); return order; } }最后在模块中注册所有提供者Module({ imports: [ConfigModule.forFeature(orderConfig)], providers: [ InventoryService, DiscountService, OrderLogger, NotificationService, OrderRepository, OrderService, { provide: ORDER_CONFIG, useFactory: (config: ConfigService) config.get(order), inject: [ConfigService] } ], controllers: [OrderController] }) export class OrderModule {}这个例子展示了如何合理组合不同类型的提供者来构建复杂的业务逻辑。我在实际项目中采用这种架构后代码的可维护性和可测试性都得到了显著提升。