{
"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);
}
}

產生如下的結果:

可以說REST的API設計是需要設計感的,需要仔細來思考API的資源,資源之間的關系和導航,URI的定義等等。對于一套設計精良的REST API,其實客戶端只要知道可用資源清單,往往就可以輕易根據約定俗成的規范以及導航探索出大部分API。比較諷刺的是,有很多網站給前端和客戶端的接口是REST的,爬蟲開發者可以輕易探索到所有接口,甚至一些內部接口,畢竟猜一下REST的接口比RPC的接口容易的多。

作為補充,下面再列幾個有關REST API設計大家爭議討論糾結的比較多的幾個方面。

創建資源使用PUT還是POST

比如https://stackoverflow.com/questions/630453/put-vs-post-in-rest,總的來說大家基本認同微軟提到的三個方面:

當然,有些公司的規范是創建資源僅僅是POST,不支持PUT

異常處理的HTTP響應狀態碼

返回數據是否需要包裝

看到過許多文章都在說,REST還是建議返回的數據本身就是實體信息(或列表信息),而不建議把數據進行一層包裝(Result<T>)。如果需要有更多的信息來補充的話,可以放到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大小寫問題),響應體一般所有的代理都不會去動。

URI的設計層級是否超過兩層

微軟的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/*

這點我比較贊同微軟的規范,太深的層級在實現起來也不方便。

GRAPHQL

如果說RPC面向過程,REST面向資源,那么GRAPHQL就是面向數據查詢了?!癎raphQL 既是一種用于 API 的查詢語言也是一個滿足你數據查詢的運行時。 GraphQL 對你的 API 中的數據提供了一套易于理解的完整描述,使得客戶端能夠準確地獲得它需要的數據,而且沒有任何冗余,也讓 API 更容易地隨著時間推移而演進,還能用于構建強大的開發者工具?!?/p>

采用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的返回結果中包含驅動客戶端去怎么做的信息,兩個層次:

之前有兩個這樣的項目采用了類似的API設計方式:

一般而言,對外的Web API是不會采用這種服務端驅動客戶端的方式來設計API的。對于某些特殊類型的項目,我們可以考慮采用這種服務端驅動的方式來設計API,讓客戶端變為一個不含邏輯的執行者,執行的是UI和交互。

選擇哪個模式

https://philsturgeon.uk/2018/05/21/picking-an-api-paradigm-implementation/此文給出了一個有關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
[...]

參考資料

文章轉自微信公眾號@隨緣主人的園子

上一篇:

SpringBoot中REST API的錯誤異常處理設計

下一篇:

沒有被了解的API?一個老碼農眼中的API世界
#你可能也喜歡這些API文章!

我們有何不同?

API服務商零注冊

多API并行試用

數據驅動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內容創意新穎性、情感共鳴力、商業轉化潛力

25個渠道
一鍵對比試用API 限時免費

#AI深度推理大模型API

對比大模型API的邏輯推理準確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費