NestJS 之服务 (Services)
Controller 像是 “前台”,负责接待客户(接收请求)、传递需求(调用 Service)、反馈结果(返回响应);Service 像是 “业务部门”,负责处理核心业务(封装逻辑)、跨部门协作(复用逻辑)、支撑公司运转(依赖注入)。理解 Service 的作用,不仅能让你写出更清晰、更可维护的代码,更能帮助你掌握 NestJS 分层架构的设计思想。在实际开发中,记得始终遵循“Controlle
深入浅出 NestJS 之服务 (Services)
在 NestJS 的分层架构中,Service(服务) 作为核心组件之一,承担着至关重要的角色。很多初学者可能会疑惑:Service 到底是用来做什么的?它和 Controller 有什么区别?今天我们就来深入剖析 NestJS 中 Service 的作用,结合实例带你掌握其使用精髓。
先搞懂:为什么需要 Service?
先思考一个问题:如果没有 Service,NestJS 项目会变成什么样?假设我们直接在 Controller(控制器)中编写所有业务逻辑,比如数据查询、计算、第三方接口调用等。这样做会导致两个严重问题:
- Controller 职责过重:Controller 的核心职责本是 “接收请求、返回响应”,若混入大量业务逻辑,会让代码变得臃肿不堪,难以维护。
- 代码无法复用:当多个 Controller 需要用到相同的业务逻辑(比如用户权限校验、数据格式化)时,只能重复编写代码,违背 “DRY(Don't Repeat Yourself)” 原则。
而 Service 的出现,正是为了解决这些问题。它就像一个 “业务逻辑处理器”,专门负责封装复杂业务,让 Controller 回归本职,同时实现代码的复用与解耦。
Service 的核心作用:3 个维度
在 NestJS 中,Service 的作用可以概括为 “封装业务、解耦职责、支持复用”,具体可从以下 3 个维度展开:
1. 封装业务逻辑:让 Controller 轻装上阵
Service 的首要作用是承载所有业务逻辑,包括数据处理、计算、规则校验、第三方服务调用等。Controller 只需要 “调用 Service 的方法”,无需关心业务逻辑的具体实现。
2. 实现代码复用:一处编写,多处调用
Service 的另一个核心作用是复用。当多个 Controller 或其他 Service 需要用到相同的业务逻辑时,只需注入对应的 Service 并调用方法,无需重复编写代码。
3. 支持依赖注入:解耦组件依赖
NestJS 基于 依赖注入(Dependency Injection, DI) 设计,而 Service 是依赖注入的核心载体。通过 @Injectable() 装饰器标记 Service,NestJS 会自动管理 Service 的实例,并在需要时注入到 Controller 或其他 Service 中。 这种机制带来两个好处:
- 解耦依赖:Controller 不需要手动创建 Service 实例,只需声明 “需要哪个 Service”,由 NestJS 负责注入,降低了组件间的耦合度。
- 便于测试:在单元测试中,可以轻松替换 Service 的实现(比如用模拟数据的 Service 替代真实数据库操作的 Service),无需修改 Controller 代码。
依赖注入的底层逻辑:
@Controller('user') export class UserController { constructor(private readonly userService: UserService) {} // 如上的代码类似于如下代码 // private readonly userService: UserService; // constructor(userService: UserService) { // this.userService = userService; // } @Get() findAll(): User[] { return this.userService.findAll(); } }
当我们在 Controller 的构造函数中声明 private userService: UserService 时,NestJS 会:
- 扫描装饰器: 检查 UserService 是否被
@Injectable()标记(确保可注入); - 分析依赖:分析构造函数的参数,确定需要注入的依赖
- 创建单例:创建 UserService 的单例实例(默认是单例,可配置作用域);
- 注入依赖:将实例注入到 UserController 中,供其调用,可以使用 this.userService 调用 Service 的方法。
PS:依赖注入(Dependency Injection,简称 DI)是一种设计模式,它让类不需要自己创建依赖对象,而是由外部(通常是框架)提供
Service 的最佳实践:避免踩坑
需遵循单一职责
一个 Service 只负责一个领域的业务,不要把所有业务逻辑都塞进一个 Service(比如创建一个 CommonService 处理所有通用逻辑)。正确的做法是:按业务领域拆分 Service,比如 UserService(用户相关)、OrderService(订单相关)、AuthService(权限相关),每个 Service 只处理自己领域内的逻辑。
这样做的好处是:代码结构清晰,便于定位问题,也利于团队协作(不同开发者负责不同领域的 Service)。
不要在 Service 中处理 HTTP 相关逻辑
Service 是 “业务逻辑层”,不应该依赖 HTTP 相关的对象(比如 Request、Response),也不应该直接返回 HTTP 状态码或响应格式。这些操作应该由 Controller 负责。
错误(Service 处理 HTTP 逻辑)❌ ❌
// 错误:Service 直接返回 HTTP 响应 @Injectable() export class UserService { async getUserById(id: string, res: Response) { // 依赖 Response 对象 const user = await this.prisma.user.findUnique({ where: { id: Number(id) } }); if (!user) { res.status(404).send('用户不存在'); // 直接操作响应 } res.send(user); } }
正确(Service 处理 HTTP 逻辑) ✅ ✅
// 正确:Service 返回数据或抛出异常 @Injectable() export class UserService { async getUserById(id: string) { const user = await this.userRepo.findUnique({ where: { id: Number(id) } }); if (!user) { throw new NotFoundException('用户不存在'); // 抛出异常 } return user; // 返回数据 } } // Controller 处理 HTTP 响应 @Controller('users') export class UserController { @Get(':id') async getUserById(@Param('id') id: string) { return this.userService.getUserById(id); // NestJS 自动处理响应状态码 } }
合理使用 Service 之间的依赖注入
当一个 Service 需要调用另一个 Service 的逻辑时,可以直接在构造函数中注入对方的 Service(注意避免循环依赖)。
示例:OrderService 依赖 UserService
@Injectable() export class OrderService { // 注入 UserService constructor(private userService: UserService, private prisma: PrismaService) {} async createOrder(userId: string, productId: string) { // 1. 先通过 UserService 校验用户是否存在 await this.userService.getUserById(userId); // 2. 创建订单, orderRepo 通常和 数据打交道 return this.orderRepo.create({ userId: Number(userId), productId: Number(productId), status: 'pending' }); } }
NestJS 中创建 Service
1. nest g service name 创建 service 类
nest g service user // user 是服务的名字
这个命令会:
- 创建 src/user/user.service.ts 文件
- 创建 src/user/user.service.spec.ts 测试文件
- 如果 user.module.ts 存在,会自动在模块中注册服务
2. 编辑 src/user/user.service.ts 文件
import { Injectable } from '@nestjs/common'; @Injectable() // 是能被依赖注入的标识 export class UserService { constructor(private readonly orderService: OrderService,private readonly userRepo: UserRepo) {} async findOne(id) { // 创建用户逻辑 const user = await this.userRepo.find(userData); // 可以调用其他服务的方法 const order = await this.orderService.findOrderByUserId(id); return { ...user, order, }; } }
@Injectable() 装饰器 的作用
- NestJS 的依赖注入系统通过
@Injectable()装饰器标记的类才能被注入 - 元数据生成:装饰器会生成元数据,帮助 NestJS 了解类的依赖关系
3. 编辑 src/user/user.controller.ts 文件
在 src/user/user.controller.ts 注入 服务
import { Injectable } from '@nestjs/common'; @Controller('user') export class UserController { constructor(private readonly userService: UserService) {} @Get(':id') findOne(@Param('id', ParseIntPipe) id: number): User { const user = this.userService.findOne(id); if (!user) { throw new Error(`User with id ${id} not found`); } return user; } }
4. 编辑 src/user/user.module.ts 和 src/app.module.ts 文件
// src/user/user.module.ts @Module({ imports: [], controllers: [UserController], providers: [UserService], exports: [], }) export class UserModule {} // src/app.module.ts @Module({ // imports 用来导入其他模块 imports: [UserModule], // controllers 用来注册控制器,控制器负责处理 HTTP 请求 controllers: [AppController, UserController], // providers 用来注册提供者,通常是服务类,包含业务的逻辑 providers: [AppService, UserService], }) export class AppModule {}
总结:Service 是 NestJS 架构的 “业务核心”
如果把 NestJS 项目比作一家公司:
- Controller 像是 “前台”,负责接待客户(接收请求)、传递需求(调用 Service)、反馈结果(返回响应);
- Service 像是 “业务部门”,负责处理核心业务(封装逻辑)、跨部门协作(复用逻辑)、支撑公司运转(依赖注入)。 理解 Service 的作用,不仅能让你写出更清晰、更可维护的代码,更能帮助你掌握 NestJS 分层架构的设计思想。在实际开发中,记得始终遵循 “Controller 负责请求响应,Service 负责业务逻辑” 的原则,让你的项目结构更优雅、扩展性更强。
更多推荐
所有评论(0)