从后端视角消费 API:在多个端点之间规范化数据
Source: Dev.to
一切的开始
我使用的第一个 API 是 Open Library API。它能工作……但并不总是可靠。
- 有些书籍返回时没有 ISBN。
- 还有的没有描述。
- 在某些情况下,作者信息显得有点不对劲或不完整。
起初,我想:
“也许情况就是这样。”
但我想要更丰富的数据,于是我加入了第二个来源:Google Books API。我的想法很简单:
“如果一个 API 缺少某些信息,另一个 API 很可能有。”
这点确实没错。我没预料到的是,随之而来的一系列新问题。
事情开始变得混乱的地方
一旦我开始从两个 API 获取数据,我几乎立刻注意到以下几点:
- 同一本书出现了多次。
- 作者姓名的格式不同。
- 有的响应中有 ISBN,而另一些没有。
- 描述并不总是一致。
同一本书。真相的不同版本。
一个简化的示例
Open Library 响应
{
"title": "The Hobbit",
"authors": [{ "name": "J.R.R. Tolkien" }],
"isbn_10": ["0345339681"]
}
Google Books 响应
{
"volumeInfo": {
"title": "The Hobbit",
"authors": ["J. R. R. Tolkien"],
"industryIdentifiers": [
{ "type": "ISBN_13", "identifier": "9780345339683" }
]
}
}
两者都是正确的,且都描述了同一本书。但如果你原样存储这些数据,就会自找麻烦。
真正的问题(我花了一段时间才看到的)
问题不在 Open Library,也绝对不是 Google Books。问题在于 我 以为外部 API 会彼此一致。事实并非如此。
每个 API 都有自己的结构、优先级以及对“完整”数据的定义。这时我遇到了一个悄然解决一切的概念:规范化。
那么……什么是规范化?
最简而言之:
规范化是决定你的数据应该是什么样子,
然后强制其他所有内容遵循它。
对非技术人员
- 它是在保存之前清理和标准化信息。
- 它确保一本书不会出现五个略有不同的身份。
对技术人员
- 它是将外部 API 响应映射到单一的内部模式。
两者方式相同,理念是一致的:
一个系统。一个结构。唯一的真实来源。
为什么规范化真的很重要
规范化前 我有:
- 数据库中有重复的图书。
- 作者姓名不一致。
- ISBN 查询不可靠。
规范化后 我得到:
- 一本书 = 一条记录。
- 字段可预测。
- 下游逻辑更清晰。
它并不显眼,但却悄悄为你节省了后期数小时的调试时间。
实现规范化
第一步:确定“图书”对你的意义
在编写任何 API 逻辑之前,我必须先回答一个简单的问题:
“在我的系统中,一本书的内部结构是什么样的?”
我最终确定的结构如下:
Book = {
"title": str,
"authors": list[str],
"isbn_10": str | None,
"isbn_13": str | None,
"description": str | None
}
这成为我的参考基准。所有外部数据都必须重新塑形以符合此结构。
第二步:分别对每个 API 进行规范化
我没有把逻辑混在一起,而是把每个 API 独立处理。
Open Library 规范化
def normalize_openlibrary(data):
return {
"title": data.get("title"),
"authors": [a.get("name") for a in data.get("authors", [])],
"isbn_10": data.get("isbn_10", [None])[0],
"isbn_13": data.get("isbn_13", [None])[0],
"description": data.get("description")
}
Google Books 规范化
def normalize_googlebooks(data):
info = data.get("volumeInfo", {})
isbn_10 = None
isbn_13 = None
for identifier in info.get("industryIdentifiers", []):
if identifier["type"] == "ISBN_10":
isbn_10 = identifier["identifier"]
elif identifier["type"] == "ISBN_13":
isbn_13 = identifier["identifier"]
return {
"title": info.get("title"),
"authors": info.get("authors", []),
"isbn_10": isbn_10,
"isbn_13": isbn_13,
"description": info.get("description")
}
此时,两套 API 终于使用相同的语言。
第三步:合并且避免重复
规范化让你的数据拥有统一的形状。合并则决定哪条数据占优。
我的规则很简单:
- 有 ISBN‑13 时优先使用。
- 当描述缺失时,以 Google Books 的结果作为补充。
def merge_books(primary, fallback):
return {
"title": primary["title"] or fallback["title"],
"authors": primary["authors"] or fallback["authors"],
"isbn_10": primary["isbn_10"] or fallback["isbn_10"],
"isbn_13": primary["isbn_13"] or fallback["isbn_13"],
"description": primary["description"] or fallback["description"],
}
没有花哨的技巧——只有明确的规则。
帮助我理清思路的模型是:API 是原材料,规范化是配方,数据库是最终成品。如果跳过配方,你仍然会得到食物,只是不能自信地端上桌。
我从中得到的收获
- 永远不要假设外部 API 会一致。
- 尽早定义唯一的真相来源。
- 在合并之前对每个来源进行标准化。
- 保持合并逻辑简洁且确定。
- 现在投资数据清洁,日后会受益。
祝编码愉快,愿你的数据永远干净!
PIs 不会为你提供一致性
- 更多的数据源 = 更多的责任
- 一旦扩展,规范化就不是可选的
最重要的是,我了解到后端工作不仅仅是获取数据。它还涉及决定系统中真相的样子并强制执行它。
如果你在使用多个 API 时感觉有点不对劲,规范化可能就是缺失的环节。
祝构建愉快 🚀