GraphQL 核心概念

A query language created by Facebook for describing data requirements on complex application data models

系列文章:javascript

  1. GraphQL 核心概念(本文)java

  2. graphql-js 淺嘗git

最近由於工做上新產品的須要,讓我有機會了解和嘗試 GraphQL。按照套路,在介紹一項新技術的時候總要回答 3 個問題:What, Why & How。github

tradition

What is GraphQL?

正如副標題所說,GraphQL 是由 Facebook 創造的用於描述複雜數據模型的一種查詢語言。這裏查詢語言所指的並非常規意義上的相似 sql 語句的查詢語言,而是一種用於先後端數據查詢方式的規範。sql

Why using GraphQL?

當今客戶端和服務端主要的交互方式有 2 種,分別是 REST 和 ad hoc 端點。GraphQL 官網指出了它們的不足之處主要在於:當需求或數據發生變化時,它們都須要創建新的接口來適應變化,而不斷添加的接口,會形成服務器代碼的不斷增加,即便經過增長接口版本,也並不可以徹底限制服務器代碼的增加。(更多不足,前往官網查看)redux

既然,GraphQL 指出了它們的缺點,那麼它天然解決了這些問題。segmentfault

如何解決的哪?那就得說說 GraphQL 的 3 大特性。後端

  • 首先,它是聲明式的。查詢的結果格式由請求方(即客戶端)決定而非響應方(即服務器端)決定,也就是說,一個 GraphQL 查詢結果的返回是同客戶端請求時的結構同樣的,很少很多,不增不減。api

  • 其次,它是可組合的。一個 GraphQL 的查詢結構是一個有層次的字段集,它能夠任意層次地進行嵌套或組合,也就是說它能夠經過對字段進行組合、嵌套來知足需求。服務器

  • 第三,它是強類型的。強類型保證,只有當一個 GraphQL 查詢知足所設定的查詢類型,那麼查詢的結果纔會被執行。

回到以前的問題,也就是說,當需求或數據發生變化時,客戶端能夠根據需求來改變查詢的結構,只要查詢結構知足以前的定義,服務器端代碼甚至不須要作任何的修改;即便不知足,也只需修改服務器端的查詢結構,而沒必要額外添加新的接口來知足需求。

Core Concepts

可能你會問,按套路這節不應是 HOW to use GraphQL,怎麼變成了 Core Concepts?

因爲,GraphQL 是一種規範,因而,它的實現不限制某種特定語言,每種語言對 GraphQL 均可以有本身的實現,好比相對 JavaScript 就有 graphql-js。既然,實現都不相同,那麼,使用的方法也會不一樣,因此便不在這裏細述了。

這篇文章主要分享的是 GraphQL 的核心概念,主要分爲:Type System, Query Syntax, Validation Introspection 四部分。

Type System

類型系統是整個 GraphQL 的核心,它用來定義每一個查詢對象和返回對象的類型,將全部定義的對象組合起來就造成了一整個 GraphQL Schema。

這個概念比較抽象,空說很難理解,仍是拿例子來邊看邊說。我的博客相信你們都很熟悉,這裏就嘗試用一個簡單的博客系統的例子來講明,這會比官網星戰的例子簡單一點。

Let's go!

既然是一個博客,那麼,文章確定少不了,咱們首先來創建一個文章的類型。

type Post {
    id: String,
    name: String,
    createDate: String,
    title: String,
    subtitle: String,
    content: String
}

這樣,一個簡單的文章類型就定義好了,它是一個自定義的類型,包含了一系列的字段,巧合的是這些字段的類型正好都是 String(字符串類型)。

String 沒有定義過,爲何能夠直接使用哪?由於,String 是 GraphQL 支持的 scalar type(標量類型),默認的標量類型還包括 IntFloat, BooleanID

許多的博客網站都支持給每篇文章打標籤,那麼咱們在來創建一個標籤的類型。

type Tag {
    id: String,
    name: String,
    label: String,
    createDate: String
}

標籤類型和文章類型怎麼整合到一塊兒哪?

GraphQL 不僅僅支持簡單類型,還支持一些其餘類型,如 Object, Enum, List, NotNull 這些常見的類型,還有 Interface, Union, InputObject 這幾個特殊類型。

PS:一直沒搞明白 InterfaceUnion 的區別在哪,它們分別適用於什麼場景?谷歌了一下,還真有篇文章說它們的區別,不過恕我愚鈍,仍是沒能領悟,還望大神點撥...

再修改一下以前的文章類型,使一個文章能夠包含多個標籤。

type Post {
    id: String,
    name: String,
    createDate: String,
    title: String,
    subtitle: String,
    content: String,
    tags: [Tag]
}

一般在博客網站的標籤列表中會顯示該標籤下的一些文章,因爲 GraphQL 是以產品爲中心的,那麼在標籤類型下也能夠有文章類型。因而,標籤類就變成了

type Tag {
    id: String,
    name: String,
    label: String,
    createDate: String,
    posts: [Post]
}

可能你會疑惑,文章類型和標籤類型這樣相互嵌套會不會形成死循環?我能夠負責任的告訴你:不會。你能夠盡情地嵌套、組合類型結構來知足你的需求。

最後,根據整個博客網站的需求,組合嵌套剛剛定義的文章類型和標籤類型,創建一個根類型做爲查詢的 schema。

type Blog {
    post: Post,        // 查詢一篇文章
    posts: [Post],    // 用於博客首頁,查詢一組文章
    tag: Tag,        // 查詢一個標籤
    tags: [Tag],    // 用於博客標籤頁,查詢全部標籤
}

OK,咱們的類型和 schema 都定義好了,就能夠開始查詢了。怎麼查哪?那咱們來看看 GraphQL 的查詢語法。

Query Syntax

GraphQL 的查詢語法同咱們如今所使用的有一大不一樣是,傳輸的數據結構並非 JSON 對象,而是一個字符串,這個字符串描述了客戶端但願服務端返回數據的具體結構。

知道了概念,那麼一個 GraphQL 的查詢到底長什麼樣哪?繼續咱們的例子,假設,咱們如今要查詢一篇文章,那麼,GraphQL 的查詢語句就能夠是這樣。

query FetchPostQuery {
    post {
        id,
        name,
        createDate,
        title,
        subtitle,
        content,
        tags {
            name,
            label
        }
    }
}

它相對應的返回就會是相似這樣的一個 JSON 數據。

{
    "data": {
        "post": {
            "id": "3",
            "name": "graphql-core-concepts",
            "createDate": "2016-08-01",
            "title": "GraphQL 核心概念",
            "subtitle": "A query language created by Facebook for decribing data requirements on complex application data models",
            "content": "省略...",
            "tags": [{
                "name": "graphql",
                "label": "GraphQL"
            }]
        }
    }
}

從中咱們能夠看到,數據返回了整個文章的屬性以及部分的標籤屬性。其中,標籤屬性並無返回所有的字段,而是隻返回了 name 和 label 字段的屬性,作到了返回數據的結構完成同請求數據的結構相同,沒有冗餘的數據。

查詢添加參數的需求也很是基本,在 GraphQL 的查詢語法中也至關簡單,就拿剛剛的例子,要查詢特定的文章就能夠把它改爲這樣。

query FetchPostQuery {
    post(name: 'graphql-core-concepts') {
        id,
        name,
        createDate,
        title,
        subtitle,
        content,
        tags {
            name,
            label
        }
    }
}

返回的結果會是和以前的同樣。查詢關鍵字只有在多個查詢時才必須,在單個查詢時能夠省略。同時,也能夠對查詢的返回起別名,再來看看博客的首頁但願展現一個粗略的文章列表,那麼這樣的一個查詢語句能夠是

{
    postList: posts {
        id,
        name,
        createDate,
        title,
        subtitle,
        tags {
            name,
            label
        }
    }
}

這裏,咱們省略了查詢關鍵字,並將 posts 起了一個別名爲 postList,返回的結果就會是

{
    "data": {
        "postList": [{
            "id": "3",
            "name": "graphql-core-concepts",
            "createDate": "2016-08-01",
            "title": "GraphQL 核心概念",
            "subtitle": "A query language created by Facebook for decribing data requirements on complex application data models",
            "tags": [{
                "name": "graphql",
                "label": "GraphQL"
            }]
        }, {
            "id": "2",
            "name": "redux-advanced",
            "createDate": "2016-07-23",
            "title": "Redux 進階",
            "subtitle": "Advanced skill in Redux",
            "tags": [{
                "name": "javascript",
                "label": "JavaScript"
            }, {
                "name": "redux",
                "label": "Redux"
            }, {
                "name": "state-management",
                "label": "State management"
            }, {
                "name": "angular-1.x",
                "label": "Angular 1.x"
            }, {
                "name": "ui-router",
                "label": "ui-router"
            }, {
                "name": "redux-ui-router",
                "label": "redux-ui-router"
            }]
        }, {
            "id": "1",
            "name": "getting-started-with-redux",
            "createDate": "2016-07-06",
            "title": "Redux 入門",
            "subtitle": "A tiny predictable state management lib for JavaScript apps",
            "tags": [{
                "name": "javascript",
                "label": "JavaScript"
            }, {
                "name": "redux",
                "label": "Redux"
            }, {
                "name": "state-management",
                "label": "State management"
            }, {
                "name": "angular-1.x",
                "label": "Angular 1.x"
            }]
        }]
    }
}

一樣,查詢全部標籤的語句就能夠是這樣

{
    tags {
        id,
        name,
        label,
        posts {
            name,
            title
        }
    }
}

這樣,一個 GraphQL 的接口,知足了一個簡單博客網站的全部需求,是否是很神奇?

萌呆

Validation

因爲 GraphQL 是一個強類型語言,因此它能夠在執行查詢以前檢查每一個查詢語句是否知足事先設定的 schema,符合則合法,若是查詢語句不合法則不進行查詢。

以上所舉的都是合法的例子,官網上舉了一些例子,這裏就不貼了,咱們就總結看看要注意的有哪幾點。

  1. fragment 不能引用本身從而造成一個循環

  2. 不能查詢類型中不存在的字段

  3. 查詢的字段若是不是 scalar type(標量類型)或 enum type(枚舉類型),則須要明確該字段下所包含的字段

  4. 同上一條相對,若是查詢字段是 scalar type(標量類型),那麼它就不能再有子字段

Introspection

Introspection 這個詞的意思是內省,自我檢查(第一次發現英語有語義如此豐富的詞,又暴露詞彙量少了-_-||)。

不扯遠了,在 GraphQL 中 Introspection 是一個很是有用的功能,它能夠用來查詢當前 GraphQL 的 schema,從而得知服務器端支持何種類型的查詢。

這是一個很是強大且有用的功能,能夠想象一下,如今大型公司的開發基本上都是先後端分離的,客戶端並不知道服務器端提供的 schema 結構,但經過 Introspection,客戶端就能得到當前服務器端所提供的 schema,這不管對開發,仍是調試錯誤都頗有幫助。

仍是拿剛剛的博客系統來作例子,咱們能夠經過查詢 __schema 字段來得到當前所支持的查詢類型。

// query string
{
    __schema {
        types {
            name
        }
    }
}

// response data
{
  "data": {
    "__schema": {
      "types": [
        {
          "name": "String"
        },
        {
          "name": "BlogType"
        },
        {
          "name": "PostType"
        },
        {
          "name": "ID"
        },
        {
          "name": "TagType"
        },
        {
          "name": "__Schema"
        },
        {
          "name": "__Type"
        },
        {
          "name": "__TypeKind"
        },
        {
          "name": "Boolean"
        },
        {
          "name": "__Field"
        },
        {
          "name": "__InputValue"
        },
        {
          "name": "__EnumValue"
        },
        {
          "name": "__Directive"
        },
        {
          "name": "__DirectiveLocation"
        }
      ]
    }
  }
}

從返回的數據中能夠看到,咱們自定義的 BlogType, PostType 和 TagType 類,剩下的都是 GraphQL 內部類型,其中又分爲兩類:一類是 ID, String 和 Bealoon 所表示的標量類型,另外一類以雙下劃線開頭的是用於自我檢查的類型。

知道了自定義類,假設,還想知道自定義類中包含哪些屬性以及屬性的類型,就能夠這樣查詢

// query string
{
    __type(name: "PostType") {
        name
        fields {
            name,
            type {
                name,
                kind
            }
        }
    }
}

// response result
{
  "data": {
    "__type": {
      "name": "PostType",
      "fields": [
        {
          "name": "id",
          "type": {
            "name": null,
            "kind": "NON_NULL"
          }
        },
        {
          "name": "name",
          "type": {
            "name": null,
            "kind": "NON_NULL"
          }
        },
        {
          "name": "createDate",
          "type": {
            "name": null,
            "kind": "NON_NULL"
          }
        },
        {
          "name": "title",
          "type": {
            "name": null,
            "kind": "NON_NULL"
          }
        },
        {
          "name": "subtitle",
          "type": {
            "name": "String",
            "kind": "SCALAR"
          }
        },
        {
          "name": "content",
          "type": {
            "name": "String",
            "kind": "SCALAR"
          }
        },
        {
          "name": "tags",
          "type": {
            "name": null,
            "kind": "LIST"
          }
        }
      ]
    }
  }
}

最後

總結一下,GraphQL 是一種客戶端同服務端之間數據交互的概念,具備強大、靈活、易擴展等的特色。既然,它是一種概念,那麼,不一樣的語言就能夠有各類不一樣的實現方式。

概念並很少,在於靈活運用。

PS:再次強調,本文主要講的是 GraphQL 的核心概念,Type System 中所定義的類,都是設計類,並非具體實現代碼。實現請聽下回分解。

相關文章
相關標籤/搜索