2019年1月31日

「Schema-First」GraphQL 伺服器開發的問題

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

The Problems of "Schema-First" GraphQL Server Development

概述:從 schema-first 到 code-first

本文概述了當前 GraphQL 伺服器開發領域的狀態。以下是涵蓋內容的快速概述

  1. 在本文中,「schema-first」是什麼意思?
  2. GraphQL 伺服器開發的演變
  3. 分析 SDL-first 開發的問題
  4. 結論:SDL-first 可能可以運作,但需要大量的工具
  5. 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 伺服器開發的演變

The evolution of GraphQL server development

階段 1:`graphql-js` 的早期

當 GraphQL 在 2015 年發布時,工具生態系統還很匱乏。只有官方規範及其在 JavaScript 中的參考實作:graphql-js。直到今天,graphql-js 仍用於最流行的 GraphQL 伺服器,例如 apollo-serverexpress-graphqlgraphql-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* 開發流程

  1. 在 GraphQL SDL 中手動編寫 GraphQL schema 定義
  2. 實作所需的解析器 (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) 同步。例如,通過使用 graphqlgengraphql-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 的 GraphQLApollo GraphQL 外掛程式。

問題 5:組合 GraphQL schema

模組化 schema 的想法也引出另一個問題:如何將許多現有的(和分散式的)schema *組合*成單個 schema。

工具/解決方案: 最流行的 schema 組合方法一直是 schema stitching,它也是前面提到的 graphql-tools 函式庫的一部分。為了更精確地控制 schema 的組合方式,您也可以直接使用 schema delegation(它是 schema stitching 的*子集*)。

結論:SDL-first *可能*可以運作,但需要大量的工具

在探討了問題領域以及為了解決這些問題而開發的各種工具之後,似乎 SDL-first 開發*最終*可能會奏效 – 但也需要開發人員學習和使用大量的額外工具。



變通方法、變通方法、變通方法,...

在 Prisma,我們在推動 GraphQL 生態系統向前發展方面發揮了重要作用。許多提到的工具都是由我們的工程師和社群成員建構的。

Workarounds cartoon

經過幾個月的開發以及與 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 開發。


🙏 非常感謝 SashkoApollo 團隊對本文的回饋!

不要錯過下一篇文章!

註冊 Prisma 電子報