Web API設計實際上是一個挺重要的設計話題,許多公司都會有公司層面的Web API設計規範,幾乎全部的項目在詳細設計階段都會進行API設計,項目開發後都會有一份API文檔供測試和聯調。本文嘗試根據本身的理解總結一下目前常見的四種API設計風格以及設計考慮點。前端
這是最多見的方式,RPC說的是本地調用遠程的方法,面向的是過程。java
這裏就不貼例子了,估計超過50%的API是這種分格的。node
是一種架構風格,有四個級別的成熟度:git
級別0其實就是類RPC的風格,級別3是真正的REST,大多數號稱REST的API在級別2。REST實現一些要點包括:github
{
"content": [ {
"price": 499.00,
"description": "Apple tablet device",
"name": "iPad",
"links": [ {
"rel": "self",
"href": "http://localhost:8080/product/1"
} ],
"attributes": {
"connector": "socket"
}
}, {
"price": 49.00,
"description": "Dock for iPhone/iPad",
"name": "Dock",
"links": [ {
"rel": "self",
"href": "http://localhost:8080/product/3"
} ],
"attributes": {
"connector": "plug"
}
} ],
"links": [ {
"rel": "product.search",
"href": "http://localhost:8080/product/search"
} ]
}
複製代碼
Spring框架也提供了相應的支持:spring.io/projects/sp…spring
@RestController
public class GreetingController {
private static final String TEMPLATE = "Hello, %s!";
@RequestMapping("/greeting")
public HttpEntity<Greeting> greeting( @RequestParam(value = "name", required = false, defaultValue = "World") String name) {
Greeting greeting = new Greeting(String.format(TEMPLATE, name));
greeting.add(linkTo(methodOn(GreetingController.class).greeting(name)).withSelfRel());
return new ResponseEntity<>(greeting, HttpStatus.OK);
}
}
複製代碼
產生以下的結果: json
能夠說REST的API設計是須要設計感的,須要仔細來思考API的資源,資源之間的關係和導航,URI的定義等等。對於一套設計精良的REST API,其實客戶端只要知道可用資源清單,每每就能夠輕易根據約定俗成的規範以及導航探索出大部分API。比較諷刺的是,有不少網站給前端和客戶端的接口是REST的,爬蟲開發者能夠輕易探索到全部接口,甚至一些內部接口,畢竟猜一下REST的接口比RPC的接口容易的多。api
做爲補充,下面再列幾個有關REST API設計你們爭議討論糾結的比較多的幾個方面。緩存
好比 stackoverflow.com/questions/6… ,總的來講你們基本認同微軟提到的三個方面:bash
固然,有些公司的規範是建立資源僅僅是POST,不支持PUT
看到過許多文章都在說,REST仍是建議返回的數據自己就是實體信息(或列表信息),而不建議把數據進行一層包裝(Result)。若是須要有更多的信息來補充的話,能夠放到HTTP Header中,好比https://developer.github.com/v3/projects/cards/的API:
GET /projects/columns/:column_id/cards
Status: 200 OK
Link: <https://api.github.com/resource?page=2>; rel="next",
<https://api.github.com/resource?page=5>; rel="last"
[
{
"url": "https://api.github.com/projects/columns/cards/1478",
"id": 1478,
"node_id": "MDExOlByb2plY3RDYXJkMTQ3OA==",
"note": "Add payload for delete Project column",
"created_at": "2016-09-05T14:21:06Z",
"updated_at": "2016-09-05T14:20:22Z",
"archived": false,
"column_url": "https://api.github.com/projects/columns/367",
"content_url": "https://api.github.com/repos/api-playground/projects-test/issues/3",
"project_url": "https://api.github.com/projects/120"
}
]
複製代碼
以前咱們給出的HATEOAS的例子是在響應體中有"content"和"links"的層級,也就是響應體並非資源自己,是有包裝的,除了links,不少時候咱們會直接以統一的格式來定義API響應結構體,好比:
{
"code" : "",
"message" : "",
"path" : ""
"time" : "",
"data" : {},
"links": []
}
複製代碼
我我的比較喜歡這種方式,不喜歡使用HTTP頭,緣由仍是由於多變的部署和網絡環境下,若是某些環節請求頭被修改了或丟棄了會很麻煩(還有麻煩的Header Key大小寫問題),響應體通常全部的代理都不會去動。
微軟的API設計指南(文末有貼地址)中指出避免太複雜的層級資源,好比**/customers/1/orders/99/products過於複雜,能夠退化爲/customers/1/orders和/orders/99/products**,不URI的複雜度不該該超過collection/item/collection,Google的一些API會層級比較多,好比:
API service: spanner.googleapis.com
A collection of instances: projects/*/instances/*.
A collection of instance operations: projects/*/instances/*/operations/*.
A collection of databases: projects/*/instances/*/databases/*.
A collection of database operations: projects/*/instances/*/databases/*/operations/*.
A collection of database sessions: projects/*/instances/*/databases/*/sessions/*
複製代碼
這點我比較贊同微軟的規範,太深的層級在實現起來也不方便。
若是說RPC面向過程,REST面向資源,那麼GraphQL就是面向數據查詢了。「GraphQL 既是一種用於 API 的查詢語言也是一個知足你數據查詢的運行時。 GraphQL 對你的 API 中的數據提供了一套易於理解的完整描述,使得客戶端可以準確地得到它須要的數據,並且沒有任何冗餘,也讓 API 更容易地隨着時間推移而演進,還能用於構建強大的開發者工具。」
採用GraphQL,甚至不須要有任何的接口文檔,在定義了Schema以後,服務端實現Schema,客戶端能夠查看Schema,而後構建出本身須要的查詢請求來得到本身須要的數據。
好比定義以下的Schema:
#
# Schemas must have at least a query root type
#
schema {
query: Query
}
type Query {
characters(
episode: Episode
) : [Character]
human(
# The id of the human you are interested in
id : ID!
) : Human
droid(
# The non null id of the droid you are interested in
id: ID!
): Droid
}
# One of the films in the Star Wars Trilogy
enum Episode {
# Released in 1977
NEWHOPE
# Released in 1980.
EMPIRE
# Released in 1983.
JEDI
}
# A character in the Star Wars Trilogy
interface Character {
# The id of the character.
id: ID!
# The name of the character.
name: String!
# The friends of the character, or an empty list if they
# have none.
friends: [Character]
# Which movies they appear in.
appearsIn: [Episode]!
# All secrets about their past.
secretBackstory : String @deprecated(reason : "We have decided that this is not canon")
}
# A humanoid creature in the Star Wars universe.
type Human implements Character {
# The id of the human.
id: ID!
# The name of the human.
name: String!
# The friends of the human, or an empty list if they have none.
friends: [Character]
# Which movies they appear in.
appearsIn: [Episode]!
# The home planet of the human, or null if unknown.
homePlanet: String
# Where are they from and how they came to be who they are.
secretBackstory : String @deprecated(reason : "We have decided that this is not canon")
}
# A mechanical creature in the Star Wars universe.
type Droid implements Character {
# The id of the droid.
id: ID!
# The name of the droid.
name: String!
# The friends of the droid, or an empty list if they have none.
friends: [Character]
# Which movies they appear in.
appearsIn: [Episode]!
# The primary function of the droid.
primaryFunction: String
# Construction date and the name of the designer.
secretBackstory : String @deprecated(reason : "We have decided that this is not canon")
}
複製代碼
採用GraphQL Playground(github.com/prisma/grap…
其實就是__schema: 而後咱們能夠根據客戶端的UI須要本身來定義查詢請求,服務端會根據客戶端給的結構來返回數據: 再來看看Github提供的GraphQL(更多參考https://developer.github.com/v4/guides/): 查詢出了最後的三個個人repo: GraphQL就是經過Schema來明確數據的能力,服務端提供統一的惟一的API入口,而後客戶端來告訴服務端我要的具體數據結構(基本能夠說不須要有API文檔),有點客戶端驅動服務端的意思。雖然客戶端靈活了,可是GraphQL服務端的實現比較複雜和痛苦的,GraphQL不能替代其它幾種設計風格,並非傳說中的REST 2.0。更多信息參見 github.com/chentsulin/… 。沒有高大上的英文縮寫,由於這種模式或風格是我本身想出來的,那就是經過API讓服務端來驅動客戶端,在以前的一些項目中也有過實踐。說白了,就是在API的返回結果中包含驅動客戶端去怎麼作的信息,兩個層次:
以前有兩個這樣的項目採用了相似的API設計方式:
通常而言,對外的Web API是不會採用這種服務端驅動客戶端的方式來設計API的。對於某些特殊類型的項目,咱們能夠考慮採用這種服務端驅動的方式來設計API,讓客戶端變爲一個不含邏輯的執行者,執行的是UI和交互。
我以爲:
不少API設計指南都提到了下面這些設計考量點,也須要在設計的時候進行考慮:
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
複製代碼
而後提供資源分段下載功能:
GET https://adventure-works.com/products/10?fields=productImage HTTP/1.1
Range: bytes=0-2499
HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Content-Type: image/jpeg
Content-Length: 2500
Content-Range: bytes 0-2499/4580
[...]
複製代碼