前言
Web API設計其實是一個挺重要的設計話題,許多公司都會有公司層面的Web API設計規範,幾乎所有的項目在詳細設計階段都會進行API設計,項目開發後都會有一份API文檔供測試和聯調。本文嘗試根據自己的理解總結一下目前常見的四種API設計風格以及設計考慮點。
正文
1. RPC
這是最常見的方式,RPC說的是本地調用遠程的方法,面向的是過程。
RPC形式的API組織形態是類和方法,或者說領域和行為。因此API的命名往往是一個動詞,比如GetUserInfo和CreateUser。因為URI會非常多而且往往沒有一些約定規範,所以需要有詳細的文檔。也是因為無拘無束,HTTP方法基本只用GET和POST,設計起來比較簡單。這裡就不貼例子了,估計超過50%的API是這種風格的。
2. REST
是一種架構風格,有四個級別的成熟度:
級別0:定義一個 URI,所有操作是對此 URI 發出的 POST 請求。級別1:為各個資源單獨創建 URI。級別2:使用 HTTP 方法來定義對資源執行的操作。級別3:使用超媒體(HATEOAS)。級別0其實就是類RPC的風格,級別3是真正的REST,大多數號稱REST的API在級別2。REST實現一些要點包括:
REST形式的API組織形態是資源和實體,一切圍繞資源(級別1的要點)。設計流程包括:確定API提供的資源確定資源之間的關係根據資源類型和關係確定資源URI結構確定資源的結構體會定義一些標準方法(級別2的要點),然後把標準方法映射到實現(比如HTTP Method):{
"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
@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);
}
}
/<greeting>
產生如下的結果:
可以說REST的API設計是需要設計感的,需要仔細來思考API的資源,資源之間的關係和導航,URI的定義等等。對於一套設計精良的REST API,其實客戶端只要知道可用資源清單,往往就可以輕易根據約定俗成的規範以及導航探索出大部分API。比較諷刺的是,有很多網站給前端和客戶端的接口是REST的,爬蟲開發者可以輕易探索到所有接口,甚至一些內部接口,畢竟猜一下REST的接口比RPC的接口容易的多。
作為補充,下面再列幾個有關REST API設計大家爭議討論糾結的比較多的幾個方面。
3. GraphQL
如果說RPC面向過程,REST面向資源,那麼GraphQL就是面向數據查詢了。GraphQL 既是一種用於 API 的查詢語言也是一個滿足你數據查詢的運行時。 GraphQL 對你的 API 中的數據提供了一套易於理解的完整描述,使得客戶端能夠準確地獲得它需要的數據,而且沒有任何冗餘,也讓 API 更容易地隨著時間推移而演進,還能用於構建強大的開發者工具。
採用GraphQL,甚至不需要有任何的接口文檔,在定義了Schema之後,服務端實現Schema,客戶端可以查看Schema,然後構建出自己需要的查詢請求來獲得自己需要的數據。
image
#
# 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)
其實就是__schema:
然後我們可以根據客戶端的UI需要自己來定義查詢請求,服務端會根據客戶端給的結構來返回數據:
再來看看Github提供的GraphQL(更多參考https://developer.github.com/v4/guides/):
查詢出了最後的三個repo:
GraphQL就是通過Schema來明確數據的能力,服務端提供統一的唯一的API入口,然後客戶端來告訴服務端我要的具體數據結構(基本可以說不需要有API文檔),有點客戶端驅動服務端的意思。雖然客戶端靈活了,但是GraphQL服務端的實現比較複雜和痛苦的,GraphQL不能替代其它幾種設計風格,並不是傳說中的REST 2.0。
小結
在下列情況考慮RPC風格的API或說是RPC:
偏向內部的API沒有太多的時間考慮API的設計或沒有架構師提供的API很難進行資源、對象抽象對性能有高要求在下列情況考慮REST風格:
偏向外部API提供的API天生圍繞資源、對象、管理展開不能耦合客戶端實現資源的CRUD是可以對齊的(功能完整的)在下列情況考慮GraphQL:
客戶端對於數據的需求多變數據具有圖的特點鏈接:https://www.jianshu.com/p/5ef018004756