[譯]GraphQL如何把查詢轉換爲響應(How GraphQL turns a query into a response)

原文連接: How GraphQL turns a query into a responsejavascript

在這篇文章中,我將回答一個簡單的問題,GraphQL如何把查詢轉換爲響應?html

若是你對GraphQL還不熟悉,那麼在閱讀以前,先了解一下「How do I GraphQL?」的三分鐘介紹。這樣你就能從這篇文章中獲得更多。java

咱們這篇文章中將會介紹如下內容:react

  • GraphQL queries - 查詢
  • Schema and resolve functions - 模式和解析函數
  • GraphQL execution — step by step - 逐步執行

準備好了嗎?讓咱們開始吧!sql

GraphQL queries

GraphQL查詢結構很是簡單,易於理解。請看下面的例子:數據庫

{
  subscribers(publication: "apollo-stack"){
    name
    email
  }
}
複製代碼

若是咱們爲Building Apollo構建了一個API,顯而易見這個查詢將會返回全部訂閱了「apollo-stack」訂閱者的nameemail。如下是響應的樣子:redux

{
  subscribers: [
    { name: "Jane Doe", email: "jane@doe.com" },
    { name: "John Doe", email: "john@doe.com" },
    ...
  ]
}
複製代碼

注意響應的結構與查詢的結構幾乎相同。GraphQL的客戶端很是簡單,它其實是自解釋的!後端

可是服務端呢?會更復雜嗎?api

事實證實,GraphQL服務端也至關簡單。在閱讀完這篇文章以後,您將清楚地瞭解GraphQL服務器內部發生了什麼,並準備好構建本身的服務器。promise

Schema and Resolve Functions

每一個GraphQL服務器都有兩個核心部分來決定它的工做方式:schema(模式)resolve functions(解析函數)

模式:模式是能夠經過GraphQL服務器獲取的數據模型。它定義了容許客戶端進行哪些查詢,能夠從服務器獲取什麼類型的數據,以及這些類型之間的關係。例如:

schema
具備三種類型的簡單GraphQL模式:Author、POST和Query

在GraphQL模式語法中,以下所示:

type Author {
  id: Int
  name: String
  posts: [Post]
}
type Post {
  id: Int
  title: String
  text: String
  author: Author
}
type Query {
  getAuthor(id: Int): Author
  getPostsByTitle(titleContains: String): [Post]
}
schema {
  query: Query
}
複製代碼

譯者注:在Apollo-Server2.0中,最後一節schema能夠不寫

這個模式很是簡單:它聲明應用程序有三種類型: -  AuthorPOSTQuery。每一個查詢都必須從它的一個字段開始:getAuthorgetPostsByTitle。你能夠把它們看做是REST端點,除了更強大以外。

AuthorPost 相互引用。你能夠經過 Authorposts字段獲取 Post,也能夠經過 Postauthor字段從獲取 Author

模式告訴服務器容許客戶端進行哪些查詢,以及不一樣類型之間的關係,可是其中有一個關鍵信息是不包含的:每種類型的數據來自哪裏!

這就是解析函數的用途。

Resolve Functions

解析功能有點像路由。它們指定模式中的類型和字段如何鏈接到各類後端,解決「如何爲 Author 獲取數據?」和「我須要用什麼參數調用哪一個後端才能得到 POST 的數據?」這樣的問題。

GraphQL解析函數能夠包含任意代碼,這意味着GraphQL服務器能夠與任何類型的後端,甚至其餘GraphQL服務器對話。例如,Author 類型能夠存儲在SQL數據庫中,而 POST 能夠存儲在MongoDB中,甚至能夠由微服務處理。

也許GraphQL最大的特色是它對客戶端隱藏了全部後端複雜性。無論您的應用程序使用了多少後端,客戶端只會看到一個帶有應用程序簡單的、自文檔化API的GraphQL端點。

下面是兩個解析函數的例子:

getAuthor(_, args){
  return sql.raw('SELECT * FROM authors WHERE id = %s', args.id);
}
posts(author){
  return request(`https://api.blog.io/by_author/${author.id}`);
}
複製代碼

固然,您不會將查詢或url直接寫入一個解析函數中,而是將其放在一個單獨的模塊中。但你已經明白瞭解析函數的使用。

Query execution — step by step

好了,如今您已經瞭解了模式和解析函數,讓咱們來看看實際查詢的執行狀況。

附帶說明:下面的代碼是GraphQL-JS的代碼,它是GraphQL的JavaScript參考實現,可是在我所知道的全部GraphQL服務器中,執行模型是相同的。

在本節的末尾,您將瞭解GraphQL服務器如何使用模式和解析函數一塊兒執行查詢並生成所需的結果。

下面是一個與前面介紹的模式對應的查詢。它獲取一個做者的姓名、該做者的全部帖子以及每一個帖子的做者的姓名。

{
  getAuthor(id: 5){
    name
    posts {
      title
      author {
        name # this will be the same as the name above
      }
    }
  }
}
複製代碼

附帶說明:若是仔細觀察,您會注意到這個查詢兩次獲取同一個做者的名稱。我在這裏這樣作只是爲了說明GraphQL,同時保持模式儘量簡單。

如下是服務器響應查詢的三個關鍵步驟:

一、解析

二、驗證

三、執行

Step 1: 解析查詢

首先,服務器解析字符串並將其轉換爲AST(抽象語法樹)。若是有任何語法錯誤,服務器將中止執行並將語法錯誤返回給客戶端。

Step 2: 驗證

一個查詢在語法上能夠是正確的,但仍然沒有任何意義,就像下面的英語句子在語法上是正確的,可是沒有任何意義:「The sand left through the idea」。

驗證階段確保在開始執行以前給定模式查詢是有效的。它檢查以下:

  • getAuthor 是查詢類型的字段嗎?
  • getAuthor 是否接受名爲id的參數?
  • getAuthor 返回的類型上是否有nameposts字段?
  • ...諸如此類

做爲一個應用程序開發人員,您不須要擔憂這個部分,由於GraphQL服務器會自動完成。這與大多數RESTfulAPI造成了對比,在這種狀況下,須要由開發人員來確保全部參數都是有效的。

Step 3: 執行

若是經過驗證,GraphQL服務器將執行查詢。

每一個GraphQL查詢都具備樹的形狀,也就是說,它從不是循環的。執行從Query的根開始。首先,執行器調用頂層字段的解析函數-在本例中,只是 getAuthor。它等待直到全部這些解析函數返回一個值,而後以級聯的方式在下一級繼續。若是一個解析函數返回一個promise,執行者將等待該promiseresolved。

這是對執行流的一段描述。我認爲,當以不一樣的方式展現事物時,它們老是更容易理解,因此我製做了一張圖表,一張表格,甚至一段視頻,一步步地帶你去看。

圖形式的執行流程:

exexution
執行從最上面開始。在同一級別上的解析函數是併發執行的

表形式的執行流程:

3.1: run Query.getAuthor
3.2: run Author.name and Author.posts (for Author returned in 3.1)
3.3: run Post.title and Post.author (for each Post returned in 3.2)
3.4: run Author.name (for each Author returned in 3.3)
複製代碼

爲了方便起見,這仍是上面的查詢:

{
  getAuthor(id: 5){
    name
    posts {
      title
      author {
        name # this will be the same as the name above
      }
    }
  }
}
複製代碼

在這個查詢中,只有一個根字段 getAuthor 和一個值爲5的參數idgetAuthor 解析函數將執行並返回Promise。

getAuthor(_, { id }){
  return DB.Authors.findOne(id);
}
// let's assume this returns a promise that then resolves to the
// following object from the database: 
{ id: 5, name: "John Doe" }
複製代碼

當數據庫調用返回時,Promise將被resolved。一旦發生這種狀況,GraphQL服務器將獲取此解析函數的返回值-在本例中爲一個對象-並將其傳遞給Author上nameposts字段的解析函數,由於這些字段是查詢中請求的字段。nameposts字段的解析函數並行運行。

name(author){
  return author.name;
}
posts(author){
  return DB.Posts.getByAuthorId(author.id);
}
複製代碼

name解析函數很是簡單:它只返回剛剛從 getAuthor 解析函數傳遞下來的Author對象的name屬性。

posts解析函數調用數據庫並返回POST對象列表:

// list returned by DB.Posts.getByAuthorId(5)
[{
  id: 1,
  title: "Hello World",
  text: "I am here",
  author_id: 5
},{
  id: 2,
  title: "Why am I still up at midnight writing this post?",
  text: "GraphQL's query language is incredibly easy to ...",
  author_id: 5
}]
複製代碼

注意: GraphQL-JS等待列表中全部的Promise被resolved或者rejected以後才執行下一級的解析函數

由於查詢請求了每一個帖子的titleauthor字段,因此GraphQL並行運行四個解析函數:每一個帖子的titleauthor

title解析函數像name同樣是微不足道的,author解析函數與 getAuthor 的函數相同,只不過它在POST上使用author_id字段,而 getAuthor 函數使用id參數:

author(post){
  return DB.Authors.findOne(post.author_id);
}
複製代碼

最後,GraphQL執行器再一次調用Author的name解析函數,這一次使用POSTS的author解析函數返回的Author對象。它執行了兩次—— 每一個帖子執行一次。

到這裏執行部分已經結束了!剩下要作的就是將結果傳遞到查詢的根目錄,並返回結果:

{
  data: {
    getAuthor: {
      name: "John Doe",
      posts: [
        {
          title: "Hello World",
          author: {
            name: "John Doe"
          }
        },{
          title: "Why am I still up at midnight writing this post?",
          author: {
            name: "John Doe"
          }
        }
      ]
    }
  }
}
複製代碼

注意:這個例子稍微簡化了一些。真正的生產GraphQL服務器將使用批處理和緩存來減小對後端的請求數量,並避免產生冗餘的請求,好比獲取同一做者兩次。但這是另外一篇文章的主題!

結語

如你所見,一旦你深刻到它,GraphQL是很是容易理解的!我認爲GraphQL在解決諸如聯表、過濾、參數驗證、文檔等傳統RESTfulAPI中很難解決的問題上,是很是出色的。

固然,GraphQL比我在這裏寫的要多得多,但這是之後文章的主題!

若是這讓您對本身嘗試GraphQL感興趣,您應該查看咱們的GraphQL server tutorial,或者閱讀有關 using GraphQL on the client together with React + Redux.的相關內容。

2018年更新:理解使用Apollo Engine執行GraphQL

自從Jonas撰寫這篇文章以來,咱們還構建了一個名爲Apollo Engine的服務,經過提供如下功能幫助開發人員瞭解和監視其GraphQL服務器中發生的事情:

若是您有興趣看到您的GraphQL查詢在實際應用中的執行,您能夠在這裏登陸並檢測您的服務器。若是您有興趣支持使用GraphQL運行高性能的現代應用程序,咱們能夠幫助您!讓咱們知道

Apollo Engine
Apollo Engine的運行概況:查詢服務時間和請求率/錯誤率圖表的熱力圖。
相關文章
相關標籤/搜索