理解 GraphQL Schema Stitching(第二部分)

在上一篇文章中,我們討論了遠端(可執行)schema 的來龍去脈。這些遠端 schema 是一組被稱為schema stitching的工具和技術的基礎。
Schema stitching 是 GraphQL 社群中的一個全新主題。通常,它指的是組合和連線多個 GraphQL schema(或schema 定義)以建立單個 GraphQL API 的行為。
Schema stitching 主要包含兩個概念
- Schema 委託:Schema 委託的核心思想是將特定解析器的呼叫轉發(委託)給另一個解析器。本質上,schema 定義的相應欄位正在被“重新佈線”。
- Schema 合併:Schema 合併的思想是建立兩個(或更多)現有 GraphQL API 的聯合。如果涉及的 schema 完全不相交,則這不是問題——如果它們不是,則需要一種方法來解決它們的命名衝突。
請注意,在大多數情況下,委託和合並實際上會一起使用,最終我們將採用混合方法,兩者兼顧。在本系列文章中,我們將分別介紹它們,以確保每個概念都能被很好地理解。
示例:構建自定義 GitHub API
讓我們從一個基於公共GitHub GraphQL API的示例開始。假設我們想構建一個提供Prisma GitHub 組織資訊的迷你應用程式。
我們的應用程式所需的 API 應公開以下功能
- 檢索 Prisma 組織的資訊(如其 ID、電子郵件地址、頭像 URL 或置頂倉庫)
- 按名稱檢索 Prisma 組織中的倉庫列表
- 檢索關於應用程式本身的簡短描述
讓我們探索GitHub 的 GraphQL schema 定義中的 Query 型別,看看如何將我們的需求對映到 schema 的根欄位。
需求 1:檢索 Graphcool 組織資訊
第一個功能,檢索 Prisma 組織資訊,可以透過在 Query 型別上使用 repositoryOwner 根欄位來實現
我們可以傳送以下查詢來獲取 Prisma 組織的資訊
當我們為 repositoryOwner 欄位提供 "prismagraphql" 作為 login 時,它會起作用。
這裡的一個問題是,我們無法直接查詢 email,因為 RepositoryOwner 只是一個介面,沒有 email 欄位。然而,由於我們知道 Prisma 組織的具體型別確實是 Organization,我們可以透過在查詢中使用內聯片段來解決這個問題
好的,這會起作用,但我們已經遇到了一些阻礙點,這些阻礙點不允許我們為應用程式的目的直接使用 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 自動化實現,如下一節所述。
如果向型別定義添加了額外欄位或重新命名了現有欄位,您需要確保實現相應的解析器,因為底層 API 當然無法處理這些新欄位的解析。
最後,您可能決定只複製型別欄位的子集。如果您不想公開型別的所有欄位(底層 schema 可能在 User 型別上有一個 password 欄位,您不希望在應用程式 schema 中公開它),這可能是可取的。
匯入 GraphQL 型別定義
包 graphql-import 透過允許您在不同的 .graphql 檔案之間共享型別定義來避免手動操作。您可以從另一個 GraphQL schema 定義匯入型別,如下所示
在您的 JavaScript 程式碼中,您現在可以使用 importSchema 函式,它將為您解決依賴關係,確保您的 schema 定義是完整的。
實現 API
有了上面的 schema 定義,我們只完成了一半。仍然缺少的是以解析器函式形式存在的 schema 實現。
如果您此時感到困惑,請務必閱讀這篇文章,它介紹了 GraphQL schema 的基本機制和內部工作原理。
讓我們來思考如何實現這些解析器!第一個版本可能如下所示
info 的解析器很簡單,我們可以返回一個描述我們應用程式的簡單字串。但是對於 prismagraphql 和 prismagraphqlRepositories,我們實際需要從 GitHub GraphQL API 返回資訊,又該如何處理呢?
這裡實現它的樸素方法是檢視 info 引數以檢索傳入查詢的選擇集——然後從頭開始構造另一個具有相同選擇集的 GraphQL 查詢並將其傳送到 GitHub API。這甚至可以透過為 GitHub GraphQL API 建立一個遠端 schema 來簡化,但總體而言仍然是一個相當冗長和繁瑣的過程。
這正是schema 委託發揮作用的地方!我們之前看到 GitHub 的 schema 暴露了兩個根欄位,它們(某種程度上)滿足了我們的需求:repositoryOwner 和 repository。我們現在可以利用這一點,省去建立全新查詢的工作,而是轉發傳入的查詢。
委託給其他 schema
因此,我們無需嘗試構造一個全新的查詢,而是直接獲取傳入的查詢並將其執行委託給另一個 schema。我們將為此使用的 API 叫做 delegateToSchema,由 graphql-tools 提供。
delegateToSchema 接收七個引數(按以下順序)
schema:GraphQLSchema 的可執行例項(這是我們希望委託執行的*目標 schema*)fragmentReplacements:一個包含內聯片段的物件(這適用於本文不討論的更高階情況)operation:一個字串,包含三個值之一("query"、"mutation" 或 "subscription"),指示我們要委託給哪個根型別fieldName:我們要委託給的根欄位的名稱args:我們要委託的根欄位的輸入引數context:透過目標 schema 解析器鏈傳遞的上下文物件info:一個包含有關要委託的查詢資訊的物件
為了使用這種方法,我們首先需要一個表示 GitHub GraphQL API 的 GraphQLSchema 可執行例項。我們可以使用 graphql-tools 中的 makeRemoteExecutableSchema 來獲取它。
請注意,GitHub 的 GraphQL API 需要身份驗證,因此您需要一個身份驗證令牌才能使其工作。您可以按照此指南獲取一個。
為了建立 GitHub API 的遠端 schema,我們需要兩樣東西
- 它的schema 定義(以
GraphQLSchema例項的形式) - 一個知道如何從中獲取資料的
HttpLink
我們可以使用以下程式碼來實現這一點
GitHubLink 只是 HttpLink 之上的一個簡單包裝器,為建立所需的 Link 元件提供了一些便利。
太棒了,我們現在有了一個可執行的 GitHub GraphQL API 版本,可以在我們的解析器中委託給它了!🎉 讓我們首先實現 prismagraphql 解析器
我們正在傳遞 delegateToSchema 函式預期的七個引數。總的來說,沒有什麼意外:schema 是 GitHub GraphQL API 的遠端可執行 schema。在其中,我們希望將我們自己的 prismagraphql 查詢的執行委託給 GitHub API 的 repositoryOwner 查詢。由於該欄位需要一個 login 引數,我們為其提供 "prismagraphql" 作為其值。最後,我們只需將 info 和 context 物件透過解析器鏈傳遞下去。
prismagraphqlRepositories 的解析器可以用類似的方式處理,但它有點棘手。與之前的實現不同之處在於,我們的 prismagraphqlRepositories: [Repository!]! 的型別與 GitHub schema 定義中原始欄位 repository: Repository 的型別不再那麼匹配。我們現在需要返回一個倉庫的陣列,而不是單個倉庫。
因此,我們繼續使用 Promise.all 來確保我們可以一次委託多個查詢,並將它們的執行結果打包成一個 Promise 陣列
就是這樣!我們現在已經為我們的自定義 GraphQL API 實現了所有三個解析器。雖然第一個(針對 info)很簡單,只返回一個自定義字串,但 prismagraphql 和 prismagraphqlRepositories 正在使用schema 委託將查詢的執行轉發到底層 GitHub API。
如果您想檢視此程式碼的執行示例,請檢視此倉庫。
使用 graphql-tools 進行 Schema 委託
在上面基於 GitHub 構建自定義 GraphQL API 的示例中,我們看到了 delegateToSchema 如何幫助我們省去編寫查詢執行的樣板程式碼。我們無需從頭構建新查詢並使用 fetch、graphql-request 或其他 HTTP 工具傳送它,而是可以使用 graphql-tools 提供的 API 將查詢的執行委託給另一個(可執行的)GraphQLSchema 例項。方便的是,此例項可以作為遠端 schema 建立。
從高層次來看,delegateToSchema 只是作為 GraphQL.js 中 execute 函式的“代理”。這意味著在底層,它將根據作為引數傳遞的資訊重新組裝 GraphQL 查詢(或變更)。一旦查詢構建完成,它所做的就是使用 schema 和查詢呼叫 execute。
因此,schema 委託不一定要求目標 schema 是遠端 schema,它也可以與本地 schema 一起使用。在這方面,schema 委託是一個非常靈活的工具——您甚至可能希望在同一個 schema 內部進行委託。這基本上是 graphql-tools 中 mergeSchemas 採用的方法,其中多個 schema 首先合併為一個,然後解析器被重新佈線。
本質上,schema 委託是關於能夠輕鬆地將查詢轉發到現有的 GraphQL API。
Schema 繫結:輕鬆重用 GraphQL API 的方法
憑藉我們新獲得的關於 schema 委託的知識,我們可以引入一個新概念,它只是在 schema 委託之上的一個薄層便利層,稱為schema 繫結。
公共 GraphQL API 的繫結
Schema 繫結的核心思想是提供一種簡單的方法,使現有 GraphQL API 可重用,以便其他開發人員現在可以透過 NPM 將其引入他們的專案。這允許一種全新的構建 GraphQL“閘道器”的方法,在這種方法中,組合多個 GraphQL API 的功能變得極其容易。
有了 GitHub API 的專用繫結,我們現在可以簡化上面的示例。無需手動建立遠端可執行 schema,現在這部分工作由 graphql-binding-github 包完成。以下是完整實現的示例,其中刪除了我們之前需要委託給 GitHub API 的所有初始設定程式碼
我們不是自己建立遠端 schema,而是簡單地例項化從 graphql-binding-github 匯入的 GitHub 類,並使用其 delegate 函式。它隨後將在底層使用 delegateToSchema 實際執行請求。
公共 GraphQL API 的 schema 繫結可以在開發人員之間共享。除了
graphql-binding-github之外,還有適用於 Yelp GraphQL API 的繫結:由 Devan Beitel 開發的graphql-binding-yelp
自動生成的委託函式
這類 schema 繫結的 API 甚至可以改進到委託函式是自動生成的程度。與其編寫 github.delegate('query', 'repository', ... ),繫結可以暴露一個以相應根欄位命名的函式:github.query.repository( ... )。
當這些委託函式在構建步驟中生成並基於強型別語言(如 TypeScript 或 Flow)時,這種方法甚至可以為與其他 GraphQL API 的互動提供編譯時型別安全!
要了解這種方法的樣子,請檢視 prisma-binding 倉庫,它允許輕鬆地為 Graphcool 服務生成 schema 繫結,並使用上述自動生成委託函式的方法。
總結
這是我們“理解 GraphQL schema stitching”系列的第二篇文章。在第一篇文章中,我們做了一些基礎工作,並瞭解了遠端(可執行)schema,它們是大多數 schema stitching 場景的基礎。
在本文中,我們主要透過一個基於 GitHub GraphQL API 的綜合示例(示例程式碼可在此處獲取)討論了schema 委託的概念。Schema 委託是一種將解析器函式的執行轉發(委託)到不同(甚至相同)GraphQL schema 中的另一個解析器的機制。其主要優點是,我們無需從頭構建一個全新的查詢,而是可以重用和轉發(部分)傳入的查詢。
當使用 schema 委託作為基礎時,可以建立專門的 NPM 包來輕鬆共享現有 GraphQL API 的可重用schema 繫結。要了解這些繫結的樣子,您可以檢視GitHub API 的繫結以及prisma-binding,後者允許輕鬆為任何 Graphcool 服務生成繫結。
不要錯過下一篇文章!
訂閱 Prisma 新聞通訊