
如何快速實(shí)現(xiàn)REST API集成以優(yōu)化業(yè)務(wù)流程
{
"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框架也提供了相應(yīng)的支持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);
}
}
產(chǎn)生如下的結(jié)果:
可以說REST的API設(shè)計(jì)是需要設(shè)計(jì)感的,需要仔細(xì)來思考API的資源,資源之間的關(guān)系和導(dǎo)航,URI的定義等等。對(duì)于一套設(shè)計(jì)精良的REST API,其實(shí)客戶端只要知道可用資源清單,往往就可以輕易根據(jù)約定俗成的規(guī)范以及導(dǎo)航探索出大部分API。比較諷刺的是,有很多網(wǎng)站給前端和客戶端的接口是REST的,爬蟲開發(fā)者可以輕易探索到所有接口,甚至一些內(nèi)部接口,畢竟猜一下REST的接口比RPC的接口容易的多。
作為補(bǔ)充,下面再列幾個(gè)有關(guān)REST API設(shè)計(jì)大家爭議討論糾結(jié)的比較多的幾個(gè)方面。
比如https://stackoverflow.com/questions/630453/put-vs-post-in-rest,總的來說大家基本認(rèn)同微軟提到的三個(gè)方面:
當(dāng)然,有些公司的規(guī)范是創(chuàng)建資源僅僅是POST,不支持PUT
看到過許多文章都在說,REST還是建議返回的數(shù)據(jù)本身就是實(shí)體信息(或列表信息),而不建議把數(shù)據(jù)進(jìn)行一層包裝(Result<T>)。如果需要有更多的信息來補(bǔ)充的話,可以放到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的例子是在響應(yīng)體中有”content”和”links”的層級(jí),也就是響應(yīng)體并不是資源本身,是有包裝的,除了links,很多時(shí)候我們會(huì)直接以統(tǒng)一的格式來定義API響應(yīng)結(jié)構(gòu)體,比如:
{
"code" : "",
"message" : "",
"path" : ""
"time" : "",
"data" : {},
"links": []
}
我個(gè)人比較喜歡這種方式,不喜歡使用HTTP頭,原因還是因?yàn)槎嘧兊牟渴鸷途W(wǎng)絡(luò)環(huán)境下,如果某些環(huán)節(jié)請(qǐng)求頭被修改了或丟棄了會(huì)很麻煩(還有麻煩的Header Key大小寫問題),響應(yīng)體一般所有的代理都不會(huì)去動(dòng)。
微軟的API設(shè)計(jì)指南(文末有貼地址)中指出避免太復(fù)雜的層級(jí)資源,比如/customers/1/orders/99/products過于復(fù)雜,可以退化為/customers/1/orders和/orders/99/products,不URI的復(fù)雜度不應(yīng)該超過collection/item/collection,Google的一些API會(huì)層級(jí)比較多,比如:
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/*
這點(diǎn)我比較贊同微軟的規(guī)范,太深的層級(jí)在實(shí)現(xiàn)起來也不方便。
如果說RPC面向過程,REST面向資源,那么GRAPHQL就是面向數(shù)據(jù)查詢了?!癎raphQL 既是一種用于 API 的查詢語言也是一個(gè)滿足你數(shù)據(jù)查詢的運(yùn)行時(shí)。 GraphQL 對(duì)你的 API 中的數(shù)據(jù)提供了一套易于理解的完整描述,使得客戶端能夠準(zhǔn)確地獲得它需要的數(shù)據(jù),而且沒有任何冗余,也讓 API 更容易地隨著時(shí)間推移而演進(jìn),還能用于構(gòu)建強(qiáng)大的開發(fā)者工具?!?/p>
采用GRAPHQL,甚至不需要有任何的接口文檔,在定義了Schema之后,服務(wù)端實(shí)現(xiàn)Schema,客戶端可以查看Schema,然后構(gòu)建出自己需要的查詢請(qǐng)求來獲得自己需要的數(shù)據(jù)。
比如定義如下的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端點(diǎn)可以看到所有支持的查詢:
?其實(shí)就是__schema:
然后我們可以根據(jù)客戶端的UI需要自己來定義查詢請(qǐng)求,服務(wù)端會(huì)根據(jù)客戶端給的結(jié)構(gòu)來返回?cái)?shù)據(jù):
再來看看Github提供的GraphQL(更多參考https://developer.github.com/v4/guides/):
查詢出了最后的三個(gè)我的repo:
GraphQL就是通過Schema來明確數(shù)據(jù)的能力,服務(wù)端提供統(tǒng)一的唯一的API入口,然后客戶端來告訴服務(wù)端我要的具體數(shù)據(jù)結(jié)構(gòu)(基本可以說不需要有API文檔),有點(diǎn)客戶端驅(qū)動(dòng)服務(wù)端的意思。雖然客戶端靈活了,但是GraphQL服務(wù)端的實(shí)現(xiàn)比較復(fù)雜和痛苦的,GraphQL不能替代其它幾種設(shè)計(jì)風(fēng)格,并不是傳說中的REST 2.0。更多信息參見https://github.com/chentsulin/awesome-graphql。
沒有高大上的英文縮寫,因?yàn)檫@種模式或風(fēng)格是我自己想出來的,那就是通過API讓服務(wù)端來驅(qū)動(dòng)客戶端,在之前的一些項(xiàng)目中也有過實(shí)踐。說白了,就是在API的返回結(jié)果中包含驅(qū)動(dòng)客戶端去怎么做的信息,兩個(gè)層次:
之前有兩個(gè)這樣的項(xiàng)目采用了類似的API設(shè)計(jì)方式:
一般而言,對(duì)外的Web API是不會(huì)采用這種服務(wù)端驅(qū)動(dòng)客戶端的方式來設(shè)計(jì)API的。對(duì)于某些特殊類型的項(xiàng)目,我們可以考慮采用這種服務(wù)端驅(qū)動(dòng)的方式來設(shè)計(jì)API,讓客戶端變?yōu)橐粋€(gè)不含邏輯的執(zhí)行者,執(zhí)行的是UI和交互。
https://philsturgeon.uk/2018/05/21/picking-an-api-paradigm-implementation/此文給出了一個(gè)有關(guān)RPC、REST、GRAPHQL選擇的決策方式可以參考,見下圖:
我覺得:
很多API設(shè)計(jì)指南都提到了下面這些設(shè)計(jì)考量點(diǎn),也需要在設(shè)計(jì)的時(shí)候進(jìn)行考慮:
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
[...]
文章轉(zhuǎn)自微信公眾號(hào)@隨緣主人的園子
對(duì)比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力
一鍵對(duì)比試用API 限時(shí)免費(fèi)