7月份咱們前端團隊推進落地了一個 toB 類型的系統,因爲服務端也由咱們前端工程師來承接,因此服務端技術選型上咱們有了話語權,API 這一起咱們選擇了 GraphQL 。本文將闡述我學習 GraphQL 這門技術的一些思考。javascript
學習一門新技術,首先要把問題域弄清楚。社區有大量 GraphQL 與傳統 API 解決方案(含 REST API)對比文章,總結下來,傳統 API 存在如下問題:html
針對以上問題,GraphQL 給出了較爲完善的解決方案。前端
接下來我經過一個實例講解 GraphQL 解決問題的思路,客戶端的述求:根據性別查詢團隊成員列表,返回 id
、 gender
、 name
、 nickName
,GrahpQL 的處理過程以下圖:java
請求參數在發送到服務端以前會先通過 GraphQL Client 轉換成客戶端 Schema,這段 Schema 實際上是一段 query
開頭的字符串,描述了客戶端的對數據的述求:調用哪一個方法,傳遞什麼樣的參數,返回哪些字段。服務端拿到這段 Schema 以後,經過事先定義好的服務端 Schema 接收請求參數並執行對應的 resolve
函數提供數據服務。整個過程能夠想象成咱們吃自助餐的過程,服務端 Schema 就比如自助餐線,擺上咱們能提供的全部食物;客戶端 Schema 就描述了咱們想要吃的食物,按需獲取就行了。 node
講到這裏,好奇心強的同窗可能已經開始思考這個問題了:客戶端 Schema 本質上就是一段字符串,服務端如何識別並響應這段字符串?react
識別與響應客戶端 Schema 依賴於官方類庫 graphql-js ,服務端拿到客戶端 Schema 字符串後會作以下處理:webpack
爲了識別客戶端 Schema, graphql-js
定義了一系列的特徵標識符:ios
export const TokenKind = Object.freeze({ BANG: '!', DOLLAR: '$', PAREN_L: '(', PAREN_R: ')', SPREAD: '...', COLON: ':', EQUALS: '=', BRACKET_L: '[', BRACKET_R: ']', ... });
並定義了 AST 語法樹規範,規定語法樹支持如下節點:git
/** * The set of allowed kind values for AST nodes. */ export const Kind = Object.freeze({ // Name NAME: 'Name', // Document DOCUMENT: 'Document', OPERATION_DEFINITION: 'OperationDefinition', VARIABLE_DEFINITION: 'VariableDefinition', VARIABLE: 'Variable', // Values INT: 'IntValue', FLOAT: 'FloatValue', STRING: 'StringValue', BOOLEAN: 'BooleanValue', ... });
有了特徵字符串與 AST 語法樹規範,GraphQL Server 對客戶端 Schema 進行逐字符掃描(charCodeAt),最終解析階段的產出物爲 document
,上文示例中的客戶端 Schema 解析完成以後的部分 document
:github
{ "kind":"Document", "definitions":[ { "kind":"OperationDefinition", "operation":"query", "name":{ "kind":"Name", "value":"DisplayMember", "loc":{ "start":13, "end":26 } }, "selectionSet":{ "kind":"SelectionSet", "selections":[ { "kind":"Field", "alias":null, "name":{ "kind":"Name", "value":"fetchByGender", "loc":{ "start":37, "end":50 } }, "arguments":[ { "kind":"Argument", "name":{ "kind":"Name", "value":"gender", "loc":{ "start":51, "end":57 } }, "value":{ "kind":"StringValue", "value":"M", "loc":{ "start":59, "end":62 } }, "loc":{ "start":51, "end":62 } } ], ...
若是客戶端 Schema 不符合服務端定義的 AST 規範,解析過程會直接拋出語法異常 Syntax Error
,拿上文的示例舉例,我將客戶端 Schema 中的 fetchByGender(gender: "M")
改成 fetchByGender(gender)
,只傳遞參數名,不傳遞參數值,則服務端會響應:
{ "errors":[ { "message":"Syntax Error GraphQL request (3:29) Expected :, found ) 2: query DisplayMember { 3: fetchByGender(gender) { ^ 4: list { ", "locations":[ { "line":3, "column":29 } ] } ] }
結構化的報錯信息也是 GraphQL 的一大特色,定位問題很是方便。只要語法沒問題解析階段就能順利完成,而後進入校驗階段。
校驗階段用於驗證客戶端 Schema 是否按照服務端 Schema 定義好的方式獲取數據,好比:獲取數據的方法名是否有誤,必填項是否有值等等,校驗範圍一共有幾十種,我沒有辦法一一舉例。拿上文的示例舉例,我將客戶端 Schema 中的 fetchByGender
改成 fetchByGen
, fetchByGen
在服務端根本沒有定義,則服務端會響應:
{ "errors":[ { "message":"Cannot query field "fetchByGen" on type "Query". Did you mean "fetchByGender"?", "locations":[ { "line":3, "column":9 } ] } ] }
不只返回結構化的報錯信息,還很是人性化的告訴你正確的調用方式是什麼。校驗階段經過以後會進入執行階段
執行階段依賴的輸入爲:解析階段的產出物 document
,服務端 Schema;其中 document
準確描述了客戶端對數據的述求:請求哪一個方法,參數是什麼,須要哪些字段;服務端 Schema 描述了提供數據的方式;拿上文的示例舉例,服務端 Schema 須要這樣定義:
const graphqlApi = require('graphql'); const { GraphQLObjectType, GraphQLList, GraphQLNonNull, GraphQLSchema, GraphQLString, } = graphqlApi; const dataSource = require('./dataSource'); const memType = new GraphQLObjectType({ name: 'Male', description: 'A member gender is Male.', fields: () => ({ id: { type: new GraphQLNonNull(GraphQLString), description: 'The id of member', }, name: { type: GraphQLString, description: 'The name of the character.', }, nickName: { type: GraphQLString, description: 'The nickName of the character.', }, gender: { type: GraphQLString, description: 'The gender of the character.', }, list: { type: new GraphQLList(memType), description: 'The mems list by gender.', }, }) }); const queryType = new GraphQLObjectType({ name: 'Query', fields: () => ({ fetchByGender: { type: memType, args: { gender: { description: 'gender of the human', type: new GraphQLNonNull(GraphQLString), }, }, resolve: (root, { gender }) => { // 訪問數據庫或三方 API 查詢成員列表 return { list: dataSource.getMembers(gender), }; }, }, }), }); module.exports = new GraphQLSchema({ query: queryType, types: [memType], });
執行服務端 Schema 中的 resolve
函數,獲得執行階段的輸出:
{ "data":{ "fetchByGender":{ "list":[ { "id":"1", "gender":"M", "name":"童開宏", "nickName":"慕冥" } ] } } }
固然要完成服務端 Schema 的定義,你須要學習 GraphQL 的 類型系統 ,你們翻閱 API 文檔便可。
原理弄清楚以後咱們須要對 GraphQL 這門技術的邊界有一個清醒的認識:
apollo
提供的 Webpack 插件 ,固然也有一些 GraphQL 客戶端連發送 Ajax 請求的活兒也幹了,無非是在底層調用其餘類庫好比 axios
發請求。因爲 GraphQL 經過客戶端 Schema 而不是經過 URL 描述數據述求,因此理論上服務端只須要對客戶端暴露一個地址便可,解決了接口數量衆多維護成本高的問題;同時,服務端提供的是全量字段,客戶端可按需獲取,面對接口擴展的需求,服務端沒有開發成本;最後,經過 GraphiQL 可視化調試界面展示服務端能提供的全部數據,開發過程再也不依賴接口文檔:
GraphQL 官方提供核心能力:
咱們還缺什麼?
官方只提供了 JavaScript 語言支持,社區愛好者很快在不一樣編程語言中實現了 GraphQL 的理念:JAVA 、 .NET 等等,更多語言支持,請查看 官網
官方提供的 Relay 解決了 GraphQL 與 React 相結合的問題,Apollo Client 提供了與其餘前端框架融合的解決方案,好比 Vue、Angular 等等。
開發體驗
graphql-tools
:在上文示例代碼的服務端 Schema 中,咱們將類型的定義(typeDefs)與處理函數的定義(resolvers)放在同一個文件中,職責上不夠單一,藉助 graphql-tools 咱們能夠將兩者分不一樣的文件定義;egg-graphql
:與 Node 框架 egg 相結合,制定 目錄規範 並提供語法糖提升開發效率;GraphQL 的優勢上文已經講過了,真的是從業務痛點出發,解決了傳統 API 存在的問題,可是 GraphQL 在解決問題的同時也帶了一些新的問題,這些問題在某種程度上阻礙了這門技術的普及:
query IAmEvil { author(id: "abc") { posts { author { posts { author { posts { author { # that could go on as deep as the client wants! } } } } } } } }
這樣的查詢語句會給數據庫帶來很大的性能開銷,服務端不得不作 限流 來規避這樣的問題,這也帶來了額外的開發成本。
任何技術都有利弊,你們要結合本身的場景權衡收益作出適合本身的技術選型。
文章可隨意轉載,但請保留此 原文連接。
很是歡迎有激情的你加入 ES2049 Studio,簡歷請發送至 caijun.hcj(at)alibaba-inc.com 。