发布于 2026-01-06 2 阅读
0

JavaScript 中的 DTO

JavaScript 中的 DTO

介绍

在讨论实际实现之前,我们先来介绍一下DTO,它的含义、使用场景以及在JavaScript/Node.js项目中的真正必要性。

什么是DTO?

DTO 代表数据传输对象,指的是定义一个容器,其中包含一组值或字段,而不是定义数据如何在各层之间传递的方法。有些人会将数据库模型和 DTO 混为一谈,请记住这句话:
DTO 用于操作和数据传输,而模型用于数据持久化。

何时使用DTO?

很多开发者在用 TypeScript/Node.js 开发复杂应用时都会使用 DTO 来表示数据以及数据在应用层之间的传递方式,这确实没错,而且是必要的。但我今天想告诉大家的是,在 JavaScript/Node.js 开发中,DTO 也同样必要,它可以防止你的代码变得糟糕!

为什么 JavaScript 中需要 DTO?

想象一下,你使用像 JavaScript 这样的高级动态语言,并使用 Node.js 开发 REST API。你创建了模型、数据验证等,express-validator并定义了路由和中间件,一切运行正常。随着需求的变化,你频繁地更新代码。你创建了多个服务和多个 API,它们以不同的方式使用同一个模型。为了将数据从控制器层传递到服务层,然后再传递到负责将数据持久化到数据库的层,你在每个服务中都复制了一些字段。一段时间后,当你查看代码时,你会发现自己搞不清楚哪些数据应该传递给服务层,哪些数据应该从服务层返回。这时,你就需要 DTO 了。
再想象一下,你连接到 Firebase 作为持久化数据库或文档数据库,没有严格的模式。你有一个端点接收 JSON 数据,进行一些验证后将express-validator数据传递给服务层,然后服务层再将数据传递给持久化层。你需要的字段如下所示:

{username: String, email: String, password: String}
Enter fullscreen mode Exit fullscreen mode

如何保证 API 使用者可以发送除已定义字段之外的更多字段?例如,API 使用者可以发送以下数据:

{
  "username": "test",
  "email": "test@gmail.com",
  "password": "specificPass",
  "birthDate": "2022-05-09T20:12:13.318Z"
}
Enter fullscreen mode Exit fullscreen mode

你看到了吗?我可以发送验证中未定义的字段,这会违反我们的服务规则,这些数据会被传递到持久层,并以未指定的方式保存到数据库中。
假设你有一个 API 和一个 WebSocket 连接,它们都使用同一个服务层,你会如何为它们定义验证规则?你最终可能会在两者中重复定义暴露的数据!

在所有这些情况下,您都需要使用数据目标对象 (DTO)。它的理念非常简单,它使您能够描述如何接收数据以及如何在各个层中公开数据。

实施与示例

首先,我们将 ExpressJS 路由定义如下:

router.post("/user/register", validations, registerController);
Enter fullscreen mode Exit fullscreen mode

我们将使用 express-validator 进行验证,如下所示:

const validations = [
  body("username").exists().isString().notEmpty(),
  body("email").exists().isEmail(),
  body("password").exists().isString().notEmpty(),
]
Enter fullscreen mode Exit fullscreen mode

然后,您可以编写如下控制器/处理程序:

const registerController = (req, res) => {
  const result = await userService.registerUser(req.body);
  return res.status(200).json(result);
}
Enter fullscreen mode Exit fullscreen mode

您的简单服务层如下所示:

const registerUser = (userData) => {
  userPersistenceLayer.add(userData);
}
Enter fullscreen mode Exit fullscreen mode

现在我们来定义基本的DTO,但在此之前,我想先确认两件事:

  • DTO 用于数据传输,而数据库模型用于数据持久化。
  • 可以将 DTO 看作一份合同,你用它来与他人进行交易,而合同规范就是其中定义的字段。
class RegisterUserDTO{
  username;
  email;
  password;

  constructor(data) {
    this.username = data.username;
    this.email = data.email;
    this.password = data.password;
  }
}
Enter fullscreen mode Exit fullscreen mode

然后我们就可以回到服务层,使用我们定义的DTO了。

const registerUser = (userData) => {
  userPersistenceLayer.add(new RegisterUserDTO(userData));
}
Enter fullscreen mode Exit fullscreen mode

如您所见,通过这种模式,我们可以控制数据传递的方式,并确保哪些字段被传递到其他层,我们还可以在 DTO 中设置一些 getter 和 setter,以便根据需要序列化/转换一些数据。

希望以上对DTO模式的解释清晰明了,您已经理解了。

文章来源:https://dev.to/tareksalem/dtos-in-javascript-118p