设计一套良好 REST API

2024-05-27 07:08
文章标签 设计 api 良好 rest 一套

本文主要是介绍设计一套良好 REST API,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

硅谷的apigee公司给出一份对REST API的设计指导原则,可以说这家公司在api开发,管理的成绩有目共睹。其提供的指导原则,可以说结合了其自身实际开发经验,诸多大型平台的实际运营经验和标准http规范。非常值得一读。

首先,你需要对REST API有一个基本的概念认知,然后再深入阅读:

1. 基于业务领域的数据建模,而非基于功能建模。

例如,取得所有的dog

GET /api/dogs 

取得一个特定的dog

GET /api/dogs/{id}

取得特定名字的dogs

GET /api/dogs/?name=xxx

创建一个dog

POST /api/dogs

更改一个dog

PUT /api/dogs/{id}

删除一个dog

DELETE /api/dogs/{id}

标准的HTTP的方法已经提供了一套约定俗成的操作语义

而一个基于功能建模的api,通常会是下面的样子:

/getAllDogs
/getDogsByNames
/getAllBabyDogs
/createDogs
/createThreeDogs
/saveDogs

以上这些我经常在新手的代码里看到。这样做的代码,没错,可以运行,但是不‘标准’ why?基于功能建模的api,首先会造成学习曲线的增长,不容易上手,也往往意味着需要记忆大量的url(swagger会解决一部分这个问题,但不是全部)。

使用HTTP的标准方法作为操作数据的基本语义,胜在其标准的普适性。这一点上,大的平台Github,Heroku等,做的是最好的。

2. 设计数据的表现形式

毫无疑问,使用JSON,今天JSON已经是事实上的web数据标准,简单易懂。但是JSON也有其缺点,比如如何表达Date和Timestamp,一个简单的做法是用string来表达,根据上下文来判断如何解释。尽量保持JSON数据的简洁性。

并且使用link去表达资源之间的联系:

例如:

{ "id": "12345678","kind": "Dog""name": "Lassie","furColor": "brown","ownerID": "98765432"
}

更好的方法:

{"id": "12345678","kind": "Dog""name": "Lassie","furColor": "brown","ownerID": "98765432","ownerLink": "https://dogtracker.com/persons/98765432"
}

更简洁的表示:

{"id": "12345678","kind": "Dog""name": "Lassie","furColor": "brown","owner": "https://dogtracker.com/persons/98765432"
}

这样做的好处是客户端不需要自己重新构建URL去获取owner,更方便使用。Google Drive API 和 Github 均使用这种方式。但这样做也不是没有坏处,最容易想到的是,如果url改变了怎么办?production,staging,testing用的是不一样的domain哟。一个方法是:使用相对路径,而不是绝对路径。但这也不能解决全部问题。


3. 设计URL的表现形式

两大原则:规范的(regular),可预测的(predictable)。

  1. 只使用名词
  2. 要有切入点
  3. 要合适的选择id形式
  4. 要表达资源间的联系
  5. 要支持查询
  6. 要支持返回部分资源
  7. 处理更复杂的计算逻辑

只使用名词:在URL中使用名词,避免动词,一旦使用动词,意味着你是在对功能建模,而非数据。


要有切入点:原则上来讲,一个api应该有一个root path '/', 其返回一个url map,包括了所有的resouces所对应的url。这样客户端更容易去发现和使用api。


要合适的选择id形式:例如api/dogs/{id} 中的{id}如何表达,是类似于‘/dogs/1’,还是‘/dogs/haha’? 一般来说取决于后端的数据库,大多数情况下,使用RDBMS,像mysql之类的主键自增功能,我比较倾向于使用自增主键的整数直接作为entity的id,避免很多问题,如果使用MongoDB的话,不妨试一试用字符串作为entity的id,可读性会提高,但是如何维护一个全局唯一的字符主键,你得三思。


要表达资源间的联系:比如 GET /persons/1/dogs 返回所有属于person 1的狗。
这种模式可以表述为:

/{relationship-name}[/{resource-id}]/…/{relationship-name}[/{resource-id}]


要支持查询

GET /persons;1/dogs GET /persons;name=blabla/dogs 

这种模式可以表述为:

/{relationship-name}[;{selector}]/…/{relationship-name}[;{selector}]

更复杂的查询条件:

GET /dogs?color=red&state=running&location=park 

注意这里的三个查询子条件之间的关系是‘与(and)’,如果要表达是‘或(or)’的逻辑,那就得设计更复杂的query解释机制。但其实实际使用中,表达‘或’的查询条件很少使用。


要支持返回部分资源:

/dogs?fields=name,color,location 

返回的resource中,只包含name,color,location三种信息。


处理更复杂的计算逻辑: 有很多例子,比如货币转换,大多数开发人员给出的方案是:

/convert/100/EUR/CNY 或者 /convert?quantity=100&unit=EUR&in=CNY

切记:URL是用来表述资源resource,而不是表述计算的过程。在URL中使用名词,避免动词。

改进的方案1:

GET /monetary-amount/100/EUR HTTP/1.1
Host: Currency-Converter.com
Accept-Currency:CNY //在http的header中添加Accept-Currency来指明货币的种类。

改进的方案2:

POST /currency-converter HTTP/1.1
Host: Currency-Converter.com 
Content-Length: 69
{
"amount": 100,
"inputCurrency": "EUR",
"outputCurrency": "CNY"
}

两个方案都可行,但是方案2有两个注意的地方:POST返回的结果可能无法再server端缓存;你是在构建一个计算的过程,而非资源的表述,如何理解?就像数据库操作中的‘store procedure’,你可以使用,并且功能强大,但是接口变得复杂,逻辑变得耦合。


4. 反思-设计数据的表现形式。

  1. 添加self link
  2. 集合数据
  3. 数据分页
  4. 数据格式

添加self link:self link提供了一个上下文环境,客户端可以更容易理解当前的resource的位置

{"user": {"html_url": "octocat (The Octocat)","type": "User","url": "https://api.github.com/users/octocat"}
}


集合数据:

方案1:集合collection也是一种resource,也具有self和kind属性,这样所有的单独entity和collection都具有更加统一的规范

{"self": "https://dogtracker.com/dogs","kind": "Collection","contents": [{"self": "https://dogtracker.com/dogs/12344","kind": "Dog","name": "Fido","furColor": "white"},{"self": "https://dogtracker.com/dogs/12345","kind": "Dog","name": "Rover","furColor": "brown"}]
}

方案2:看起来更加简洁,但是客户端可能需要去添加额外的逻辑去处理collection

[{"self": "https://dogtracker.com/dogs/12344","kind": "Dog","name": "Fido","furColor": "white"},{"self": "https://dogtracker.com/dogs/12345","kind": "Dog","name": "Rover","furColor": "brown"}
]

还有一种做法是,针对一个collection,使用自定义的media type header(比如‘Collection+JSON’)这个方法可行,但是会让客户端的处理逻辑复杂。


数据分页:当数据返回的集合变大时,显然不可能一次性把所有数据都返回给客户端,最好能分批的返回,比如:

GET https://dogtracker.com/dogs?limit=25,offset=0 
返回
{
"self": "https://dogtracker.com/dogs?limit=25,offset=0",
"kind": "Page",
"pageOf": "https://dogtracker.com/dogs",
"next": "https://dogtracker.com/dogs?limit=25,offset=25",
"contents": [...】
}

在返回的数据中,加入‘pageOf’来指明查询的起点,‘next’指明下一页的url,当返回第二页的时候,还需加入‘previous’来指明上一页。


数据格式:现在大多数的api几乎只支持JOSN格式的数据来作为input和output,如果要支持更多的数据格式,那么应该要支持Http Accept Header。

能否用HTML作为输出的格式?可以,但是这样就丧失的rest API的灵活性。现代的web应用,大多使用REST API + SPA的设计,SPA端使用Angular等框架,自己渲染HTML,REST API只提供数据服务,前端后端通过JSON数据来交流,从而实现了前后端的彻底解耦。

如果选择JSON作为唯一的数据格式,那么最好支持Http的patch方法,现在有两种patch的模式:JSON Patch和JSON Merge Patch,选择一个来用于资源的更新操作。现在也有很多API只提供PUT来更新资源,这意味着每次请求都必须发送整个resource enrity,势必会消耗更多的payload,但是实现起来更容易。


5. 错误处理

总的原则:使用标准http的status code来表示错误的类型。具体的错误内容,也要被返回。


6. 认证和授权

人生苦短,使用OAuth2。最起码也要使用基于token的鉴权模式。


7. SDK

可以推出SDK来作为你的REST API的一个补充,就像AWS那样,针对每一个服务,都有相应的编程语言的SDK。这样更方便第三方的开发人员使用你的api。多见于SaaS平台。但是小型的平台,得考虑维护的成本。


8. Versioning 多版本

REST API的版本控制问题是一个非常有争议的话题,网上的提议有很多,在这里我们不是简单的给定具体的方法,而是提供几种可行的想法,具体的实施还需自己拿捏:

  1. 不(显式)支持多版本
  2. 使用Http Accept Header

第一种,什么都不做,不支持多版本的api。这个想法的背后依据是,根据调研发现,大多数的中小型规模的平台服务,客户规模都在一个可控的范围,api的升级不会很频繁,你只需通知你的客户,在某个时间点api会更新,然后再server端做一些兼容性的数据迁移,比如增加或删除某个数据库中的表的某个列的名字。大多数情况下,支持多版本api费力不讨好,测试和部署的成本很大,收益却很小。你要做就是保持唯一个可用api服务的兼容性,而不是提供多个版本的api让用户使用。

第二种,如果你一定要支持versioning,那么就在http的accept header中添加version信息,不要在url中使用version信息,千万不要用/api/v1/xxx。

除此之外还需要对API设计进行精简:参考如下文章

https://www.tuicool.com/articles/2MZZJz6

1.前言

对于前端开发而言,肯定会和API打交道,大家也都会想过怎么设计自己的API。优秀的 API 之于代码,就如良好内涵对于每个人。好的 API 不但利于使用者理解,开发时也会事半功倍,后期维护更是顺风顺水。至于怎么设计API,今天就提下我自己的一些建议。如果大家有什么好的想法,欢迎指点。

2.命名

良好的一个命名习惯,就是效率开发的第一步。如果命名规范,对自己而言,文件整理有很大的帮助,后期修改文件、可以快速的定位文件,命名规范,也显得自己专业。对团队而言,如果有统一的规范命名,交接时可以减少大量的学习和沟通成本。

关于命名,下面提点几个小建议

2-1.正确拼写

这个应该说是命名的一个底线了,经常性出现,单词拼写错误,搞得自己或者团队的人都一头雾水的情况不再少数。我遇到情况比较深刻的有

中文大意期望实际
表单formfrom
报名sign-upsign-in
采纳adoptadept
内容contentcontend
测试testtext
联系contactcontract
高度heightheigth
宽度widthwidht
移动mobilemoblie
标签tabtap

这些单词,如果是拼写错误还好,至少编辑器都会提醒。但是如果写错了,但是单词又是正确的单词就可大可小了(表单,报名,采纳,内容这些例子,单词写错了,意思变了,但是单词是正确的,编辑器都不会提醒)。

试过挖坑比较深的一次就是:一个活动,有报名,有签到的功能!处理方法如下

获取已报名用户信息:getSignUpUserList,

重置报名的数据:resetSignUpUser,

提交报名操作:signInDo

获取已签到用户信息:getSignInUserList,

重置签到的表单数据:resetSignInUser,

提交签到的操作:signUpDo

修改bug的时候,完全懵逼了,原因大家懂的。

2-2.注意单复数

所有涉及遍历,操作对象,数组,集合的函数,建议都采用复数。

对于展现复数,不同公司有不同的习惯,但是得统一,比如产品列表- productList 。这里用了list表示复数,再其它地方,就不建议使用 products 这种方式表示复数了

2-3.用词准确

这个主要的两方面的内容

2-3-1.单词意思搞错

比如弹窗上面的信息,有些时候见到,使用包含 notice 的字样,但是实际上, notice 的中文意思,准确的应该是‘公告,告示,声明’之类。

一个弹窗这样的会话消息,建议使用 message 这个字样。 notice 应该像‘公告,告示,声明’之类的情况使用。

2-3-2.正反词义单词错用

比如关闭弹窗的方法,的方法是 closeDialog ,然后显示弹窗用的又是 showDialog 。 show 的意思是‘显示’,反义词应该是 hide ‘隐藏’。而 close 意思是关闭,反义词应该是 open 。

附常用反义词组(有些带缩写)

inout
onoff
prevnext
showhide
closeopen
successfail
beforeafter
beginend

2-4.命名意义

这一块,本来打算放在2-2里面讲的,因为命名如果有意义也是一个底线。但是最后放在这里,是因为这个情况在函数里面出现得不多,更多应该出现在普通变量里面(相信很多人会遇到过这样的命名:var n1,n2,n3;)。关于命名,还是建议大家要起有意义名称,不使用没意义的命名。

遇到最多的情况,就是图标的命名方面。

比如下面的图标(选自某平台的底部导航栏),点击不同的图标出发不同的方法。

很多人喜欢下面的命名

//版本1
function handle1(){}
function handle2(){}
//版本2
function handleA(){}
function handleB(){}
//版本3
function handleOne(){}
function handleTwo(){}

这样的命名,别人函数了,就算是元素的 class 。这样的命名在后期维护绝对增加了难度。甚至可能导致重构。

建议的姿势

function handleHome(){}
function handleCollect(){}

2-5.命名格式

文章说的API,主要针对的是函数,但是在这一小块里面,也列举一下其它的目标的建议命名方式。

待命名对象推荐名称
图片‘-’ ‘_’ 分割
class,id‘-’ 分割
文件,变量驼峰命名
临时变量‘_’ 开头,驼峰命名

2-6.处理中文拼音

对于中文拼音,应该说只有一种情况,被中国人创造出来,没有英文翻译的。

命名含义
taobao淘宝
weibo微博
zongzi粽子
pinyin拼音

在一年多以前,遇到一个中二的命名-dengluDo。当时一直不知道是什么玩意,后来向那个人打听才知道,是执行登录的操作,denglu是中文拼音,do又是英文,这样的命名。后期如果维护,他不哭,算我输。

2-7.命名潜规则

有些情况,给特定的对象命名,还要用特定的名字,可以说是潜规则吧。印象最清楚的就是给按钮命名要么全拼,要么写btn。很清楚的记得我一个老师说过:写but,bto的程序也能正常运行,也没人说你错,但是我做面试官,就是不录用你,就说你不专业。

待命名对象推荐名称错误示范
按钮btnbut bto
背景bgback background
模板tpltem
提示信息msgmes
标签栏tabtit
网站大图(广告宣传图)bannerban
注册registersign-in

3.参数

对于函数而言,参数是用户设置最频繁,也是最关心的部分,合理设计函数参数,这一步很重要,直接影响函数的使用。

3-1.const入参

这个应该说是一个习惯吧,不要直接改变入参的值。这个规则的初衷是解决函数副作用问题。如果参数是一个引用类型的数据,如果在函数内修改了参数,到时候将会使得原本的数据发生改变,往往会发生难以追踪的问题。

3-2.控制参数数量

参数的数量,个人建议就是,超过3个,使用对象进行封装。因为如果API参数越多,那么使用对于这个API的记忆成本就越大,易用性也很受影响。

比如下面的例子:

encryptStr: function (str, regArr, type, replacement) {var regtext = '',Reg = null,_type=type||0,replaceText = replacement || '*';//ecDo.encryptStr('18819322663',[3,5,3],0)//result:188*****663//repeatStr是在上面定义过的(字符串循环复制),大家注意哦if (regArr.length === 3 && type === 0) {regtext = '(\\w{' + regArr[0] + '})\\w{' + regArr[1] + '}(\\w{' + regArr[2] + '})'Reg = new RegExp(regtext);var replaceCount = this.repeatStr(replaceText, regArr[1]);return str.replace(Reg, '$1' + replaceCount + '$2')}//ecDo.encryptStr('asdasdasdaa',[3,5,3],1)//result:***asdas***else if (regArr.length === 3 && type === 1) {regtext = '\\w{' + regArr[0] + '}(\\w{' + regArr[1] + '})\\w{' + regArr[2] + '}'Reg = new RegExp(regtext);var replaceCount1 = this.repeatStr(replaceText, regArr[0]);var replaceCount2 = this.repeatStr(replaceText, regArr[2]);return str.replace(Reg, replaceCount1 + '$1' + replaceCount2)}//ecDo.encryptStr('1asd88465asdwqe3',[5],0)//result:*****8465asdwqe3else if (regArr.length === 1 && type === 0) {regtext = '(^\\w{' + regArr[0] + '})'Reg = new RegExp(regtext);var replaceCount = this.repeatStr(replaceText, regArr[0]);return str.replace(Reg, replaceCount)}//ecDo.encryptStr('1asd88465asdwqe3',[5],1,'+')//result:"1asd88465as+++++"else if (regArr.length === 1 && type === 1) {regtext = '(\\w{' + regArr[0] + '}$)'Reg = new RegExp(regtext);var replaceCount = this.repeatStr(replaceText, regArr[0]);return str.replace(Reg, replaceCount)}
}

大家可以看上面的注释,就知道这段代码的具体作用了,如果想想就找个参数,我必须要除了记得4个参数的作用,还要记得参数的顺序。

如果使用对象记录参数,用户只需要记得4个参数的作用,不需要记参数的顺序。

encryptStr: function (obj) {var _default={type:0,replacement:'*'};for(var key in obj){_default[key]=obj[key];}
},//调用方式
ecDo.encryptStr({str:'18819266335',regArr:[5],type:0,replacement:'-'});

这样还有一个好处就是,比如像刚才的函数,type这个参数,我想保留默认值,偷懒不传。原来的方案,就得这样传。

ecDo.encryptStr('1asd88465asdwqe3',[5],'','+');

这样肯定是会激起不少有代码洁癖的开发者,比如我。如果使用对象,就很好避免了。

ecDo.encryptStr({str:'18819266335',regArr:[5],replacement:'-'});

3-3.前置相关性高的参数

这个应该没什么可能,就一个意思:必填重要的参数前置,可省略的参数后置。

比如下面的例子

/格式化处理字符串
//ecDo.formatText('1234asda567asd890')
//result:"12,34a,sda,567,asd,890"
//ecDo.formatText('1234asda567asd890',4,' ')
//result:"1 234a sda5 67as d890"
//ecDo.formatText('1234asda567asd890',4,'-')
//result:"1-234a-sda5-67as-d890"
formatText: function (str, size, delimiter) {var _size = size || 3, _delimiter = delimiter || ',';var regText = '\\B(?=(\\w{' + _size + '})+(?!\\w))';var reg = new RegExp(regText, 'g');return str.replace(reg, _delimiter);
},

调用大家都看得出来。如果API这样设计

formatText: function (size, delimiter, str) {var _size = size || 3, _delimiter = delimiter || ',';var regText = '\\B(?=(\\w{' + _size + '})+(?!\\w))';var reg = new RegExp(regText, 'g');return str.replace(reg, _delimiter);
},

就得这样调用,如果这样写API,被批斗的可能性很大!

ecDo.formatText('','','1234asda567asd890')

4.作用

4-1.支持批量处理

比如这个例子,页面有这样的元素

<div class="div1"></div>
<div class="div1"></div>
<div id="div2"></div>

有一个类似jQuery的css这个API的API。

css: function (dom, json) {for (var attr in json) {dom.style[attr] = json[attr];}
}

然后给这些div设置样式的时候,代码如下

var oDiv1 =document.querySelectorAll(".div1");
var oDiv2=document.querySelector("#div1");
ecDo.css(oDiv2,{'height':'100px','width':'100px','background':'#333'});
ecDo.css(oDiv1,{'height':'100px','width':'100px','background':'#09f'});

当运行到 ecDo.css(oDiv1,{'height':'100px','width':'100px','background':'#09f'}); 会提示报错,原因大家也知道。css这个API里面,只处理了单个元素,并没有处理元素的集合。

建议的方式是把 css 这个API改成可批量处理元素集合的。

css: function (dom, json) {if (dom.length) {for (var i = 0; i < dom.length; i++) {for (var attr in json) {dom[i].style[attr] = json[attr];}}}else {for (var attr in json) {dom.style[attr] = json[attr];}}
},

4-2.多态处理

一个类似jQuery的html这个API的API-html

之前遇到一个开发者的处理方式是:获取元素的innerHTML和设置元素innerHTML分开为两个方法-getHtml,setHtml。这样的问题又在于记忆的成本比原生的 innerHTML 还要高。建议的姿势就是,获取和设置用同一个API。

html: function (dom) {if (arguments.length === 1) {return dom.innerHTML;} else if (arguments.length === 2) {dom.innerHTML = arguments[1];}
}ecDo.html(oDiv);//获取
ecDo.html(oDiv,'守候');//设置

4-3.可扩展性

可扩展性,就是建议遵守开放-封闭原则。对扩展开放,对修改关闭。比如jQuery的$.fn和$.fn.extend()。

说一个简单的例子-计算加薪额度

var addMoney = (function () {//定义策略类var strategies = {A:function(money){return money + 2000;},B:function(money){return money + 1000;}};//暴露接口return {//根据等级和现工资,输入加薪后的工资compute:function(lv,money){return strategies[lv](money)}};
})();//比如:等级为A,5000+2000
console.log(addMoney.compute('A',5000))//7000
//比如:等级为B,20000+1000
console.log(addMoney.compute('B',20000))//21000

代码看着没有问题,但是如果以后需求要增加C等级呢?这就不得不修改strategies。在里面增加方法。

如下

var strategies = {A:function(money){return money + 2000;},B:function(money){return money + 1000;},C:function(money){return money + 500;}
};

这样实现也简单,如果以后要增加S等级呢?又得改strategies。这里还有一个问题就是,如果增加的C等级只有在A模块需要用到,在B模块不会出现,那么在B模块引用addMoney的时候,又会把C等级的计算方式也引入进去,造成不必要的资源浪费。

建议的方式是,设置一个接口,扩展strategies。

var addMoney = (function () {//定义策略类let strategies = {A:function(money){return money + 2000;},B:function(money){return money + 1000;}};//暴露接口return {//根据等级和现工资,输入加薪后的工资compute:function(lv,money){return strategies[lv](money)},//扩展等级addRule:function(lv,fn){strategies[lv]=fn;}};
})();
//增加C等级的调用
addMoney.addRule('C',function(money){return money + 500;
});
console.log(addMoney.compute('C',20000))//20500

4-4.避免副作用

函数的副作用,相信很多人都会遇到过,比如在一个函数体内修改一个外部作用域的变量,或者全局变量,在函数体内修改引用类型的参数,这些情况多少都会遇到过。

如何避免呢?主要是以下两个写代码习惯。

1.函数体内可以使用参数,进行操作,但是不能修改。如果修改,用一个临时变量记录参数(如果是引用类型,需要用深拷贝记录)。这样可以避免直接修改参数。

2.对于函数外的变量,如全局变量。函数体内可以访问,但是不能修改。

3.如果需要给函数外的变量赋值,不能在函数体内操作,把值返回到外部,在外部进行赋值。(感觉这里有点啰嗦,因为赋值了,就是修改了外部变量,就违反了第二点)。

//不好做法
var myName='';
function setName(firstName,lastName){myName=firstName+lastName;
}
setName('守','侯');
//推荐做法
var myName='';
function setName(firstName,lastName){return firstName+lastName;
}
myName=setName('守','侯');

5.向下兼容

这个建议主要就是为了兼顾以前的写法。还是拿上面的那个例子吧!

原本传参方式是这样

encryptStr: function (str, regArr, type, replacement) {};

后来升级改成这样

encryptStr: function (obj){}

这样问题就来了,一个项目里面,因为历史的原因难免会使用这个API,并且使用了第一种方式传参。现在API改了,解决的方案有两个,要么把整个项目使用的这个API的方式,都改成第二种的传参方式,要么就是对接口进行向下兼容,兼容以前的方案。

encryptStr: function (obj) {var _default={type:0,replacement:'*'};//如果还是以之前的方式调用函数,兼容性判断if(arguments.length>1){_default.str=arguments[0];_default.regArr=arguments[1];_default.type=arguments[2]||0;_default.replacement=arguments[3]||'*';}else{for(var key in obj){_default[key]=obj[key];}}//下面代码略
},

如果API已经准备来一个大版本的更新,(比如从1.0.0升级到2.0.0,不是1.0.0升级到1.0.1,或者1.0.0升级到1.1.0)。不打算兼容以前的版本了。可以忽略这一步,毕竟兼容性的代码可能也很多。

6.简单

这一步可以说是API设计最高级的一步,也是最难开发的一步,这就是为什么这篇文章会带有‘大道至简’的字样,即使API的实现很难,但使用起来简单感觉就是高级的API。这一步也直接影响API的好用与否。简单的API不但是用起来简单,试试可以一看就懂的API。这样的API更易理解、记忆、调试和变更使用方式。

原生的API,比如Date,some、map、find等所有数组遍历操作函数,es6提供的Object.assign,Object.keys,Object.values等。

曾经的霸主jQuery,现在的王者react,黑马vue。这些项目让人拍手称赞的原因虽然有很多,但也不可否认的,那便是它们的API设计非常的巧妙。如:jQuery的$,siblings,toogleClass,animate等,react的cloneElement,replaceProps等,vue的nextTick,set等。

jQuery对于现在而言,虽然是过时了,但里面的知识还是值得学习,比如使用的淋漓尽致的 js 写作技巧,设计模式,以及 API 设计等。

自己写的API,我也是把API写得尽量的简单,最高境界就是让别人扫一眼文档,就知道记牢了API的使用方式。这个是我追求的目标,只是现在距离还是有点远。大家看我 encryptStr 这个API就知道(此处尴尬一天)。

7.小结

在我的眼里,一个好的API,会有一个一看就懂的名字,一个强大的功能,一个简单的调用方式。虽然只有三个条件,但是这三个条件结合起来,可不是那么容易做到的。一个好的API,无论是对自己,对团队,对项目开发都是一个很好的帮助。

对于设计API的一些个人建议,就到这里了,如果以后有更好的想法,会第一时间分享,和大家交流意见。如果大家有什么想法,欢迎指点迷津。


总结

https://docs-apis.apigee.io/files/Web-design-the-missing-link-ebook-2016-11.pdf​docs-apis.apigee.io

在实际的工作中,对于刚入职的小盆友,在介绍REST API的时候,我会推荐他们读这个。

这篇文章给出的是原则上的指导,对于开发团队,我建议在团队内部要制定一个更具体详细的规范specification,具体的可以参考:Zalando RESTful API and Event Scheme Guidelines其中的规范涵盖了很多细节内容,细节的规范越多,代码风格才越统一,团队沟通效率才越高。

这篇关于设计一套良好 REST API的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1006783

相关文章

Python中的可视化设计与UI界面实现

《Python中的可视化设计与UI界面实现》本文介绍了如何使用Python创建用户界面(UI),包括使用Tkinter、PyQt、Kivy等库进行基本窗口、动态图表和动画效果的实现,通过示例代码,展示... 目录从像素到界面:python带你玩转UI设计示例:使用Tkinter创建一个简单的窗口绘图魔法:用

使用SpringBoot创建一个RESTful API的详细步骤

《使用SpringBoot创建一个RESTfulAPI的详细步骤》使用Java的SpringBoot创建RESTfulAPI可以满足多种开发场景,它提供了快速开发、易于配置、可扩展、可维护的优点,尤... 目录一、创建 Spring Boot 项目二、创建控制器类(Controller Class)三、运行

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

怎么让1台电脑共享给7人同时流畅设计

在当今的创意设计与数字内容生产领域,图形工作站以其强大的计算能力、专业的图形处理能力和稳定的系统性能,成为了众多设计师、动画师、视频编辑师等创意工作者的必备工具。 设计团队面临资源有限,比如只有一台高性能电脑时,如何高效地让七人同时流畅地进行设计工作,便成为了一个亟待解决的问题。 一、硬件升级与配置 1.高性能处理器(CPU):选择多核、高线程的处理器,例如Intel的至强系列或AMD的Ry

基于51单片机的自动转向修复系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订阅👇🏻 单片机

SprinBoot+Vue网络商城海鲜市场的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 application.yml3.5 SpringbootApplication3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍:CSDN认证博客专家,CSDN平台Java领域优质创作者,全网30w+

【LabVIEW学习篇 - 21】:DLL与API的调用

文章目录 DLL与API调用DLLAPIDLL的调用 DLL与API调用 LabVIEW虽然已经足够强大,但不同的语言在不同领域都有着自己的优势,为了强强联合,LabVIEW提供了强大的外部程序接口能力,包括DLL、CIN(C语言接口)、ActiveX、.NET、MATLAB等等。通过DLL可以使用户很方便地调用C、C++、C#、VB等编程语言写的程序以及windows自带的大

如何更优雅地对接第三方API

如何更优雅地对接第三方API 本文所有示例完整代码地址:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/third 我们在日常开发过程中,有不少场景会对接第三方的API,例如第三方账号登录,第三方服务等等。第三方服务会提供API或者SDK,我依稀记得早些年Maven还没那么广泛使用,通常要对接第三方

单片机毕业设计基于单片机的智能门禁系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍程序代码部分参考 设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订

Spring的设计⽬标——《Spring技术内幕》

读《Spring技术内幕》第二版,计文柯著。 如果我们要简要地描述Spring的设计⽬标,可以这么说,Spring为开发者提供的是⼀个⼀站式的轻量级应⽤开发框架(平台)。 作为平台,Spring抽象了我们在 许多应⽤开发中遇到的共性问题;同时,作为⼀个轻量级的应⽤开发框架,Spring和传统的J2EE开发相⽐,有其⾃⾝的特点。 通过这些⾃⾝的特点,Spring充分体现了它的设计理念:在