文档章节

再谈RestAPI最佳实践

山哥
 山哥
发布于 2014/06/09 11:33
字数 2192
阅读 248
收藏 20
点赞 0
评论 0

本文由 伯乐在线 - Justin Wu 翻译自 javacodegeeks

近一年半,我参与了两到三个项目的工作,这些项目涉及到大量供“外部”使用的Rest API,稍后我们会看到为什么要将“外部”这个词放在引号之中。在项目工作期间,我不得不对这些API进行反复地设计,再设计和重构,这篇文章是我对Rest API最佳实践的一些个人看法,希望读者能够从中获益。


更好、更早地设计

对于很多语言来说,实现Rest Service是一项极其微不足道的任务。换言之,无论你选择什么底层框架,只要辅以少量配置和代码,你可以在一小时之内就拥有一个Rest?Service。虽然对于缺乏经验的人来说,这确实很方便,但它也很容易让你迅速写出一个质量低下的API。因此,在你编写代码之前,先留出一分钟的时间思考一下,试着去设计你的API,花足够的时间去理解业务范畴,判断客户端需要从你的系统中获取什么。举个例子,如果你的系统是针对一群硬币收藏家所建立的数据库,此时你需要决定的是:你是否允许客户端添加新的硬币,或者仅仅允许取出原有的硬币;客户需要什么样的查询方式;如果遇上涉及大量数据检索的请求,你如何处理它?尽早地回答这些问题能够帮助你开发出更贴近用户需求的API。


名称与方法

现在已经很有多关于资源(Resource)命名和组织的讨论了,在这里我基于自己的经验再老调重弹一下,以下是三种易于遵循的规范。

1. 只使用名词:举个例子,如果你想提供一项在数据库中搜索硬币的服务,要避免将端点(Endpoint)命名为/searchCoins或/findCoins或/getAllCoins 等等,一个简单的/coins就已经足够了,当客户端发送一个GET请求的时候,可以获得所有有效硬币的集合。类似的,如果你想提供一项在数据库中添加硬币的服务,要避免使用诸如/addCoin或/saveCoin或/insertCointToDatabase这样的名称,你可以使用与上面相同的资源名称,要改变的仅仅是用POST请求代替GET请求。同样地,对于更新硬币,可以使用PUT请求。

2. 如果需要获取单个硬币,又应该怎么做呢?我所建议的最佳方式是在端点中加入一个参数,比如说客户端需要拿到一个ID是20的硬币,那么发送一个请求到/coins/20就足够了。我们再来看一个更复杂的例子,如果要让客户端能够为每个硬币添加一张图片,一个快速而丑陋的方式是/addCoinImage或/addNewImageToCoin等等,一个稍好一点的方式是/coins/addImage,但是正如我之前所说的,不应该有任何动词存在。还记得我们之前提到的获取某种硬币的方法吗?我们可以将其稍微增强一下,发送POST请求给/coins/20/images如何?目前看起来很不错。不过天下没有完美的事物,假设一下,如果我们要让一些超级用户能够从系统中删除硬币,根据我们之前的讨论,一个简单的DELETE请求发送给/coins/{id}就足够了,但是请你想一下,如果{id}仅仅是COINS表中的一个顺序编号,那会产生多大的问题?某人可以轻易地一个接一个的发送DELETE请求,最后系统中所有的数据全没了。我想说的重点是,使用标识符作为请求参数是不错,但是前提是这些标识符必须很难猜测或根本无法猜测。所以,如果你想要用一串序号去确定一个实体,那就忘了这种实现吧。我的建议是,不要使用资源参数,直接发送一个DELETE请求给/coins,结合一个request body(比如json),其中含有足够的参数能够定位所要被删除的实体即可。

3. 尽可能使用特定领域的名称。如果你的业务域中有一群硬币收藏家(Coin Collectors),那么当你设计API的时候,应当使用collectors这个词,而不是users或accounts。要避免使用一些意义过于宽泛的名称,这些名称不能表示什么,到了客户端又容易产生误解。对于请求参数的命名,道理也是一样的。另外,强烈建议给请求参数取一个尽可能短,同时又有意义的名称,举个例子,如果你想要查找在某一指定年份发行的硬币,一个很赞的参数名称是issueYear,比较典型的反例是:year(意义不明确),yearOfFirstIssue(包含无用信息)。


错误处理和响应

对于这个话题,我的经验是让客户端在每次发送请求后,无论结果是成功还是失败,都能获得相同格式的json响应,这将会给客户端处理带来极大的帮助。举个例子,你想要添加一个新的硬币,向/coins发送POST请求,一个成功的响应包含以下json文档:

{
     "meta":{
      "code":200
   },
   "data":{
      "coinId":"a7sad-123kk-223"
   }
}

一个错误的响应可能是这样的:

{
     "meta":{
      "code":60001,
      "error":"Can not add coin",
      "info":"Missing one ore more required fields"
   },
   "data":{
   }
}

请注意,对所有可能的结果(成功或失败),json响应的文档都具备相同的结构,其中有两种基本元素:meta和data,meta包含结果信息,在出错的情况下,其中还会包含一个特殊的错误码(error code),在错误码之后,”error”表示出错的内容,”info”表示出错的具体描述;data是可选的,包含从服务器返回的所有数据,就拿上面的例子来说,当添加硬币成功后,服务器会返回一个唯一的自动生成的标识符,如果有错误,这项就为空。这种做法的优势是,对于同一个API的各种服务类型和结果,客户端都可以采用相同的方式进行处理。此外,当有意外情况发生时,我们也可以传递一些额外的信息,正如上面例子中所展示的,”error”传达信息,”info”记录日志。我们还有一种选择,可以基于错误码去处理响应,只要明确每个数字的含义即可,请注意这些数字并非http状态码,你依然要为每个请求返回正确的http状态码(如400、401等)。


在我们讨论下一节之前,我想强调另一件值得重视的事,假设我们不允许删除硬币,但是客户端尝试向/coins/{id}发送一个DELETE请求,通常情况下Web容器会返回一个405的状态码,但我发现,如果我们对这些响应进行过滤并返回相同的json文档,会很有帮助。比如我们可以返回:

{
     "meta":{
      "code":405,
      "error":"Method not allowed for the /coins/{id} resource",
      "info":"Method DELETE is not allowed for that resource. Available methods : GET, POST, OPTIONS"
   },
   "data":{
   }
}

这比原来好多了,不是吗?现在,响应内容不但包含原有的信息(405状态码),还通知客户端该资源可用的方法。


文档

最后但也是最重要的一点,花一点时间,提供一份专业的、对开发人员友好的文档,并保证及时更新,一份过期文档的危害性比没有文档更甚。你可以使用一些开源免费的工具对你的API进行文档化。再好一点的做法是,对每一项资源的使用方式都能提供范例,对成功或错误的响应都能提供预期结果。不要忘了,在最后要记录下每一个错误码并提供完整的信息,这样客户端才能在错误发生时做出反应,有一些客户端不会理会你的响应内容,它们会根据你的错误码自行提供信息。


我还有若干个更为实用的建议待写,特别是关于API的版本控制和安全性方面的建议,但我想它们更适合在另一篇博文中进行探讨。


本文转载自:http://blog.jobbole.com/70511/

共有 人打赏支持
山哥

山哥

粉丝 242
博文 306
码字总数 136465
作品 0
南京
程序员
wechat-admin:Flask使用篇

wechat-admin:Flask使用篇 小明明s à domicile2017-08-2025 阅读 Python 在 Flask最佳实践 里面有三项在本项目也有应用: 怎么用扩展 自定义RESTAPI的处理 local_settings.py 这我就不再复...

小明明s à domicile ⋅ 2017/08/20 ⋅ 0

最美应用-从Android研发工程师的角度之[最美时光]

最美应用-从Android研发工程师的角度之最美时光 @author ASCE1885的 Github 简书 微博 CSDN 最近发现最美应用这样一个网站,它会定期推介一些很有意思的app,作为开发者,每次看到很棒的app...

小树coding ⋅ 2015/08/19 ⋅ 0

web安全实践系列导航

作者:玄魂 安全技术区http://space.cnblogs.com/group/groupdetail.aspx?gid=100566 web 安全实践(1)基于http的架构分析常用工具 Web安全实践(2)基于http的web架构剖析 web安全实践(3...

吞吞吐吐的 ⋅ 2017/11/08 ⋅ 0

vakinge/jeesuite-bestpl

项目介绍 这是一个轻社区web应用,作为使用jeesuite开发分布式系统的实践项目。 麻雀虽小(业务简单),五脏俱全(常用分布式场景都有体现)。 为了演示一些分布式场景,请忽略某些业务拆分的合理...

vakinge ⋅ 2017/08/30 ⋅ 0

基于 Spring + Dubbo 开发分布式REST服务实战

本课程主要是使用 Spring技术栈 + dubbo 开发一个类似当当的图书电商后台的实战教程。 课程特点: 1.课程的技术体系足够系统、全面以及细致:课程中涉及的主要技术包括: Spring IO (依赖版本...

登录404 ⋅ 2017/05/17 ⋅ 3

RestAPI设计疑问

平台:java 对于RestApi 设计时是否需要把model(实体类)暴露给使用方 业界一般怎么做的 如果不暴露 仅仅依靠契约 那么提供方每一次修改 对于使用方来说岂不是要了命 而且使用方八成也会自己...

ZeRur_ ⋅ 2016/09/17 ⋅ 2

NUTZ-ONEKEY 3.0 新增认证中心和配置中心

NUTZ-ONEKEY 3.0 发布了。更新如下: 1. 新增 acl 模块提供 restapi ,直接可作为认证中心使用 添加通用码本模块 通用码本支持级联,排序等功能 提供通用码本restapi ,可直接作为配置中心使用 ...

Kerbores ⋅ 2017/01/23 ⋅ 5

SpringBoot项目web端请求其他的微服务提供的RestApi

SpringBoot项目web端请求其他的微服务提供的RestApi 目前我想到的有两种方案: 1. 在web端写controller采用RestTemplate方式请求其他的微服务RestApi,然后页面请求web端体提供的controller,...

微风徐徐 ⋅ 2016/11/20 ⋅ 2

PHP数组去重/高德地图API

数组去重 $term = array_column ( $grade , 'term' ); $term =arrayflip(arrayflip($term)); printr($term); 效果: 原理: arrayflip(): 函数返回一个反转后的数组,如果同一值出现了多次,...

wsy5344 ⋅ 2015/09/18 ⋅ 0

后台服务框架--JavaBaas

JavaBaas 是基于Java语言开发的后台服务框架,其核心设计目标是实现移动客户端、网页应用的后台数据存储、物理文件存储、消息推送等功能,极大的降低后台开发难度,实现快速开发。 使用 Java...

Codi ⋅ 2016/03/30 ⋅ 1

没有更多内容

加载失败,请刷新页面

加载更多

下一页

MyBatis四大核心概念

本文讲解 MyBatis 四大核心概念(SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession、Mapper)。 MyBatis 作为互联网数据库映射工具界的“上古神器”,训有四大“神兽”,谓之:Sql...

waylau ⋅ 22分钟前 ⋅ 0

以太坊java开发包web3j简介

web3j(org.web3j)是Java版本的以太坊JSON RPC接口协议封装实现,如果需要将你的Java应用或安卓应用接入以太坊,或者希望用java开发一个钱包应用,那么用web3j就对了。 web3j的功能相当完整...

汇智网教程 ⋅ 36分钟前 ⋅ 0

2个线程交替打印100以内的数字

重点提示: 线程的本质上只是一个壳子,真正的逻辑其实在“竞态条件”中。 举个例子,比如本题中的打印,那么在竞态条件中,我只需要一个方法即可; 假如我的需求是2个线程,一个+1,一个-1,...

Germmy ⋅ 48分钟前 ⋅ 0

Springboot2 之 Spring Data Redis 实现消息队列——发布/订阅模式

一般来说,消息队列有两种场景,一种是发布者订阅者模式,一种是生产者消费者模式,这里利用redis消息“发布/订阅”来简单实现订阅者模式。 实现之前先过过 redis 发布订阅的一些基础概念和操...

Simonton ⋅ 今天 ⋅ 0

error:Could not find gradle

一.更新Android Studio后打开Project,报如下错误: Error: Could not find com.android.tools.build:gradle:2.2.1. Searched in the following locations: file:/D:/software/android/andro......

Yao--靠自己 ⋅ 昨天 ⋅ 0

Spring boot 项目打包及引入本地jar包

Spring Boot 项目打包以及引入本地Jar包 [TOC] 上篇文章提到 Maven 项目添加本地jar包的三种方式 ,本篇文章记录下在实际项目中的应用。 spring boot 打包方式 我们知道,传统应用可以将程序...

Os_yxguang ⋅ 昨天 ⋅ 0

常见数据结构(二)-树(二叉树,红黑树,B树)

本文介绍数据结构中几种常见的树:二分查找树,2-3树,红黑树,B树 写在前面 本文所有图片均截图自coursera上普林斯顿的课程《Algorithms, Part I》中的Slides 相关命题的证明可参考《算法(第...

浮躁的码农 ⋅ 昨天 ⋅ 0

android -------- 混淆打包报错 (warning - InnerClass ...)

最近做Android混淆打包遇到一些问题,Android Sdutio 3.1 版本打包的 错误如下: Android studio warning - InnerClass annotations are missing corresponding EnclosingMember annotation......

切切歆语 ⋅ 昨天 ⋅ 0

eclipse酷炫大法之设置主题、皮肤

eclipse酷炫大法 目前两款不错的eclipse 1.系统设置 Window->Preferences->General->Appearance 2.Eclipse Marketplace下载【推荐】 Help->Eclipse Marketplace->搜索‘theme’进行安装 比如......

anlve ⋅ 昨天 ⋅ 0

vim编辑模式、vim命令模式、vim实践

vim编辑模式 编辑模式用来输入或修改文本内容,编辑模式除了Esc外其他键几乎都是输入 如何进入编辑模式 一般模式输入以下按键,均可进入编辑模式,左下角提示 insert(中文为插入) 字样 i ...

蛋黄Yolks ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部