为什么在 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 可能会多写几个类,但它们能保护系统免于无声的破坏和未来的痛苦。