咱們知道 GraphQL 使用 Schema 來描述數據,並經過制定和實現 GraphQL 規範 定義了支持 Schema 查詢的 DSQL (Domain Specific Query Language,領域特定查詢語言)。Schema 幫助將複雜的業務模型數據抽象拆分紅細粒度的基礎數據結構,而 DSQL 的實現則賦予了前端開發者自由組織和定製請求數據的能力。若是以一張圖來表示的話,能夠將 GraphQL 看作一條以 通用基礎業務數據模型 爲基礎、將傳統後端服務和前端頁面緊密且自由地聯繫在一塊兒的紐帶。javascript
爲何 GraphQL 的 Schema 可以表示出服務器所支持的複雜業務模型數據,GraphQL 的 Query 又是怎樣賦予前端開發者對數據的定製能力,本文將經過分析和理解 GraphQL 的設計來和你們一塊兒探討解答這些問題。php
GraphQL 由如下組件構成:前端
做爲將數據模型和具體接口實現解耦的 DSL,GraphQL 的基礎組件,也是它最重要的組件之一就是類型系統。java
能夠將 GraphQL 的類型系統分爲標量類型(Scalar Types,標量類型)和其餘高級數據類型,標量類型便可以表示最細粒度數據結構的數據類型,能夠和 JavaScript 的原始類型對應。GraphQL 規範目前規定支持的標量類型有:git
Int
:整數,對應 JavaScript 的 NumberFloat
:浮點數,對應 JavaScript 的 NumberString
:字符串,對應 JavaScript 的 StringBoolean
:布爾值,對應 JavaScript 的 BooleanID
:ID 值,是一個序列化後值惟一的字符串,能夠視做對應 ES 2015 新增的 SymbolScalar Types
的 JavaScript 參考實現代碼能夠查看 這裏 。github
其餘高級數據類型包括:數據庫
Object
:對象後端
用於描述層級或者樹形數據結構。對於樹形數據結構來講,葉子字段的類型都是標量數據類型。幾乎全部 GraphQL 類型都是對象類型。Object 類型有一個 name 字段,以及一個很重要的 fields 字段。fields 字段能夠描述出一個完整的數據結構。例如一個表示地址數據結構的 GraphQL 對象爲:服務器
const AddressType = new GraphQLObjectType({ name: 'Address', fields: { street: { type: GraphQLString }, number: { type: GraphQLInt }, formatted: { type: GraphQLString, resolve(obj) { return obj.number + ' ' + obj.street } } } });
Interface
:接口數據結構
接口用於描述多個類型的通用字段,例如一個表示實體數據結構的 GraphQL 接口爲:
const EntityType = new GraphQLInterfaceType({ name: 'Entity', fields: { name: { type: GraphQLString } } });
Union
:聯合
聯合類型用於描述某個字段可以支持的全部返回類型以及具體請求真正的返回類型,例如一個表示寵物(能夠是貓或者狗)的 GraphQL 聯合類型爲:
const PetType = new GraphQLUnionType({ name: 'Pet', types: [DogType, CatType], resolveType(value) { if (value instanceof Dog) { return DogType; } if (value instanceof Cat) { return CatType; } } });
Enum
:枚舉
用於表示可枚舉數據結構的類型,例如表示 RGB 色值的 GraphQL 枚舉類型爲:
const RGBType = new GraphQLEnumType({ name: 'RGB', values: { RED: { value: 0 }, GREEN: { value: 1 }, BLUE: { value: 2 }, } });
Input Object
:輸入對象
是爲了查詢(query)而定義的數據類型,不直接重用 Object 類型是由於 Object 的字段可能存在循環引用,或者字段引用了不能做爲查詢輸入對象的接口和聯合類型。參考實現中 Input Object
的定義代碼爲:
export type GraphQLInputType =
GraphQLScalarType |
GraphQLEnumType |
GraphQLInputObjectType |
GraphQLList<GraphQLInputType> |
GraphQLNonNull<
GraphQLScalarType |
GraphQLEnumType |
GraphQLInputObjectType |
GraphQLList<GraphQLInputType>
>;
export function isInputType(type: ?GraphQLType): boolean { const namedType = getNamedType(type); return ( namedType instanceof GraphQLScalarType || namedType instanceof GraphQLEnumType || namedType instanceof GraphQLInputObjectType ); }
能夠看到,Object、Interface 和 Union 三種類型是不能做爲輸入對象類型的。
List
:列表
列表是其餘類型的封裝,一般用於對象字段的描述。例以下面 PersonType 類型數據的 parents 和 children 字段:
const PersonType = new GraphQLObjectType({ name: 'Person', fields: () => ({ parents: { type: new GraphQLList(Person) }, children: { type: new GraphQLList(Person) }, }) });
Non-Null
:不能爲 Null
Non-Null 強制類型的值不能爲 null,而且在請求出錯時必定會報錯。能夠用於必須保證值不能爲 null 的字段。例如數據庫的行的 id 字段不能爲 null:
const RowType = new GraphQLObjectType({ name: 'Row', fields: () => ({ id: { type: new GraphQLNonNull(GraphQLString) } }) });
還有一種重要的數據類型,即 schema 類型,它描述了後端服務器可以提供的數據支持。這裏先暫時不介紹,由於它涉及 GraphQL 的其餘組件,等所有介紹完咱們再來看 GraphQL 中 schema 的 具體實現 。
類型系統對應咱們開頭提到的 Schema,是對服務器端數據的描述,而查詢語言則解耦了前端開發者與後端接口的依賴。前端開發者利用查詢語言能夠自由地組織和定製系統可以提供的業務數據。
GraphQL 的一個查詢請求被稱爲一份 query 文檔(query document),即 GraphQL 服務可以解析驗證並執行的一串請求字符串。query 由操做(Operation)和片斷(Fragments)組成。一個 query 能夠包含多個操做和片斷。只有包含操做的 query 纔會被 GraphQL 服務執行。可是不包含操做,只有片斷的 query 也會被 GraphQL 服務解析驗證,這樣一份片斷就能夠在多個 query 文檔內使用。
只包含一個操做的 query 能夠不帶操做名稱或者使用簡寫形式(即 query 關鍵字加操做名)。query 包含多個操做時,全部操做都必須帶上名稱。
GraphQL 規範支持兩種操做:
在官方提供的參考實現中咱們會發現還支持一種操做 subscription ,這是爲了處理訂閱更新這種比較複雜的實時數據更新場景而設計的操做,不過目前這種操做還處於試驗階段,不建議在生產環境中使用。
查詢請求的模型能夠用下面的圖來表示:
選擇集合表示當前選中的數據內容,格式爲:
{
Field // 字段名
FragmentSpread // 片斷展開 InlineFragment // 內聯片斷 }
關於選擇集合的使用,能夠參考 graphql-js 的代碼 。參考實現代碼在 這裏 。
字段格式爲:
alias:name(argument:value)
其中 alias
是字段的別名,即結果中顯示的字段名稱。
name
爲字段名稱,對應 schema 中定義的 fields 字段名。
argument
爲參數名稱,對應 schema 中定義的 fields 字段的參數名稱。
value
爲參數值,值的類型對應標量類型的值。
例如這樣的請求: http://yunhe.taobao.com/?query={banner{backgroundURL:bg,biaoti:slogan}}
backgroundURL 就是 bg 字段的別名。
片斷是 GraphQL 的主要組合數據結構,經過片斷能夠重用重複的字段選擇,減小 query 中的重複內容。片斷又分爲 FragmentSpread 和 InlineFragment。例如沒有片斷時須要這樣編寫 query:
query noFragments { user(id: 4) { friends(first: 10) { id name profilePic(size: 50) } mutualFriends(first: 10) { id name profilePic(size: 50) } } }
query 中存在下列重複的選擇集合:
{ id name profilePic(size: 50) }
能夠用片斷簡化爲:
query withFragments { user(id: 4) { friends(first: 10) { ...friendFields } mutualFriends(first: 10) { ...friendFields } } } fragment friendFields on User { id name profilePic(size: 50) }
使用片斷時須要加上 ...
操做符表示展開片斷內容。
內聯片斷示例以下:
query inlineFragmentTyping { profiles(handles: ["zuck", "cocacola"]) { handle ... on User { friends { count } } ... on Page { likers { count } } } }
指令要解決的是 query 執行時字段參數沒法覆蓋的狀況,例如引入或者忽略某個字段。指令爲 GraphQL 執行添加了更多的信息。
指令實例以下:
query hasConditionalFragment($condition: Boolean) {
...maybeFragment @include(if: $condition) } fragment maybeFragment on Query { me { name } }
include 指令表示只有在 if 參數爲 true 時才引入片斷表示的字段。
skip 指令表示在 if 參數爲 true 時忽略片斷中的字段。
熟悉了 類型系統 和 查詢語言 咱們就能夠用 GraphQL 來實現應用層的數據請求了。其餘三個 GraphQL 組件更偏向於 DSL 的實現和原理,所以本文再也不作詳細介紹,感興趣的同窗能夠對照 規範 和 參考實現 本身研究。
GraphQL 是在應用層對業務數據模型的抽象,是對數據請求定製的 DSQL,它解除了接口和數據之間的綁定,對業務數據結構作了抽象和整理,業務邏輯中的數據依賴於底層數據庫結構,而且能夠由具體業務場景來定製,不一樣的業務場景只要基於一樣一套基礎業務數據模型就能夠獲得複用,在我看來,這纔是 GraphQL 帶來的最大改變和收益。