理解 GraphQL schema stitching(第二部分)

在上一篇文章中,我們討論了遠端(可執行)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"
作為 login
給 repositoryOwner
欄位時,它會起作用。
這裡的一個問題是我們無法以直接的方式詢問 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 定義本身是不完整的:它缺少 Organization
和 Repository
類型的定義。解決這個問題的一個直接方法是手動複製和貼上 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 很簡單,我們可以傳回一個描述我們應用程式的簡單字串。但是如何處理 prismagraphql
和 prismagraphqlRepositories
的 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
接收七個參數(依以下順序)
schema
:GraphQLSchema 的可執行實例(這是我們想要將執行委派到的*目標 schema*)fragmentReplacements
:包含 inline fragment 的物件(這適用於更進階的情況,我們將不在本文中討論)operation
:一個字串,具有三個值之一( "query" 、 "mutation" 或 "subscription" ),指示我們想要委派到哪個根類型fieldName
:我們想要委派到的根欄位的名稱args
:我們委派到的根欄位的輸入參數context
:透過目標 schema 的 resolver 鏈傳遞的上下文物件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 鏈中傳遞 info
和 context
物件。
prismagraphqlRepositories
的 resolver 可以以類似的方式處理,但它有點棘手。它與之前實作的不同之處在於,我們的 prismagraphqlRepositories: [Repository!]!
類型和 GitHub schema 定義中的原始欄位 repository: Repository
不像以前那樣完美匹配。我們現在需要傳回一個儲存庫陣列,而不是單個儲存庫。
因此,我們繼續使用 Promise.all 來確保我們可以一次委派多個查詢,並將它們的執行結果捆綁到承諾陣列中
就是這樣!我們現在已經為我們的自訂 GraphQL API 實作了所有三個 resolver。雖然第一個(用於 info
)是微不足道的,並且只傳回一個自訂字串,但 prismagraphql
和 prismagraphqlRepositories
正在使用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-yelp
由 Devan 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 電子報