2019年1月31日

“Schema-First” GraphQL 伺服器開發的問題

過去兩年間,GraphQL 伺服器開發工具呈爆炸式增長。我們認為大多數工具的需求源於流行的“schema-first”方法——而另一種替代方案“code-first”可以解決這些問題。

The Problems of "Schema-First" GraphQL Server Development
 
(當前正在閱讀)
“Schema-First” GraphQL 伺服器開發的問題
 
介紹 GraphQL Nexus:Code-First GraphQL 伺服器開發
 
將 GraphQL Nexus 與資料庫結合使用

概述:從 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*”(模式優先)這個術語相當模糊,但通常傳達了一個非常積極的理念:將模式設計作為開發過程中的優先事項。

在實現 API 之前思考模式(以及隨之而來的 API)通常會帶來更好的 API 設計。如果模式設計不足,則存在 API 最終成為後端實現方式的產物,而忽略業務領域原語和 API 消費者需求的風險。

在本文中,我們將討論一種開發過程的缺點:GraphQL 模式首先在 SDL 中*手動*定義,然後才實現解析器。在這種方法中,SDL 是 API 的*事實來源*。為了明確區分 schema-first 設計和這種具體的實現方法,我們在此將其稱為 *SDL-first*。

相比之下,code-first(有時也稱為 *resolver-first*)是一個過程,其中 GraphQL 模式是*程式化*實現的,而模式的 SDL 版本是其*生成產物*。透過 code-first,您仍然可以非常關注前期模式設計!


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 模式被定義為普通的 JavaScript 物件

從這些示例中可以看出,使用 graphql-js 建立 GraphQL 模式的 API 非常冗長。模式的 SDL 表示法則簡潔得多,更容易理解

本文中瞭解更多關於使用 graphql-js 構建 GraphQL 模式的資訊。

階段2:Schema-first 因 graphql-tools 而普及

為了簡化開發並提高實際 API 定義的可見性,Apollo 於 2016 年 3 月開始構建 graphql-tools 庫(此處是首次提交)。

目標是將模式*定義*與實際*實現*分離,這催生了當前流行的*模式驅動*或 *schema-first* / *SDL-first* 開發過程

  1. 在 GraphQL SDL 中手動編寫 GraphQL 模式定義
  2. 實現所需的解析器函式

採用這種方法,上面的示例現在看起來像這樣

這些程式碼片段與上面使用 graphql-js 的程式碼 100% 等效,只是它們更具可讀性且更容易理解。

可讀性並非 SDL-first 的唯一優勢

  • 這種方法易於理解非常適合快速構建專案
  • 由於每個新的 API 操作都需要首先在模式定義中體現,因此 GraphQL 模式設計並非事後才考慮
  • 模式定義可以作為 API 文件
  • 模式定義可以作為前端和後端團隊之間的溝通工具——前端開發人員因此獲得了賦能,並更多地參與到 API 設計中
  • 模式定義能夠快速模擬 API

階段3:開發新工具以“修復”SDL-first

儘管 SDL-first 具有許多優勢,但過去兩年表明,將其擴充套件到大型專案具有挑戰性。在更復雜的環境中會出現許多問題(我們將在下一節詳細討論這些問題)。

這些問題本身大多確實是可解決的——*實際*問題是解決它們需要使用(並*學習*)許多額外的工具。在過去的兩年中,已經發布了大量工具,試圖改進 SDL-first 開發的工作流程:從編輯器外掛,到 CLI,再到語言庫。

學習、管理和整合所有這些工具的開銷減慢了開發人員的速度,並使得跟上 GraphQL 生態系統的步伐變得困難。


分析 SDL-first 開發的問題

現在讓我們更深入地探討 SDL-first 開發中的問題領域。請注意,其中大多數問題特別適用於當前的 JavaScript 生態系統。

問題1:模式定義與解析器之間的一致性問題

在使用 SDL-first 時,模式定義*必須*與解析器實現的確切結構匹配。這意味著開發人員需要確保模式定義始終與解析器保持同步!

儘管即使對於小型模式來說這已經是一個挑戰,但當模式增長到數百或數千行時,這幾乎變得不可能(例如,GitHub GraphQL 模式有超過 1 萬行)。

工具/解決方案:有一些工具可以幫助保持模式定義和解析器同步。例如,透過使用 graphqlgengraphql-code-generator 等庫進行程式碼生成。

問題2:GraphQL 模式的模組化

在編寫大型 GraphQL 模式時,您通常不希望所有 GraphQL 型別定義都存在於同一個檔案中。相反,您希望將它們拆分為更小的部分(例如,根據*功能*或*產品*)。



工具/解決方案:諸如 graphql-import 或最近的 graphql-modules 庫有助於解決此問題。graphql-import 使用編寫為 SDL 註釋的自定義匯入語法。graphql-modules 是一個工具集,旨在幫助實現 GraphQL 伺服器的*模式分離*、*解析器組合*和*可伸縮結構*的實現。

問題3:模式定義中的冗餘(程式碼重用)

另一個問題是如何*重用* SDL 定義。一個常見示例是 Relay 風格的連線。儘管它們提供了一種強大的分頁實現方法,但它們需要*大量*的樣板程式碼和重複程式碼。

目前沒有工具可以解決這個問題。開發人員可以編寫自定義工具來減少重複程式碼的需求,但目前該問題缺乏通用解決方案。

問題4:IDE 支援與開發者體驗

GraphQL 模式基於強大的型別系統,這在開發過程中具有巨大優勢,因為它允許對程式碼進行靜態分析。不幸的是,SDL 通常在程式中表示為普通*字串*,這意味著工具無法識別其中的任何結構。

那麼問題就變成了如何在編輯器工作流中利用 GraphQL 型別,從而受益於自動補全和 SDL 程式碼的構建時錯誤檢查等功能。

工具/解決方案:graphql-tag 庫暴露了 gql 函式,該函式將 GraphQL 字串轉換為 AST,從而實現靜態分析及其帶來的功能。除此之外,還有各種編輯器外掛,例如適用於 VS Code 的 GraphQLApollo GraphQL 外掛。

問題5:組合 GraphQL 模式

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

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

結論:SDL-first *可能*可行,但需要大量工具

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



權宜之計,權宜之計,權宜之計,...

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

Workarounds cartoon

經過幾個月的開發以及與 GraphQL 社群的密切互動,我們意識到我們只是在解決症狀。這就像在與九頭蛇作戰——解決一個問題會導致出現幾個新問題。

生態系統鎖定:全盤接受整個工具鏈

我們非常感謝 Apollo 的朋友們在不斷改進 SDL-first 開發工作流程方面所做的工作。

另一個以 SDL-first 方式構建 GraphQL 伺服器的流行示例是 AWS AppSync。它與 Apollo 模型略有不同,因為解析器(通常)不是透過程式設計實現的,而是從模式定義自動生成的。

儘管社群從如此多的工具中受益匪淺,但當開發人員需要完全依賴某個組織的工具鏈時,存在生態系統鎖定的風險。*真正的*解決方案可能是將許多 SDL-first 觀點融入 GraphQL 核心本身——但這在可預見的未來不太可能發生。

SDL-first 忽視了程式語言的個性化特徵

SDL-first 的另一個問題在於,無論使用哪種程式語言,它都會施加相似的原則,從而忽視了程式語言的個性化特徵。

Code-first 方法在其他語言中執行良好:Scala 庫 sangria-graphql 利用 Scala 強大的型別系統優雅地構建 GraphQL 模式,graphlq-ruby 則利用了 Ruby 語言許多出色的 DSL 特性。


Code-first:一種符合語言習慣的 GraphQL 伺服器開發方式

您唯一需要的工具就是您的程式語言

SDL-first 的大多數問題都源於我們需要將*手動編寫的 SDL 模式對映到程式語言*。正是這種對映導致了對額外工具的需求。如果我們遵循 SDL-first 路徑,所需的工具將需要在*每個*語言生態系統中重新發明,並且每個生態系統的*外觀*也會不同。

與其透過更多工具增加 GraphQL 伺服器開發的複雜性,我們不如追求更簡單的開發模型。理想情況下,這種模型應該讓開發人員能夠利用他們已經使用的程式語言——這就是 *code-first* 的理念。

Code-first 到底是什麼?

還記得最初在 graphql-js 中定義模式的例子嗎?這就是 code-first 的*精髓*。您的模式定義沒有手動維護的版本,相反,SDL 是從實現該模式的程式碼中*生成*的。

儘管 graphql-js 的 API 非常冗長,但其他語言中也有許多流行的框架基於 code-first 方法工作,例如前面提到的 graphlq-rubysangria-graphql,以及適用於 Python 的 graphene 或適用於 Elixir 的 absinthe-graphql



Code-first 的實踐

雖然本文主要旨在理解 SDL-first 的問題,但這裡簡要介紹一下使用 code-first 框架構建 GraphQL 模式是什麼樣子


透過這種方法,您可以直接在 TypeScript/JavaScript 中定義 GraphQL 型別。透過正確的設定和智慧程式碼補全的幫助,您的編輯器將能夠在您定義 GraphQL 型別、欄位和引數時提供建議。

典型編輯器工作流程包括在後臺執行的開發伺服器,每當檔案儲存時都會重新生成型別定義。

一旦所有 GraphQL 型別定義完畢,它們就會被傳遞到一個函式中,以建立一個可在 GraphQL 伺服器中使用的 GraphQLSchema 例項。透過指定 outputs,您可以定義生成的 SDL 和型別定義應位於何處。

本系列文章的後續部分將更詳細地討論 code-first 開發。

獲得 SDL-first 的優勢,而無需所有工具

前面我們列舉了 SDL-first 開發的優勢。實際上,在使用 code-first 方法時,您無需犧牲其中大部分優勢。

將 GraphQL 模式用作前端和後端團隊之間關鍵溝通工具這一最重要的優勢得以保留。

以 GitHub GraphQL API 為例:GitHub 使用 Ruby 和 code-first 方法實現其 API。SDL 模式定義是根據實現該 API 的程式碼生成的。然而,模式定義仍然被提交到版本控制中。這使得在開發過程中跟蹤 API 更改變得異常容易,並改善了不同團隊之間的溝通。

API 文件或賦能前端開發人員等其他優勢也不會在 code-first 方法中丟失。

Code-first 框架,即將登陸您的 IDE

本文偏理論化,程式碼不多——我們仍然希望它能激發您對 code-first 開發的興趣。如需瞭解更多實際示例並深入體驗 code-first 開發,請持續關注,並在未來幾天留意 Prisma Twitter 賬號 👀

您覺得這篇文章怎麼樣?加入 Prisma Slack 與其他 GraphQL 愛好者討論 SDL-first 和 code-first 開發。


🙏 衷心感謝 SashkoApollo 團隊對本文提出的反饋意見!

不要錯過下一篇文章!

訂閱 Prisma 電子報

© . This site is unofficial and not affiliated with Prisma Data, Inc.