
使用NestJS和Prisma構建REST API:身份驗證
{
"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設計大家爭議討論糾結的比較多的幾個方面。
比如https://stackoverflow.com/questions/630453/put-vs-post-in-rest,總的來說大家基本認同微軟提到的三個方面:
當然,有些公司的規范是創建資源僅僅是POST,不支持PUT
看到過許多文章都在說,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大小寫問題),響應體一般所有的代理都不會去動。
微軟的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就是面向數據查詢了?!癎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設計方式:
一般而言,對外的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
[...]
文章轉自微信公眾號@隨緣主人的園子