最近在實踐GraphQL,項目算是作完了,這會須要總結一下。如下三篇文章都是基於的demo代碼是: graphql-todo-demoreact
解讀的graphql-js的版本是v14.3.0
,規範是2018年6月份發佈的標準git
GraphQL是給API設計的一種查詢語言,一個依據已有數據執行查詢的運行時,爲你的API中的數據提供一種徹底且容易理解的描述,使得API可以更容易的隨着時間而演變,還支持強大的開發者工具。github
具備如下特性:(翻譯自Getting up and running with GraphQL)web
咱們先來看看平時項目的一些api使用場景。數據庫
假如某個訂單業務的接口在app端使用到如下數據:express
{
id,
tradeNo,
price
}
複製代碼
而一樣的接口在web端用到的是以下數據:api
{
id,
price,
tags,
toAddr
}
複製代碼
因而在咱們開發中就會有這樣的對話:數組
服務端A: 小林啊,這是xx接口返回的dto,大家網關直接使用就好了。bash
{
id
tradeNo
price
tags
hasPaied
toAddr
fromAddr
shopId
...
}
複製代碼
小林:好的,我將數據都透傳給app app端B: 小林啊,你這接口怎麼這麼多字段?哪些字段是咱們用得上的咧?你就不能修剪一下參數再給咱們嗎? 小林: 很差修剪呀,這個接口web端的童鞋也在用呀,大家就挑選本身用獲得的字段就好了 app端B: ....服務器
app端童鞋這會很無語,感受很費解,明明我只要四個字段,你給我返回幾十個字段幹啥呢?那不是浪費帶寬嗎?
是的,這類現象就是所謂的「過分獲取」。同時也暴露出網關這層,在處理同一個接口返回給不一樣終端的數據差別性的複雜,須要你增長額外的判斷去實現,這種問題咱們稱之爲「冗餘判斷」,這會致使系統維護成本增長。
那麼有解決辦法嗎?固然有!咱們反過來操做如何?由客戶端來告訴網關須要哪些字段,網關這邊準備完整的數據,而後有個什麼東東來pick這些數據給客戶端。因而乎,這個東東就是後來Facebook在2015年開源的GraphQL。
那麼GraphQL除了解決剛纔說兩個廣泛問題,還有別的其餘優點嗎?
of course。
以官方文檔來闡述,即是:
在後面的三篇文章中會逐一體現其優點。
能夠將 GraphQL 的類型系統分爲標量類型(Scalar Types,標量類型)和其餘高級數據類型,標量類型便可以表示最細粒度數據結構的數據類型,能夠和 JavaScript 的原始類型對應。
GraphQL 規範目前規定支持的標量類型有:
Scalar Types
的JavaScript參考實現代碼能夠查看這裏。
咱們可使用scalar
關鍵字,自定義標量類型,好比咱們能夠定義一個Date類型: scalar Date
Interface
是包含一組肯定字段的集合的抽象類型,實現該接口的類型必須包含interface
定義的全部字段。好比:
interface Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
}
type Human implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
starships: [Starship]
totalCredits: Int
}
type Droid implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
primaryFunction: String
}
複製代碼
對於interface
的返回須要你使用inline fragments
來實現,以下:
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
... on Droid {
primaryFunction
}
}
}
複製代碼
Union類型很是相似於interface,可是他們在類型之間不須要指定任何共同的字段。一般用於描述某個字段可以支持的全部返回類型以及具體請求真正的返回類型。好比定義:
union SearchResult = Human | Droid | Starship
複製代碼
這個時候的查詢語句多是這樣的:
{
search(text: "an") {
__typename
... on Human {
name
height
}
... on Droid {
name
primaryFunction
}
... on Starship {
name
length
}
}
}
複製代碼
又稱Enums,這是一種特殊的標量類型,經過此類型,咱們能夠限制值爲一組特殊的值。好比:
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
複製代碼
input類型對mutations來講很是重要,在 GraphQL schema 語言中,它看起來和常規的對象類型很是相似,可是咱們使用關鍵字input而非type,input類型按以下定義:
# Input Type
input CommentInput {
body: String!
}
複製代碼
爲何不直接使用Object Type呢?由於 Object 的字段可能存在循環引用,或者字段引用了不能做爲查詢輸入對象的接口和聯合類型。
使用[]
來表示數組,使用!
來表示非空。Non-Null
強制類型的值不能爲null
,而且在請求出錯時必定會報錯。能夠用於必須保證值不能爲null
的字段
GraphQL schema最基本的類型就是Object Type。用於描述層級或者樹形數據結構。好比:
type Character {
name: String!
appearsIn: [Episode!]!
}
複製代碼
GraphQL的一次操做請求被稱爲一份文檔(document),即GraphQL服務可以解析驗證並執行的一串請求字符串(Source Text)。完整的一次操做由操做(Operation
)和片斷(Fragments
)組成。一次請求能夠包含多個操做和片斷。只有包含操做的請求才會被GraphQL服務執行。
其規範釋義爲:
只包含一個操做的請求能夠不帶OperationName
,若是是operationType是query的話,能夠所有省略掉,即:
{
getMessage {
# query也能夠擁有註釋,註釋以#開頭
content
author
}
}
複製代碼
當query包含多個操做時,全部操做都必須帶上名稱。
GraphQL中,咱們會有這樣一個約定,Query和與之對應的Resolver是同名的,這樣在GraphQL才能把它們對應起來。
Query
用作讀操做,也就是從服務器獲取數據。以上圖的請求爲例,其返回結果以下,能夠看出一一對應,精準返回數據
{
"data": {
"single": [
{
"content": "test content 1",
"author": "pp1"
}
],
"all": [
{
"content": "test content",
"author": "pp",
"id": "0"
},
{
"content": "test content 1",
"author": "pp1",
"id": "1"
}
]
}
}
複製代碼
Field是咱們想從服務器獲取的對象的基本組成部分。query
是數據結構的頂層,其下屬的all
和single
都屬於它的字段。
字段格式應該是這樣的:alias:name(argument:value)
其中 alias 是字段的別名,即結果中顯示的字段名稱。
name 爲字段名稱,對應 schema 中定義的 fields 字段名。
argument 爲參數名稱,對應 schema 中定義的 fields 字段的參數名稱。
value 爲參數值,值的類型對應標量類型的值。
和普通的函數同樣,query能夠擁有參數,參數是可選的或必須的。參數使用方法如上圖所示。
須要注意的是,GraphQL中的字符串須要包裝在雙引號中。
除了參數,query還容許你使用變量來讓參數可動態變化,變量以$開頭書寫,使用方式如上圖所示
變量還能夠擁有默認值:
query gm($id: ID = 2) {
# 查詢數據
single: getMessage(id: $id) {
...entity
}
all: getMessage {
...entity
id
}
}
複製代碼
別名,好比說,咱們想分別獲取所有消息和ID爲1的消息,咱們能夠用下面的方法:
query gm($id: ID = 2) {
# 查詢數據
getMessage(id: $id) {
...entity
}
getMessage {
...entity
id
}
}
複製代碼
因爲存在相同的name,上述代碼會報錯,要解決這個問題就要用到別名了Allases。
query gm($id: ID = 2) {
# 查詢數據
single: getMessage(id: $id) {
...entity
}
all: getMessage {
...entity
id
}
}
複製代碼
Fragments是一套在queries中可複用的fields。好比說咱們想獲取Message
,在沒有使用fragment以前是這樣的:
query gm($id: ID = 2) {
# 查詢數據
single: getMessage(id: $id) {
content
author
}
all: getMessage {
content
author
id
}
}
複製代碼
可是若是fields過多,就會顯得重複和冗餘。Fragments在此時就能夠起做用了。使用了Fragment以後的語法就如上圖所示,簡單清晰。
Fragment支持多層級地繼承。
Directives提供了一種動態使用變量改變咱們的queries的方法。如本例,咱們會用到如下兩個directive:
query gm($id: ID = 2, $isNotShowId: Boolean!, $showAuthor: Boolean!) {
# 查詢數據
single: getMessage(id: $id) {
...entity
}
all: getMessage {
...entity
id @skip(if: $isNotShowId)
}
}
fragment entity on Message {
content
author @include(if: $showAuthor)
}
### 入參是:
{
"id": 1,
"isNotShowId": true,
"showAuthor": false
}
複製代碼
@include: 只有當if中的參數爲true時,纔會包含對應fragment或field;
@skip:當if中的參數爲true時,會跳過對應fragment或field;
結果以下:
{
"data": {
"single": [
{
"content": "test content 1"
}
],
"all": [
{
"content": "test content"
},
{
"content": "test content 1"
}
]
}
}
複製代碼
傳統的API使用場景中,咱們會有須要修改服務器上數據的場景,mutations就是應這種場景而生。mutations被用以執行寫操做,經過mutations咱們會給服務器發送請求來修改和更新數據,而且會接收到包含更新數據的反饋。mutations和queries具備相似的語法,僅有些許的差異。
Subscription
是GraphQL最後一個操做類型,它被稱爲訂閱
。當另外兩個由客戶端經過HTTP
請求發送,訂閱是服務器在某個事件發生時將數據自己推送給感興趣的客戶端的一種方式。這就是GraphQL 處理實時通訊的方式。
在demo中咱們實現了TodoList的發佈訂閱功能,在語法上和別的operation相似,惟一變化的就是operation Type變爲了Subscription
,好比:
subscription todoSubscription($filter: String!) {
todoChanged(filter: $filter) {
type
payload {
id
title
complete
}
}
}
複製代碼
更多的改造實現是在服務端上,須要藉助graphql-subscriptions
和subscriptions-transport-ws
來實現整個流程。結合express-graphql
的例子能夠參考:Subscriptions support
至此,GraphQL的基礎篇講完了,爲了加深這些基礎,歡迎繼續閱讀第二篇文章原理篇