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框架也提供了相應的支持:https://spring.io/projects/spring-hateoas,好比以下的代碼: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設計你們爭議討論糾結的比較多的幾個方面。緩存
好比 https://stackoverflow.com/questions/630453/put-vs-post-in-rest ,總的來講你們基本認同微軟提到的三個方面:restful
固然,有些公司的規範是建立資源僅僅是POST,不支持PUT
看到過許多文章都在說,REST仍是建議返回的數據自己就是實體信息(或列表信息),而不建議把數據進行一層包裝(Result
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(https://github.com/prisma/graphql-playground)來查看graphql端點能夠看到全部支持的查詢:
其實就是__schema:
而後咱們能夠根據客戶端的UI須要本身來定義查詢請求,服務端會根據客戶端給的結構來返回數據:
再來看看Github提供的GraphQL(更多參考https://developer.github.com/v4/guides/):
查詢出了最後的三個個人repo:
GraphQL就是經過Schema來明確數據的能力,服務端提供統一的惟一的API入口,而後客戶端來告訴服務端我要的具體數據結構(基本能夠說不須要有API文檔),有點客戶端驅動服務端的意思。雖然客戶端靈活了,可是GraphQL服務端的實現比較複雜和痛苦的,GraphQL不能替代其它幾種設計風格,並非傳說中的REST 2.0。更多信息參見 https://github.com/chentsulin/awesome-graphql 。
沒有高大上的英文縮寫,由於這種模式或風格是我本身想出來的,那就是經過API讓服務端來驅動客戶端,在以前的一些項目中也有過實踐。說白了,就是在API的返回結果中包含驅動客戶端去怎麼作的信息,兩個層次:
以前有兩個這樣的項目採用了相似的API設計方式:
通常而言,對外的Web API是不會採用這種服務端驅動客戶端的方式來設計API的。對於某些特殊類型的項目,咱們能夠考慮採用這種服務端驅動的方式來設計API,讓客戶端變爲一個不含邏輯的執行者,執行的是UI和交互。
https://user-gold-cdn.xitu.io/2019/2/15/168eff296f015115 此文給出了一個有關RPC、REST、GRAPHQL選擇的決策方式能夠參考,見上圖。
我以爲:
不少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 [...]