从后端视角消费 API:在多个端点之间规范化数据

发布: (2026年1月19日 GMT+8 22:06)
7 min read
原文: Dev.to

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 时感觉有点不对劲,规范化可能就是缺失的环节。

祝构建愉快 🚀

Back to Blog

相关文章

阅读更多 »

为什么学习 Java 或 C#

封面图片:Porque estudar Java ou C https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploa...