译文Restful API规范
Posted 在路上的德尔菲
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了译文Restful API规范相关的知识,希望对你有一定的参考价值。
我们在android和ios系统中构建各种形状和大小的应用程序(App),但它们都是与服务器连通的,如今几乎没有App是不连接Internet情况下运行的,这意味着它们可以与后端、Web服务及API进行交互,这些API可以由Google,亚马逊,Facebook或类似的第三方提供,也可以由内部开发。
由于公司一开始没有花时间计划好API,内部很多API是重复的此外,即使有大量的App,并非所有人都专门为App构建了Web服务或API,根据以往经验,我们发现建立一个如何为移动App构建更好的API规则可以节省开发过程中的时间和精力。
本指南概述了移动客户端构建API、Web服务和数据库的最佳实践,在这篇文章中,我们将深入介绍如何专门为移动应用程序创建RESTful API,它对我们的客户以及任何希望合理构建和维护以App为中心的后端开发团队有帮助。
-
让服务器而不是客户端完成繁重的工作。
-
对您的API进行版本控制,以便处理来自新用户和旧用户的请求。
-
考虑多设备的离线使用情况。
-
在选择使用的服务器时,优先考虑性能和可扩展性。
-
使用标准安全协议和身份验证/加密库。
-
构建三个后端环境:开发,预发布和线上。
-
让数据决定的数据库类型。
-
构造API URL端点,以便清楚展示资源包含的内容。
-
客户端发送完整请求对象,服务器使用它需要的字段。
-
GET和PUT请求必须是幂等的。
尤其是在移动世界中,REST是迄今为止用于设计API的最常用样式,还有一些特殊的REST子集,如OData,它们进一步定义了App和服务器之间如何传输数据,使用通用的RESTful架构风格将确保负责维护服务器开发人员熟悉其原理,更重要的是未来构建新的服务。
在本指南中,我们将通过移动App系统来讨论RESTful API,但是这些规则肯定有助于使用API支持Web应用程序和其他系统,在大多数情况下,展示资源的客户端(Java/Obiective-C/Swift等)和发送资源的后端服务器(Java/C++/Python等)是以不同的编程语言编写,并且通常由不同的开发团队开发,遵循下面列出几项的REST原则将确保多个团队在两个平台上按照一定标准去开发。
移动应用程序的后端如何不同
在我们深入研究创建RESTful API的部署、安全性、体系结构和其他注意事项之前,让我们先看一下为移动应用程序构建API与其他系统不同。
HTTPS 而不是HTTP
一般互联网都是基于HTTP协议的,但移动平台使用现代加密技术和受信任签名证书会强制执行HTTPS协议,开发环境、预发布环境以及线上环境服务器应该使用同一种类型的签名证书,这将大大减少在不同环境下迁移或测试功能时的麻烦,使在影响用户之前捕捉到安全问题。
服务器做了大部分工作
为了节约网络数据成本和手机的电量,一般希望移动客户端做尽量少的工作,一个移动应用从远程查询和存储数据服务器中获益,或者将繁重而敏感的任务放到到更强大的远程服务器上。加入远程API可确保你的存储数据安全,并允许你的应用程序为用户提供平稳运行,而专注于快速呈现数据而不是计算。移动应用用户期待他们的数据在各个设施上同步,这可以通过特定API将数据移除设备来解决。
让服务器完成繁重的工作还可以为在多个平台上工作的开发人员节省时间,方法是将代码移动并整合到服务器,并将结果呈现给iOS和Android应用程序。利用数据运算,服务器可以使用比移动客户端更好的硬件,因此利用服务器好是明智之举。
服务器故障会杀死客户端应用
如果某些功能无法正常运行,那么现在的用户几乎没有耐心的,尤其是移动应用的用户。 如果出现问题,服务器需要使用用户友好的错误消息或错误代码进行响应,客户端可以使用它来安抚用户,并希望有助于解决问题。一个简单的错误可能会造成1星评论,而积极的5星好评会对App的成功非常重要,太多的又服务器造成的负面评论会影响下载量。
版本控制很重要
随着用户以不同频率更新其APP(或不更新),对API进行版本控制变得很重要。 随着APP的几个不同版本在同时使用,服务器需要整合和处理来自新旧用户的各种请求。 我们将在后面篇幅中深入研究处理这个问题的有效策略。
为发送通知做计划
移动专用的通信途径是推送通知。 可以使用第三方工具专门用于推送通知,但有时你需要自己管理该过程。 你的服务器可能负责跟踪token,即将设备映射到用户以发送推送通知。
协调离线活动
许多移动用户都希望应用在离线状态下也能拥有一些有限的功能(炉石最新更新就是可以离线状态下组套牌,连接网络后套牌同步)。 重新连接到服务器后,需要考虑将离线活动与数据库进行协调。 这对于用户可以在多种设备上访问的应用程序尤为重要,例如手机和平板电脑。 协调具有时间戳和操作顺序的API调用是前端和后端开发人员需要讨论的事情。
建立App的RESTful考虑
现在我们已经了解了移动设备与其他系统的区别,下面我们可以深入研究规划RESTful API,以下tips是设计时通用的关注点,包括服务器部署、安全问题,创建后端架构、选择数据库和存储策略、选择合适的工具以及支持多平平台问题。
服务器部署
如何部署服务器是很关键的, 如果不具备托管自己的裸机服务器的能力,那么现在有很多云托管解决方案可用。 每种云服务选项都是不同的性能,可满足伸缩性的特定管理需求。 在评估服务器服务时要注意的一些核心因素包括:
- 服务如何扩展其资源? 这可以是水平(添加更多机器)或垂直(升级现有机器的硬件资源)。
- 随着资源使用的增加服务器成本变化情况?
- 是否有迁移功能可以轻松地将项目构建多个环境(开发/预发布/线上)?
- 该服务采取了哪些措施来保护系统(即加密,自动备份,监控)?
- 将数据移植到另一个服务器或平台上容易吗?
保护数据
根据需要,可以利用各种身份验证机制。选择的任何托管服务都应该由HTTPS和可信CA证书的集成。 HTTP基本身份验证是最容易实现的,但它也是最不安全的。 OAuth2是一种被广泛接受的标准的身份验证方式,强烈建议使用。有很多用于社交登录或电话号码身份验证的库可以选择使用,不要尝试编写自己的身份验,当大量客户端和服务器端的已经在使用现有协议和库时,无需重新造轮子,在身份验证需求背后应该保护每个API端点。
敏感数据必须保护好。这是给定的,但安全性是一种频谱,而不是绝对的。需要加密用户的敏感数据,而没必要加密用户的每个数据,但是应始终考虑加密的问题。比如不要以纯文本格式存储密码,不仅应该对密码进行哈希处理,而且对每个密码使用随机盐(random salt)机制可显着提高安全性。
做好架构
正如我们已经讨论过的那样,应该构建三个独立的后端环境:开发,预发布和线上。开发环境是开发人员不断开发并修改的地方,这里的数据可以由开发人员通过自动脚本来实现,使用正常数量的测试数据填充数据库。当代码在持续集成环境中通过所有测试并完成QA测试后,它会进入预发布环境。
预发布环境会尽可能地类似于线上环境。理想情况下,此处的数据是对已删除个人信息的已转换实际/实时采样数据的导入。这里使用的数据越真实,对系统在线上环境中的性能就越有信心。
制定数据库和存储策略
无论使用哪种类型的数据库,值得注意的是实体ID应该是随机生成的UUID,而不是按顺序递增的,这样可使ID更难猜测以保护资源。 在存储数据时,一般可能考虑使用传统的关系数据库,如mysql或MariaDB, 或许更喜欢像MongoDB这样的noSQL文档数据库的可伸缩性,或许更喜欢类似PostgreSQL可以提供关系或文档存储支持的混合这样灵活的方法,项目应该使用哪个数据库实际上取决于数据。
MYSQL/ MariaDB
- 完善,稳定,可靠
- 大量的库,框架和工具可以利用
- 数据定义明确,结构良好,降低数据不匹配的情况
MongoDB
- 没有表,没有正式的架构,非关系型
- 比基于SQL的数据库更容易扩展
- 轻松提升和迭代数据库模型
POSTGRESQL
- 社区支持和普及正在迅速增长
- 围绕为数据库管理员提供更多功能和工具
- 足够灵活,可以将关系型数据与模型无关的数据混合在一起
云文档存储
- 一个廉价的解决方案可能就像使用Amazon S3 Buckets存储大量整个文档一样
寻找合适的工具
需要合适的工具来完成工作,无论是项目团队中当前团队成员或未来团队成员之间的沟通,沟通都是任何项目成功的关键。在开发过程中,请确保使用的是明确的开发跟踪工具,并且所有队友都可以访问。
关键是要保持公开进度并保持历史记录。对于项目中的多个开发人员来说,这一点尤其重要负责编写代码以在iOS应用程序中登录用户的开发人员真的想知道服务器端身份验证API何时可以使用。他们甚至可以围绕改变服务器开发人员的优先级进行讨论,以便尽快完成该任务,如果它阻止iOS应用程序进一步发展的话。让整个团队不仅了解当前正在进行的工作,还了解接下来的工作,这将使协调工作时间表的流程更加流畅,从而以最有效的方式完成任务。这也有助于避免API过早发生变化,导致延迟或以其他方式对移动应用程序中的开发产生负面影响的问题。值得注意的是,就像任何软件迭代一样,发布说明在部署新API时非常宝贵,即使在开发过程中也是如此。
如果操作正确,RESTful API端点应该易于测试,并且应该涵盖明显的用例以及每个端点的预期边缘情况的测试用例。 RESTful的核心原则之一就是无状态,这使得我们的API端点小巧,类似模块化的黑盒子,适合测试。新数据输入,成功的消息输出,新的数据发生改变在数据库中很容易得到验证。对数据的请求进入,预期的数据响应出来,使用的测试框架取决于编写后端服务器逻辑的语言,无论如何编写测试用例,它们都应该得到良好的维护,并在每次部署之前以100%的通过率运行。
无论如何写API文档都应该包含请求信息的成功响应码和失败响应码,以及可能的错误代码和消息,还需要确保团队中的每个人都可以访问此文档。
支持多平台
如果正在为移动App开发一套API,需要为未来支持都平台做好准备,最好的做法是使客户端变得笨拙,而让如排序、过滤、数据聚合以及数字运算在服务器上进行,这可以利用服务器强大的硬件设施,使客户端在拿数据和展示数据给用户时使尽可能保持逻辑简单。
因为当在iOS,Android和Web上构建应用程序时,不希望重复三次复杂的过滤和解析逻辑,因此支持多平台很重要。让客户端只获取它所需的内容,例如不要把所有内容都放在集合中返回,并强制客户端对数据进行排序以查找所需的内容;在使用长数据列表的分页时,客户端确定需要返回多少数量的结果,避免要求过多的数据影响客户端和服务器性能;支持多平台时,捕获请求头中的设备名称、操作系统版本及类型有帮助的,因为这些在将来阅读日志和调试时非常有用。
如何执行移动App的RESTful API
现在我们已经讨论怎样部署服务器,继续探究如何设计RESTful形式的API,这一节通过如何使用合适的URL路径、需求、响应以及设计准则应用于RESTful API.
处理URL路径
举个栗子,你的一些资源可能是一本书,一个书店,一个作者,或一个图书馆地址。客户端访问这些资源通过API,因此你可以建立一些到各个资源的映射
GET /books
POST /books
GET /books/{bookId}
PUT /books/{bookId}
PATCH /books/{bookId}
DELETE /books/{bookId}
GET /books/{bookId}/authors
GET /bookClubs/book
GET /users/{userId}
GET /users/{userId}/favoriteBooks
注意这种模式出现,我们希望建立一种API URL映射规则,使资源包括的内容很直接表示出来。首先介绍两种RESTful设计工具:名词和动词,GET POST PUT PATCH DELETE是动词,告诉服务器是哪一种请求方式。URL路径中为名词,代表着将被操作的资源对象,做出这种区分非常重要,否则你最终可能会遇到一个难以理解的混乱API。
对比看一下“不好的”映射形式
/getBooks
/createNewBook
/checkOutBook/{bookId}
/returnBook/{bookId}
/addBookToFavorites
/addNewMemberToBookClub
/changeBookClubMeetingTime
/changeBookClubMeetingLocation
/removeBookClubMember
你可能对这些API映射做什么一目了然,但现在你将为该资源上的每个可能的操作添加新的API映射,这样做会导致尝试使用API的客户端开发感到头痛。 如果希望保持API简洁和模块化, 不仅是为应用创建API,还需要考虑其他功能或数据类型如何适用于API。
为了提高API的鲁棒性,就让请求驱使服务器响应。这似乎是一个很蠢的表述,因为客户端当然会发出请求,服务器的工作就是返回响应。 如果服务器决定客户端获取或更改数据的内容,方式,那么这意味着每次想要开发新功能时,客户端开发人员将不得不等待服务器完成这些更新。 服务器。 允许服务器拒绝请求。 这就是错误消息的用途。 但是通过使用名词构建端点和对请求做出反应,将提供一个稳定的API——随着时间的推移需要更少的维护,因为允许客户端用很少的资源完成更多工作。
仅仅是因为我们使用名词驱动URL,不意味着我们的资源需要和我们的数据模型对象一样,URL应该包含适当的模型对象,但我们希望这些路径易于阅读和直观。 例如资源部/favoriteBooks
,可能意味着归还对象例如/books
,但是我们会强调取回什么类型的书,这个分隔号/
可将查找、过滤、计算favoriteBooks的逻辑转移到服务器端,让移动客户端保持专注于得到所需数据并将其呈现给用户。
这直接导致了如何处理过滤,排序,分页和搜索。这四个子操作都是相关的,因为它们让客户端表示要从服务器检索哪些资源。同样,我们希望构建我们的API,为客户提供尽可能多的自由。我们使用URL参数处理过滤,排序,分页和搜索。这使我们能够保持较低API端点数量且易于管理,同时为客户端提供复杂操作所需的工具。一旦我们添加了处理这些查询参数类型的能力,我们就会为客户端打开了大门,让客户端根据需要更好地向用户提供内容。今天,也许他们希望按字母顺序显示所有内容,但明天他们意识到按地理位置排序对它们用户更有价值,或者他们希望让用户自己决定内容分类的规则。客户端可以在服务器端没有任何额外工作的情况下完成所有这些工作,这正是我们想要的。
下面是一些处理URL参数的例子
GET /libraries?sortBy=name&isCurrentlyOpen=true&pageCount=10
GET /books?queryTitle=Sherlock+Holmes&queryAuthor=Arthur+Conan+Doyle
GET /bookClubs?genre=mystery
API版本控制是我们应该实现的另一个功能,因为鲁棒性非常重要。 移动开发人员并不会让所有用户强制进行软件更新的,因此我们的API必须能够处理新旧请求。 我们将通过使用版本号路由请求来完成此操作。 有些人会争辩说这个版本号应该放在URL路径中,有些人会认为它应该放在请求头中。 我更喜欢将其放在URL路径中以便于发现,但这取决于团队。 我们将不同版本资源视为不同的资源,这就是我们认为它们应该放在URL中获得唯一路径而不是放在请求头的原因。 无论哪种方式,版本控制API的好处是允许在不影响低版本请求的情况下请求更新后的功能。如下形式:
GET /v1/books
GET /v2/books
使用上述所有URL示例进行调用的另一个重要指导原则是它们从左到右,从广泛到特定。 让客户端遍历API端点树,从根单点入口开始,在那里他们可以请求任何内容,并让相关资源从那里级联。 同样,目标是使这些API更加直观,以便客户端使用。 确定在API中公开的资源将由开发团队决定,如果有关于客户端应用程序如何工作的任何UI或UX设计,那么在后端工作的开发人员必须了解客户端需要什么数据以及何时需要这些数据。 问问自己这样的问题:
处理请求和响应的规则
因此我们已经阐述了如何构建我们的API端点,现在谈谈如何在每个端点内有效地通信。 处理请求时,请勿强制客户端仅发送一个或两个字段。 如果允许,客户端发送完整的对象,而服务器可只使用它需要的字段, 如果请求确实只包含几个字段,请不要以为缺少的字段为null, 为null的字段应在请求和响应中明确声明, 这可以防止另一端猜测数据是否为null。 看看下面的例子:
PATCH /books/123
{
"author": "Arthur Conan Doyle"
"publicationYear": "1902"
}
虽然这不是一个完整的书籍对象,但我们将构建足够稳定的服务器来处理重要信息。 在这里,客户端已明确更新ID为123的书籍资源,如上URL所述。 请求体告诉我们这本书应该更新其作者和出版年份,即只应更新这些字段,此请求对其他字段并不重要,因此不包括在内,但这并不意味着书的标题应该设置为null,它应该保持不变。 这样可只使用部分内容作为请求对象,可以减少对无关重复数据的解析和处理。
请注意请求中的Content-Type和Accept标头,对于大多数请求可能仍然是application / json格式,但是如果你需要支持xml,需要在此修改而不是写在URL中,其他类型的资源(如文件,图像,音频等)也应在此处设置其内容类型。
处理完请求后,我们需要返回响应,事情可能不像客户端期待的那样,或者请求被成功地处理而不需要有任何数据返回,或者所有事情如预期一样进行,可以通过返回的HTTP状态码帮助我们弄明白。
仔细考虑返回的状态代码,其中200,201和204表示成功响应,4XX状态码表示错误,客户端会关注这些状态码,因此我们需要小心处理我们的响应。但是在每一个响应中,我们都需要保持一致性。我们不应该将“camelCase”和“snake_case”混合为我们的JSON键,规范是camelCase。此外,与在端点上命名字段的内容保持一致。比如在谈论书籍的ISBN时,请确保不要混合使用isbn,ISBN,isbnNumber,internationalStandardBookNumber,在需要传递该值的任何位置使用特定格式。
响应可能包含一个或多个对象时,请勿混合发送一个对象而不是数组。 如果客户端请求得到的集合中只有一个对象,则应该将其放入数组中并始终以该方式返回。 对于每种编程语言,处理一个对象与多个对象是不同的,但如果保持一致,则不会浪费时间来解析各种平台上的特殊情况。
除非提供价值,否则此建议的另一面是不将您的回复或请求包装在信封字段中。 将对象包装在“data”:{. . .}只会浪费数据和时间。 作为后续操作,请勿在未请求的对象上包含元数据。 理想情况下,我们希望服务器和客户端的数据模型尽可能接近,因此出现不属于请求对象的额外字段只会妨碍使用。 以下是一些不合理的示例:
不要用无用的括号包装数据
GET /authors?genre=mystery
{
"data": {
[{ "authorName": "Arthur Conan Doyle",
...
}]
}
}
客户端希望返回一个集合时不要只返回一个对象
GET /authors?genre=mystery
{
"authorName": "Arthur Conan Doyle",
...
}
不要包含与客户端请求的数据无关的元数据。
GET /authors?genre=mystery
{
"requestInfo": {
"genre": "mystery"
},
[{
"authorName": "Arthur Conan Doyle",
...
}]
}
在处理日期和时间时,记住一致性是关键。 接受并发送带有UTC值的ISO 8601格式的所有日期。 不要让服务器选择时区、显示格式,或确定日期时间数据需要的精度; 客户端移动App将弄清楚如何最好地向用户显示日期和时间。
除了读取请求并创建或查找其响应之外,服务器可能还需要一些编程逻辑来最好地执行每个请求。 在编写此逻辑时,我们需要记住GET和PUT请求是幂等的。 这意味着无论客户端在我们的服务器上调用GET / books多少次,数据都将保持不变,我们应该避免做任何“额外”或客户可能不期望的幕后操作。 无论服务器响应请求需要执行何种逻辑或间接操作,都需要进行通信和记录,以避免以后出现意外情况。
总结
希望这篇文章对你有帮助,当设计或创造我们自己的后端,我们专注模块化、可扩展的方式放置资源,有效地传输决策和优先级,客户端与后端开发者间合作应该确保避免路障和持续的更新,我们花越少的时间猜测API在每个场景中工作,那么我们就会有更多的时间开发有意义的功能。
以上是关于译文Restful API规范的主要内容,如果未能解决你的问题,请参考以下文章