nestjs 中有很多对象
- Controller 对象,接收 http 请求,调用 service,返回响应
- Service 对象,实现业务逻辑
- Repository 对象,实现对数据库的增删改查
- 数据库连接对象 DataSource
- 配置对象 Config
对象间关系复杂
- Controller 依赖 Service 实现业务逻辑
- Service 依赖 Repository 来做增删改查
- Repository 依赖 DataSource 来建立连接
- DataSource 依赖 Config 获取用户名和密码等信息
导致创建这些对象很复杂
- 问题1:需要理清它们之间的关系,哪个先创建,哪个后创建:
1
2
3
4
5
6
7
8
9
10
// @ts-nocheck
const config = new Config({ username: "foo", password: "123456" });
const dataSource = new DataSource(config);
const repository = new Repository(dataSource);
const service = new Service(repository);
const controller = new Controller(service);
- 问题2: 要经过一系列初始化后,才能使用 Controller 对象
- 问题3: config,dataSource, repository, service, controller 等这些对象不需要每次都new一个新的,一直用一个就行,也就是保持单例。
解决方式 - 反转控制 IoC(Inverse of Control)
之前手动创建和组装对象很麻烦,改成在 class 上声明依赖了什么,然后,让工具分析声明的依赖关系, 根据先后顺序自动把对象创建好并组装起来。

IoC 有一个放对象的容器,程序初始化的时候,会扫描 class 上声明的依赖关系, 然后,把这些 class 都 new 一个实例放到容器里。 创建对象的时候,还会把它们依赖的对象注入进去。
这种依赖注入的方式叫做 DI(Dependency Injection)。
为什么叫 IoC?
- 以前是手动 new 依赖对象,然后,组装起来。
- 现在是声明依赖了什么,等待注入。
- 从主动创建依赖到被动等待依赖注入,这就是反转控制 IoC
AppService 声明了 @Injectable,代表这个class 可注入,nest 会把它的对象放到 IoC 容器里

AppController 声明了 @Controller,代表这个 class 可以被注入,nestjs 会把它放到 IoC 容器里

AppController 构造器参数依赖了 AppService
为什么 Controller 是单独的装饰器呢?
- 因为 Service 是可以被注入的,也可以注入到别的对象,所以用 @Injectable 声明。
- 而 Controller 只需要被注入,所以,nest 大度给它加了 @Controller 的装饰器。
通过 @Module 声明模块,其中 controllers 是控制器,只能被注入。providers 中的可以被注入,也可以注入别的对象。

然后,在入口模块里跑起来:

那么 nest 就会从 AppModule 开始解析 class 上通过装饰器声明的依赖信息,自动创建和组装对象。

nest 模块机制
把不同业务的 controller,service等放到不同的模块里。
nest g module other 生成一个模块,会在 AppModule 里自动 imports 这个模块,当 import 别的模块后,那个模块 exports
的 provider就可以在当前模块注入了。



1
2
3
4
5
6
7
8
9
10
11
12
13
// @ts-nocheck
import { OtherService } from "./other/other.service";
import { Inject, Injectable } from "@nestjs/common";
@Injectable()
export class AppService {
@Inject(OtherService)
private otherService: OtherService;
getHello(): string {
return "Hello World!" + this.otherService.xxx();
}
}
题外话
Strictly speaking,Ruby on Rails 框架没有内置类似 NestJS 或 Spring 那种 IoC 容器(Inversion of Control Container)或严格的依赖注入(DI)机制。
在 NestJS(基于 TypeScript/Node)或 Java Spring 中,IoC 容器是核心,你需要通过 @Injectable()、constructor 注入等方式将类交给框架管理。但在 Rails 中,情况截然不同。
以下是关于 Rails 与 IoC/DI 的几个核心结论:
1. 为什么 Rails 没有内置 IoC 容器?
- 动态语言的特性(Duck Typing):Ruby 是高度动态的语言,没有“接口(Interface)”的概念。在 Java 或 TS 中,依赖注入很大程度上是为了基于接口解耦,方便测试时替换 Mock 对象;而在 Ruby 中,即使你不做依赖注入,也可以在测试时利用动态修改内存库(比如
rspec-mocks)极其容易地把任何类或方法的行为替换掉。 - Rails 的设计哲学理念:Rails 的创造者 DHH 曾明确发文表示过 “Dependency injection is not a virtue in Ruby”(依赖注入在 Ruby 中并非美德)。Rails 推崇“约定优于配置(Convention over Configuration)”,主张直接调用,认为 DI 引入的样板代码在 Ruby 中弊大于利。
- Active Record 模式:Rails 深度绑定 Active Record,习惯在 Controller 或 Service 中直接调用全局可见的类,例如直接写
User.find(1)或者OrderService.new.call,而不是注入一个UserRepository或OrderService实例。
2. Rails 中怎么处理依赖关系?
虽然没有内置的高级 IoC 容器,但开发者依然可以使用最朴素的方式来解耦(广义上的依赖注入):
- 构造器注入(PORO):使用普通的 Ruby 对象(Plain Old Ruby Objects),在
initialize方法中传入依赖。1 2 3 4 5
class StripePaymentGateway def initialize(client = Stripe::Client.new) # 默认依赖,也可被外部替换 @client = client end end
3. 如果硬要在 Ruby/Rails 里用 IoC 容器怎么办?
如果项目非常庞大,且深受领域驱动设计(DDD)或 Clean Architecture 影响,开发者会引入第三方 Gem(第三方库) 来实现类似 NestJS 的 IoC 体验。目前业界最流行的是 dry-rb 生态:
dry-system或dry-auto_inject:提供了类似于 IoC 容器的注册表和自动依赖注入功能。