停止与 NestJS 模块的斗争:认识 Rikta
Source: Dev.to
请提供您希望翻译的完整文本内容,我将按照要求保留源链接、格式和代码块,仅翻译正文部分。
模块问题
NestJS 模块解决了一个真实的问题:依赖边界。它们可以防止大型代码库陷入混乱,但也会带来代价。
每新增一个服务,你需要:
- 使用
@Injectable()创建服务。 - 将它加入模块的
providers数组。 - 如果其他模块需要,加入
exports数组。 - 在所有使用它的地方导入该模块。
一个典型的 NestJS 模块如下所示:
// user.module.ts
@Module({
imports: [
DatabaseModule,
ConfigModule,
AuthModule,
LoggingModule,
],
controllers: [UserController],
providers: [
UserService,
UserRepository,
UserValidator,
UserMapper,
],
exports: [UserService, UserRepository],
})
export class UserModule {}
这会产生大量的样板代码——纯粹的配置开销会迅速增长。
一个中等规模的应用可能拥有 20+ 个模块,每个模块包含 5‑10 个 provider。你最终花在管理数组上的时间会超过编写业务逻辑的时间。
最糟糕的地方? 任意数组中的一个拼写错误就会破坏整个依赖图,调试可能需要数小时。
如果不需要模块会怎样?
Rikta 是一个基于 Fastify 的全新 TypeScript 框架。它保留了开发者喜爱的 NestJS 特性(装饰器、依赖注入、结构),并且完全去除了模块配置。
- 没有
imports数组。 - 没有
exports数组。 - 没有
providers数组。
只需给类加上装饰器,一切即可工作。
// user.service.ts
import { Injectable } from '@riktajs/core';
@Injectable()
export class UserService {
getUsers() {
return ['Alice', 'Bob'];
}
}
// user.controller.ts
import { Controller, Get, Autowired } from '@riktajs/core';
import { UserService } from './user.service';
@Controller('/users')
export class UserController {
@Autowired()
private userService!: UserService;
@Get()
getUsers() {
return this.userService.getUsers();
}
}
就是这样——没有模块文件,也无需注册。Rikta 在启动时会扫描你的代码并自动解析依赖。
零配置自动装配工作原理
Rikta 使用三种机制:
- 自动发现 – 启动时它会扫描项目中使用
@Controller()或@Injectable()装饰的类。它根据构造函数参数和@Autowired()装饰器构建依赖图。 - 全局提供者注册表 – 所有提供者都存放在同一个注册表中。任何可注入的类在应用的任何位置都可用;无需显式导出或导入。
- 依赖解析 – Rikta 读取 TypeScript 元数据(通过
reflect-metadata)来了解每个类的需求,按正确顺序创建实例,并自动注入它们。
在初始化期间会检测循环依赖,并给出明确的错误信息:
Error: Circular dependency detected:
UserService -> AuthService -> UserService
没有晦涩的堆栈跟踪,也没有运行时的意外。
并排比较
在 NestJS 中创建新功能
// 1. Create the service
@Injectable()
export class PaymentService {
constructor(private configService: ConfigService) {}
}
// 2. Create the module
@Module({
imports: [ConfigModule],
providers: [PaymentService],
exports: [PaymentService],
})
export class PaymentModule {}
// 3. Import in app.module.ts
@Module({
imports: [PaymentModule, /* ... */],
})
export class AppModule {}
// 4. Import in any module that needs it
@Module({
imports: [PaymentModule],
providers: [OrderService],
})
export class OrderModule {}
在 Rikta 中创建相同功能
@Injectable()
export class PaymentService {
@Autowired()
private configService!: ConfigService;
}
完成。
性能优势
Rikta 使用 Fastify 作为其 HTTP 层。Fastify 在基准测试中每秒处理高达 30,000 请求。
官方基准结果:
| 指标 | Rikta 对比 NestJS |
|---|---|
| 启动时间 | 快 43 % |
| GET 请求 | 快 41 % |
| POST 请求 | 快 25 % |
| 路由参数 | 快 46 % |
Rikta 只在原生 Fastify 上增加 2‑5 % 的开销,为您提供依赖注入和装饰器,同时不牺牲速度。
内置 Zod 验证
NestJS 依赖 class-validator 装饰器,这意味着你需要定义两次类型:一次用于 TypeScript,另一次用于验证。
Rikta 原生集成 Zod。只需定义一次模式,即可自动获得验证功能和 TypeScript 类型。
import { Controller, Post, Body, z } from '@riktajs/core';
const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(2),
age: z.number().optional(),
});
@Controller('/users')
export class UserController {
@Post()
create(@Body(CreateUserSchema) user: z.infer<typeof CreateUserSchema>) {
// 'user' is validated AND typed
// Invalid requests return 400 automatically
return { created: user };
}
}
没有重复的类型定义,也不需要手动处理验证失败的错误。
何时使用 Rikta
Rikta 最适合:
- 初创公司和 MVP,在开发速度至关重要的情况下。
- 中小型团队(1‑15 名开发者),希望拥有干净、零配置的 DI 系统。
如果你喜欢 NestJS 的易用性,但厌倦了模块样板代码,试试 Rikta。
- 微服务——每个服务保持专注。
- 熟悉 NestJS 的开发者,希望减少样板代码。
考虑使用 NestJS 的情况:
- 如果你的团队规模很大,需要严格的模块边界。
- 你需要庞大的 NestJS 生态系统(特定适配器、插件)。
- 你的组织要求明确的依赖文档。
入门指南
几秒钟内创建一个新项目:
npx @riktajs/cli new my-app
cd my-app
npm run dev
你的 API 运行在 http://localhost:3000。
CLI 会生成一个完整的项目,包含:
- 针对 Rikta 优化的 TypeScript 配置
- 带有 REST 端点的示例控制器
- 带有依赖注入的示例服务
- 热重载开发服务器
资源
- Documentation: (link)
- GitHub: (link)
- NPM: (link)
Rikta 使用 MIT 许可证,且为开源项目。
你尝试过 zero‑config 框架吗?对于模块化与自动装配的取舍,你有什么看法?请在评论中分享你的经验。