购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

3.3 控制器与服务的默契配合

在了解模块的基础知识之后,本节将深入学习模块中的核心部分:控制器和服务。首先介绍它们各自在模块中承担的职责。通过请求流程解释控制器如何管理应用路由和处理请求参数,以及服务是如何进行数据处理的。此外,还将介绍服务与服务提供者之间的区别,以帮助读者理清思路,从而更好地理解依赖注入的核心概念。

3.3.1 基本概念

Nest提供了分层结构,其中Controller和Service分别扮演着不同但紧密相关的角色。它们各自承担的任务也不相同,你必须清楚地了解它们的职责,才能确保你的应用既清晰又可维护。

1.Controller的职责

(1)处理请求和路由:接收HTTP请求并确定路由,一个控制器中通常存在多个路由,不同的路由执行不同的操作。

(2)解析和验证输入数据:控制器通常负责解析并验证请求的数据。例如,通过使用管道或数据传输对象(Data Transfer Object,DTO),控制器可以定义和实施数据验证、格式化或转换操作。

(3)调用Service:Controller层用于调度Service层执行业务逻辑处理,是Service层的入口。

2.Service的职责

(1)处理业务逻辑:包含对数据的处理、外部系统的调用和复杂业务逻辑处理。

(2)数据持久化:与数据库、Redis缓存等存储库进行交互,执行持久化操作。通常在Service中使用模型或实体来进行数据的增删改查操作。

(3)由此可见,Controller负责与HTTP交互相关事项,如路由管理和参数处理等,而Service则负责业务数据处理。

下面我们通过实际案例来演示说明。

3.3.2 Controller管理请求路由

创建一个新的Nest项目,运行“nest n nest-controller-service -p pnpm”命令,结果如图3-12所示。

图3-12 创建项目

接着通过CLI快速创建User模块的CRUD控制器,执行“nest g resource User”命令,选择REST API和CRUD入口,如图3-13所示。

图3-13 生成User模块

创建完成后,打开user.controller.ts文件,核心代码如下:

在Nest应用程序中,User控制器会自动生成对应的路由API,并在Controller()装饰器中指定一个路径前缀,例如user。这样做的好处是可以实现路由的分组,同时最大限度地减少代码重复。在大型应用系统中,这种路由分组方式使得开发者能够通过路径快速识别路由所属的控制器。例如,通过/orders/*路径可以清楚地知道这些路由是由OrderController管理的,而/products/*路由则属于ProductController。

Create方法通过@Post()装饰器声明了请求方式,告诉HTTP客户端以/user路径和POST方法进行请求访问。如果使用/user路径和GET方法请求Nest应用,则会映射到findAll这个路由处理程序中。

如果一个控制器中存在多个POST请求或GET请求,可以通过向装饰器传递参数来区分它们。例如,设置@Post("create-user")会将请求路径组合为/user/create-user,并将该路径映射到create方法。示例代码如下:

另外,Nest支持所有标准的HTTP方法,如@Get()、@Post()、@Put()、@Delete()、@Patch()、@Options()和@Head()等,读者可以根据需要自由组合路径。

3.3.3 Controller处理请求参数与请求体

在Express中,通常需要通过Request、Response请求对象来获取header、query、body等属性。而Nest基于Express作为底层的HTTP框架,自然也提供了类似的对象,如@Req、@Res,但一般情况下不需要那么麻烦。Nest提供了开箱即用的装饰器@Headers、@Body、@Param、@Query等,示例代码如下:

     @Get(':id')
     findOne(@Param('id') id: string) {

       return this.userService.findOne(+id);
     } 

     @Patch(':id')
     update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
       return this.userService.update(+id, updateUserDto);
     } 

     @Delete(':id')
     remove(@Param('id') id: string) {
       return this.userService.remove(+id);
     }

首先,我们来探讨路由中携带参数的情况。使用@Get(':id')装饰器可以构建出像/user/123这样的请求URL,而@Param('id')用于从URL中提取参数id,其值为123。如果是查询参数形式的请求URL,如/user?id=123,则可以通过@Query('id')来获取参数id,其值为123。

接下来是POST请求的处理。我们通常通过@Body()来获取请求体,并利用最常见的数据传输对象(Data Transfer Object,DTO)进行数据验证。例如,如上文代码所示的updateUserDto,它将用于验证数据。更详细的DTO介绍将在本章后续部分进行。

完成一系列操作后,我们将调用服务层(Service)进行数据持久化。服务层的方法通常与控制器(Controller)中的路由方法名一一对应。例如,在use.service.ts中定义的方法,其代码如下:

     import { Injectable } from '@nestjs/common';
     import { CreateUserDto } from './dto/create-user.dto';
     import { UpdateUserDto } from './dto/update-user.dto';

     @Injectable()
     export class UserService {
       create(createUserDto: CreateUserDto) {
         return 'This action adds a new user';
       } 

       findAll() {
         return `This action returns all user`;
       } 

       findOne(id: number) {
         return `This action returns a #${id} user`;
       } 

       update(id: number, updateUserDto: UpdateUserDto) {
         return `This action updates a #${id} user`;
       } 

       remove(id: number) {
         return `This action removes a #${id} user`;
       }
     }

3.3.4 Service处理数据层

至此,我们已经了解到,当客户端发出一个请求时,首先由控制器(Controller)处理,然后控制器会调度服务层(Service)的特定方法来执行业务逻辑,例如与数据库交互以执行数据的增加、删除、更新和查询操作。那么,在服务层中,数据又是如何被处理的呢?

先来看下面的伪代码:

由此可见,服务层(Service)会调用Repository实例方法对实体数据进行CRUD操作,而实体对象数据的改变将会同步映射到数据库中对应字段的更新。这就是对象关系映射(Object-Relational Mapping,ORM)的作用,后面会更加详细地介绍ORM的概念。

在Nest框架中,客户端发出的请求会经历一个分层处理过程:首先由控制器(Controller)接收请求,然后传递给服务层(Service),接着服务层可能会调用仓库层(Repository)进行数据操作,最终将处理结果返回给客户端。这一流程正是Nest分层架构的典型体现。

3.3.5 服务与服务提供者

Service服务是Nest分层架构中的一个组成部分。通过使用@Injectable装饰器声明,它可以被控制器(Controller)调度使用,或者被其他服务共享。这时,一些读者可能会有疑问:服务与服务提供者之间是什么关系?实际上,所有通过@Injectable装饰器装饰的类都是服务提供者。这是一个抽象概念,它不仅包括服务类,还可能包括中间件、拦截器、管道等,它们可以是工厂函数或常量。这些服务提供者被依赖注入系统注入其他组件中(例如控制器、服务等),以提供特定的功能、数据或操作。 HF47mu6Sjl0DVo0e2Lgilj9HbQ7IAsIxn05REPfagj32RjPQntm0h89wZvv1+Yxb

点击中间区域
呼出菜单
上一章
目录
下一章
×