为什么在 API 中暴露持久化实体是一种危险的捷径

发布: (2025年12月28日 GMT+8 15:48)
3 min read
原文: Dev.to

Source: Dev.to

示例控制器

@PostMapping("/books")
public BookItem create(@RequestBody BookItem bookItem) {
    return bookService.save(bookItem);
}

为什么觉得这样做是对的

  • 实体已经拥有所有字段。
  • 不需要额外编写类。
  • 开发更快。
  • 对于小项目来说,DTO 看起来是多余的样板代码。

直接暴露实体的弊端

当实体直接用作请求体时,每个字段都变成可写的。这会让客户端能够:

  • 手动设置数据库 ID。
  • 修改本不该由他们控制的字段。
  • 发送仅用于内部逻辑的值。

框架只是简单地映射字段,而不了解业务意图,因此 API 对客户端的信任度过高。

实体特有的顾虑

  • 内部标记。
  • 审计时间戳。
  • 对客户端而言无关或不安全的字段。

一旦客户端开始使用这些字段,它们就会成为公共契约的一部分——即使你从未打算如此。这是最关键的问题,且往往在太晚的时候才显现。

示例实体及 JSON 负载

@Entity
public class BookItem {
    @Id
    private String id;
    private String title;
    private String author;
    private int pages;
}
{
  "id": "123",
  "title": "Clean Code",
  "author": "Robert Martin",
  "pages": 464
}

客户端会围绕这个响应构建自己的逻辑。

更改实体会破坏 API

假设你决定 pages 不准确,改为使用 wordCount

@Entity
public class BookItem {
    @Id
    private String id;
    private String title;
    private String author;
    private int wordCount;
}

你没有修改控制器,但响应现在变成了:

{
  "id": "123",
  "title": "Clean Code",
  "author": "Robert Martin",
  "wordCount": 150000
}
  • pages 消失了。
  • wordCount 出现了。

结果: 客户端崩溃,前端报错,移动端应用失效。之所以会出现这种情况,是因为实体充当了 API。

使用 DTO 的好处

DTO 在内部模型与外部消费者之间建立了稳定的边界。

  • 客户端只能发送被允许的字段。
  • 响应只暴露安全的数据。
  • 实体可以自由更改。
  • API 保持稳定。

结论

在 API 中直接使用实体起初可能感觉高效,但真正的代价会在需求变更、重构风险增大时显现。实体不是 API。DTO 可能会多写几个类,但它们能保护系统免于无声的破坏和未来的痛苦。

Back to Blog

相关文章

阅读更多 »

安全不是特性,它是基础

GitHub 首页 我艰难学到的教训 大约在职业生涯的第十年,我经历了一起至今仍让我毛骨悚然的安全事件。当时我们正在开发……

别再像2015年那样写API

我们已经进入2025年,仍有许多代码库把 API 视为简单的“返回 JSON 的端点”。如果你的 API 设计仍停留在基本的 CRUD 路由上,你正……