跳转到主要内容
Chinese, Simplified

一般来说,只要 HTTP 协议存在,Web 服务就已经存在。但是,自从云计算出现以来,它们已成为实现客户端与服务和数据交互的无处不在的方法。


作为一名开发人员,我很幸运能够使用一些仍然围绕@work 的 SOAP 服务。但是,我主要使用 REST,这是一种用于开发 API 和 Web 服务的基于资源的架构风格。
在我职业生涯的大部分时间里,我都参与了构建、设计和使用 API 的项目。
我见过的大多数 API 都“声称”是“RESTful” —— 意思是符合 REST 架构的原则和约束
然而,我与少数几个合作过的人给 REST 一个非常非常糟糕的代表。
HTTP 状态代码的不准确使用、纯文本响应、不一致的模式、插入端点的动词……我觉得我已经看到了这一切(或者至少,很多)。
因此,我决定写一篇文章来描述我个人认为设计 REST API 时的一些最佳实践。


只是为了让我们清楚……

我并不声称自己是权威,也不打算推断以下实践与任何“神圣的 REST 原则”100% 同步(如果存在这样的事情)。 我从我自己在整个职业生涯中构建和使用不同 API 的经验中拼凑出这些想法。
另外,我也不会假装精通 REST API 设计! 我相信这是一门艺术/运动——你练习得越多,你得到的就越好。
我将列出一些代码片段作为“糟糕设计的例子”。 如果它们看起来像你会写的东西,那很好! 🙂 唯一重要的是我们一起学习。
这里有一些技巧、建议和指导,可以帮助您设计出色的 REST API,让您的消费者(和开发人员)满意。


1. 学习HTTP的基础知识

如果您渴望构建一个设计良好的 REST API,您必须了解 HTTP 协议的基础知识。我坚信这将帮助您做出好的设计选择。
我发现 Mozilla 开发人员网络文档上的 HTTP 概述是有关此主题的非常全面的参考。
虽然,就 REST API 设计而言,这是一个适用于 RESTful 设计的 HTTP TLDR:
HTTP 有动词(动作或方法):GET、POST、PUT、PATCH 和 DELETE 是最常见的。
REST 是面向资源的,资源由 URI 表示:/library/
端点是动词和 URI 的组合,例如:GET: /books/
端点可以解释为对资源执行的操作。示例: POST: /books/ 可能意味着“创建一本新书”。
概括地说,动词映射到 CRUD 操作:GET 表示读取,POST 表示创建,PUT 和 PATCH 表示更新,DELETE 表示删除
响应的状态由其状态代码指定:1xx 表示信息,2xx 表示成功,3xx 表示重定向,4xx 表示客户端错误和 5xx 表示服务器错误
当然,您可以使用 HTTP 协议为 REST API 设计提供的其他内容,但我相信您必须牢记这些基本内容。


2.不返回纯文本

尽管这不是任何 REST 架构风格强加或强制的,但大多数 REST API 按照惯例使用 JSON 作为数据格式。
但是,仅返回包含 JSON 格式字符串的响应正文还不够好。 您仍应指定 Content-Type 标头。 它必须设置为值 application/json。
这在处理应用程序/编程客户端时尤为重要(例如,另一个服务/API 通过 Python 中的请求库与您的 API 交互) — 其中一些依赖此标头来准确解码响应。
💡专业提示:您可以使用 Firefox 轻松验证响应的内容类型。 它具有内置的漂亮显示内容类型:application/json 的响应。 🔥

在 Firefox 中,“Content-Type: text/plain”看起来……很简单。

“Content-Type: application/json” 不错,这是多么漂亮和实用。🕺


3. 不要在 URI 中使用动词

到目前为止,如果您已经了解了基础知识,您就会开始意识到将动词放在 URI 中不是 RESTful。
这是因为 HTTP 动词应该足以准确描述对资源执行的操作。
示例:假设您正在提供一个端点来生成和检索一本书的封面。 我会注意到 :param 是 URI 参数的占位符(如 ID 或 slug)。 您的第一个想法可能是创建一个与此类似的端点:


GET: /books/:slug/generateBookCover/


但是,GET 方法在语法上足以说明我们正在检索(“GETting”)一本书的封面。 所以,让我们使用:


GET: /books/:slug/bookCover/


同样,对于创建新书的端点:

# Don’t do this
POST: /books/createNewBook/
# Do this
POST: /books/


4.资源使用复数名词

这可能很难确定,是否应该对资源名词使用复数形式或单数形式。
我们应该使用 /book/:id/ (单数)还是 /books/:id/ (复数)?
我个人的建议是使用复数形式。
为什么? 因为它非常适合所有类型的端点。
我可以看到 GET /book/2/ 很好。 但是 GET /book/ 呢? 我们是要拿到图书馆里唯一的一本书,还是两本书,还是全部?
为了防止这种歧义,让我们保持一致(💡软件职业建议!)并在任何地方使用复数:

GET: /books/2/
POST: /books/
...

5.在响应正文中返回错误详细信息

当 API 服务器处理错误时,在 JSON 正文中返回错误详细信息很方便(*并且推荐*)以帮助消费者进行调试。 如果您包含受错误影响的字段,那就更好了!


{
    "error": "Invalid payload.",
    "detail": {
        "name": "This field is required."
    }
}


6. 特别注意 HTTP 状态码

我觉得这个很重要。如果您需要从本文中记住一件事,那可能就是它。
您的 API 可能做的最糟糕的事情是返回带有 200 OK 状态代码的错误响应。
这只是糟糕的语义。相反,返回一个准确描述错误类型的有意义的 HTTP 状态代码。
不过,您可能想知道,“但我按照您的建议在响应正文中发送了错误详细信息,那有什么问题呢?”
让我给你讲一个故事。 🙂
我曾经不得不集成一个 API,它为每个响应返回 200 OK 并通过状态字段指示请求是否成功:


{
    "status": "success",
    "data": {}
}


尽管 HTTP 状态代码返回 200 OK,但我不能绝对确定它没有无法处理我的请求。
事实上,API 可以像这样返回响应:

HTTP/1.1 200 OK
Content-Type: text/html
{
    "status": "failure",
    "data": {
        "error": "Expected at least three items in the list."
    }
}

(是的——它也返回了 HTML 内容。因为,为什么不呢?)
因此,在读取数据之前,我必须检查状态代码和临时状态字段以确保一切正常。
太烦人了! 🤦‍♂️
这种设计是真正的禁忌,因为它破坏了 API 与其消费者之间的信任。您开始担心 API 可能会骗您。

所有这些都非常非 RESTful。 你应该怎么做?
使用 HTTP 状态代码,仅使用响应正文提供错误详细信息。
HTTP/1.1 400 错误请求


HTTP/1.1 400 Bad Request
Content-Type: application/json
{
    "error": "Expected at least three items in the list."
}


7. 你应该一致地使用 HTTP 状态码

一旦您掌握了 HTTP 状态代码,您应该致力于始终如一地使用它们。
例如,如果您选择 POST 端点在某处返回 201 Created,则对每个 POST 端点使用相同的 HTTP 状态代码。
为什么? 因为消费者不必担心在哪种情况下哪个端点上的哪个方法将返回哪个状态代码。
所以,要可预测(一致)。 如果您必须偏离惯例,请将其记录在带有大标志的地方。
通常,我坚持以下模式:


GET: 200 OK
PUT: 200 OK
POST: 201 Created
PATCH: 200 OK
DELETE: 204 No Content


8.不要嵌套资源

您现在可能已经注意到 REST API 处理资源。检索列表或资源的单个实例很简单。但是,当您处理相关资源时会发生什么?
例如,假设我们要检索特定作者的书籍列表 - name=Cagan 的作者。基本上有两种选择。
第一个选项是将书籍资源嵌套在作者资源下,例如:


GET: /authors/Cagan/books/


一些架构师推荐这种约定,因为它确实代表了作者与其书籍之间的一对多关系。
但是,现在还不清楚您请求的是什么类型的资源。是作者吗?是书吗? …
同样扁平比嵌套更好,所以必须有更好的方法......而且有! :)
我个人的建议是直接使用查询字符串参数过滤书籍资源:


GET: /books?author=Cagan


这显然意味着:“获取作者姓名 Cagan 的所有书籍”,对吗? 🙂


9. 优雅地处理尾部斜杠

URIs 是否应该有一个斜杠/ 并不是一个真正的争论。您应该简单地选择一种方式或另一种方式(即带有或不带有尾部斜杠),坚持使用它并在客户端使用错误的约定时优雅地重定向客户端。
(我承认,我自己不止一次犯过这个罪。🙈)
讲故事的时间! 📙 有一天,当我将 REST API 集成到我的一个项目中时,我在每次调用时不断收到 HTTP 500 内部错误。我使用的端点看起来像这样:


POST: /buckets


我很生气,我终其一生都无法弄清楚我到底做错了什么。 🤪
最后,结果是服务器出现故障,因为我缺少尾部斜杠!所以,我开始使用:


POST: /buckets/


Aaaand 之后一切顺利。 🤦‍♂️
API 尚未修复,但希望您可以为您的消费者避免此类问题。
💡专业提示:大多数基于 Web 的框架(Angular、React 等)都有一个选项可以优雅地重定向到 URL 的尾随或非尾随版本。找到该选项并尽早激活它。


10.利用查询字符串进行过滤和分页

大多数时候,一个简单的端点不足以满足各种复杂的业务案例。
您的消费者可能希望检索满足特定条件的项目,或一次少量检索它们以提高性能。
这正是过滤和分页的目的。
通过过滤,消费者可以指定返回的项目应该具有的参数(或属性)。
分页允许消费者检索数据集的一部分。最简单的一种分页是页码分页,它由一个page和一个page_size决定。
现在,问题是:您如何在 REST API 中合并这些功能?
我的回答是:使用查询字符串。
我会说为什么你应该使用查询字符串进行分页是很明显的。它看起来像这样:


GET: /books?page=1&page_size=10


但是,过滤可能不太明显。起初,您可能会想到做这样的事情来检索仅已出版书籍的列表:


GET: /books/published/


设计问题:已发布不是资源!相反,它是您正在检索的数据的特征。那种事情应该放在查询字符串中。
所以最后,用户可以像这样检索“包含 20 个项目的已出版书籍的第二页”:


GET: /books?published=true&page=2&page_size=10


非常明确,不是吗?


11、了解401 Unauthorized和403 Forbidden的区别

如果我每次看到开发人员甚至一些有经验的架构师都把它搞砸的话,我有一个季度的时间……
在处理 REST API 中的安全错误时,很容易混淆错误是与身份验证还是授权(也称为权限)有关 — 过去一直发生在我身上。
这是我的备忘单,用于了解我正在处理的内容,具体取决于情况:

  • 消费者是否未提供身份验证凭据?他们的 SSO 令牌是否无效/超时? 👉 401 未经授权。
  • 消费者是否通过了正确的身份验证,但他们没有访问资源所需的权限/适当的许可? 👉 403 禁止。


12.善用HTTP 202 Accepted


我发现 202 Accepted 是 201 Created 的一个非常方便的替代品。它基本上意味着:
我,服务器,已理解您的请求。我还没有创建资源(还),但这很好。
我发现 202 Accepted 特别适用于两种主要情况:

  • 如果资源将作为未来处理的结果创建 - 例如:在作业/进程完成后。
  • 如果资源已经以某种方式存在,但这不应被解释为错误。


13. 使用专门用于 REST API 的 Web 框架

作为最后一个最佳实践,让我们讨论这个问题:您实际上如何在您的 API 中实施最佳实践?
大多数情况下,您希望创建一个快速的 API,以便一些服务可以相互交互。
Python 开发者会使用 Flask,JavaScript 开发者会使用 Node(Express),他们会实现一些简单的路由来处理 HTTP 请求。
这种方法的问题在于,该框架通常不针对构建 REST API 服务器。
例如,Flask 和 Express 都是两个非常通用的框架,但它们并不是专门用来帮助您构建 REST API 的。
因此,您必须采取额外的步骤在 API 中实施最佳实践。大多数时候,懒惰或缺乏时间意味着你不会付出努力——并且会给你的消费者留下一个古怪的 API。


解决方案很简单:为工作使用正确的工具。


新框架已经出现在各种语言中,专门用于构建 REST API。它们可帮助您轻松遵循最佳实践,而不会牺牲生产力。
在 Python 中,我发现的最好的 API 框架之一是 Falcon。它就像 Flask 一样简单易用,非常快速且非常适合在几分钟内构建 REST API。

Falcon:减轻我们 0.0564 多个世纪以来 API 的负担。
如果你更喜欢 Django 类型的人,那么首选是 Django REST 框架。 它不是那么直观,但非常强大。
在 Node 中,Restify 似乎也是一个不错的选择,尽管我还没有开始尝试。
我强烈建议您试一试这些框架。 它们将帮助您构建美观、优雅且设计良好的 REST API。


结束语

我们都应该努力使 API 使用起来很愉快。 两者都是为了消费者和我们自己的开发人员。
我希望本文能帮助您学习一些技巧,并启发您构建更好的 REST API 的技术。 对我来说,它归结为良好的语义、简单性和常识。
REST API 设计是一门艺术,比什么都重要。
如果您对我上面分享的任何提示有不同的方法,请分享。 我很想听听。
与此同时,让他们的 API 不断涌现!

原文:https://abdulrwahab.medium.com/api-architecture-best-practices-for-desi…

本文:http://jiagoushi.pro/node/1633

Article
知识星球
 
微信公众号
 
视频号