原文链接:Web API design best practices – Azure Architecture Center | Microsoft Docs
当初网络上曾经有了很多服务商的公开API,能够让各类客户端调用,那么怎么才是一个设计低劣的web API呢?一般来讲应该具备以下规范:
- 平台无关性:应用API的能够是任何客户端,它们不必关怀API是怎么实现的。这就要求了交互时应用到的协定要标准化,并且要存在一种机制,能确保客户端和服务提供方在数据格式上达成统一。
- 服务演变: web API能够自行更新迭代本人的性能,应用它的客户端不必做出任何批改就能持续应用这些API。服务端提供的所有性能要具备可发现性,使得客户端能充沛应用到它们。
上面来说说设计web API时要思考的一些关键问题。
什么是REST?
在2000年,Roy Fielding提出应用表述性状态转移(Representational State Transfer ,简称REST)来设计网络服务的构建办法。REST是一种基于超媒体来构建分布式系统的架构格调,它不应关注底层服务如何,也不必跟HTTP绑定,不过大部分REST API的实现还是基于了HTTP协定。让咱们先关注下如何应用HTTP来设计REST API接口。
在HTTP上应用REST的益处是它是一个有公开规范的协定,不须要这些API的提供方或应用方依赖任何特定实现计划,服务方和应用方能够用任意语言、工具包来提供REST服务实现,或创立HTTP申请以及解析HTTP响应报文。
基于HTTP设计RESTful API的次要准则有:
- REST API的外围是资源,能够是客户能拜访到的任意物体、数据或服务
-
每种资源都要有一个举世无双的URI来作为惟一标识符定位到该资源。比方一种客户订单能够这样形容:
https://adventure-works.com/orders/1
-
客户通过替换资源表述来与服务交互。许多web API应用JSON作为数据转换格局。例如,一个针对下面URI的GET申请可能失去如下内容:
{"orderId":1,"orderValue":99.90,"productId":1,"quantity":1}
- REST API应用一套对立的接口,来帮忙解耦调用端和服务实现。在HTTP上构建REST API时,对立接口应用规范的HTTP动词来执行资源的操作,常常应用到的操作有GET,POST,PUT,PATCH和DELETE。
- REST API是无状态的。因为收回的HTTP申请是独立且无序的,因而没方法在申请间放弃这种长期会话状态。能存储信息的中央只能是API资源本身,而每个申请该当是原子操作。这样的束缚要求客户和特定服务器间不能保留任何关联,从而使得服务具备了高度的可伸缩性。任意的服务器能够解决任何的客户申请。然而,其余因素可能会限度这种可伸缩性,比方很多服务都要写数据到后端存储中,而这种繁多存储就很难扩大。对于这种数据存储该如何扩大的策略办法,能够参考Horizontal, vertical, and functional data partitioning.
-
REST API的重要驱动外围是其表述内容中蕴含的超媒体链接。举个例子,上面展现了一个蕴含订单信息的JSON格局内容,它蕴含了一些链接,用来取得或更新与该订单关联的客户数据。
{ "orderID":3, "productID":2, "quantity":4, "orderValue":16.60, "links": [ {"rel":"product","href":"https://adventure-works.com/customers/3", "action":"GET" }, {"rel":"product","href":"https://adventure-works.com/customers/3", "action":"PUT" } ] }
在2008年,Leonard Richardson提出了一个web API成熟度模型:
- Level 0:仅有一个URI,用来解决所有POST申请 .
- Level 1:不同的资源提供独自的URI申请门路.
- Level 2:应用HTTP的method来定义在资源上的不同操作.
-
Level 3:应用了超媒体(HATEOS,上面会提到)
围绕资源设计API
web API须要裸露一些业务实体, 拿电商零碎来举例,次要的实体基本上就是客户和订单了。能够发送一个蕴含了订单信息的HTTP POST申请来创立一个订单,而响应报文该当告知这个订单是否创立胜利。请记得,提供进去的这些资源操作URI应尽量应用名词(资源名)来形容,而非动词(操作动作)。
https://adventure-works.com/orders // Good https://adventure-works.com/create-order // Avoid
一个所谓的资源不肯定要是一个实在的物理实体,比方对于订单来说,它可能在外部实现上用到了数张关系型数据库中的表,然而对于客户而言,它就是一个独自的实体。不要创立一些仅仅是把数据库对象做了个简略镜像展现的API。REST的指标是形容实体和可在其上执行的操作,而客户不应接触到外部实现。
实体通常会有其汇合状态(一组订单、一些顾客)。相比与其中的繁多实体,一个汇合也是一种独自的资源,也应提供其独有的拜访URI。例如上面这个URI就示意了一组订单:
https://adventure-works.com/orders
应用HTTP GET调用该URI,就会失去一组数据,而其中每一个元素也应有其独自的拜访URI,通过HTTP GET办法拜访它们,就能失去每一个元素的详细信息。
采纳一个对立的命名标准来定义这些URI。通常提供的内容是汇合时应该应用名字的复数模式。一个很好的做法是在汇合和元素间提供层级构造,比方应用/customers
来代表客户汇合,而/customers/5
则指向ID是5的这个独自客户。这样做会让你的web API很直观。并且很多web API框架提供了参数URI门路的反对,所以你能够应用/customers/{id}
来做资源路由。
另外须要思考的是不同资源间的关系,和你应该如何暴露出这种分割。例如咱们晓得/customers/5/orders
应该代表了顾客5的所有订单,然而如果门路是从订单开始,列出其关联的所有客户的URI就可能是/orders/99/customer
。然而,这种模式的过分扩大实现起来会很繁冗轻便。一个更好的计划是,在HTTP的响应报文体中提供关联资源的导航链接。咱们会在上面章节开展具体探讨。
在一些简单零碎中,像/customers/1/orders/99/products
这样的提供给客户端多级关系门路拜访的URI,看起来仿佛很不便,然而这样的简单级别会让保护变得艰难,并且难以在未来调整资源间的层级关系。因而,好的做法是让资源URI尽量简单明了,一旦应用程序有了对资源的援用,就应该能够应用这个援用来查找与该资源相干的项。比方后面查找客户1所有订单的查问URI能够替换为/customers/1/orders
,而后通过/orders/99/products
来失去订单中的所有商品。
留神:不要应用比
/collection/item/collection
更简单的查问URI。
另外要思考每次申请对服务器的负载影响。申请越多,负载越高。因而,不要提供太多过于细小琐碎的资源接口。这样的接口可能须要客户端执行屡次申请能力失去想到的数据。能够思考把一些相干数据组合到一个资源中,以使得一次查问申请便能满足需要。不过,你还是得做好衡量,来防止拿到过多的无用数据。另外,检索的数据过大,还会让申请变得迟缓,并产生额定的带宽老本。跟多对于性能错误模式的介绍,可参看Chatty I/O 和 Extraneous Fetching.
要防止让web API和底层数据库产生依赖关系,例如,当你应用了关系数据库存储数据,web API并不需要把所有的表都裸露为资源汇合,这样设计很不好,适合的做法是,把web API当成是数据库的形象,或者能够思考应用一个映射层来建设数据库和web API的映射关系。这样客户端就能够与底层数据库隔离开来,从而在底层数据表变动时不受到影响。
最初,可能不太容易做到将所有web API实现的操作找到适合的资源形容来建设映射关系,一些场景里,收回的HTTP申请只是用来执行了某个函数,而后将后果作为HTTP响应报文返回,比方用作简略加减计算的web API,可能会应用伪资源作为URI,并将查问字符串当作计算的参数。例如,一个URI为/add?operand1=99&operand2的GET申请,会失去一个报文内容为100的返回后果。不过最好还是有节制的应用这种模式的URIs。
通过HTTP办法定义API操作
HTTP协定定义了一些有非凡语义的申请办法,在RESTful接口中最罕用到的有:
- GET 获取指定URI表述的资源数据,响应报文里蕴含申请资源的具体内容。
- POST 应用指定URI创立资源对象,并返回对象的具体内容。留神POST有时也用来触发实际上并不会创立资源的操作。
- PUT 依据申请音讯的不同指定,创立或更新指定URI的资源对象
- PATCH 能够在申请体中指定一系列变更内容,来执行对应资源的局部更新动作
- DELETE 应用指定URI删除资源
资源是汇合还是独自个体,会使得申请的成果有所不同。上面总结了一些电商零碎中常见的RESTful实现常规。有些申请没有解决 – 这取决于场景。
Resource | POST | GET | PUT | DELETE |
---|---|---|---|---|
/customers | 创立一个新客户 | 检索所有客户 | 批量更新客户 | 删除所有客户 |
/customers/1 | 无 | 检索客户1的详细信息 | 更新客户1的详细信息(若存在) | 删除客户1 |
customers/1/orders | 为客户1创立一个新订单 | 检索客户1的所有订单 | 批量更新客户1的所有订单 | 删除客户1的所有订单 |
强调下POST、PUT、PATCH办法的差别:
- POST申请会创立一个资源,服务端会给新创建的资源分配一个URI,并将其返回给客户端。在REST模式下,常常会对汇合应用POST操作,新资源被创立后便被退出到汇合中。POST申请也用在提交数据给现存资源来实现一些操作,过程中并不会有新资源被创立。
- PUT申请用来创立新资源或更新曾经存在的资源。客户端指定资源的URI,并在申请体中蕴含资源的残缺示意。如果指定的资源曾经存在,它便会被申请内容替换掉,否则便会创立一个新资源(如果服务端反对该操作)。PUT申请更多的用在独自资源上,而不是汇合资源,比方一个特定的客户。服务端通常会反对PUT申请的更新操作,但不肯定会反对创立资源,这取决于客户端是否能够在资源不存在时候派新URI给它。如果不反对,请应用POST来创立新资源,而后用PUT或PATCH来更新它。
- PATCH申请用来执行已存在资源的局部更新。客户端指定资源的URI,并在申请体中携带须要执行变更的内容汇合。这种做法会比PUT更无效,鉴于客户端只须要发送变更的局部,而不是整个资源数据。从技术上来说PATCH也能够创立资源,通过把所有资源变更数据都指定为null来实现,不过最终要看服务端是否反对这种做法。
PUT申请必须是幂等的。如果客户端发送了屡次同样的申请,失去的后果应该是一样的(同样的资源被变更为了同样的内容)。POST和PATCH则没有这样的要求。
恪守HTTP语义
本节介绍HTTP标准下须要思考的一些常见注意事项,没有涵盖所有可能细节和场景,如有疑难请参阅HTTP标准。
Media types
下面提到,客户端和服务端会替换资源数据。比方在POST申请中,申请体需蕴含要创立资源的表述,而GET申请中,响应报文体中也会蕴含检索资源的表述内容。
在HTTP协定里,格局通过media types来指定,也被称为MIME types。对于非二进制数据,大部分web API提供了JSON(media type = application/json)或者XML(media type = application/xml)格局的反对。
在申请或响应中通过Content-Type音讯头来指定数据格式。上面有一个蕴含了JSON数据的POST申请示例:
POST https://adventure-works.com/orders HTTP/1.1
Content-Type: application/json; charset=utf-8
Content-Length: 57
{"Id":1,"Name":"Gizmo","Category":"Widgets","Price":1.99}
如果服务端不反对申请的media type,那么它应该返回415这个HTTP状态码(Unsupported Media Type)。
客户端申请能够携带一个Accept的音讯头来向服务端表明它能够承受报文的media type列表,例如:
GET https://adventure-works.com/orders/2 HTTP/1.1
Accept: application/json
如果服务端不能满足列出的media type申请,它应该返回HTTP状态码406(Not Acceptable)。
GET办法
GET申请胜利后通常应返回状态码200(OK)。如果申请的资源不存在,则应返回404(Not Found)。
POST办法
如果POST申请创立了新资源,它应返回HTTP状态码201(Created)。新创建资源的URI应在响应报文头Location中携带,而报文体则应蕴含该资源的表述。
如果办法做了一些解决,然而并没有创立新资源,它同样能够返回200状态码,并把处理结果放在响应报文中,而当并没有任何处理结果须要返回时,能够应用204状态码(No Content)来代替,并返回空响应报文。
如果客户端在申请中携带了有效数据,服务端应该返回状态码400(Bad Request),响应报文中则能够放入对于谬误的具体阐明信息,或者提供一个链接以用来查看更多信息。
PUT办法
如果PUT办法创立了新的资源,它会返回201状态码(Created),跟POST一样。如果它更新了一个存在的资源,能够返回200(OK)或者204(No Content)。某些状况下,可能无奈更新指定的资源,这时能够思考返回HTTP状态码409(Conflict)。
能够思考提供一个批量更新PUT办法,在办法体里放上要更新的所有资源内容,并应用URI指明要更新的资源汇合。这样能升高网络开销和进步性能。
PATCH办法
应用PATCH申请时,客户端应用一种 补丁文件 的格局,向曾经存在的数据资源发送更新申请。服务端会应用这个补丁文件解决更新。补丁文件并不需要形容资源的所有内容,只须要通知更新哪些局部即可。PATCH办法的标准 (RFC 5789)并没有定义这个补丁文件要有什么样的特定格局,格局须要依据申请的media type而定。
JSON应该是web API届最通用的数据格式了。而patch办法用到的基于JSON的补丁文件格式有两个,叫做JSON patch和JSON merge patch。
JSON merge patch绝对简略一点。它的构造就像是原始资源对象的JSON模式内容,只是它蕴含的仅仅是须要更新或者增加的数据字段汇合,在补丁文件中通过将字段指定为 null
甚至还能够删除字段(不过不适用于原始资源能够显式的蕴含’null’值的状况)
举个例子,如果原始资源对象的JSON模式内容为:
{
"name":"gizmo",
"category":"widgets",
"color":"blue",
"price":10
}
可能对应的更新文件的内容会是这样:
{
"price":12,
"color":null,
"size":"small"
}
这个文件通知服务端,要更新 price
,删掉 color
,并增加 size
字段,同时 name
和 category
字段没有任何改变。无关JSON merge patch的更多具体内容能够参考 RFC 7396。它对应的media type为 application/merge-patch+json
。
Merge patch并不适用于本来资源中蕴含显式’null’值的状况,因为补丁文件中的null有非凡含意(代表删除)。并且补丁文件不能指定更新被执行的程序,不过这个有没有具体影响要看数据对象的解决逻辑。在RFC 6902中定义的JSON patch,绝对就灵便一些。它能够指明具体要执行的操作序列,操作的类型能够是减少、删除、替换、拷贝和测试(用来验证特定值)。它的media type是 application/json-patch+json
。
下表列出了一些解决PATCH申请时可能会遇到的一些典型谬误条件,和对应适当的HTTP状态码。
Error condition | HTTP status code |
---|---|
The patch document format isn’t supported. | 415 (Unsupported Media Type) |
Malformed patch document. | 400 (Bad Request) |
The patch document is valid, but the changes can’t be applied to the resource in its current state. | 409 (Conflict) |
DELETE办法
如果一个删除申请被胜利执行,web服务端应该返回一个204状态码(No Content),这代表着执行曾经胜利,响应报文不须要有任何内容。如果申请的资源不存在,应该失去一个HTTP 404(Not Found)。
异步操作
有时一个POST、PUT、PATCH或者DELETE操作可能会须要执行一段时间,如果在胜利相应之前始终期待,过长的提早可能不太好承受。这时能够思考将操作改成异步来执行。返回一个202(Accepted)状态码来表明申请曾经承受并在解决中,然而还没有实现。
你应该裸露一个端点,来提供异步申请执行状态的查问,这样客户端就能轮询它来监测状态。能够在202报文头中蕴含这个状态端点的地址,如:
HTTP/1.1 202 Accepted
Location: /api/status/12345
如果客户端向该端点发动了一个GET申请,响应报文中应该蕴含操作的执行状态。另外,也能够提供其余一些信息,比方预计要耗费的工夫和一个能够勾销操作的链接。
HTTP/1.1 200 OK
Content-Type: application/json
{
"status":"In progress",
"link": { "rel":"cancel", "method":"delete", "href":"/api/status/12345" }
}
如果异步操作创立了新的资源,那么这个状态端点应该在操作执行实现后返回一个303(See Other),并在响应报文头信息中提供新资源的拜访地址。
HTTP/1.1 303 See Other
Location: /api/orders/12345
更多相干信息,可参考 Asynchronous Request-Reply pattern.
数据过滤和分页
提供一个能够查问资源汇合的URI,可能会呈现客户端其实只须要一小部分数据,但却发动了一个很大数据量的申请。比方,客户程序须要查问所有老本大于某个值的所有订单,那么可能它会先申请 /orders 这个URI拿到所有订单数据,而后在客户端把这些数据作一下过滤。很显然这样解决十分的不效率,而且节约了贷款和服务器算力。
绝对的,这个API也能够提供申请URI时携带查问字符串的反对,比方 /orders?minCost=n
。而后服务端解析解决这个 minCost
查问项,并返回过滤后的后果。
一个对资源汇合的GET申请总是有可能会返回一大堆数据,所以最好在你的web API中提供对单次申请返回后果数量的限度。能够思考提供一个最大数量申请查问参数,来指明能失去的最大后果数量,同时须要提供一个偏移量参数,例如:
/orders?limit=25&offset=50
同时为了防止可能受到的拒绝服务攻打,要对这个最大数量参数设置个下限。另外,为了帮助客户应用程序实现分页配置,服务端对GET申请返回分页数据时,要在响应报文中蕴含一些元数据,用来表明资源可用的总页数等。
相似的,还能够提供一个sort参数用来对申请数据排序,能够应用后果字段中的任意一个,比方 /orders?sort=ProductID
。不过这种做法有一个害处,因为有些缓存实现用查问字符串作为缓存的key,sort参数的调整可能会让之前的缓存生效。
在查问数据的字段数量特地多时,还能够对返回字段作一下限度。提供一个反对逗号分隔的查问字符串参数来示意须要的字段列表,比方 /orders?fields=ProductID,Quantity
。
对每一个反对的查问字符串参数,提供一个有意义的默认值。例如,如果反对分页,最好将数据条数默认为10,偏移量(页码)默认为0。如果反对排序,将sort列默认为资源的主键,如果反对投影(可选查问列),将参数默认为所有列。
大型二进制文件的局部响应
有些资源可能蕴含二进制字段,比方图片和文件。这些内容在不稳固网络环境下传输可能会呈现问题,比方连贯中断,或者解决工夫过长,为了克服这些问题,能够思考分块获取。首先,API须要对GET申请中的Accept-Ranges这个用来申请大文件资源的音讯头提供反对,这个音讯头表明GET操作反对局部申请,客户端能够发动一个指定了字节数范畴的GET申请,来失去一部分资源数据。
同时,最好也实现对这些资源的HTTP HEAD申请解决。HEAD申请相似于GET,只不过它只返回形容资源的HTTP音讯头,而音讯体是空的。客户程序能够收回HEAD申请来判断是否须要应用GET局部申请形式获取资源。例如:
HEAD https://adventure-works.com/products/10?fields=productImage HTTP/1.1
对应的响应报文为:
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Type: image/jpeg
Content-Length: 4580
Content-Length
报文头给出了资源的总大小,而 Accept-Ranges
报文头则表明对应的GET操作反对局部后果的申请。有了这些信息,客户程序便能够把图片分成小块获取。第一个申请应用了 Range
头获取了2500个字节:
GET https://adventure-works.com/products/10?fields=productImage HTTP/1.1
Range: bytes=0-2499
响应报文应用HTTP状态码206来表明这是残缺内容的一部分。Content-Length
报文头阐明了音讯体内的理论字节数(而非资源的残缺大小),Content-Range
报文头则表明这是整个资源的哪一部分(残缺4580中的0-2499这部分)
HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Content-Type: image/jpeg
Content-Length: 2500
Content-Range: bytes 0-2499/4580
[...]
客户程序能够发动后续申请来取得资源的残余局部。
应用资源导航 (HATEOAS)
REST有一个推崇的概念,该当在不须要先理解URI计划的状况下,做到对整个资源集的导航。每一个HTTP GET申请的响应报文,都应该蕴含一系列超链接信息,用来获取跟申请对象有间接关系的资源,并提供这些资源上的可用操作阐明。这个准则被称为 HATEOAS ,即 Hypertext as the Engine of Application State
。这个零碎实际上是一种无限状态机,申请的响应报文蕴含了从一种状态转移到另一种状态的必要信息,跟状态转换无关的内容则不应蕴含在内。
以后并没有针对HATEOAS准则的通用建模规范,本节示例只展现了一种特定用处下的计划。
比方,解决订单和客户关系时,能够在订单的体现上蕴含一些链接,用来标识跟这个订单相干客户的一些可用操作。比方:
{
"orderID":3,
"productID":2,
"quantity":4,
"orderValue":16.60,
"links":[
{
"rel":"customer",
"href":"https://adventure-works.com/customers/3",
"action":"GET",
"types":["text/xml","application/json"]
},
{
"rel":"customer",
"href":"https://adventure-works.com/customers/3",
"action":"PUT",
"types":["application/x-www-form-urlencoded"]
},
{
"rel":"customer",
"href":"https://adventure-works.com/customers/3",
"action":"DELETE",
"types":[]
},
{
"rel":"self",
"href":"https://adventure-works.com/orders/3",
"action":"GET",
"types":["text/xml","application/json"]
},
{
"rel":"self",
"href":"https://adventure-works.com/orders/3",
"action":"PUT",
"types":["application/x-www-form-urlencoded"]
},
{
"rel":"self",
"href":"https://adventure-works.com/orders/3",
"action":"DELETE",
"types":[]
}]
}
在这个例子里,links这个数组节点蕴含了一个链接数据汇合,每个链接数据都代表了一个相干实体的可用操作。每个链接数据又蕴含了关系(“customer”)、URI(https://adventure-works.com/c…)、HTTP办法,和可用的MIME类型。这些信息足够一个客户程序用来执行操作了。
链接数组里还蕴含了一些自援用信息,跟本来获取到的申请资源无关,它们的关系被标记为 ‘self’。
依据不同的资源状态,links数组节点中的内容可能会有所不同。这也正是超文本作为 “engine of application state.” 的含意。
RESTful web API的版本控制
通常web API不会变化无穷,随着业务需要变动,会有更多资源汇合呈现,资源之间的关系会变动,资源的数据结构也会有所扭转。更新web API来解决变动的需要或者没什么难度,然而必须得思考这些改变对于那些正在应用这些API的客户端程序的影响。设计和保护这些web API的程序员,对这些接口有齐全的控制权,然而对那些客户端程序可并不一定是这样,有可能开发它们的是一些远处的第三方。所以要害的是能让现存的客户端程序持续失常运行,同时新的客户端程序能够充沛应用这些新性能和资源。
版本控制能够使web API表明它公开的性能和资源,同时能让客户端程序向性能和资源的某个特定版本发动申请。上面几节形容了不同的版本控制办法,各有其优劣。
无版本控制
这是最简略的办法,一些外部调用API能够承受这么做。重大的改变个别体现为新的资源或新链接,对曾经存在的资源增加内容个别也不会有什么问题,因为申请端程序遇到冀望之外的数据内容,个别会主动疏忽掉。
比方,向URI https://adventure-works.com/customers/3
发动申请,会返回一个蕴含了id、name和address字段的单个客户明细数据,这些数据合乎申请端程序的预期:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":3,"name":"Contoso LLC","address":"1 Microsoft Way Redmond WA 98053"}
简略起见,本节示例响应报文并没有蕴含
HATEOAS
链接.
如果向客户资源的构造中增加了 DateCreated
字段,响应内容会变为:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":3,"name":"Contoso LLC","dateCreated":"2014-09-04T12:11:38.0376089Z","address":"1 Microsoft Way Redmond WA 98053"}
曾经存在的客户程序会持续失常运行,只有它们能够疏忽掉不意识的字段,而新的客户程序能够设计为可能解决这个新增加的字段。不过,如果资源的构造产生了更彻底的改变(比方删除或者重命名了某些字段),或者资源间的关系产生了变动,那就有可能使得这些正在运行的客户程序无奈再正确运行。这种状况下,你就得思考上面的这些办法了。
URI 版本控制
每当对web API或者资源构造进行改变时,能够对每个资源的URI增加一个版本号,先前的URI应持续像之前那样运作,返回本来的资源数据。
对后面的例子作下扩大,如果将 address 字段重构为蕴含不同组成部分的子字段(比方街道、城市、省份和邮政编码),这个版本的资源能够公布为一个蕴含了版本号的URI,例如 https://adventure-works.com/v2/customers/3
:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":3,"name":"Contoso LLC","dateCreated":"2014-09-04T12:11:38.0376089Z","address":{"streetAddress":"1 Microsoft Way","city":"Redmond","state":"WA","zipCode":98053}}
这种版本控制机制非常简单,然而它依赖于服务器能将申请路由到正确的服务端点。而且,随着web API的逐渐迭代和服务器提供反对的版本数量增多,它会变得很轻便。另外,从纯正主义角度去看,返回了同样数据(客户3)的URI,它们的版本不应该是不一样的。这个计划还会让 HATEOAS
的实现更加简单,因为所有链接都不得不将版本号蕴含在它们的URI中。
查问字符串版本控制
相比于提供多个不通的URI,更好的做法可能是在HTTP申请上附带一个指定了资源版本的查问字符串参数,比方 https://adventure-works.com/customers/3?version=2
。给这个版本参数一个有意义的默认值,比方1,这样旧的客户程序能够疏忽掉它而不受到影响。
这种做法的益处是,在语义上,同样的资源申请用了雷同的URI,不过这须要相应的代码正确的解析申请中的查问字符串,并给与正确的HTTP响应。另外此办法同样会遇到跟URI版本控制一样的问题,即实现 HATEOAS
会比拟麻烦。
一些旧版浏览器和网络代理不会缓存蕴含查问字符串的申请响应内容,在其上运行的网络程序调用web API时的性能可能会受到影响。
音讯头版本控制
除了将版本号放到查问字符串参数中,还能够实现一个自定义音讯头来指明须要的资源版本。这个做法须要客户端程序在所有申请上携带正确的音讯头,只管服务代码能够在申请疏忽了这个音讯头时给一个默认值(比方版本1)。上面这个例子应用了名为 Custom-Header
的音讯头,外面的值指明了web API的版本。
Version 1:
GET https://adventure-works.com/customers/3 HTTP/1.1
Custom-Header: api-version=1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":3,"name":"Contoso LLC","address":"1 Microsoft Way Redmond WA 98053"}
Version 2:
GET https://adventure-works.com/customers/3 HTTP/1.1
Custom-Header: api-version=2
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":3,"name":"Contoso LLC","dateCreated":"2014-09-04T12:11:38.0376089Z","address":{"streetAddress":"1 Microsoft Way","city":"Redmond","state":"WA","zipCode":98053}}
和后面两种版本控制计划一样,实现 HATEOAS
也须要在所有链接中带上正确的音讯头。
媒体类型版本控制
在后面有介绍过,当客户端程序收回一个HTTP GET申请到web服务端时,它该当应用 Accept
音讯头明确它能够解决的内容格局。Accept
音讯头常常用来指明客户端程序心愿失去的响应报文格式应该是XML、JSON或者其余一些通用格局。不过,也能够扩大一些自定义媒体类型,使得客户程序能够在其中加一些信息来指明须要的资源版本。
上面这个例子在 Accept
音讯头中指定了 application/vnd.adventure-works.v1+json
,其中的 vnd.adventure-works.v1
就向web服务端表明了它须要的是1这个资源版本,并心愿失去json格局的响应内容:
GET https://adventure-works.com/customers/3 HTTP/1.1
Accept: application/vnd.adventure-works.v1+json
解决申请的代码须要解决这个音讯头,并尽可能的满足它(客户端程序可能会在 Accept
中指定多个格局,服务端只须要抉择其中最合适的一个作为响应格局即可)。Web服务端在响应体中应用 Content-Type
报文头来对数据格式进行确认:
HTTP/1.1 200 OK
Content-Type: application/vnd.adventure-works.v1+json; charset=utf-8
{"id":3,"name":"Contoso LLC","address":"1 Microsoft Way Redmond WA 98053"}
如果 Accept
音讯头中指定了无奈解决的媒体类型,服务端能够返回HTTP状态码406(Not Acceptable),或者应用一个默认类型来解决。
这种做法被认为是最纯正的版本控制计划了(译者猜应该是说这种做法能够在连贯上只是明确指向资源即可,不须要做其余调整,URI版本计划,和查问字符串版本计划,都会影响这个URI的洁净度),并且它原生反对 HATEOAS
,因为能够在链接节点中指明 MIME 类型。
在抉择一种版本控制计划时,须要同时思考到对性能的潜在影响,特地是在web服务端的缓存解决。URI和查问字符串版本控制计划是能够缓存的,因为携带了版本信息的残缺URI和查问字符串会作为缓存的惟一标识,同样的版本申请每次都指向同样的数据。
The Header versioning and Media Type versioning mechanisms typically require additional logic to examine the values in the custom header or the Accept header. In a large-scale environment, many clients using different versions of a web API can result in a significant amount of duplicated data in a server-side cache. This issue can become acute if a client application communicates with a web server through a proxy that implements caching, and that only forwards a request to the web server if it does not currently hold a copy of the requested data in its cache.(这段译者未能齐全了解,猜其意思,可能跟代理的缓存策略无关,因为Header和MediaType的版本控制计划,在申请不同版本时的URI和query string并没有变动,代理可能会应用不正确的缓存数据间接返回,而不是向web服务端发动新的申请来获取数据)
Open API 打算
Open API 打算是由一个行业联盟创立的,目标是标准化供应商之间的REST API形容。作为这一打算的一部分,Swagger 2.0标准被重新命名为OpenAPI标准(OAS),并纳入了Open API 打算。
如果你想要把本人的web API革新为OpenAPI,能够参考上面几点:
- OpenAPI标准附带了一套对于REST API应该如何设计的执著的指导方针。这对互操作性有益处,但在设计API以符合规范时须要更加小心。
- OpenAPI提倡契约优先的办法,而不是实现优先的办法。契约优先意味着首先设计API契约(接口),而后编写实现该契约的代码。
- Swagger这样的工具能够从API契约间接生成客户端代码或文档。可参考 ASP.NET Web API help pages using Swagger。
More information
- Microsoft REST API guidelines. Detailed recommendations for designing public REST APIs.
- Web API checklist. A useful list of items to consider when designing and implementing a web API.
- Open API Initiative. Documentation and implementation details on Open API.