首頁/ 家居/ 正文

如何優雅地擴充套件GraphQL系統能力

作者 | 杜艮魁

編輯 | 蔡芳芳

1

背景

為什麼要擴充套件 GraphQL 系統能力

GraphQL 可將 API 表示的資料透過解析函式對映到 GraphQL 的 schema 中,為 API 提供一套型別化的完整描述,使得客戶端能夠根據所需準確地獲取相應資料。

在真實業務場景中,除了獲取基礎資料外,往往還會有一些對資料進行加工轉換和編排控制的需求,例如對數值欄位取精或者轉換成展示文案、對列表欄位進行排序過濾去重、根據條件判斷是否請求查詢中的某些欄位、將一個欄位的解析結果作為另外一個欄位的入參等。

原生的 GraphQL 查詢為獲取基礎資料提供了便捷,但是計算能力不足導致其結果經常不能滿足業務需求,資料往往需要加工轉換、甚至經過多次編排查詢,才能展示給使用者。

GraphQL 的能力擴充套件機制

GraphQL 提供指令作為執行和校驗能力的擴充套件機制。指令的定義包括指令名稱、引數列表、可使用位置和是否可在同一位置重複使用等四個元素,使用者可以使用指令描述自定義的執行行為或校驗規則。

以內建指令為例,該指令定義如下:

主要是解決指定條件滿足時跳過某些欄位的獲取解析。判斷條件結果為指令引數。該指令可使用的位置有查詢欄位、命名片段和內聯片段,使用時將指令放置在要生效的元素後即可,示例如下:

在實際業務場景中,是否跳過某些欄位獲取的條件大多情況需要根據請求變數進行計算判斷。例如為 App 渲染資料時,低於指定版本的客戶端不用請求某些欄位,該條件判斷無法透過請求變數只有客戶端版本號的原生查詢實現。

GraphQL 原生指令只有 、 、 和 ,說明見 Type-System。Directives,提供的能力有限,不能滿足業務計算所需。

2

GraphQL 系統能力擴充套件實踐

本文以 GraphQL Calculator 為例,介紹對 GraphQL 系統能力進行擴充套件的實踐。

開原始碼託管地址:https://github。com/graphql-calculator/graphql-calculator

指令分類

指令使用位置分為兩類:可執行位 ExecutableDirectiveLocation 和型別系統位 TypeSystemDirectiveLocation。

GraphQL 規範並不會限制指令只能定義在可執行位或者型別系統位,但是為了明確指令是用在查詢上、還是對於型別系統生效,往往只將指令的生效位置限定在其中一種:

對於可執行位指令,其作用往往跟業務場景相關。例如,每個查詢所要跳過的欄位都可能不同,因此的生效位置為;

對於型別系統位指令,主要是對型別系統本身額外資訊、執行行為的描述。例如說明了一個欄位將要被廢棄的原因,其定義位置為。

本文重點講解查詢指令的實現:根據不同的業務場景,對查詢進行不同的計算。

定義指令

指令應該服務於特定型別的資料結構和通用的演算法處理,而不是特定的業務場景,為特定的業務場景定義指令將使得指令系統變得臃腫、難以維護。GraphQL Calculator 參考了常見的程式設計概念對指令進行定義:

欄位加工:透過表示式對結果欄位進行加工轉換;

陣列處理:對結果中的陣列欄位進行過濾、排序、去重;

引數轉換:對請求引數進行轉換,包括加工、過濾、使用其他欄位獲取結果進行替換;

資料編排:將指定欄位的獲取結果作為全域性可獲取的上下文,為其他欄位或引數的加工轉換提供可依賴的資料;

控制流:和拓展版本,透過表示式判斷是否請求註解的欄位或片斷。

指令的命名會直接影響指令的易用性。GraphQL Calculator 指令的命名和語義參考了和 GraphQL 規範原生指令,易於理解和使用,例如、、。

執行引擎

GraphQL 的 Java 實現提供了 Instrumentation 機制,該機制可在查詢的各個階段獲取到執行上下文,可對執行資訊進行記錄、修改。該機制的核心介面有、和。

主要可獲取指令及執行上下文資訊,並對資料進行記錄、修改。該介面部分方法及說明例舉如下:

為 部分方法的返回結果,該物件包含兩個回撥方法,回撥動作將在 的方法對應的執行階段被呼叫。

可儲存查詢產生的中間資料,經常在記錄中間資料或在不同的執行執行緒間傳遞資料。為了保證該物件可被多個執行緒同時讀寫,其實現一般是執行緒安全的。

此外,指令的合法使用往往有些前置條件,例如過濾指令不可用在簡單物件或基本型別欄位上。GraphQL 的 Java 庫提供了基於訪問者模式實現的 ,可在其方法中獲取到查詢的欄位、內聯片段和片段定義的上下文資訊,便於實現自定義的校驗規則。

實現示例

定義指令

定義對陣列型別欄位進行過濾,保留斷言表示式 結果為 true 的元素,引數為所註解陣列元素的欄位名稱與欄位值的對映 Map。

實現指令

GraphQL 執行引擎獲取查詢結果可分為 fetch 和 complete 兩個階段:fetch 階段根據請求引數和上下文獲取該節點的原始資料,並分析該節點型別,遞迴獲取其子孫節點的原始資料;complete 階段對應 fetch 的遞迴出棧,處於 complete 階段的節點及其子孫節點已經全部完成解析和異常處理。

例如,對於獲取使用者詳情列表的查詢 queryUserList,對應的示意圖如下。

如何優雅地擴充套件GraphQL系統能力

在中可獲取到解析完成的列表欄位結果,該方法可過濾不符合斷言的元素。繼承實現如下:

由於在 節點只能獲取到陣列物件,但不能返回新的物件進行替換,因此需要保證在此獲取到的陣列型別是可進行過濾操作的,例如的實現類,不可是不能改變大小的陣列型別。可在中對 fetch 階段的結果進行轉換,替換為可進行過濾操作的集合型別。

校驗指令使用

透過實現自定義指令的校驗規則,以校驗引數表示式不可為空為例,其實現核心程式碼如下:

使用指令

獲取使用者詳情列表時,透過過濾出年齡大於等於 18 的使用者。

參考資料:

https://spec。graphql。org

https://github。com/graphql-calculator/graphql-calculator

https://www。graphql-java。com/documentation/v17/instrumentation

作者介紹:

杜艮魁,GraphQL Calculator 作者,GraphQL Java 活躍 contributor

相關文章

頂部