淺談三種API設計風格RPC、REST、GraphQL

前言

Web API設計其實是一個挺重要的設計話題,許多公司都會有公司層面的Web API設計規範,幾乎所有的項目在詳細設計階段都會進行API設計,項目開發後都會有一份API文檔供測試和聯調。本文嘗試根據自己的理解總結一下目前常見的四種API設計風格以及設計考慮點。

淺談三種API設計風格RPC、REST、GraphQL

正文

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):
  • GET:獲取資源詳情或資源列表。對於collection類型的URI(比如/customers)就是獲取資源列表,對於item類型的URI(比如/customers/1)就是獲取一個資源。
  • POST:創建資源,請求體是新資源的內容。往往POST是用於為集合新增資源。
  • PUT:創建或修改資源,請求體是新資源的內容。往往PUT用於單個資源的新增或修改。實現上必須冪等。
  • PATCH:部分修改資源,請求體是修改的那部分內容。PUT一般要求提交整個資源進行修改,而PATCH用於修改部分內容(比如某個屬性)。
  • DELETE:移除資源。和GET一樣,對於collection類型的URI(比如/customers)就是刪除所有資源,對於item類型的URI(比如/customers/1)就是刪除一個資源。
淺談三種API設計風格RPC、REST、GraphQL

  • 需要考慮資源之間的導航(級別3的要點,比如使用HATEOAS,HATEOAS是Hypertext as the Engine of Application State的縮寫)。有了資源導航,客戶端甚至可能不需要參閱文檔就可以找到更多對自己有用的資源,不過HATEOAS沒有固定的標準,比如:
{
"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>

產生如下的結果:

淺談三種API設計風格RPC、REST、GraphQL

  • 除了之前提到的幾個要點,REST API的設計還有一些小點:
  • 必須無狀態的,相互獨立的,不區分順序的
  • API需要有一致的接口來解耦客戶端和服務實現,如果基於HTTP那麼務必使用HTTP
  • Method來操作資源,而且儘量使用HTTP響應碼來處理錯誤
  • 需要儘量考慮緩存、版本控制、內容協商、部分響應等實現

可以說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,然後構建出自己需要的查詢請求來獲得自己需要的數據。

淺談三種API設計風格RPC、REST、GraphQL

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)

淺談三種API設計風格RPC、REST、GraphQL

其實就是__schema:

淺談三種API設計風格RPC、REST、GraphQL

然後我們可以根據客戶端的UI需要自己來定義查詢請求,服務端會根據客戶端給的結構來返回數據:

淺談三種API設計風格RPC、REST、GraphQL

再來看看Github提供的GraphQL(更多參考https://developer.github.com/v4/guides/):

淺談三種API設計風格RPC、REST、GraphQL

查詢出了最後的三個repo:

淺談三種API設計風格RPC、REST、GraphQL

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


分享到:


相關文章: