前言
在過去的十多年中奏属,REST已經(jīng)成為設(shè)計(jì)web api的一個(gè)模糊標(biāo)準(zhǔn)藻肄。它提供了一套比較完整語義化結(jié)構(gòu)化的標(biāo)準(zhǔn),然后這種設(shè)計(jì)還是相對而已比較僵硬皆串,不夠靈活,不能快速的對API進(jìn)行擴(kuò)容眉枕,無法跟上目前客戶的快速需求變化恶复。然而Facebook提供了一套比較完整的API接口設(shè)計(jì)GraphQL,GraphQL解決了REST對于開發(fā)人員的一些不便,大大提供了開發(fā)效率齐遵,以及擴(kuò)展性寂玲。
官方定義:
GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.
什么是GraphQl
GraphQL全稱Graph Query Language 塔插,語義上指圖表化查詢語言梗摇,是一種描敘客戶端香服務(wù)器請求數(shù)據(jù)的API語法,類似于RESTful API規(guī)范想许,它與數(shù)據(jù)庫沒有任何關(guān)系伶授,
它不依賴任何數(shù)據(jù)庫,可以與任何的MySQL流纹、NoSQL數(shù)據(jù)庫一起使用糜烹。
GraphQl的特征
- schema
GraphQl schema是強(qiáng)類型的,可使用SDL來定義類似于數(shù)據(jù)庫的Schema定義漱凝。每一個(gè)schema中允許出現(xiàn)三種根類型:Query疮蹦、Mutation、Subscription茸炒,每一次調(diào)用GraphQL服務(wù)愕乎,需要指定嗲用Schema中的哪種根類型(默認(rèn)是Query)
query是用來查詢數(shù)據(jù)、mutation是用來增加壁公、修改感论、刪除數(shù)據(jù)的
query {
users(): [User!]!
user(id: Int): User!
}
mutation {
createUser(): User!
deleteUser(id: Int): User!
}
-
Type
是對于數(shù)據(jù)模型的抽象, 包含兩種類型:一種是scalar type(標(biāo)量類型) 紊册,另一種是object type(對象類型)比肄。
-
scalar type:
GraphQL中的內(nèi)建的標(biāo)量包含,String、Int芳绩、Float掀亥、Boolean、Enum妥色,除此之外铺浇,GraphQL中可以通過scalar聲明一個(gè)新的標(biāo)量
object type
一個(gè) GraphQL schema 中的最基本的組件是對象類型,如上面的User垛膝,它表示服務(wù)器將要返回的所有字段鳍侣,如:
type User { id: ID! #id mobile: String #電話好嗎 nickName: String #昵稱 avatar:String #頭像 createTime:Date #創(chuàng)建時(shí)間 updateTime:Date #更新時(shí)間 workList:[Work] }
-
User是一個(gè)GraphQL對象類型,可以理解就是一個(gè)object對象吼拥,里面有一些字段屬性倚聚, id、mobile凿可、nickName惑折、avatar就是scalar type,
createTime枯跑、updateTime是自定義的scalar type惨驶,workList就是一個(gè)object type
在每一次操作User查詢時(shí),只會出現(xiàn)定義的字段如id敛助、mobile等等粗卜,
id: ID!表示這個(gè)字段是非空,也就是GraphQL服務(wù)一定能保證這個(gè)字段有值纳击。[Work] 表示一個(gè)work對象的數(shù)組對象续扔。
- Resolve 解析器
上述中的schema(query、mutation)是用來操作數(shù)據(jù)焕数,type是數(shù)據(jù)類型纱昧,而resolve就是說明如何執(zhí)行相關(guān)的(query、mutation)操作放回?cái)?shù)據(jù)的邏輯堡赔,
也是連接schema與type的
GraphQL中识脆,默認(rèn)有這樣的約定,Query(包括query善已、mutation灼捂、subscription)和與之對應(yīng)的Resolve是同名的,比如關(guān)于users(): [User!]!這個(gè)query雕拼,它的Resolve的名字必然叫做user
如:前端調(diào)用query語句時(shí):
query{
users{
id
mobile
workList{
id
}
}
}
它的解析過程:
- 第一次解析時(shí)纵东,當(dāng)前的的類型時(shí)query,所以我們定義一個(gè)同名的 query:users 的Resolver
- 我們會使用query:user的Resolve獲取解析數(shù)據(jù)
- 如id啥寇,mobile 第一層順利解析偎球,在解析workList時(shí)洒扎,我們會使用users:workList 的Resolve去解析
- 然后解析出來workList中的id
概括總結(jié)GraphQL大體解析流程就是遇見一個(gè)Query之后,嘗試使用它的Resolver取值衰絮,
之后再對返回值進(jìn)行解析袍冷,這個(gè)過程是遞歸的,直到所有解析Field類型是Scalar Type(標(biāo)量類型)為止猫牡。
整個(gè)解析過程可以想象為一個(gè)很長的Resolver Chain(解析鏈)胡诗。
為什么使用GraphQL
-
GraphQL解決了什么問題
- 接口多難維護(hù):接口的數(shù)量通常由業(yè)務(wù)場景的數(shù)量決定,為了盡量減少接口數(shù)量淌友,我們經(jīng)常對業(yè)務(wù)進(jìn)行抽象煌恢,即使如此,由于業(yè)務(wù)總是多變的
露的接口還是很多。 - 接口不夠靈活:出于帶寬的考慮移動端我們要求接口返回盡量少的字段,PC 端通常要展現(xiàn)更多字段欣鳖;
- 接口合并:如考慮首屏性能,我們又要求對接口做合并柒室;傳統(tǒng) API 應(yīng)對這些需求,前后端都面臨改造,成本較高。
- 文檔不全:由于接口文檔幾乎總是不能及時(shí)更新肴颊,前端工程師無法預(yù)知接口響應(yīng)的數(shù)據(jù)格式,影響前端開發(fā)進(jìn)度渣磷。
- 接口多難維護(hù):接口的數(shù)量通常由業(yè)務(wù)場景的數(shù)量決定,為了盡量減少接口數(shù)量淌友,我們經(jīng)常對業(yè)務(wù)進(jìn)行抽象煌恢,即使如此,由于業(yè)務(wù)總是多變的
-
GraphQL如何解決問題
接口多難維護(hù):
GraphQL并不需要為每個(gè)業(yè)務(wù)提供一個(gè)對應(yīng)的接口婿着,只需要定義一些基本的schema即可-
接口不夠靈活:
在GraphQL中,是client需要哪些數(shù)據(jù)幸海,server才精確返回哪些數(shù)據(jù)祟身。如:query{ user(id:1):{ id mobile } } // response user:{ id:1 mobile:'139XXXXX139' }
query{ user(id:1):{ id mobile name } } // response user:{ id:1 mobile:'139XXXXX139' name:'YUX' }
基于Resolve會解析query中所有的字段奥务,其他字段不會返回物独。
-
接口合并減少網(wǎng)絡(luò)請求
基于一些數(shù)據(jù)量大的接口,rest一般會是通過多個(gè)接口并/串行組合數(shù)據(jù)氯葬,而graphql則可以通過嵌套編寫復(fù)雜的schema達(dá)到一次請求{ user(id:1) { id mobile workList(limit: 10) { id url name comment(limit: 20) { id author title } } } }
如上挡篓,我們可以用一個(gè)借口獲得與用戶相關(guān)的所有信息,并不需要多個(gè)接口返回帚称。
文檔不全
GraphQL讓你的整個(gè)應(yīng)用共享一套API官研,通過GraphQL API能夠更好的利用你的現(xiàn)有數(shù)據(jù)和代碼,就是說通過我們定義的schema闯睹,很清晰的能看到
入?yún)⒁约俺鰠⑾酚穑⒉恍枰^多的API文檔說明。
概括總結(jié):
客戶端的對數(shù)據(jù)的述求:調(diào)用哪個(gè)方法楼吃,傳遞什么樣的參數(shù)始花,返回哪些字段妄讯。服務(wù)端拿到這段 Schema 之后,
通過事先定義好的服務(wù)端 Schema 接收請求參數(shù)并執(zhí)行對應(yīng)的 resolve 函數(shù)提供數(shù)據(jù)服務(wù)酷宵。
整個(gè)過程可以想象成我們吃自助餐的過程亥贸,服務(wù)端 Schema 就好比自助餐線,
擺上我們能提供的所有食物浇垦;客戶端 Schema 就描述了我們想要吃的食物炕置,按需獲取就好了
GraphQL 潛在問題
- N+1問題:在實(shí)現(xiàn)GraphQL代碼中我們很容易寫出性能比較差的查詢,引起N+1的問題
```
works = Works.getAll();
works.forEach(item=>{
label = Label.get(item.id)
})
```
由于作品標(biāo)簽不存在work表中男韧,所有第一次查詢出作品列表朴摊,第二步循環(huán)去查詢每個(gè)作品的標(biāo)簽信息,原本是一個(gè)查詢的此虑,結(jié)果
導(dǎo)致來N+1次仍劈,這就是N+1問題,在graphql中寡壮,很容易造成這樣贩疙,主要是由于 GraphQL query 的逐層遞歸解析方式所引起的。
node解決方案采用DataLoader
-
安全問題
由于graphql自帶強(qiáng)大的內(nèi)省自檢機(jī)制也就是我們上面提到的靜態(tài)檢測况既,可以直接獲取后端定義的所有接口信息
{ __schema { types { name } } } response: { "data": { "__schema": { "types": [ { "name": "Query" }, { "name": "Int" }] } } }
所以對于每個(gè)接口的權(quán)限管理这溅,就需要開發(fā)者定制比較合理的鑒權(quán)機(jī)制
-
拒絕服務(wù)
如果我們定義如下查詢就會導(dǎo)致,服務(wù)器拒絕服務(wù)blogs(blogId: $blogId, systemType: $systemType) { _id title type content author { name blog { author { name blog { author { name blog { author { name blog { author { name # and so on... } } } } } } } title createdAt publishedAt } } publishedAt }
我們定義接口的時(shí)候盡量避免如此定義棒仍,也可以在GraphQL服務(wù)器上限制查詢深度悲靴,可以采用graphql-depth-limit解決該問題
-
不易緩存
官方的翻譯:在一個(gè)基于端點(diǎn)的API,客戶端能夠使用HTTP緩存輕易的避免重復(fù)獲取資源和識別什么時(shí)候兩個(gè)資源是一樣的莫其●校客戶端可以根據(jù)API中的URL作為全局唯一的標(biāo)識符構(gòu)建緩存。
在GraphQL中乱陡,沒有類似URL的對象能夠作為全局唯一的標(biāo)識符浇揩。最佳實(shí)踐是提供這么一個(gè)標(biāo)識符供客戶端使用。
小結(jié)
GraphQL只是一個(gè)API技術(shù)憨颠,它為API連接的前后端提供了一種新的便捷處理方案胳徽,與語言無關(guān)。無論如何爽彤,該做鑒權(quán)的就鑒權(quán)养盗,該校驗(yàn)數(shù)據(jù)的還是一定得校驗(yàn)。
GraphQL會提供我們的開發(fā)效率适篙,減少前后端的溝通成本往核,提高了程序的可擴(kuò)展以及維護(hù)性。