乐闻世界logo
搜索文章和话题

NestJS 为什么同时需要使用 dto 和 interface?

9 个月前提问
5 个月前修改
浏览次数245

6个答案

1
2
3
4
5
6

NestJS在设计模式上提倡使用Data Transfer Objects(DTOs)和接口(Interfaces)来实现应用程序逻辑的分离以及类型安全。

1. DTOs(Data Transfer Objects)

DTOs在NestJS中通常用于定义数据的传输格式。它们是用来约束客户端发送到服务器端的数据结构,确保数据的一致性和验证。DTOs通常带有装饰器(decorators),这些装饰器可以提供验证规则,确保只有符合这些规则的数据才被接受和处理。

例子:

假设我们有一个创建用户的API,我们可能会定义一个CreateUserDto类,来确保我们接收到的数据包含usernamepassword,并且它们都是字符串:

typescript
import { IsString } from 'class-validator'; export class CreateUserDto { @IsString() readonly username: string; @IsString() readonly password: string; }

在上面的例子中,class-validator库提供了@IsString()装饰器来验证传入数据的类型。

2. 接口(Interfaces)

接口在TypeScript和NestJS中用于定义对象的结构,它们是为了编译时的类型检查而存在。接口定义了对象可以有哪些属性以及这些属性的类型。它们不会编译到JavaScript中,因此不会在运行时提供任何的验证。

例子:

在服务或者模块之间共享数据结构时,我们可以定义一个接口来约定数据的形状。

typescript
interface User { id: number; username: string; password: string; }

在上述例子中,User接口描述了用户对象必须包含的属性和类型。任何实现了User接口的对象都必须有idusernamepassword这三个属性。

为什么同时需要?

使用DTOs和接口结合起来可以带来以下优势:

  • 分层的数据验证:DTOs可以在应用层对传入的数据进行严格的验证,而接口则在编译时提供类型检查,确保代码的正确性。
  • 代码可维护性:接口提供了一个清晰的契约定义,可以被服务、控制器和其他类实现,这使得代码更加模块化和可维护。
  • 灵活性和扩展性:DTOs可以为特定的操作定义数据格式,例如创建、更新,而接口则定义了应用程序级别的数据模型。这两者的结合使得扩展和重构变得更加容易。
  • 隔离变化:如果来自外部的数据需求变化,通常只需要调整DTO,而不需要修改内部使用的接口,这样可以最小化变化对系统的影响。

综上所述,DTOs和接口共同为NestJS提供了一个灵活、可靠和可维护的数据处理框架。通过在编译时和运行时各自发挥作用,它们确保了类型安全和数据一致性,同时也提高了代码的可读性和维护性。

2024年6月29日 12:07 回复

根据Nestjs 文档

但首先(如果您使用 TypeScript),我们需要确定 DTO(数据传输对象)架构。DTO 是一个定义数据如何通过网络发送的对象。我们可以通过使用 TypeScript 接口或简单的类来确定 DTO 模式。有趣的是,我们建议在这里使用类。为什么?类是 JavaScript ES6 标准的一部分,因此它们在编译后的 JavaScript 中作为真实实体保留。另一方面,由于 TypeScript 接口在转译过程中被删除,因此 Nest 无法在运行时引用它们。这很重要,因为管道等功能在运行时可以访问变量的元类型时可以提供额外的可能性。

2024年6月29日 12:07 回复

我不是专家,但我根本不使用 DTO。我真的看不出它们有什么用处。在每个模块中,我都有一个服务、模块、实体和控制器。

2024年6月29日 12:07 回复

NestJS中使用DTO和Interface的原因

基本上,在 REST API 中,我们有两种类型的操作,一种是输入,另一种是输出。这是请求响应

在响应期间,我们不需要验证返回值。我们只需要根据接口传递数据即可

但根据请求,我们需要验证正文

例如,您要创建一个用户。那么请求体可能是这样的

shell
const body = { name: "Test Name", email: "test@gmail.com", phone: "0393939", age: 25 }

因此,在请求期间,我们需要验证电子邮件电话号码密码是否与正则表达式等匹配。

所以在DTO中我们可以做所有的验证

这是我的 DTO 示例之一

shell
import { IsEmail, IsNotEmpty, IsString, MinLength } from 'class-validator'; export class RegisterUserRequest { @IsString() @IsNotEmpty() name: string; @IsEmail() @IsNotEmpty() email: string; @IsNotEmpty() @MinLength(6) password: string; } export class LoginUserRequest { @IsEmail() @IsNotEmpty() email: string; @IsNotEmpty() @MinLength(6) password: string; }

这是界面示例

shell
import { UserRole } from './user.schema'; export interface UserType { _id?: string; email?: string; name?: string; role: UserRole; createdAt?: Date; updatedAt?: Date; }

希望你能理解。

谢谢

2024年6月29日 12:07 回复

我想DTO用最简单的例子来解释这个概念,以便您更好地理解。DTO 代表数据传输对象。现在,DTO 用于减少代码重复。它只是定义了一个模式,该模式在函数的参数中传递,以便轻松地从函数中获取所需的数据。这是一个例子DTO

shell
export class AuthCredentialsDto { email: string; password: string; }

现在如果我们制作一个方法来检查密码是否正确

shell
async password_check(usercredentials: AuthCredentialsDTO) { //Destructuring in JAVASCRIPT const {email} = usercredentials; //Database Logic to find the email return user; }

现在,如果我们不使用 DTO,那么代码将如下所示

shell
async password_check(email: string, password: string) { //Database Logic to find the email return user; }

另外一点是,这只是一个函数,在一个框架中,多个函数调用多个其他函数,需要一次又一次地传递参数。假设一个函数需要 10 个参数。你必须多次通过它们。尽管可以在没有 a 的情况下工作DTO,但这不是一种开发友好的做法。一旦习惯了,DTO您就会喜欢使用它们,因为它们节省了大量额外的代码和精力。问候

2024年6月29日 12:07 回复

总长DR

你的问题的答案是肯定的,如果你愿意的话,你可以使用它们来塑造形状,但在某些情况下可能没有必要。


当您需要对数据强制执行某种形状(特别是在 Nestjs 生态系统中,它与类验证器交互很多)或以某种方式对其进行转换时,DTO 是一个很好的解决方案。例如,当您从客户端或其他服务接收数据时。在这种情况下,DTO 是制定合同的最佳方式。

但是,当您在同一应用程序的两层之间发送数据时(例如在控制器和用例之间或用例和存储库之间),您可能希望在那里使用一个接口,因为您知道您的数据是在这种情况下以正确的格式发送。

需要理解的一个关键区别是,接口作为一种开发工具,它会让您犯错误,例如在两个类之间传递缺少特定必需属性的对象,而 DTO 会影响应用程序本身,它是一个将要实例化的对象在运行时并可能用于验证和数据转换目的,这就是想法,但当然它也具有接口的功能。

根据您想要的架构,此经验法则可能有例外。例如,在我正在从事的一个项目中,域和表示层之间的契约等于前端和 API 之间的契约是很常见的。在这个项目中,为了避免创建两个类似的合约,我选择使用 DTO 来设置表示层和域层之间的合约。现在,在大多数情况下,我只是在设置 API 和客户端之间的合同时扩展 DTO。

2024年6月29日 12:07 回复

你的答案