2017 年 12 月 12 日

GraphQL Schema Stitching 解釋:Schema 委派

理解 GraphQL schema stitching(第二部分)

GraphQL Schema Stitching explained

上一篇文章中,我們討論了遠端(可執行)schema 的來龍去脈。這些遠端 schema 是一組工具和技術的基礎,這些工具和技術被稱為 schema stitching

Schema stitching 是 GraphQL 社群中一個全新的主題。一般來說,它指的是組合和連接多個 GraphQL schema(或schema 定義)以建立單一 GraphQL API 的行為。

Schema stitching 中有兩個主要概念

  • Schema 委派:Schema 委派的核心思想是將特定 resolver 的調用轉發(委派)到另一個 resolver。本質上,schema 定義的各個欄位正在被「重新佈線」。
  • Schema 合併:Schema 合併是建立兩個(或多個)現有 GraphQL API 的聯集的想法。如果涉及的 schema 完全不相交,這就沒有問題——如果它們不相交,則需要一種方法來解決它們的命名衝突。

請注意,在大多數情況下,委派和合併實際上會一起使用,我們最終會得到一個同時使用兩者的混合方法。在本系列文章中,我們將分別介紹它們,以確保每個概念都能被充分理解。

範例:建立自訂 GitHub API

讓我們從一個基於公開的 GitHub GraphQL API 的範例開始。假設我們想要建立一個小型應用程式,提供關於 Prisma GitHub 組織的資訊。

我們應用程式所需的 API 應公開以下功能

  • 檢索關於 Prisma 組織的資訊(例如其 ID電子郵件 地址頭像 URL釘選的儲存庫
  • 依名稱從 Prisma 組織檢索儲存庫列表
  • 檢索關於應用程式本身的簡短描述

讓我們探索 Query 類型,從 GitHub 的 GraphQL schema 定義,看看我們如何將需求映射到 schema 的根欄位。

需求 1:檢索關於 Graphcool 組織的資訊

第一個功能,檢索關於 Prisma 組織的資訊,可以透過使用 Query 類型上的 repositoryOwner 根欄位來實現

我們可以發送以下查詢以詢問關於 Prisma 組織的資訊

當我們提供 "prismagraphql" 作為 loginrepositoryOwner 欄位時,它會起作用。

這裡的一個問題是我們無法以直接的方式詢問 email,因為 RepositoryOwner 只是一個沒有 email 欄位的介面。但是,由於我們知道 Prisma 組織的具體類型確實是 Organization,我們可以透過在查詢中使用 inline fragment 來解決這個問題

好的,這會起作用,但我們已經遇到了一些摩擦點,這些摩擦點不允許為了我們的應用程式的目的而直接使用 GitHub GraphQL API。

理想情況下,我們的 API 應該只公開一個根欄位,允許直接詢問我們想要的資訊,而無需在每次查詢時都提供參數,並讓我們直接詢問 Organization 上的欄位

需求 2:依名稱檢索 Graphcool 儲存庫列表

第二個需求,依名稱檢索 Graphcool 儲存庫列表,又如何呢?再次查看 Query 類型,這變得有點複雜。API 不允許直接檢索儲存庫列表——相反,您可以透過提供 owner 和儲存庫的 name 使用以下根欄位來詢問單個儲存庫

以下是相應的查詢

但是,我們實際想要為我們的應用程式做的是(為了避免發出多個請求)是一個看起來如下的根欄位

需求 3:檢索關於應用程式本身的簡短描述

我們的 API 應該能夠傳回一個句子來描述我們的應用程式,例如 "這個應用程式提供關於 Prisma GitHub 組織的資訊"

這當然是一個完全自訂的需求,我們無法基於 GitHub API 來滿足——但很明顯,我們需要自己實作它,可能使用一個簡單的 Query 根欄位,如下所示

定義應用程式 schema

我們現在了解了我們的 API 所需的功能以及我們需要為 schema 定義的理想 Query 類型

顯然,這個 schema 定義本身是不完整的:它缺少 OrganizationRepository 類型的定義。解決這個問題的一個直接方法是手動複製和貼上 GitHub 的 schema 定義中的定義。

這種方法很快就會變得繁瑣,因為這些類型定義本身取決於 schema 中的其他類型(例如,Repository 類型有一個類型為 codeOfconduct 的欄位 CodeOfConduct),然後您也需要手動複製過來。這種依賴鏈深入 schema 的深度沒有限制,您甚至可能最終手動複製完整的 schema 定義。

請注意,當手動複製類型時,有三種方法可以完成

  • 複製整個類型,不新增其他欄位
  • 複製整個類型並新增其他欄位(或重新命名現有欄位)
  • 僅複製類型欄位的子集

簡單地複製整個類型的第一種方法是最直接的。這可以使用 graphql-import 自動化,如下一節所述。

如果將其他欄位新增到類型定義或重新命名現有欄位,您需要確保實作相應的 resolver,因為底層 API 當然無法處理解析這些新欄位。

最後,您可能會決定僅複製類型欄位的子集。如果您不想公開類型的所有欄位,這可能是理想的選擇(底層 schema 可能在 User 類型上具有 password 欄位,而您不想在應用程式 schema 中公開它)。

匯入 GraphQL 類型定義

graphql-import 套件透過讓您跨不同的 .graphql 檔案共用類型定義,從而使您免於手動工作。您可以像這樣從另一個 GraphQL schema 定義匯入類型

在您的 JavaScript 程式碼中,您現在可以使用 importSchema 函數,它將為您解析依賴關係,確保您的 schema 定義是完整的。

實作 API

有了上面的 schema 定義,我們才完成了一半。仍然缺少的是 schema 的實作,以 resolver 函數的形式。

如果您此時感到迷失,請務必閱讀這篇文章,其中介紹了 GraphQL schema 的基本機制和內部運作方式。

讓我們思考一下如何實作這些 resolver!第一個版本可能如下所示

info 的 resolver 很簡單,我們可以傳回一個描述我們應用程式的簡單字串。但是如何處理 prismagraphqlprismagraphqlRepositories 的 resolver 呢?我們實際上需要從 GitHub GraphQL API 傳回資訊?

這裡實作此功能的簡單方法是查看 info 參數以檢索傳入查詢的選取集——然後從頭開始建構另一個 GraphQL 查詢,該查詢具有相同的選取集並將其發送到 GitHub API。這甚至可以透過為 GitHub GraphQL API 建立一個 remote schema 來促進,但總體而言仍然是一個非常冗長且繁瑣的過程。

這正是schema 委派發揮作用的地方!我們之前看到 GitHub 的 schema 公開了兩個根欄位,它們(在某種程度上)滿足了我們的需求:repositoryOwner 和 repository。我們現在可以利用這一點來節省建立全新查詢的工作,而是轉發傳入的查詢。

委派到其他 schema

因此,我們不是嘗試建構一個全新的查詢,而是簡單地取得傳入的查詢並將其執行委派到另一個 schema。delegateToSchema 提供的 API 就是我們要使用的 API,由 graphql-tools 提供。

delegateToSchema 接收七個參數(依以下順序)

  1. schemaGraphQLSchema 的可執行實例(這是我們想要將執行委派到的*目標 schema*)
  2. fragmentReplacements:包含 inline fragment 的物件(這適用於更進階的情況,我們將不在本文中討論)
  3. operation:一個字串,具有三個值之一( "query" 、 "mutation" 或 "subscription" ),指示我們想要委派到哪個根類型
  4. fieldName:我們想要委派到的根欄位的名稱
  5. args:我們委派到的根欄位的輸入參數
  6. context:透過目標 schema 的 resolver 鏈傳遞的上下文物件
  7. info:包含關於要委派的查詢資訊的物件

為了讓我們使用這種方法,我們首先需要一個 GraphQLSchema 的可執行實例,它代表 GitHub GraphQL API。我們可以使用來自 graphql-tools 的 makeRemoteExecutableSchema 來取得它。

請注意,GitHub 的 GraphQL API 需要驗證,因此您需要一個驗證權杖才能使其運作。您可以按照這個 指南來取得一個。

為了建立 GitHub API 的遠端 schema,我們需要兩件事

  • schema 定義(以 GraphQLSchema 實例的形式)
  • 一個 HttpLink,它知道如何從中獲取資料

我們可以使用以下程式碼來實現這一點

GitHubLink 只是 HttpLink 之上的簡單包裝器,為建立所需的 Link 元件提供了一些便利。

太棒了,我們現在有了 GitHub GraphQL API 的可執行版本,我們可以在我們的 resolver 中委派給它!🎉 讓我們從首先實作 prismagraphql resolver 開始

我們正在傳遞 delegateToSchema 函數期望的七個參數。總體而言,沒有什麼意外:schema 是 GitHub GraphQL API 的遠端可執行 schema。在其中,我們想要將我們自己的 prismagraphql 查詢的執行委派給 GitHub API 中的 repositoryOwner 查詢。由於該欄位需要一個 login 參數,我們將 "prismagraphql" 作為其值提供。最後,我們只是在 resolver 鏈中傳遞 infocontext 物件。

prismagraphqlRepositories 的 resolver 可以以類似的方式處理,但它有點棘手。它與之前實作的不同之處在於,我們的 prismagraphqlRepositories: [Repository!]! 類型和 GitHub schema 定義中的原始欄位 repository: Repository 不像以前那樣完美匹配。我們現在需要傳回一個儲存庫陣列,而不是單個儲存庫。

因此,我們繼續使用 Promise.all 來確保我們可以一次委派多個查詢,並將它們的執行結果捆綁到承諾陣列中

就是這樣!我們現在已經為我們的自訂 GraphQL API 實作了所有三個 resolver。雖然第一個(用於 info)是微不足道的,並且只傳回一個自訂字串,但 prismagraphqlprismagraphqlRepositories 正在使用schema 委派將查詢的執行轉發到底層 GitHub API。

如果您想查看此程式碼的運作範例,請查看這個 儲存庫

使用 graphql-tools 的 Schema 委派

在上面建立在 GitHub 之上的自訂 GraphQL API 範例中,我們看到了 delegateToSchema 如何幫助我們免於編寫用於查詢執行的樣板程式碼。我們可以使用 graphql-tools 提供的 API,將查詢的執行委派給另一個 GraphQLSchema 的(可執行)實例,而不是從頭開始建構新查詢並使用 fetch、graphql-request 或其他 HTTP 工具發送它。方便的是,這個實例可以建立為一個 remote schema

在高階層次上,delegateToSchema 只是充當 execute 函數的「代理」,來自 GraphQL.js。這表示在底層,它將根據作為參數傳遞的資訊重新組裝 GraphQL 查詢(或 mutation)。一旦查詢建構完成,它所做的就是 使用 schema 和查詢調用 execute

因此,schema 委派不一定要求目標 schema 是遠端 schema,它也可以使用本機 schema 完成。在這方面,schema 委派是一種非常靈活的工具——您甚至可能想要在同一個 schema 內委派。這基本上是 graphql-tools 中的 mergeSchemas 中採用的方法,其中首先將多個 schema 合併為一個 schema,然後重新佈線 resolver。

本質上,schema 委派是關於能夠輕鬆地將查詢轉發到現有的 GraphQL API。

Schema binding:重用 GraphQL API 的簡單方法

憑藉我們新獲得的關於 schema 委派的知識,我們可以介紹一個新概念,它不過是 schema 委派之上的薄薄的便利層,稱為 schema binding

公共 GraphQL API 的 Binding

schema binding 的核心思想是提供一種簡單的方法,使現有的 GraphQL API 可重複使用,以便其他開發人員現在可以透過 NPM 將其拉入他們的專案中。這允許一種全新的建立 GraphQL「閘道」的方法,在這種方法中,組合多個 GraphQL API 的功能非常容易。

憑藉 GitHub API 的專用 binding,我們現在可以簡化上面的範例。這個部分現在由 graphql-binding-github 套件完成,而不是手動建立遠端可執行 schema。以下是完整實作的外觀,其中刪除了我們之前需要委派給 GitHub API 的所有初始設定程式碼

我們只是實例化從 graphql-binding-github 匯入的 GitHub 類,並使用其 delegate 函數,而不是自己建立遠端 schema。然後它將在底層使用 delegateToSchema 來實際執行請求。

公共 GraphQL API 的 Schema binding 可以在開發人員之間共用。除了 graphql-binding-github 之外,Yelp GraphQL API 也已經有一個 binding 可用:graphql-binding-yelpDevan Beitel 提供

自動產生的委派函數

這些 schema binding 的 API 甚至可以改進到自動產生委派函數的程度。binding 可以公開一個以相應根欄位命名的函數:github.query.repository( ... ),而不是編寫以下 github.delegate('query', 'repository', ... )

當這些委派函數在建置步驟中產生並基於強型別語言(例如 TypeScript 或 Flow)時,這種方法甚至將為與其他 GraphQL API 互動提供編譯時型別安全!

若要了解這種方法的外觀,請查看 prisma-binding 儲存庫,它允許輕鬆為 Graphcool 服務產生 schema binding,並使用上述自動產生委派函數的方法。

總結

這是我們「理解 GraphQL schema stitching」系列的第二篇文章。在第一篇文章中,我們做了一些基礎工作,並了解了遠端(可執行)schema,它是大多數 schema stitching 場景的基礎。

在本文中,我們主要討論了schema 委派的概念,方法是提供一個基於 GitHub GraphQL API 的綜合範例(該範例的程式碼可在此處取得)。Schema 委派是一種將 resolver 函數的執行轉發(委派)到不同(甚至相同)GraphQL schema 中的另一個 resolver 的機制。它的主要優點是我們不必從頭開始建構一個全新的查詢,而是可以重複使用和轉發(部分)傳入的查詢。

當使用 schema 委派作為基礎時,可以建立專用的 NPM 套件,以便輕鬆共用現有 GraphQL API 的可重複使用schema binding。若要了解它們的外觀,您可以查看 GitHub API 的 binding 以及 prisma-binding,它允許輕鬆為任何 Graphcool 服務產生 binding。

不要錯過下一篇文章!

註冊 Prisma 電子報