GraphQL學習之基礎篇

前言

最近在實踐GraphQL,項目算是作完了,這會須要總結一下。如下三篇文章都是基於的demo代碼是: graphql-todo-demoreact

解讀的graphql-js的版本是v14.3.0,規範是2018年6月份發佈的標準git

GraphQL介紹

GraphQL是給API設計的一種查詢語言,一個依據已有數據執行查詢的運行時,爲你的API中的數據提供一種徹底且容易理解的描述,使得API可以更容易的隨着時間而演變,還支持強大的開發者工具。github

具備如下特性:(翻譯自Getting up and running with GraphQL)web

  • 可描述性的:使用GraphQL,你獲取的都是你想要的數據,很少也不會少; {:&.rollIn}
  • 分級性的:GraphQL自然遵循對象間的關係,經過一個簡單的請求,咱們能夠獲取到一個對象及其相關的對象,好比說,經過一個簡單的請求,咱們能夠獲取一個做者和他建立的全部文章,而後能夠獲取文章的全部評論;
  • 強類型的:使用GraphQL的類型系統,咱們能夠描述可以被服務器查詢的可能的數據,而後確保從服務器獲取到的數據和咱們查詢的一致;
  • 不作語言限制:並不綁定於某一特定的語言,實際上如今已經有一些不一樣的語言有了實踐;
  • 兼容於任何後臺:GraphQL不限於某一特定數據庫,可使用已經存在的數據,代碼,甚至能夠鏈接第三方的APIs.
  • 可自省的:GraphQL服務器可以查詢架構的細節

爲何須要GraphQL

咱們先來看看平時項目的一些api使用場景。數據庫

假如某個訂單業務的接口在app端使用到如下數據:express

{
  id,
  tradeNo,
  price
}
複製代碼

而一樣的接口在web端用到的是以下數據:api

{
  id,
  price,
  tags,
  toAddr
}
複製代碼

因而在咱們開發中就會有這樣的對話:數組

服務端A: 小林啊,這是xx接口返回的dto,大家網關直接使用就好了。bash

{
  id
  tradeNo
  price
  tags
  hasPaied
  toAddr
  fromAddr
  shopId
  ...
}
複製代碼

小林:好的,我將數據都透傳給app app端B: 小林啊,你這接口怎麼這麼多字段?哪些字段是咱們用得上的咧?你就不能修剪一下參數再給咱們嗎? 小林: 很差修剪呀,這個接口web端的童鞋也在用呀,大家就挑選本身用獲得的字段就好了 app端B: ....服務器

app端童鞋這會很無語,感受很費解,明明我只要四個字段,你給我返回幾十個字段幹啥呢?那不是浪費帶寬嗎?

是的,這類現象就是所謂的「過分獲取」。同時也暴露出網關這層,在處理同一個接口返回給不一樣終端的數據差別性的複雜,須要你增長額外的判斷去實現,這種問題咱們稱之爲「冗餘判斷」,這會致使系統維護成本增長。

那麼有解決辦法嗎?固然有!咱們反過來操做如何?由客戶端來告訴網關須要哪些字段,網關這邊準備完整的數據,而後有個什麼東東來pick這些數據給客戶端。因而乎,這個東東就是後來Facebook在2015年開源的GraphQL。

那麼GraphQL除了解決剛纔說兩個廣泛問題,還有別的其餘優點嗎?

of course。

以官方文檔來闡述,即是:

  • 跨平臺使用
  • 強類型的 GraphQL
  • GraphQL 版本化
  • GraphQL的語義化 => schema自己就是一份完美的自動生成的 API 文檔

在後面的三篇文章中會逐一體現其優點。

GraphQL的類型系統

能夠將 GraphQL 的類型系統分爲標量類型(Scalar Types,標量類型)和其餘高級數據類型,標量類型便可以表示最細粒度數據結構的數據類型,能夠和 JavaScript 的原始類型對應。

Scalar Type

GraphQL 規範目前規定支持的標量類型有:

  • Int:帶符號的32位整數,對應 JavaScript 的 Number
  • Float:帶符號的雙精度浮點數,對應 JavaScript 的 Number
  • String:UTF-8字符串,對應 JavaScript 的 String
  • Boolean:布爾值,對應 JavaScript 的 Boolean
  • ID:ID 值,是一個序列化後值惟一的字符串,能夠視做對應 ES 2015 新增的 Symbol

Scalar Types的JavaScript參考實現代碼能夠查看這裏

咱們可使用scalar關鍵字,自定義標量類型,好比咱們能夠定義一個Date類型: scalar Date

高級類型

接口類型

Interface是包含一組肯定字段的集合的抽象類型,實現該接口的類型必須包含interface定義的全部字段。好比:

interface Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
}

type Human implements Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
  starships: [Starship]
  totalCredits: Int
}

type Droid implements Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
  primaryFunction: String
}
複製代碼

對於interface的返回須要你使用inline fragments來實現,以下:

query HeroForEpisode($ep: Episode!) {
  hero(episode: $ep) {
    name
    ... on Droid {
      primaryFunction
    }
  }
}
複製代碼

聯合類型

Union類型很是相似於interface,可是他們在類型之間不須要指定任何共同的字段。一般用於描述某個字段可以支持的全部返回類型以及具體請求真正的返回類型。好比定義:

union SearchResult = Human | Droid | Starship
複製代碼

這個時候的查詢語句多是這樣的:

{
  search(text: "an") {
    __typename
    ... on Human {
      name
      height
    }
    ... on Droid {
      name
      primaryFunction
    }
    ... on Starship {
      name
      length
    }
  }
}
複製代碼

枚舉類型

又稱Enums,這是一種特殊的標量類型,經過此類型,咱們能夠限制值爲一組特殊的值。好比:

enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}
複製代碼

輸入類型

input類型對mutations來講很是重要,在 GraphQL schema 語言中,它看起來和常規的對象類型很是相似,可是咱們使用關鍵字input而非type,input類型按以下定義:

# Input Type

  input CommentInput {
    body: String!
  }
複製代碼

爲何不直接使用Object Type呢?由於 Object 的字段可能存在循環引用,或者字段引用了不能做爲查詢輸入對象的接口和聯合類型。

數組類型和非空類型

使用[]來表示數組,使用!來表示非空。Non-Null強制類型的值不能爲null,而且在請求出錯時必定會報錯。能夠用於必須保證值不能爲null的字段

對象類型

GraphQL schema最基本的類型就是Object Type。用於描述層級或者樹形數據結構。好比:

type Character {
  name: String!
  appearsIn: [Episode!]!
}
複製代碼

GraphQL查詢語法

GraphQL的一次操做請求被稱爲一份文檔(document),即GraphQL服務可以解析驗證並執行的一串請求字符串(Source Text)。完整的一次操做由操做(Operation)和片斷(Fragments)組成。一次請求能夠包含多個操做和片斷。只有包含操做的請求才會被GraphQL服務執行。

其規範釋義爲:

只包含一個操做的請求能夠不帶OperationName,若是是operationType是query的話,能夠所有省略掉,即:

{
  getMessage {
    # query也能夠擁有註釋,註釋以#開頭
    content
    author
  }
}
複製代碼

當query包含多個操做時,全部操做都必須帶上名稱。

GraphQL中,咱們會有這樣一個約定,Query和與之對應的Resolver是同名的,這樣在GraphQL才能把它們對應起來。

Query

Query用作讀操做,也就是從服務器獲取數據。以上圖的請求爲例,其返回結果以下,能夠看出一一對應,精準返回數據

{
  "data": {
    "single": [
      {
        "content": "test content 1",
        "author": "pp1"
      }
    ],
    "all": [
      {
        "content": "test content",
        "author": "pp",
        "id": "0"
      },
      {
        "content": "test content 1",
        "author": "pp1",
        "id": "1"
      }
    ]
  }
}

複製代碼

Field

Field是咱們想從服務器獲取的對象的基本組成部分。query是數據結構的頂層,其下屬的allsingle都屬於它的字段。

字段格式應該是這樣的:alias:name(argument:value)

其中 alias 是字段的別名,即結果中顯示的字段名稱。

name 爲字段名稱,對應 schema 中定義的 fields 字段名。

argument 爲參數名稱,對應 schema 中定義的 fields 字段的參數名稱。

value 爲參數值,值的類型對應標量類型的值。

Argument

和普通的函數同樣,query能夠擁有參數,參數是可選的或必須的。參數使用方法如上圖所示。

須要注意的是,GraphQL中的字符串須要包裝在雙引號中。

Variables

除了參數,query還容許你使用變量來讓參數可動態變化,變量以$開頭書寫,使用方式如上圖所示

變量還能夠擁有默認值:

query gm($id: ID = 2) {
  # 查詢數據
  single: getMessage(id: $id) {
    ...entity
  }
  all: getMessage {
    ...entity
    id
  }
}
複製代碼

Allases

別名,好比說,咱們想分別獲取所有消息和ID爲1的消息,咱們能夠用下面的方法:

query gm($id: ID = 2) {
  # 查詢數據
  getMessage(id: $id) {
    ...entity
  }
  getMessage {
    ...entity
    id
  }
}
複製代碼

因爲存在相同的name,上述代碼會報錯,要解決這個問題就要用到別名了Allases。

query gm($id: ID = 2) {
  # 查詢數據
  single: getMessage(id: $id) {
    ...entity
  }
  all: getMessage {
    ...entity
    id
  }
}
複製代碼

Fragments

Fragments是一套在queries中可複用的fields。好比說咱們想獲取Message,在沒有使用fragment以前是這樣的:

query gm($id: ID = 2) {
  # 查詢數據
  single: getMessage(id: $id) {
    content
    author
  }
  all: getMessage {
    content
    author
    id
  }
}
複製代碼

可是若是fields過多,就會顯得重複和冗餘。Fragments在此時就能夠起做用了。使用了Fragment以後的語法就如上圖所示,簡單清晰。

Fragment支持多層級地繼承。

Directives

Directives提供了一種動態使用變量改變咱們的queries的方法。如本例,咱們會用到如下兩個directive:

query gm($id: ID = 2, $isNotShowId: Boolean!, $showAuthor: Boolean!) {
  # 查詢數據
  single: getMessage(id: $id) {
    ...entity
  }
  all: getMessage {
    ...entity
    id @skip(if: $isNotShowId)
  }
}

fragment entity on Message {
  content
  author @include(if: $showAuthor)
}

### 入參是:
{
  "id": 1,
  "isNotShowId": true,
  "showAuthor": false
}
複製代碼

@include: 只有當if中的參數爲true時,纔會包含對應fragment或field;

@skip:當if中的參數爲true時,會跳過對應fragment或field;

結果以下:

{
  "data": {
    "single": [
      {
        "content": "test content 1"
      }
    ],
    "all": [
      {
        "content": "test content"
      },
      {
        "content": "test content 1"
      }
    ]
  }
}
複製代碼

Mutation

傳統的API使用場景中,咱們會有須要修改服務器上數據的場景,mutations就是應這種場景而生。mutations被用以執行寫操做,經過mutations咱們會給服務器發送請求來修改和更新數據,而且會接收到包含更新數據的反饋。mutations和queries具備相似的語法,僅有些許的差異。

  1. operationType爲mutation
  2. 爲了保證數據的完整性mutations是串形執行,而queries能夠並行執行,這點在原理篇會介紹到

Subscription

Subscription是GraphQL最後一個操做類型,它被稱爲訂閱。當另外兩個由客戶端經過HTTP請求發送,訂閱是服務器在某個事件發生時將數據自己推送給感興趣的客戶端的一種方式。這就是GraphQL 處理實時通訊的方式。

demo中咱們實現了TodoList的發佈訂閱功能,在語法上和別的operation相似,惟一變化的就是operation Type變爲了Subscription,好比:

subscription todoSubscription($filter: String!) {
  todoChanged(filter: $filter) {
    type
    payload {
      id
      title
      complete
    }
  }
}
複製代碼

更多的改造實現是在服務端上,須要藉助graphql-subscriptionssubscriptions-transport-ws來實現整個流程。結合express-graphql的例子能夠參考:Subscriptions support

最後

至此,GraphQL的基礎篇講完了,爲了加深這些基礎,歡迎繼續閱讀第二篇文章原理篇

參考

  1. Schemas and Types
  2. howtographql.cn/
相關文章
相關標籤/搜索