GraphQL 伺服器開發的工具在過去兩年中呈爆炸式增長。我們認為,大多數工具的需求來自流行的 schema-first 方法 — 並且可以通過另一種方法解決:code-first。

概述:從 schema-first 到 code-first
本文概述了當前 GraphQL 伺服器開發領域的狀態。以下是涵蓋內容的快速概述
- 在本文中,「schema-first」是什麼意思?
- GraphQL 伺服器開發的演變
- 分析 SDL-first 開發的問題
- 結論:SDL-first 可能可以運作,但需要大量的工具
- Code-first:一種符合語言習慣的 GraphQL 伺服器開發方式
雖然本文主要提供 JavaScript 生態系統的範例,但其中大部分也適用於其他語言生態系統中的 GraphQL 伺服器開發。
在本文中,「schema-first」是什麼意思?
術語 schema-first 相當模糊,通常傳達一個非常積極的想法:在開發過程中將 schema 設計放在首位。
在實作之前思考 schema(以及因此 API)通常會產生更好的 API 設計。如果 schema 設計不足,則有可能最終得到一個 API,該 API 是後端實作方式的結果,而忽略了業務領域的基本要素和 API 消費者的需求。
在本文中,我們將討論在 SDL 中*手動*首先定義 GraphQL schema,然後實作解析器 (resolvers) 的開發過程的缺點。在這種方法中,SDL 是 API 的*真理來源*。為了闡明 schema-first 設計與這種特定實作方法之間的區別,從現在開始我們將其稱為 *SDL-first*。
相反,code-first(有時也稱為 *resolver-first*)是一個程式,GraphQL schema 是以*程式化*方式實作的,而 schema 的 SDL 版本是它的*生成產物*。使用 code-first,您仍然可以非常關注預先的 schema 設計!
GraphQL 伺服器開發的演變

階段 1:`graphql-js` 的早期
當 GraphQL 在 2015 年發布時,工具生態系統還很匱乏。只有官方規範及其在 JavaScript 中的參考實作:graphql-js
。直到今天,graphql-js
仍用於最流行的 GraphQL 伺服器,例如 apollo-server
、express-graphql
和 graphql-yoga
。
當使用 graphql-js
建構 GraphQL 伺服器時,GraphQL schema 被定義為一個普通的 JavaScript 物件
從這些範例可以看出,使用 graphql-js
建立 GraphQL schema 的 API 非常冗長。schema 的 SDL 表示形式更簡潔且更容易掌握
在此文章中了解更多關於使用
graphql-js
建構 GraphQL schema 的資訊。
階段 2:`graphql-tools` 推廣的 Schema-first
為了簡化開發並提高實際 API 定義的可見性,Apollo 在 2016 年 3 月開始建構 graphql-tools
函式庫(此處是第一次提交)。
目標是將 schema *定義*與實際的*實作*分開,這導致了目前流行的 *schema-driven* 或 *schema-first* / *SDL-first* 開發流程
- 在 GraphQL SDL 中手動編寫 GraphQL schema 定義
- 實作所需的解析器 (resolver) 函式
使用這種方法,上面的範例現在看起來像這樣
這些程式碼片段與上面使用 graphql-js
的程式碼 100% 等效,只是它們更具可讀性且更易於理解。
可讀性並不是 SDL-first 的唯一優勢
- 這種方法易於理解且非常適合快速建構事物
- 由於每個新的 API 操作首先需要在 schema 定義中體現,GraphQL schema 設計並非事後才考慮
- schema 定義可以作為 API 文件
- schema 定義可以作為 前端和後端團隊之間的溝通工具 — 前端開發人員正在獲得授權並更多地參與 API 設計
- schema 定義能夠 快速模擬 API
階段 3:開發新工具來「修復」SDL-first
雖然 SDL-first 有許多優點,但過去兩年表明,將其擴展到更大的專案具有挑戰性。在更複雜的環境中會出現許多問題(我們將在下一節中詳細討論這些問題)。
這些問題本身確實大多是可以解決的 — *實際*問題是解決這些問題需要使用(和*學習*)許多額外的工具。在過去兩年中,已經發布了許多試圖改進圍繞 SDL-first 開發的工作流程的工具:從編輯器外掛程式到 CLI 再到語言函式庫。
學習、管理和整合所有這些工具的額外開銷會拖慢開發人員的速度,並且難以跟上 GraphQL 生態系統。
分析 SDL-first 開發的問題
現在讓我們更深入地探討圍繞 SDL-first 開發的問題領域。請注意,這些問題中的大多數特別適用於當前的 JavaScript 生態系統。
問題 1:schema 定義和解析器 (resolver) 之間的不一致
使用 SDL-first,schema 定義*必須*與解析器 (resolver) 實作的確切結構相符。這表示開發人員需要確保 schema 定義始終與解析器同步!
雖然即使對於小型 schema 來說,這已經是一個挑戰,但隨著 schema 增長到數百甚至數千行(作為參考,GitHub GraphQL schema 有超過 10k 行),這實際上變得不可能。
工具/解決方案: 有一些工具可以幫助保持 schema 定義和解析器 (resolver) 同步。例如,通過使用 graphqlgen
或 graphql-code-generator
等函式庫進行程式碼生成。
問題 2:GraphQL schema 的模組化
在編寫大型 GraphQL schema 時,您通常不希望所有 GraphQL 類型定義都位於同一個檔案中。相反,您希望將它們拆分成更小的部分(例如,根據*功能*或*產品*)。
工具/解決方案: 像 graphql-import
或更新的 graphql-modules
函式庫等工具對此有所幫助。graphql-import
使用自訂的 import 語法,以 SDL 註解的形式編寫。graphql-modules
是一組工具,可幫助*schema 分離*、*解析器 (resolver) 組合*以及為 GraphQL 伺服器實作*可擴展的結構*。
問題 3:schema 定義中的冗餘(程式碼重用)
另一個問題是如何*重用* SDL 定義。這個問題的一個常見範例是 Relay 風格的連線。雖然它們提供了一種強大的方法來實作分頁,但它們需要*大量*的樣板程式碼和重複程式碼。
目前沒有任何工具可以幫助解決這個問題。開發人員可以編寫自訂工具來減少重複程式碼的需求,但目前該問題缺乏通用解決方案。
問題 4:IDE 支援和開發人員體驗
GraphQL schema 基於強型別系統,這在開發過程中可能是一個巨大的優勢,因為它允許對您的程式碼進行靜態分析。不幸的是,SDL 通常在您的程式中表示為純*字串*,這意味著工具無法識別其中的任何結構。
然後問題就變成了如何在您的編輯器工作流程中利用 GraphQL 類型,從自動完成和 SDL 程式碼的建構時錯誤檢查等功能中受益。
工具/解決方案: graphql-tag
函式庫公開了 gql
函式,該函式將 GraphQL 字串轉換為 AST,因此可以進行靜態分析以及隨之而來的功能。除此之外,還有各種編輯器外掛程式,例如 VS Code 的 GraphQL 或 Apollo GraphQL 外掛程式。
問題 5:組合 GraphQL schema
模組化 schema 的想法也引出另一個問題:如何將許多現有的(和分散式的)schema *組合*成單個 schema。
工具/解決方案: 最流行的 schema 組合方法一直是 schema stitching,它也是前面提到的 graphql-tools
函式庫的一部分。為了更精確地控制 schema 的組合方式,您也可以直接使用 schema delegation(它是 schema stitching 的*子集*)。
結論:SDL-first *可能*可以運作,但需要大量的工具
在探討了問題領域以及為了解決這些問題而開發的各種工具之後,似乎 SDL-first 開發*最終*可能會奏效 – 但也需要開發人員學習和使用大量的額外工具。
變通方法、變通方法、變通方法,...
在 Prisma,我們在推動 GraphQL 生態系統向前發展方面發揮了重要作用。許多提到的工具都是由我們的工程師和社群成員建構的。
經過幾個月的開發以及與 GraphQL 社群的密切互動,我們意識到我們只是在解決症狀。這就像與九頭蛇 (Hydra) 作戰一樣 – 解決一個問題會導致幾個新問題。
生態系統鎖定:購買整個工具鏈
我們非常感謝 Apollo 的朋友們的工作,他們不斷努力改進圍繞 SDL-first 開發的開發工作流程。
以 SDL-first 方式建構 GraphQL 伺服器的另一個流行範例是 AWS AppSync。它與 Apollo 模型略有不同,因為解析器 (resolvers)(通常)不是以程式化方式實作的,而是從 schema 定義自動生成的。
雖然社群從如此多的工具中受益匪淺,但當開發人員需要完全押注於某個組織的工具鏈時,就會存在生態系統鎖定的風險。*真正*的解決方案可能是將許多 SDL-first 的觀點納入 GraphQL 核心本身 – 這在可預見的未來不太可能發生。
SDL-first 忽略了程式語言的個別特性
SDL-first 的另一個問題方面是,無論使用哪種程式語言,它都通過強加相似的原則來忽略程式語言的個別功能。
Code-first 方法在其他語言中效果很好:Scala 函式庫 sangria-graphql
利用 Scala 強大的型別系統來優雅地建構 GraphQL schema,graphlq-ruby
使用了 Ruby 語言的許多出色的 DSL 功能。
Code-first:一種符合語言習慣的 GraphQL 伺服器開發方式
您唯一需要的工具就是您的程式語言
大多數 SDL-first 問題都來自我們需要*將手動編寫的 SDL schema 映射到程式語言*的事實。這種映射是導致需要額外工具的原因。如果我們遵循 SDL-first 路徑,則需要為*每個*語言生態系統重新發明所需的工具,並且每個工具的*外觀*也會有所不同。
我們應該努力尋求更簡單的開發模型,而不是使用更多工具來增加 GraphQL 伺服器開發的複雜性。理想情況下,這種模型應該讓開發人員能夠利用他們已經在使用的程式語言 – 這就是 *code-first* 的理念。
code-first 到底是什麼?
還記得在 graphql-js
中定義 schema 的初始範例嗎?這是 code-first 含義的*本質*。沒有手動維護的 schema 定義版本,而是 SDL 從實作 schema 的程式碼中*生成*。
雖然 graphql-js
的 API 非常冗長,但在其他語言中,有許多流行的框架基於 code-first 方法工作,例如前面提到的 graphlq-ruby
sangria-graphql
,以及 Python 的 graphene
或 Elixir 的 absinthe-graphql
。
code-first 的實踐
雖然本文主要關於理解 SDL-first 的問題,但這裡有一個關於使用 code-first 框架建構 GraphQL schema 的樣子的簡短預告
使用這種方法,您可以直接在 TypeScript/JavaScript 中定義您的 GraphQL 類型。通過正確的設置並感謝智慧程式碼完成,您的編輯器將能夠在您定義它們時建議可用的 GraphQL 類型、欄位和參數。
典型的編輯器工作流程包括在後台運行的開發伺服器,該伺服器在每次儲存檔案時都會重新生成類型。
定義所有 GraphQL 類型後,它們會被傳遞到一個函式中,以建立可用於您的 GraphQL 伺服器的 GraphQLSchema
實例。通過指定 ouputs
,您可以定義生成 SDL 和類型應位於何處。
本系列文章的下一部分將更詳細地討論 code-first 開發。
在無需所有工具的情況下獲得 SDL-first 的優勢
早些時候我們列舉了 SDL-first 開發的優勢。事實上,在使用 code-first 方法時,無需在其中大多數優勢上妥協。
使用 GraphQL schema 作為前端和後端團隊之間關鍵溝通工具的最重要優勢仍然存在。
以 GitHub GraphQL API 為例:GitHub 使用 Ruby 和 code-first 方法來實作他們的 API。SDL schema 定義是根據實作 API 的程式碼生成的。但是,schema 定義仍然被簽入版本控制中。這使得在開發過程中追蹤 API 的變更變得非常容易,並改善了各個團隊之間的溝通。
API 文件或授權前端開發人員等其他優勢也不會隨著 code-first 方法而消失。
Code-first 框架,即將登陸您的 IDE
本文相當理論化,並且沒有包含太多程式碼 – 我們仍然希望我們能夠激發您對 code-first 開發的興趣。要查看更多實際範例並了解更多關於 code-first 開發體驗的信息,請保持關注並在接下來的幾天內關注 Prisma Twitter 帳戶 👀
您如何看待這篇文章?加入 Prisma Slack 與其他 GraphQL 愛好者討論 SDL-first 和 code-first 開發。
🙏 非常感謝 Sashko 和 Apollo 團隊對本文的回饋!
不要錯過下一篇文章!
註冊 Prisma 電子報