在上一篇文章RPC vs REST vs GraphQL中,對於這三者的優缺點進行了比較宏觀的對比,並且咱們也會發現,通常比較簡單的項目其實並不須要GraphQL,可是咱們仍然須要對新的技術有必定的瞭解和掌握,在新技術普及時纔不會措手不及。前端
這篇文章主要介紹一些我接觸GraphQL的這段時間,以爲須要瞭解的比較核心的概念,比較適合一下人羣:node
這些概念並不侷限於服務端或者是客戶端,若是你熟悉這些概念,在接觸任意使用GraphQL做爲技術背景的庫或者框架時,均可以經過文檔很快的上手。ios
若是你已經GraphQL應用於了實際項目中,那麼這篇文章可能不適合你,由於其中並無包含一些實踐中的總結和經驗,關於實踐的東西我會在以後再單另寫一篇文章總結。web
介紹GraphQL是什麼的文章網上一搜一大把,篇幅有長有短,可是從最核心上講,它是一種查詢語言,再進一步說,是一種API查詢語言。數據庫
這裏可能有的人就會說,什麼?API還能查?API不是用來調用的嗎?是的,這正是GraphQL的強大之處,引用官方文檔的一句話:django
ask exactly what you want.
咱們在使用REST接口時,接口返回的數據格式、數據類型都是後端預先定義好的,若是返回的數據格式並非調用者所指望的,做爲前端的咱們能夠經過如下兩種方式來解決問題:編程
通常若是是我的項目,改後端接口這種事情能夠隨意搞,可是若是是公司項目,改後端接口每每是一件比較敏感的事情,尤爲是對於三端(web、andriod、ios)公用同一套後端接口的狀況。大部分狀況下,均是按第二種方式來解決問題的。segmentfault
所以若是接口的返回值,能夠經過某種手段,從靜態變爲動態,即調用者來聲明接口返回什麼數據,很大程度上能夠進一步解耦先後端的關聯。後端
在GraphQL中,咱們經過預先定義一張Schema
和聲明一些Type
來達到上面說起的效果,咱們須要知道:api
這麼說可能比較抽象,咱們一個一個來講明。
對於數據模型的抽象是經過Type來描述的,每個Type有若干Field組成,每一個Field又分別指向某個Type。
GraphQL的Type簡單能夠分爲兩種,一種叫作Scalar Type(標量類型)
,另外一種叫作Object Type(對象類型)
。
GraphQL中的內建的標量包含,String
、Int
、Float
、Boolean
、Enum
,對於熟悉編程語言的人來講,這些都應該很好理解。
值得注意的是,GraphQL中能夠經過Scalar
聲明一個新的標量,好比:
DateTime
和ID
這兩個標量分別表明日期格式和主鍵Upload
標量來表明要上傳的文件總之,咱們只須要記住,標量是GraphQL類型系統中最小的顆粒,關於它在GraphQL解析查詢結果時,咱們還會再說起它。
僅有標量是不夠的抽象一些複雜的數據模型的,這時候咱們須要使用對象類型,舉個例子(先忽略語法,僅從字面上看):
type Article { id: ID text: String isPublished: Boolean }
上面的代碼,就聲明瞭一個Article
類型,它有3個Field,分別是ID
類型的id,String
類型的text和Boolean
類型的isPublished。
對於對象類型的Field的聲明,咱們通常使用標量,可是咱們也可使用另一個對象類型,好比若是咱們再聲明一個新的User
類型,以下:
type User { id: ID name: String }
這時咱們就能夠稍微的更改一下關於Article
類型的聲明代碼,以下:
type Article { id: ID text: String isPublished: Boolean author: User }
Article
新增的author
的Field是User
類型, 表明這篇文章的做者。
總之,咱們經過對象模型來構建GraphQL中關於一個數據模型的形狀,同時還能夠聲明各個模型之間的內在關聯(一對多、一對一或多對多)。
關於類型,還有一個較重要的概念,即類型修飾符,當前的類型修飾符有兩種,分別是List
和Required
,它們的語法分別爲[Type]
和Type!
, 同時這二者能夠互相組合,好比[Type]!
或者[Type!]
或者[Type!]!
(請仔細看這裏!
的位置),它們的含義分別爲:
咱們進一步來更改上面的例子,假如咱們又聲明瞭一個新的Comment
類型,以下:
type Comment { id: ID! desc: String, author: User! }
你會發現這裏的ID
有一個!
,它表明這個Field是必填的,再來更新Article
對象,以下:
type Article { id: ID! text: String isPublished: Boolean author: User! comments: [Comment!] }
咱們這裏的做出的更改以下:
最終的Article
類型,就是GraphQL中關於文章這個數據模型,一個比較簡單的類型聲明。
如今咱們開始介紹Schema
,咱們以前簡單描述了它的做用,即它是用來描述對於接口獲取數據邏輯
的,但這樣描述仍然是有些抽象的,咱們其實不妨把它當作REST架構中每一個獨立資源的uri
來理解它,只不過在GraphQL中,咱們用Query來描述資源的獲取方式。所以,咱們能夠將Schema
理解爲多個Query組成的一張表。
這裏又涉及一個新的概念Query
,GraphQL中使用Query
來抽象數據的查詢邏輯,當前標準下,有三種查詢類型,分別是query(查詢)、mutation(更改)和subscription(訂閱)。
Note: 爲了方便區分,Query
特指GraphQL中的查詢(包含三種類型),query
指GraphQL中的查詢類型(僅指查詢類型)
上面所說起的3中基本查詢類型是做爲Root Query(根查詢)
存在的,對於傳統的CRUD項目,咱們只須要前兩種類型就足夠了,第三種是針對當前日趨流行的real-time
應用提出的。
咱們按照字面意思來理解它們就好,以下:
仍然以一個例子來講明。
首先,咱們分別以REST和GraphQL的角度,以Article
爲數據模型,編寫一系列CRUD的接口,以下:
Rest 接口
GET /api/v1/articles/ GET /api/v1/article/:id/ POST /api/v1/article/ DELETE /api/v1/article/:id/ PATCH /api/v1/article/:id/
GraphQL Query
query { articles(): [Article!]! article(id: Int): Article! } mutation { createArticle(): Article! updateArticle(id: Int): Article! deleteArticle(id: Int): Article! }
對比咱們較熟悉的REST的接口咱們能夠發現,GraphQL中是按根查詢的類型來劃分Query職能的,同時還會明確的聲明每一個Query所返回的數據類型,這裏的關於類型的語法和上一章節中是同樣的。須要注意的是,咱們所聲明的任何Query
都必須是Root Query
的子集,這和GraphQL內部的運行機制有關。
例子中咱們僅僅聲明瞭Query類型和Mutation類型,若是咱們的應用中對於評論列表有real-time
的需求的話,在REST中,咱們可能會直接經過長鏈接或者經過提供一些帶驗證的獲取長鏈接url的接口,好比:
POST /api/v1/messages/
以後長鏈接會將新的數據推送給咱們,在GraphQL中,咱們則會以更加聲明式的方式進行聲明,以下
subscription { updatedArticle() { mutation node { comments: [Comment!]! } } }
咱們沒必要糾結於這裏的語法,由於這篇文章的目的不是讓你在30分鐘內學會GraphQL的語法,而是理解的它的一些核心概念,好比這裏,咱們就聲明瞭一個訂閱Query,這個Query會在有新的Article被建立或者更新時,推送新的數據對象。固然,在實際運行中,其內部實現仍然是創建於長鏈接之上的,可是咱們可以以更加聲明式的方式來進行聲明它。
若是咱們僅僅在Schema中聲明瞭若干Query,那麼咱們只進行了一半的工做,由於咱們並無提供相關Query所返回數據的邏輯。爲了可以使GraphQL正常工做,咱們還須要再瞭解一個核心概念,Resolver(解析函數)
。
GraphQL中,咱們會有這樣一個約定,Query和與之對應的Resolver是同名的,這樣在GraphQL才能把它們對應起來,舉個例子,好比關於articles(): [Article!]!
這個Query, 它的Resolver的名字必然叫作articles
。
在介紹Resolver以前,是時候從總體上了解下GraphQL的內部工做機制了,假設如今咱們要對使用咱們已經聲明的articles
的Query,咱們可能會寫如下查詢語句(一樣暫時忽略語法):
Query { articles { id author { name } comments { id desc author } } }
GraphQL在解析這段查詢語句時會按以下步驟(簡略版):
Query
的Root Query
類型是query
,同時須要它的名字是articles
articles
的Resolver
獲取解析數據,第一層解析完畢以後對第一層解析的返回值,進行第二層解析,當前articles
還包含三個子Query
,分別是id
、author
和comments
User
的Resolver
獲取數據,當前field解析完畢author
還包含一個Query
, name
,因爲它是標量類型,解析結束咱們能夠發現,GraphQL大致的解析流程就是遇到一個Query以後,嘗試使用它的Resolver取值,以後再對返回值進行解析,這個過程是遞歸的,直到所解析Field的類型是Scalar Type(標量類型)
爲止。解析的整個過程咱們能夠把它想象成一個很長的Resolver Chain(解析鏈)。
這裏對於GraphQL的解析過程只是很簡單的歸納,其內部運行機制遠比這個複雜,固然這些對於使用者是黑盒的,咱們只須要大概瞭解它的過程便可。
Resolver自己的聲明在各個語言中是不同的,由於它表明數據獲取的具體邏輯。它的函數簽名(以js爲例子)以下:
function(parent, args, ctx, info) { ... }
其中的參數的意義以下:
article(id: Int)
中的id
)值得注意的是,Resolver內部實現對於GraphQL徹底是黑盒狀態。這意味着Resolver如何返回數據、返回什麼樣的數據、從哪返回數據,徹底取決於Resolver自己,基於這一點,在實際中,不少人每每把GraphQL做爲一箇中間層來使用,數據的獲取經過Resolver來封裝,內部數據獲取的實現可能基於RPC、REST、WS、SQL等多種不一樣的方式。同時,基於這一點,當你在對一些未使用GraphQL的系統進行遷移時(好比REST),能夠很好的進行增量式遷移。
大概就這麼多,首先感謝你耐心的讀到這裏,雖然題目是30分鐘熟悉GraphQL核心概念,可是可能已經超時了,不過我相信你對GraphQL中的核心概念已經比較熟悉了。可是它自己所涉及的東西遠遠比這個豐富,同時它還處於飛速的發展中。
最後我嘗試根據這段時間的學習GraphQL的經驗,提供一些進一步學習和了解GraphQL的方向和建議,僅供參考:
我建議再仔細去官網,讀一下官方文檔,若是有興趣的話,看看GraphQL的spec也是極好的。這篇文章雖然介紹了核心概念,可是其餘一些概念沒有涉及,好比Union、Interface、Fragment等等,這些概念均是基於核心概念之上的,在瞭解核心概念後,應當會很容易理解。
偏向服務端方向的話,除了須要進一步瞭解GraphQL在某個語言的具體生態外,還須要瞭解一些關於緩存、上傳文件等特定方向的東西。若是是想作系統遷移,還須要對特定的框架作一些調研,好比graphene-django。
若是是想使用GraphQL自己作系統開發,這裏推薦瞭解一個叫作prisma的框架,它自己是在GraphQL的基礎上構建的,而且與一些GraphQL的生態框架兼容性也較好,在各大編程語言也均有適配,它自己能夠當作一個ORM來使用,也能夠當作一個與數據庫交互的中間層來使用。
偏向客戶端方向的話,須要進一步瞭解關於graphql-client的相關知識,我這段時間瞭解的是apollo,一個開源的grapql-client框架,而且與各個主流前端技術棧如Angular、React等均有適配版本,使用感受良好。
同時,還須要瞭解一些額外的查詢概念,好比分頁查詢中涉及的Connection、Edge等。
大概就這麼多,若有錯誤,還望指正。
歡迎關注公衆號 全棧101,只談技術,不談人生