原文連接: How GraphQL turns a query into a responsejavascript
在這篇文章中,我將回答一個簡單的問題,GraphQL如何把查詢轉換爲響應?html
若是你對GraphQL還不熟悉,那麼在閱讀以前,先了解一下「How do I GraphQL?」的三分鐘介紹。這樣你就能從這篇文章中獲得更多。java
咱們這篇文章中將會介紹如下內容:react
準備好了嗎?讓咱們開始吧!sql
GraphQL查詢結構很是簡單,易於理解。請看下面的例子:數據庫
{
subscribers(publication: "apollo-stack"){
name
email
}
}
複製代碼
若是咱們爲Building Apollo構建了一個API,顯而易見這個查詢將會返回全部訂閱了「apollo-stack」訂閱者的name
和email
。如下是響應的樣子:redux
{
subscribers: [
{ name: "Jane Doe", email: "jane@doe.com" },
{ name: "John Doe", email: "john@doe.com" },
...
]
}
複製代碼
注意響應的結構與查詢的結構幾乎相同。GraphQL的客戶端很是簡單,它其實是自解釋的!後端
可是服務端呢?會更復雜嗎?api
事實證實,GraphQL服務端也至關簡單。在閱讀完這篇文章以後,您將清楚地瞭解GraphQL服務器內部發生了什麼,並準備好構建本身的服務器。promise
每一個GraphQL服務器都有兩個核心部分來決定它的工做方式:schema(模式) 和 resolve functions(解析函數)。
模式:模式是能夠經過GraphQL服務器獲取的數據模型。它定義了容許客戶端進行哪些查詢,能夠從服務器獲取什麼類型的數據,以及這些類型之間的關係。例如:
在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能夠不寫
這個模式很是簡單:它聲明應用程序有三種類型: - Author、POST 和 Query。每一個查詢都必須從它的一個字段開始:getAuthor 或 getPostsByTitle。你能夠把它們看做是REST端點,除了更強大以外。
Author 和 Post 相互引用。你能夠經過 Author 的posts
字段獲取 Post,也能夠經過 Post 的author
字段從獲取 Author。
模式告訴服務器容許客戶端進行哪些查詢,以及不一樣類型之間的關係,可是其中有一個關鍵信息是不包含的:每種類型的數據來自哪裏!
這就是解析函數的用途。
解析功能有點像路由。它們指定模式中的類型和字段如何鏈接到各類後端,解決「如何爲 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直接寫入一個解析函數中,而是將其放在一個單獨的模塊中。但你已經明白瞭解析函數的使用。
好了,如今您已經瞭解了模式和解析函數,讓咱們來看看實際查詢的執行狀況。
附帶說明:下面的代碼是GraphQL-JS的代碼,它是GraphQL的JavaScript參考實現,可是在我所知道的全部GraphQL服務器中,執行模型是相同的。
在本節的末尾,您將瞭解GraphQL服務器如何使用模式和解析函數一塊兒執行查詢並生成所需的結果。
下面是一個與前面介紹的模式對應的查詢。它獲取一個做者的姓名、該做者的全部帖子以及每一個帖子的做者的姓名。
{
getAuthor(id: 5){
name
posts {
title
author {
name # this will be the same as the name above
}
}
}
}
複製代碼
附帶說明:若是仔細觀察,您會注意到這個查詢兩次獲取同一個做者的名稱。我在這裏這樣作只是爲了說明GraphQL,同時保持模式儘量簡單。
如下是服務器響應查詢的三個關鍵步驟:
一、解析
二、驗證
三、執行
首先,服務器解析字符串並將其轉換爲AST(抽象語法樹)。若是有任何語法錯誤,服務器將中止執行並將語法錯誤返回給客戶端。
一個查詢在語法上能夠是正確的,但仍然沒有任何意義,就像下面的英語句子在語法上是正確的,可是沒有任何意義:「The sand left through the idea」。
驗證階段確保在開始執行以前給定模式查詢是有效的。它檢查以下:
id
的參數?name
和posts
字段?做爲一個應用程序開發人員,您不須要擔憂這個部分,由於GraphQL服務器會自動完成。這與大多數RESTfulAPI造成了對比,在這種狀況下,須要由開發人員來確保全部參數都是有效的。
若是經過驗證,GraphQL服務器將執行查詢。
每一個GraphQL查詢都具備樹的形狀,也就是說,它從不是循環的。執行從Query
的根開始。首先,執行器調用頂層字段的解析函數-在本例中,只是 getAuthor。它等待直到全部這些解析函數返回一個值,而後以級聯的方式在下一級繼續。若是一個解析函數返回一個promise
,執行者將等待該promise
resolved。
這是對執行流的一段描述。我認爲,當以不一樣的方式展現事物時,它們老是更容易理解,因此我製做了一張圖表,一張表格,甚至一段視頻,一步步地帶你去看。
圖形式的執行流程:
表形式的執行流程:
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的參數id
。getAuthor 解析函數將執行並返回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上name
和posts
字段的解析函數,由於這些字段是查詢中請求的字段。name
和posts
字段的解析函數並行運行。
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以後才執行下一級的解析函數
由於查詢請求了每一個帖子的title
和author
字段,因此GraphQL並行運行四個解析函數:每一個帖子的title
和author
。
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.的相關內容。
自從Jonas撰寫這篇文章以來,咱們還構建了一個名爲Apollo Engine的服務,經過提供如下功能幫助開發人員瞭解和監視其GraphQL服務器中發生的事情:
若是您有興趣看到您的GraphQL查詢在實際應用中的執行,您能夠在這裏登陸並檢測您的服務器。若是您有興趣支持使用GraphQL運行高性能的現代應用程序,咱們能夠幫助您!讓咱們知道。