<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Graphql on Tarragon</title><link>https://tarrragon.github.io/blog/tags/graphql/</link><description>Recent content in Graphql on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 03 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/graphql/index.xml" rel="self" type="application/rss+xml"/><item><title>GraphQL Schema 演進：versionless 的紀律代價</title><link>https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/graphql-schema-evolution/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/graphql-schema-evolution/</guid><description>&lt;p>GraphQL 的 schema 演進機制建立在一條因果鏈上：client 只拿到明確請求的欄位、所以新增 type 與 field 對既有 query 不可見、所以加法演進永遠安全、所以版本號可以不存在。&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-versionless-evolution/" data-link-title="11.C26 GraphQL 官方：versionless API 與 nullable-by-default" data-link-desc="no-versioning 的成本轉嫁鏈：只加不改、deprecation、nullable 預設三個紀律換掉版本號">11.C26&lt;/a> 收錄的官方立場把「永遠避免 breaking change、提供 versionless API」稱為 common practice。本文追這條因果鏈的三個支撐紀律、以及各自的隱藏帳單。跨風格的變更紀律框架（格式層 / 工具層 / 流程層）主寫在 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/backward-compatibility-discipline/" data-link-title="11.6 向後相容的變更紀律" data-link-desc="哪些變更算 breaking、相容性檢查放人工還是 CI、檢查粒度怎麼選 — 讓介面變更可審可擋的日常紀律">11.6&lt;/a>、本文的 lens 是 GraphQL 內部機制的深化。&lt;/p>
&lt;h2 id="紀律一只加不改">紀律一：只加不改&lt;/h2>
&lt;p>加法安全的機制基礎是 client 的顯式選取：REST 回應裡新增欄位、所有消費者都會收到（多數忽略、少數壞掉）；GraphQL 新增欄位、沒請求它的 query 完全不受影響。這讓「加」在 GraphQL 是真正的零風險操作 — 但「改」與「刪」的風險跟任何風格相同、versionless 的意思是把這兩類操作用紀律排除、而非讓它們變安全。&lt;/p>
&lt;p>隱藏帳單是 schema 只增不減的膨脹：欄位一旦發布、有沒有人用、用的人肯不肯走、都要靠量測回答。GraphQL 在這點上有結構優勢 — client 逐欄位聲明取數、server 端可以精確統計每個欄位的使用量與呼叫方、比 REST 的「整包回應、不知道誰讀了哪個欄位」可觀測得多。這個優勢要主動兌現：欄位使用量進 metrics 是 versionless 能長期運作的基礎設施前提、對應 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/versioning-and-deprecation/" data-link-title="11.5 版本策略與 deprecation" data-link-desc="版本方案怎麼選（URI/header/date-based）、支援窗口怎麼承諾、舊版怎麼安全退場 — 承諾分期與回收的操作設計">11.5&lt;/a> 退場量測段的同一條原則。&lt;/p>
&lt;h2 id="紀律二deprecation-標注">紀律二：deprecation 標注&lt;/h2>
&lt;p>&lt;code>@deprecated&lt;/code> directive 把退場資訊放進 schema 本身：欄位標注後、introspection 與工具鏈（IDE 自動完成、linter）會對新的使用者顯示警告、既有 query 照常運作。這是 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/versioning-and-deprecation/" data-link-title="11.5 版本策略與 deprecation" data-link-desc="版本方案怎麼選（URI/header/date-based）、支援窗口怎麼承諾、舊版怎麼安全退場 — 承諾分期與回收的操作設計">deprecation 執行工具箱&lt;/a> 裡 in-band warning 的 schema 層版本 — 訊號出現在開發者寫 query 的當下、時點比 response warning 更早。&lt;/p>
&lt;p>隱藏帳單是「標注不等於退場」：&lt;code>@deprecated&lt;/code> 沒有強制力、沒有日期語意、long tail 消費者可以永遠不動。實務上的補法是把欄位使用量量測跟 deprecation 標注綁在一起 — 標注後看用量衰減曲線、歸零才真正刪除；用量不動、回到 11.5 的遷移壓力工具。&lt;/p>
&lt;h2 id="紀律三nullable-預設">紀律三：nullable 預設&lt;/h2>
&lt;p>GraphQL type system 把每個欄位預設為 nullable、官方理由包含後端局部故障與細粒度授權（C26 觀察層）：某個 resolver 失敗或某個欄位被權限拒絕時、該欄位回 null、response 的其餘部分照常返回 — 局部失敗不炸掉整個回應。這個設計跟演進的關係在第三層：nullable 欄位的移除路徑比 non-null 平緩（消費者本來就要處理 null、欄位「永遠 null」是移除前的可用中繼態）。&lt;/p>
&lt;p>隱藏帳單是 null 語意的多義：欄位是 null、消費者無法區分「值就是空」「resolver 失敗」「權限拒絕」三種情況 — 錯誤資訊要靠 response 的 &lt;code>errors&lt;/code> 陣列補充、而這正是 GraphQL 把 transport status 與業務錯誤解耦的設計（錯誤格式的跨風格交鋒、掛在 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/error-model-design/" data-link-title="11.4 錯誤模型設計" data-link-desc="錯誤該分幾類、格式怎麼定才有演化空間、機器判讀跟人類訊息怎麼分工 — 錯誤作為契約一級公民的設計判準">11.4&lt;/a> 的爭論文章 backlog）。schema 設計的實務判準：業務上不可能缺席的欄位（id、type）明文標 non-null、其餘保留 nullable 預設 — 全部標 non-null 換到的型別安全、會在第一次局部故障時以整包 response 失敗的形式付還。&lt;/p>
&lt;h2 id="versionless-是承諾結構不是免維護">versionless 是承諾結構、不是免維護&lt;/h2>
&lt;p>三個紀律合起來看、versionless 的實質是把版本管理的工作換了位置：版本號消失、換來的是欄位級的使用量量測、deprecation 生命週期管理、null 語意設計三項常態工作。跟 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/versioning-and-deprecation/" data-link-title="11.5 版本策略與 deprecation" data-link-desc="版本方案怎麼選（URI/header/date-based）、支援窗口怎麼承諾、舊版怎麼安全退場 — 承諾分期與回收的操作設計">11.5&lt;/a> 的日期版本方案相比、差異在粒度 — 日期版本以「版本」為單位管理遷移、GraphQL 以「欄位」為單位；粒度變細讓大翻版消失、也讓管理點的數量成長一個量級。組織層的判讀：schema 治理（誰能加欄位、誰審 deprecation、linting 進 CI）承擔的角色跟 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/api-governance/" data-link-title="11.10 API 規範治理" data-link-desc="設計規範怎麼讓幾十個團隊持續遵守 — 提案制、Guild 制、分軌制的治理模式比較、linting 進 CI、規範失敗的成因">11.10&lt;/a> 的 guidelines 治理同構、schema registry 類工具是這一層的基礎設施。&lt;/p></description><content:encoded><![CDATA[<p>GraphQL 的 schema 演進機制建立在一條因果鏈上：client 只拿到明確請求的欄位、所以新增 type 與 field 對既有 query 不可見、所以加法演進永遠安全、所以版本號可以不存在。<a href="/blog/backend/11-api-design/cases/graphql-versionless-evolution/" data-link-title="11.C26 GraphQL 官方：versionless API 與 nullable-by-default" data-link-desc="no-versioning 的成本轉嫁鏈：只加不改、deprecation、nullable 預設三個紀律換掉版本號">11.C26</a> 收錄的官方立場把「永遠避免 breaking change、提供 versionless API」稱為 common practice。本文追這條因果鏈的三個支撐紀律、以及各自的隱藏帳單。跨風格的變更紀律框架（格式層 / 工具層 / 流程層）主寫在 <a href="/blog/backend/11-api-design/backward-compatibility-discipline/" data-link-title="11.6 向後相容的變更紀律" data-link-desc="哪些變更算 breaking、相容性檢查放人工還是 CI、檢查粒度怎麼選 — 讓介面變更可審可擋的日常紀律">11.6</a>、本文的 lens 是 GraphQL 內部機制的深化。</p>
<h2 id="紀律一只加不改">紀律一：只加不改</h2>
<p>加法安全的機制基礎是 client 的顯式選取：REST 回應裡新增欄位、所有消費者都會收到（多數忽略、少數壞掉）；GraphQL 新增欄位、沒請求它的 query 完全不受影響。這讓「加」在 GraphQL 是真正的零風險操作 — 但「改」與「刪」的風險跟任何風格相同、versionless 的意思是把這兩類操作用紀律排除、而非讓它們變安全。</p>
<p>隱藏帳單是 schema 只增不減的膨脹：欄位一旦發布、有沒有人用、用的人肯不肯走、都要靠量測回答。GraphQL 在這點上有結構優勢 — client 逐欄位聲明取數、server 端可以精確統計每個欄位的使用量與呼叫方、比 REST 的「整包回應、不知道誰讀了哪個欄位」可觀測得多。這個優勢要主動兌現：欄位使用量進 metrics 是 versionless 能長期運作的基礎設施前提、對應 <a href="/blog/backend/11-api-design/versioning-and-deprecation/" data-link-title="11.5 版本策略與 deprecation" data-link-desc="版本方案怎麼選（URI/header/date-based）、支援窗口怎麼承諾、舊版怎麼安全退場 — 承諾分期與回收的操作設計">11.5</a> 退場量測段的同一條原則。</p>
<h2 id="紀律二deprecation-標注">紀律二：deprecation 標注</h2>
<p><code>@deprecated</code> directive 把退場資訊放進 schema 本身：欄位標注後、introspection 與工具鏈（IDE 自動完成、linter）會對新的使用者顯示警告、既有 query 照常運作。這是 <a href="/blog/backend/11-api-design/versioning-and-deprecation/" data-link-title="11.5 版本策略與 deprecation" data-link-desc="版本方案怎麼選（URI/header/date-based）、支援窗口怎麼承諾、舊版怎麼安全退場 — 承諾分期與回收的操作設計">deprecation 執行工具箱</a> 裡 in-band warning 的 schema 層版本 — 訊號出現在開發者寫 query 的當下、時點比 response warning 更早。</p>
<p>隱藏帳單是「標注不等於退場」：<code>@deprecated</code> 沒有強制力、沒有日期語意、long tail 消費者可以永遠不動。實務上的補法是把欄位使用量量測跟 deprecation 標注綁在一起 — 標注後看用量衰減曲線、歸零才真正刪除；用量不動、回到 11.5 的遷移壓力工具。</p>
<h2 id="紀律三nullable-預設">紀律三：nullable 預設</h2>
<p>GraphQL type system 把每個欄位預設為 nullable、官方理由包含後端局部故障與細粒度授權（C26 觀察層）：某個 resolver 失敗或某個欄位被權限拒絕時、該欄位回 null、response 的其餘部分照常返回 — 局部失敗不炸掉整個回應。這個設計跟演進的關係在第三層：nullable 欄位的移除路徑比 non-null 平緩（消費者本來就要處理 null、欄位「永遠 null」是移除前的可用中繼態）。</p>
<p>隱藏帳單是 null 語意的多義：欄位是 null、消費者無法區分「值就是空」「resolver 失敗」「權限拒絕」三種情況 — 錯誤資訊要靠 response 的 <code>errors</code> 陣列補充、而這正是 GraphQL 把 transport status 與業務錯誤解耦的設計（錯誤格式的跨風格交鋒、掛在 <a href="/blog/backend/11-api-design/error-model-design/" data-link-title="11.4 錯誤模型設計" data-link-desc="錯誤該分幾類、格式怎麼定才有演化空間、機器判讀跟人類訊息怎麼分工 — 錯誤作為契約一級公民的設計判準">11.4</a> 的爭論文章 backlog）。schema 設計的實務判準：業務上不可能缺席的欄位（id、type）明文標 non-null、其餘保留 nullable 預設 — 全部標 non-null 換到的型別安全、會在第一次局部故障時以整包 response 失敗的形式付還。</p>
<h2 id="versionless-是承諾結構不是免維護">versionless 是承諾結構、不是免維護</h2>
<p>三個紀律合起來看、versionless 的實質是把版本管理的工作換了位置：版本號消失、換來的是欄位級的使用量量測、deprecation 生命週期管理、null 語意設計三項常態工作。跟 <a href="/blog/backend/11-api-design/versioning-and-deprecation/" data-link-title="11.5 版本策略與 deprecation" data-link-desc="版本方案怎麼選（URI/header/date-based）、支援窗口怎麼承諾、舊版怎麼安全退場 — 承諾分期與回收的操作設計">11.5</a> 的日期版本方案相比、差異在粒度 — 日期版本以「版本」為單位管理遷移、GraphQL 以「欄位」為單位；粒度變細讓大翻版消失、也讓管理點的數量成長一個量級。組織層的判讀：schema 治理（誰能加欄位、誰審 deprecation、linting 進 CI）承擔的角色跟 <a href="/blog/backend/11-api-design/api-governance/" data-link-title="11.10 API 規範治理" data-link-desc="設計規範怎麼讓幾十個團隊持續遵守 — 提案制、Guild 制、分軌制的治理模式比較、linting 進 CI、規範失敗的成因">11.10</a> 的 guidelines 治理同構、schema registry 類工具是這一層的基礎設施。</p>
<p>no-versioning 立場的跨流派交鋒（Fielding 的 hypermedia 路線、Stripe 的 date-based 路線、GraphQL 的欄位粒度路線）、收在掛 11.5 的版本策略爭論文章 backlog（見 <a href="/blog/backend/11-api-design/" data-link-title="模組十一：API 設計與對外契約" data-link-desc="整理 API 風格選型、資源建模、錯誤模型、版本與相容策略、冪等與對外流量語意的設計判準；主流做法與各流派的深度論證分層收錄">模組頁</a>）。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>跨風格的變更紀律框架：<a href="/blog/backend/11-api-design/backward-compatibility-discipline/" data-link-title="11.6 向後相容的變更紀律" data-link-desc="哪些變更算 breaking、相容性檢查放人工還是 CI、檢查粒度怎麼選 — 讓介面變更可審可擋的日常紀律">11.6 向後相容的變更紀律</a></li>
<li>執行層的代價：<a href="/blog/backend/11-api-design/styles/graphql/graphql-execution-cost-security/" data-link-title="GraphQL 執行成本與攻擊面" data-link-desc="resolver 執行模型讓請求成本不再是常數 — N&#43;1 的基礎設施化、成本計點限流、introspection 偵察、persisted queries 的收斂路線">執行成本與攻擊面</a></li>
<li>組織層的進退：<a href="/blog/backend/11-api-design/styles/graphql/graphql-public-api-tradeoffs/" data-link-title="公開 API 的 GraphQL 進退" data-link-desc="GitHub 雙軌、Shopify all-in、與撤退案例 — 同一技術不同結局的情境變數、GraphQL 的適用邊界">公開 API 的 GraphQL 進退</a></li>
<li>案例原文：<a href="/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫</a></li>
</ul>
]]></content:encoded></item><item><title>GraphQL 流派：schema 演進、執行成本與公開 API 進退</title><link>https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/</guid><description>&lt;p>GraphQL 的爭論結構跟 REST 不同：定義沒有歧義（spec 明確、基金會治理）、爭的是代價 — client 聲明取數的彈性、由誰在哪一層付出成本。本目錄三篇各追一條代價線：schema 層（versionless 的紀律轉嫁）、執行層（resolver 模型的成本與攻擊面）、組織層（公開 API 的採用與撤退）。中性選型判準見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/api-style-selection/" data-link-title="11.2 風格選型總覽" data-link-desc="REST 式 HTTP&amp;#43;JSON、GraphQL、gRPC、tRPC、JSON-RPC、event 之間選哪個 — 用消費者形狀、演進成本、操作可及性三軸判讀">11.2 風格選型總覽&lt;/a>。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>文章&lt;/th>
 &lt;th>主題&lt;/th>
 &lt;th>案例支撐&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/graphql-schema-evolution/" data-link-title="GraphQL Schema 演進：versionless 的紀律代價" data-link-desc="只加不改、deprecation 標注、nullable 預設怎麼共同取代版本號 — 以及每個紀律各自的隱藏帳單">Schema 演進：versionless 的紀律代價&lt;/a>&lt;/td>
 &lt;td>只加不改、deprecation、nullable 預設的因果鏈&lt;/td>
 &lt;td>C26&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/graphql-execution-cost-security/" data-link-title="GraphQL 執行成本與攻擊面" data-link-desc="resolver 執行模型讓請求成本不再是常數 — N&amp;#43;1 的基礎設施化、成本計點限流、introspection 偵察、persisted queries 的收斂路線">執行成本與攻擊面&lt;/a>&lt;/td>
 &lt;td>N+1、成本計點、introspection、persisted queries&lt;/td>
 &lt;td>C19、C22、C24、C25、C27&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/graphql-public-api-tradeoffs/" data-link-title="公開 API 的 GraphQL 進退" data-link-desc="GitHub 雙軌、Shopify all-in、與撤退案例 — 同一技術不同結局的情境變數、GraphQL 的適用邊界">公開 API 的 GraphQL 進退&lt;/a>&lt;/td>
 &lt;td>同一技術的多種結局與適用邊界&lt;/td>
 &lt;td>C18、C20-C23、C27&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<p>GraphQL 的爭論結構跟 REST 不同：定義沒有歧義（spec 明確、基金會治理）、爭的是代價 — client 聲明取數的彈性、由誰在哪一層付出成本。本目錄三篇各追一條代價線：schema 層（versionless 的紀律轉嫁）、執行層（resolver 模型的成本與攻擊面）、組織層（公開 API 的採用與撤退）。中性選型判準見 <a href="/blog/backend/11-api-design/api-style-selection/" data-link-title="11.2 風格選型總覽" data-link-desc="REST 式 HTTP&#43;JSON、GraphQL、gRPC、tRPC、JSON-RPC、event 之間選哪個 — 用消費者形狀、演進成本、操作可及性三軸判讀">11.2 風格選型總覽</a>。</p>
<table>
  <thead>
      <tr>
          <th>文章</th>
          <th>主題</th>
          <th>案例支撐</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/11-api-design/styles/graphql/graphql-schema-evolution/" data-link-title="GraphQL Schema 演進：versionless 的紀律代價" data-link-desc="只加不改、deprecation 標注、nullable 預設怎麼共同取代版本號 — 以及每個紀律各自的隱藏帳單">Schema 演進：versionless 的紀律代價</a></td>
          <td>只加不改、deprecation、nullable 預設的因果鏈</td>
          <td>C26</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/11-api-design/styles/graphql/graphql-execution-cost-security/" data-link-title="GraphQL 執行成本與攻擊面" data-link-desc="resolver 執行模型讓請求成本不再是常數 — N&#43;1 的基礎設施化、成本計點限流、introspection 偵察、persisted queries 的收斂路線">執行成本與攻擊面</a></td>
          <td>N+1、成本計點、introspection、persisted queries</td>
          <td>C19、C22、C24、C25、C27</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/11-api-design/styles/graphql/graphql-public-api-tradeoffs/" data-link-title="公開 API 的 GraphQL 進退" data-link-desc="GitHub 雙軌、Shopify all-in、與撤退案例 — 同一技術不同結局的情境變數、GraphQL 的適用邊界">公開 API 的 GraphQL 進退</a></td>
          <td>同一技術的多種結局與適用邊界</td>
          <td>C18、C20-C23、C27</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>GraphQL 執行成本與攻擊面</title><link>https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/graphql-execution-cost-security/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/graphql-execution-cost-security/</guid><description>&lt;p>一個只有 128 bytes 的惡意查詢、可以耗掉 10 秒 CPU。這組數字出自一位六年 GraphQL 使用者的撤退紀錄（&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-bessey-retreat/" data-link-title="11.C22 Matt Bessey：六年 GraphQL 老手的撤退清單（反例）" data-link-desc="反例：授權下推到 field、成本不可預測、解析層攻擊面的執行期代價清單、附撤退判準">11.C22&lt;/a>、反例、含畸形 directives 造成 2,000 倍記憶體放大的並列觀察）、它濃縮了 GraphQL 執行層的結構性質：&lt;strong>請求的成本由 query 的結構決定、而 query 的結構由消費者決定&lt;/strong> — 傳統「一個請求約等於一份成本」的容量假設、在 resolver 執行模型下不成立。下面沿這個性質追出四個工程後果；限流的判準層語意已由 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/external-traffic-semantics/" data-link-title="11.9 對外流量語意" data-link-desc="rate limit 對消費者承諾什麼、429 與 Retry-After 怎麼設計、配額 header 該不該信 — 限流作為契約的語意設計">11.9&lt;/a> 承擔、這裡往機制層走。&lt;/p>
&lt;h2 id="n1從偶發問題變成預設行為">N+1：從偶發問題變成預設行為&lt;/h2>
&lt;p>resolver-per-field 的執行模型讓 N+1 從查詢寫壞才發生的偶發問題、變成不做處理就必然發生的預設：列表的每個元素各自觸發子欄位的 resolver、一層巢狀就是一輪 N 次資料庫存取。&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-dataloader-n-plus-one/" data-link-title="11.C24 DataLoader 譜系：N&amp;#43;1 的官方解法變成基礎設施" data-link-desc="resolver-per-field 讓 N&amp;#43;1 從偶發變預設、官方生態把 batching 做成基礎設施而非優化技巧">11.C24&lt;/a> 記錄了官方生態的回應方式 — batching 做成基礎設施而非優化技巧：DataLoader 把單一執行 frame 內的個別 load 合併成 batch、概念源自 Facebook 2010 年的內部 Loader API、早於 GraphQL 開源；GitHub 2016 年上線 GraphQL 時、技術棧 day one 就帶著 Shopify 維護的 graphql-batch。判讀：評估 GraphQL 的建置成本時、dataloader 層是基礎配備、不是後期優化項 — 缺少它的 GraphQL 服務、第一個帶列表的巢狀 query 就會對資料庫造成 N 倍讀放大。&lt;/p>
&lt;h2 id="成本計點限流模型的被迫重建">成本計點：限流模型的被迫重建&lt;/h2>
&lt;p>請求成本不是常數的直接後果是 per-request 限流失效。&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-github-cost-rate-limiting/" data-link-title="11.C19 GitHub：GraphQL point system 成本計點限流" data-link-desc="GraphQL 打破 per-request 限流假設、平台被迫發明查詢成本模型、加 node 上限雙層防線">11.C19&lt;/a> 記錄了 GitHub 的完整應對：對每個 query 依 connection 展開計算 point、每小時 5,000 點；另設 500,000 node 上限與分頁參數 1-100 的限制；消費者可事前預估、也可事後查 &lt;code>rateLimit.cost&lt;/code>。動靜兩層各擋一類風險（C19 判讀）— 成本計點管累積用量、node 上限管單發炸彈；成本模型對消費者透明可預估、是它能當契約的前提（對外流量語意的承諾邊界、見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/external-traffic-semantics/" data-link-title="11.9 對外流量語意" data-link-desc="rate limit 對消費者承諾什麼、429 與 Retry-After 怎麼設計、配額 header 該不該信 — 限流作為契約的語意設計">11.9&lt;/a>）。自建 GraphQL 公開 API 時這一整層都要自己蓋 — 這是 REST 世界拿現成 gateway 限流就能用的能力。&lt;/p>
&lt;h2 id="introspection型別系統是雙面刃">Introspection：型別系統是雙面刃&lt;/h2>
&lt;p>introspection 讓 schema 自我描述、工具鏈（IDE、codegen、文件生成）全建立在它上面 — 同一個能力對攻擊者是免 fuzzing 的偵察工具。&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-introspection-auth-bypass/" data-link-title="11.C25 HackerOne：introspection 列舉出未授權的 CreateAdminUser" data-link-desc="introspection 作為攻擊面偵察工具的實證：schema 自我揭露讓隱藏 mutation 免 fuzzing 直接可見">11.C25&lt;/a> 是具體實證：某電商平台的第三方服務暴露 GraphQL 端點、introspection 開啟、研究者列舉 schema 後發現未加驗證的 &lt;code>CreateAdminUser&lt;/code> mutation、直接取得管理權限。REST 世界要靠字典檔猜端點、GraphQL 用型別系統直接把地圖交出去。加上授權模型的難度 — 每個 field 都要各自做授權檢查、且授權檢查本身也會 N+1（C22 觀察）— GraphQL 的攻擊面治理是欄位粒度的、middleware 式的單點防護模型在結構上對不上。&lt;/p>
&lt;h2 id="persisted-queries介於全開與撤退之間">Persisted queries：介於全開與撤退之間&lt;/h2>
&lt;p>執行成本與攻擊面的問題有一條收斂路線：把 named operations 存在 server 端、對外只暴露操作 ID、完全不接受任意 query。&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-wundergraph-not-for-internet/" data-link-title="11.C27 WunderGraph：GraphQL 不該直接暴露在公網" data-link-desc="介於全開與撤退之間的第三條路：GraphQL 當 server-side 查詢語言、對外只開 persisted operations；vendor 立場需標明">11.C27&lt;/a> 把這條路線推到論證的極端 —「GraphQL 不該直接暴露在公網、該當 server-side 查詢語言用」；來源是販售此方案的 vendor、立場要標明、但攻擊面描述與 C22、C25 獨立互證。persisted queries 的效果是把 GraphQL 的彈性收回開發期：開發時保有 client 聲明取數的 DX、上線後對外面積等於一組預先審核過的操作 — 成本可預算、introspection 可關閉、任意 query 的攻擊面消失。代價是把「第三方自由組合查詢」這個公開 API 的賣點一起收掉 — 對內部 client 幾乎是純收益、對開放平台則等於換了一種產品。&lt;/p></description><content:encoded><![CDATA[<p>一個只有 128 bytes 的惡意查詢、可以耗掉 10 秒 CPU。這組數字出自一位六年 GraphQL 使用者的撤退紀錄（<a href="/blog/backend/11-api-design/cases/graphql-bessey-retreat/" data-link-title="11.C22 Matt Bessey：六年 GraphQL 老手的撤退清單（反例）" data-link-desc="反例：授權下推到 field、成本不可預測、解析層攻擊面的執行期代價清單、附撤退判準">11.C22</a>、反例、含畸形 directives 造成 2,000 倍記憶體放大的並列觀察）、它濃縮了 GraphQL 執行層的結構性質：<strong>請求的成本由 query 的結構決定、而 query 的結構由消費者決定</strong> — 傳統「一個請求約等於一份成本」的容量假設、在 resolver 執行模型下不成立。下面沿這個性質追出四個工程後果；限流的判準層語意已由 <a href="/blog/backend/11-api-design/external-traffic-semantics/" data-link-title="11.9 對外流量語意" data-link-desc="rate limit 對消費者承諾什麼、429 與 Retry-After 怎麼設計、配額 header 該不該信 — 限流作為契約的語意設計">11.9</a> 承擔、這裡往機制層走。</p>
<h2 id="n1從偶發問題變成預設行為">N+1：從偶發問題變成預設行為</h2>
<p>resolver-per-field 的執行模型讓 N+1 從查詢寫壞才發生的偶發問題、變成不做處理就必然發生的預設：列表的每個元素各自觸發子欄位的 resolver、一層巢狀就是一輪 N 次資料庫存取。<a href="/blog/backend/11-api-design/cases/graphql-dataloader-n-plus-one/" data-link-title="11.C24 DataLoader 譜系：N&#43;1 的官方解法變成基礎設施" data-link-desc="resolver-per-field 讓 N&#43;1 從偶發變預設、官方生態把 batching 做成基礎設施而非優化技巧">11.C24</a> 記錄了官方生態的回應方式 — batching 做成基礎設施而非優化技巧：DataLoader 把單一執行 frame 內的個別 load 合併成 batch、概念源自 Facebook 2010 年的內部 Loader API、早於 GraphQL 開源；GitHub 2016 年上線 GraphQL 時、技術棧 day one 就帶著 Shopify 維護的 graphql-batch。判讀：評估 GraphQL 的建置成本時、dataloader 層是基礎配備、不是後期優化項 — 缺少它的 GraphQL 服務、第一個帶列表的巢狀 query 就會對資料庫造成 N 倍讀放大。</p>
<h2 id="成本計點限流模型的被迫重建">成本計點：限流模型的被迫重建</h2>
<p>請求成本不是常數的直接後果是 per-request 限流失效。<a href="/blog/backend/11-api-design/cases/graphql-github-cost-rate-limiting/" data-link-title="11.C19 GitHub：GraphQL point system 成本計點限流" data-link-desc="GraphQL 打破 per-request 限流假設、平台被迫發明查詢成本模型、加 node 上限雙層防線">11.C19</a> 記錄了 GitHub 的完整應對：對每個 query 依 connection 展開計算 point、每小時 5,000 點；另設 500,000 node 上限與分頁參數 1-100 的限制；消費者可事前預估、也可事後查 <code>rateLimit.cost</code>。動靜兩層各擋一類風險（C19 判讀）— 成本計點管累積用量、node 上限管單發炸彈；成本模型對消費者透明可預估、是它能當契約的前提（對外流量語意的承諾邊界、見 <a href="/blog/backend/11-api-design/external-traffic-semantics/" data-link-title="11.9 對外流量語意" data-link-desc="rate limit 對消費者承諾什麼、429 與 Retry-After 怎麼設計、配額 header 該不該信 — 限流作為契約的語意設計">11.9</a>）。自建 GraphQL 公開 API 時這一整層都要自己蓋 — 這是 REST 世界拿現成 gateway 限流就能用的能力。</p>
<h2 id="introspection型別系統是雙面刃">Introspection：型別系統是雙面刃</h2>
<p>introspection 讓 schema 自我描述、工具鏈（IDE、codegen、文件生成）全建立在它上面 — 同一個能力對攻擊者是免 fuzzing 的偵察工具。<a href="/blog/backend/11-api-design/cases/graphql-introspection-auth-bypass/" data-link-title="11.C25 HackerOne：introspection 列舉出未授權的 CreateAdminUser" data-link-desc="introspection 作為攻擊面偵察工具的實證：schema 自我揭露讓隱藏 mutation 免 fuzzing 直接可見">11.C25</a> 是具體實證：某電商平台的第三方服務暴露 GraphQL 端點、introspection 開啟、研究者列舉 schema 後發現未加驗證的 <code>CreateAdminUser</code> mutation、直接取得管理權限。REST 世界要靠字典檔猜端點、GraphQL 用型別系統直接把地圖交出去。加上授權模型的難度 — 每個 field 都要各自做授權檢查、且授權檢查本身也會 N+1（C22 觀察）— GraphQL 的攻擊面治理是欄位粒度的、middleware 式的單點防護模型在結構上對不上。</p>
<h2 id="persisted-queries介於全開與撤退之間">Persisted queries：介於全開與撤退之間</h2>
<p>執行成本與攻擊面的問題有一條收斂路線：把 named operations 存在 server 端、對外只暴露操作 ID、完全不接受任意 query。<a href="/blog/backend/11-api-design/cases/graphql-wundergraph-not-for-internet/" data-link-title="11.C27 WunderGraph：GraphQL 不該直接暴露在公網" data-link-desc="介於全開與撤退之間的第三條路：GraphQL 當 server-side 查詢語言、對外只開 persisted operations；vendor 立場需標明">11.C27</a> 把這條路線推到論證的極端 —「GraphQL 不該直接暴露在公網、該當 server-side 查詢語言用」；來源是販售此方案的 vendor、立場要標明、但攻擊面描述與 C22、C25 獨立互證。persisted queries 的效果是把 GraphQL 的彈性收回開發期：開發時保有 client 聲明取數的 DX、上線後對外面積等於一組預先審核過的操作 — 成本可預算、introspection 可關閉、任意 query 的攻擊面消失。代價是把「第三方自由組合查詢」這個公開 API 的賣點一起收掉 — 對內部 client 幾乎是純收益、對開放平台則等於換了一種產品。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<p>資料庫讀放大跟 API 請求量的比值持續高於預期、先查 dataloader 覆蓋率而非加 read replica；限流被繞過的事故發生在「配額內的重查詢」、代表還在用 per-request 模型計量；滲透測試報告第一項是 introspection 開啟、關掉它之後要接著問「欄位級授權有沒有做」— introspection 只是地圖、權限缺口才是漏洞本體。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>判準層的流量語意與承諾邊界：<a href="/blog/backend/11-api-design/external-traffic-semantics/" data-link-title="11.9 對外流量語意" data-link-desc="rate limit 對消費者承諾什麼、429 與 Retry-After 怎麼設計、配額 header 該不該信 — 限流作為契約的語意設計">11.9 對外流量語意</a></li>
<li>schema 層的演進紀律：<a href="/blog/backend/11-api-design/styles/graphql/graphql-schema-evolution/" data-link-title="GraphQL Schema 演進：versionless 的紀律代價" data-link-desc="只加不改、deprecation 標注、nullable 預設怎麼共同取代版本號 — 以及每個紀律各自的隱藏帳單">Schema 演進</a></li>
<li>這些成本在組織層的總帳：<a href="/blog/backend/11-api-design/styles/graphql/graphql-public-api-tradeoffs/" data-link-title="公開 API 的 GraphQL 進退" data-link-desc="GitHub 雙軌、Shopify all-in、與撤退案例 — 同一技術不同結局的情境變數、GraphQL 的適用邊界">公開 API 的 GraphQL 進退</a></li>
<li>案例原文：<a href="/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫</a></li>
</ul>
]]></content:encoded></item><item><title>公開 API 的 GraphQL 進退</title><link>https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/graphql-public-api-tradeoffs/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/graphql-public-api-tradeoffs/</guid><description>&lt;p>同一個技術、公開 API 領域至少有四種結局在一手資料裡並存：GitHub 採用後走向雙軌共存、Shopify 宣告 all-in、一類團隊從執行成本撤退、另一類從開發體驗撤退。四種結局沒有對錯排序 — 每一種都對應一組可辨識的情境變數、本文的目標是把變數抽出來。&lt;/p>
&lt;h2 id="採用動機要能量化">採用：動機要能量化&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-github-adoption/" data-link-title="11.C18 GitHub：採用 GraphQL 的可量化動機" data-link-desc="REST 佔資料庫層 60% 請求、over/under-fetching 並存的重構動機；什麼規模的痛才值得換風格的錨點">11.C18&lt;/a> 記錄了 GitHub 2016 年的採用動機、關鍵在它的可量化性：既有 REST API 佔資料庫層超過 60% 的請求、且 over-fetching 與 under-fetching 並存 — 送太多資料、又缺消費者要的資料。這是基礎設施成本層的痛、不只是開發體驗敘事。判讀：GraphQL 的採用決策值得用同樣的標準檢驗 — 說得出「哪個資源層指標會因 client 聲明取數而改善」、動機成立；只說得出「前端想要彈性」、先確認這個彈性有多少會被實際用到（消費者形狀判準、見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/api-style-selection/" data-link-title="11.2 風格選型總覽" data-link-desc="REST 式 HTTP&amp;#43;JSON、GraphQL、gRPC、tRPC、JSON-RPC、event 之間選哪個 — 用消費者形狀、演進成本、操作可及性三軸判讀">11.2&lt;/a>）。&lt;/p>
&lt;h2 id="穩態一雙軌共存">穩態一：雙軌共存&lt;/h2>
&lt;p>GitHub 的十年後狀態記錄在 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-github-rest-parallel/" data-link-title="11.C20 GitHub：REST 與 GraphQL 雙軌並行的十年穩態" data-link-desc="2016 採用者的長期終點是共存而非取代、功能覆蓋不對等被官方明文承認">11.C20&lt;/a>：官方立場是 REST 與 GraphQL 並行、依情境選用、且明文說明功能覆蓋不對等 — 某功能可能只在其中一個 API 支援。這是「新風格取代舊風格」預期的反面實證：兩套 API 各自累積消費者之後、任何一套的退場都是大規模 breaking change（成本結構見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/api-boundary-responsibility/" data-link-title="11.1 API 作為服務邊界的責任" data-link-desc="介面變更該由誰付成本、哪些介面性質算對外承諾、承諾違約有哪些模式 — 動手設計 endpoint 前的責任框架">11.1&lt;/a>）、共存從過渡狀態變成永久狀態。雙軌的隱藏成本是每個新功能的「要不要兩邊都做」決策與文件、SDK、支援的雙倍表面積 — 採用前把這筆帳算進去、雙軌不是免費的中間路線。&lt;/p>
&lt;h2 id="穩態二平台強制的-all-in">穩態二：平台強制的 all-in&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-shopify-all-in/" data-link-title="11.C21 Shopify：宣告 GraphQL 為唯一 API、REST 轉 legacy" data-link-desc="跟共存路線相反的策略極端：用新功能只上 GraphQL 製造遷移壓力、配套降成本加倍配額">11.C21&lt;/a> 記錄了反方向的極端：Shopify 2024 年把 REST Admin API 標為 legacy、新上架 app 強制只用 GraphQL、配套 rate limit 加倍與 connection query 成本降 75%。這條路線的成立條件寫在案例的結構裡 — Shopify 對 app 生態有審核強制力（新 app 不遷就上不了架）、遷移壓力不靠說服。判讀有兩層：對平台方、all-in 的前提是強制力、沒有 app store 式關卡的組織複製這個策略只會得到雙軌的事實與 all-in 的公告；對生態方、成本降 75% 的配套反向印證了執行成本（&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/graphql-execution-cost-security/" data-link-title="GraphQL 執行成本與攻擊面" data-link-desc="resolver 執行模型讓請求成本不再是常數 — N&amp;#43;1 的基礎設施化、成本計點限流、introspection 偵察、persisted queries 的收斂路線">前一篇&lt;/a> 的計點模型）是 GraphQL 採用的隱含稅 — 平台要自己吸收一部分、生態才動得起來。&lt;/p>
&lt;h2 id="撤退兩類動機兩個教訓">撤退：兩類動機、兩個教訓&lt;/h2>
&lt;p>撤退案例分兩類、動機幾乎正交。執行成本類（&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-bessey-retreat/" data-link-title="11.C22 Matt Bessey：六年 GraphQL 老手的撤退清單（反例）" data-link-desc="反例：授權下推到 field、成本不可預測、解析層攻擊面的執行期代價清單、附撤退判準">11.C22&lt;/a>、反例）：六年使用者列出的代價全在執行期與安全面 — 欄位級授權、成本不可預測、解析層攻擊面、防禦性 dataloader；撤退判準句是「控制得了 client、就不需要 GraphQL 的彈性」（C22 判讀核心句）。開發體驗類（&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-echobind-trpc-retreat/" data-link-title="11.C23 Echobind：從 GraphQL 撤到 tRPC 的量化帳（反例）" data-link-desc="反例：五層重複宣告與三層 codegen 拖垮 DX 的量化紀錄、同時自列 tRPC 的適用前提">11.C23&lt;/a>、反例）：同一資料形狀在五層重複宣告、三層 codegen 產出 8,200 行型別檔拖垮 IDE、遷移到 tRPC 後淨減 1,608 行；作者自列的前提是全 TypeScript 同倉 — schema 作為跨團隊契約的價值、在單團隊同構技術棧下變成純開銷。&lt;/p>
&lt;p>兩類撤退指向同一條邊界的兩側：GraphQL 的 schema 中介層、價值在「跨團隊 / 跨 client 的契約協調」— 消費者異質且不受控、彈性有買家、中介層成本值得；消費者單一且同構、彈性沒有買家、中介層是稅。C23 的 tRPC 面向（型別共享的前提與代價）主寫在 rpc-revival 流派層（backlog）。&lt;/p>
&lt;h2 id="中間路線與適用邊界">中間路線與適用邊界&lt;/h2>
&lt;p>全開與撤退之間有 persisted queries 的收斂路線（案例 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-wundergraph-not-for-internet/" data-link-title="11.C27 WunderGraph：GraphQL 不該直接暴露在公網" data-link-desc="介於全開與撤退之間的第三條路：GraphQL 當 server-side 查詢語言、對外只開 persisted operations；vendor 立場需標明">11.C27&lt;/a>；機制與 vendor 立場標注見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/graphql-execution-cost-security/" data-link-title="GraphQL 執行成本與攻擊面" data-link-desc="resolver 執行模型讓請求成本不再是常數 — N&amp;#43;1 的基礎設施化、成本計點限流、introspection 偵察、persisted queries 的收斂路線">執行成本篇&lt;/a> 的 persisted queries 段）：內部保留 GraphQL 的開發彈性、對外只暴露預審操作。把四種結局加中間路線並排、適用邊界收斂成三個問句：消費者是誰、有多異質（單團隊同構 → 撤退案例的前車）；對生態有沒有強制力（沒有 → 雙軌是實際終點、all-in 只是公告）；執行層的計點限流、欄位授權、dataloader 誰來蓋（沒人蓋 → 攻擊面與容量問題按 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/graphql-execution-cost-security/" data-link-title="GraphQL 執行成本與攻擊面" data-link-desc="resolver 執行模型讓請求成本不再是常數 — N&amp;#43;1 的基礎設施化、成本計點限流、introspection 偵察、persisted queries 的收斂路線">執行成本篇&lt;/a> 的清單逐項到期）。&lt;/p></description><content:encoded><![CDATA[<p>同一個技術、公開 API 領域至少有四種結局在一手資料裡並存：GitHub 採用後走向雙軌共存、Shopify 宣告 all-in、一類團隊從執行成本撤退、另一類從開發體驗撤退。四種結局沒有對錯排序 — 每一種都對應一組可辨識的情境變數、本文的目標是把變數抽出來。</p>
<h2 id="採用動機要能量化">採用：動機要能量化</h2>
<p><a href="/blog/backend/11-api-design/cases/graphql-github-adoption/" data-link-title="11.C18 GitHub：採用 GraphQL 的可量化動機" data-link-desc="REST 佔資料庫層 60% 請求、over/under-fetching 並存的重構動機；什麼規模的痛才值得換風格的錨點">11.C18</a> 記錄了 GitHub 2016 年的採用動機、關鍵在它的可量化性：既有 REST API 佔資料庫層超過 60% 的請求、且 over-fetching 與 under-fetching 並存 — 送太多資料、又缺消費者要的資料。這是基礎設施成本層的痛、不只是開發體驗敘事。判讀：GraphQL 的採用決策值得用同樣的標準檢驗 — 說得出「哪個資源層指標會因 client 聲明取數而改善」、動機成立；只說得出「前端想要彈性」、先確認這個彈性有多少會被實際用到（消費者形狀判準、見 <a href="/blog/backend/11-api-design/api-style-selection/" data-link-title="11.2 風格選型總覽" data-link-desc="REST 式 HTTP&#43;JSON、GraphQL、gRPC、tRPC、JSON-RPC、event 之間選哪個 — 用消費者形狀、演進成本、操作可及性三軸判讀">11.2</a>）。</p>
<h2 id="穩態一雙軌共存">穩態一：雙軌共存</h2>
<p>GitHub 的十年後狀態記錄在 <a href="/blog/backend/11-api-design/cases/graphql-github-rest-parallel/" data-link-title="11.C20 GitHub：REST 與 GraphQL 雙軌並行的十年穩態" data-link-desc="2016 採用者的長期終點是共存而非取代、功能覆蓋不對等被官方明文承認">11.C20</a>：官方立場是 REST 與 GraphQL 並行、依情境選用、且明文說明功能覆蓋不對等 — 某功能可能只在其中一個 API 支援。這是「新風格取代舊風格」預期的反面實證：兩套 API 各自累積消費者之後、任何一套的退場都是大規模 breaking change（成本結構見 <a href="/blog/backend/11-api-design/api-boundary-responsibility/" data-link-title="11.1 API 作為服務邊界的責任" data-link-desc="介面變更該由誰付成本、哪些介面性質算對外承諾、承諾違約有哪些模式 — 動手設計 endpoint 前的責任框架">11.1</a>）、共存從過渡狀態變成永久狀態。雙軌的隱藏成本是每個新功能的「要不要兩邊都做」決策與文件、SDK、支援的雙倍表面積 — 採用前把這筆帳算進去、雙軌不是免費的中間路線。</p>
<h2 id="穩態二平台強制的-all-in">穩態二：平台強制的 all-in</h2>
<p><a href="/blog/backend/11-api-design/cases/graphql-shopify-all-in/" data-link-title="11.C21 Shopify：宣告 GraphQL 為唯一 API、REST 轉 legacy" data-link-desc="跟共存路線相反的策略極端：用新功能只上 GraphQL 製造遷移壓力、配套降成本加倍配額">11.C21</a> 記錄了反方向的極端：Shopify 2024 年把 REST Admin API 標為 legacy、新上架 app 強制只用 GraphQL、配套 rate limit 加倍與 connection query 成本降 75%。這條路線的成立條件寫在案例的結構裡 — Shopify 對 app 生態有審核強制力（新 app 不遷就上不了架）、遷移壓力不靠說服。判讀有兩層：對平台方、all-in 的前提是強制力、沒有 app store 式關卡的組織複製這個策略只會得到雙軌的事實與 all-in 的公告；對生態方、成本降 75% 的配套反向印證了執行成本（<a href="/blog/backend/11-api-design/styles/graphql/graphql-execution-cost-security/" data-link-title="GraphQL 執行成本與攻擊面" data-link-desc="resolver 執行模型讓請求成本不再是常數 — N&#43;1 的基礎設施化、成本計點限流、introspection 偵察、persisted queries 的收斂路線">前一篇</a> 的計點模型）是 GraphQL 採用的隱含稅 — 平台要自己吸收一部分、生態才動得起來。</p>
<h2 id="撤退兩類動機兩個教訓">撤退：兩類動機、兩個教訓</h2>
<p>撤退案例分兩類、動機幾乎正交。執行成本類（<a href="/blog/backend/11-api-design/cases/graphql-bessey-retreat/" data-link-title="11.C22 Matt Bessey：六年 GraphQL 老手的撤退清單（反例）" data-link-desc="反例：授權下推到 field、成本不可預測、解析層攻擊面的執行期代價清單、附撤退判準">11.C22</a>、反例）：六年使用者列出的代價全在執行期與安全面 — 欄位級授權、成本不可預測、解析層攻擊面、防禦性 dataloader；撤退判準句是「控制得了 client、就不需要 GraphQL 的彈性」（C22 判讀核心句）。開發體驗類（<a href="/blog/backend/11-api-design/cases/graphql-echobind-trpc-retreat/" data-link-title="11.C23 Echobind：從 GraphQL 撤到 tRPC 的量化帳（反例）" data-link-desc="反例：五層重複宣告與三層 codegen 拖垮 DX 的量化紀錄、同時自列 tRPC 的適用前提">11.C23</a>、反例）：同一資料形狀在五層重複宣告、三層 codegen 產出 8,200 行型別檔拖垮 IDE、遷移到 tRPC 後淨減 1,608 行；作者自列的前提是全 TypeScript 同倉 — schema 作為跨團隊契約的價值、在單團隊同構技術棧下變成純開銷。</p>
<p>兩類撤退指向同一條邊界的兩側：GraphQL 的 schema 中介層、價值在「跨團隊 / 跨 client 的契約協調」— 消費者異質且不受控、彈性有買家、中介層成本值得；消費者單一且同構、彈性沒有買家、中介層是稅。C23 的 tRPC 面向（型別共享的前提與代價）主寫在 rpc-revival 流派層（backlog）。</p>
<h2 id="中間路線與適用邊界">中間路線與適用邊界</h2>
<p>全開與撤退之間有 persisted queries 的收斂路線（案例 <a href="/blog/backend/11-api-design/cases/graphql-wundergraph-not-for-internet/" data-link-title="11.C27 WunderGraph：GraphQL 不該直接暴露在公網" data-link-desc="介於全開與撤退之間的第三條路：GraphQL 當 server-side 查詢語言、對外只開 persisted operations；vendor 立場需標明">11.C27</a>；機制與 vendor 立場標注見 <a href="/blog/backend/11-api-design/styles/graphql/graphql-execution-cost-security/" data-link-title="GraphQL 執行成本與攻擊面" data-link-desc="resolver 執行模型讓請求成本不再是常數 — N&#43;1 的基礎設施化、成本計點限流、introspection 偵察、persisted queries 的收斂路線">執行成本篇</a> 的 persisted queries 段）：內部保留 GraphQL 的開發彈性、對外只暴露預審操作。把四種結局加中間路線並排、適用邊界收斂成三個問句：消費者是誰、有多異質（單團隊同構 → 撤退案例的前車）；對生態有沒有強制力（沒有 → 雙軌是實際終點、all-in 只是公告）；執行層的計點限流、欄位授權、dataloader 誰來蓋（沒人蓋 → 攻擊面與容量問題按 <a href="/blog/backend/11-api-design/styles/graphql/graphql-execution-cost-security/" data-link-title="GraphQL 執行成本與攻擊面" data-link-desc="resolver 執行模型讓請求成本不再是常數 — N&#43;1 的基礎設施化、成本計點限流、introspection 偵察、persisted queries 的收斂路線">執行成本篇</a> 的清單逐項到期）。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>選型判準層：<a href="/blog/backend/11-api-design/api-style-selection/" data-link-title="11.2 風格選型總覽" data-link-desc="REST 式 HTTP&#43;JSON、GraphQL、gRPC、tRPC、JSON-RPC、event 之間選哪個 — 用消費者形狀、演進成本、操作可及性三軸判讀">11.2 風格選型總覽</a></li>
<li>執行層機制：<a href="/blog/backend/11-api-design/styles/graphql/graphql-execution-cost-security/" data-link-title="GraphQL 執行成本與攻擊面" data-link-desc="resolver 執行模型讓請求成本不再是常數 — N&#43;1 的基礎設施化、成本計點限流、introspection 偵察、persisted queries 的收斂路線">GraphQL 執行成本與攻擊面</a></li>
<li>schema 層紀律：<a href="/blog/backend/11-api-design/styles/graphql/graphql-schema-evolution/" data-link-title="GraphQL Schema 演進：versionless 的紀律代價" data-link-desc="只加不改、deprecation 標注、nullable 預設怎麼共同取代版本號 — 以及每個紀律各自的隱藏帳單">Schema 演進</a></li>
<li>案例原文：<a href="/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫</a></li>
</ul>
]]></content:encoded></item><item><title>11.C18 GitHub：採用 GraphQL 的可量化動機</title><link>https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-github-adoption/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-github-adoption/</guid><description>&lt;p>這個案例的核心責任是記錄大平台採用 GraphQL 的可量化動機。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>GitHub 2016 年公開 GraphQL API 時明言：既有 REST API 負責超過 60% 的資料庫層請求、且同時「送太多資料、又缺消費者需要的資料」（over-fetching 與 under-fetching 並存）。技術棧為 graphql-ruby 加 Shopify 的 graphql-batch、公告前已在 production 運行、前後端用 Relay 協作。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>採用動機是可量化的基礎設施成本（DB 層負載）、不只是 DX 敘事 — 這讓它成為「什麼規模的 over-fetching 痛才值得換風格」的錨點案例。graphql-batch 出現在 day-one 技術棧、顯示 N+1 是第一天就要面對的問題、不是後期優化。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/graphql-public-api-tradeoffs/" data-link-title="公開 API 的 GraphQL 進退" data-link-desc="GitHub 雙軌、Shopify all-in、與撤退案例 — 同一技術不同結局的情境變數、GraphQL 的適用邊界">公開 API 的 GraphQL 進退&lt;/a>（anchor、已引用）、11.2 風格選型交叉。GitHub cluster 之一（C18-C20）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://github.blog/engineering/the-github-graphql-api/">The GitHub GraphQL API（GitHub engineering blog、2016）&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是記錄大平台採用 GraphQL 的可量化動機。</p>
<h2 id="觀察">觀察</h2>
<p>GitHub 2016 年公開 GraphQL API 時明言：既有 REST API 負責超過 60% 的資料庫層請求、且同時「送太多資料、又缺消費者需要的資料」（over-fetching 與 under-fetching 並存）。技術棧為 graphql-ruby 加 Shopify 的 graphql-batch、公告前已在 production 運行、前後端用 Relay 協作。</p>
<h2 id="判讀">判讀</h2>
<p>採用動機是可量化的基礎設施成本（DB 層負載）、不只是 DX 敘事 — 這讓它成為「什麼規模的 over-fetching 痛才值得換風格」的錨點案例。graphql-batch 出現在 day-one 技術棧、顯示 N+1 是第一天就要面對的問題、不是後期優化。</p>
<h2 id="對應大綱">對應大綱</h2>
<p><a href="/blog/backend/11-api-design/styles/graphql/graphql-public-api-tradeoffs/" data-link-title="公開 API 的 GraphQL 進退" data-link-desc="GitHub 雙軌、Shopify all-in、與撤退案例 — 同一技術不同結局的情境變數、GraphQL 的適用邊界">公開 API 的 GraphQL 進退</a>（anchor、已引用）、11.2 風格選型交叉。GitHub cluster 之一（C18-C20）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://github.blog/engineering/the-github-graphql-api/">The GitHub GraphQL API（GitHub engineering blog、2016）</a></li>
</ul>
]]></content:encoded></item><item><title>11.C19 GitHub：GraphQL point system 成本計點限流</title><link>https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-github-cost-rate-limiting/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-github-cost-rate-limiting/</guid><description>&lt;p>這個案例的核心責任是說明 GraphQL 對傳統限流模型的衝擊與平台的應對設計。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>GitHub GraphQL API 依 connection 展開的請求數對每個 query 計 point（總請求數除以 100、最低 1 點）、user 每小時 5,000 點、Enterprise 10,000 點；另有 500,000 node 上限與 &lt;code>first&lt;/code> / &lt;code>last&lt;/code> 參數 1-100 的限制；client 可事後查 &lt;code>rateLimit.cost&lt;/code>、也可事前預估。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>一個 request 的成本在 GraphQL 下不再是常數 — per-request rate limiting 的假設被打破、平台被迫發明成本模型。node 上限與分頁參數上限是防執行爆炸的靜態防線、動靜兩層並存本身就是教學重點。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/graphql-execution-cost-security/" data-link-title="GraphQL 執行成本與攻擊面" data-link-desc="resolver 執行模型讓請求成本不再是常數 — N&amp;#43;1 的基礎設施化、成本計點限流、introspection 偵察、persisted queries 的收斂路線">執行成本與攻擊面&lt;/a>（機制主寫、已引用）、&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/external-traffic-semantics/" data-link-title="11.9 對外流量語意" data-link-desc="rate limit 對消費者承諾什麼、429 與 Retry-After 怎麼設計、配額 header 該不該信 — 限流作為契約的語意設計">11.9 對外流量語意&lt;/a>（現象層、已引用）。GitHub cluster 之一。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://docs.github.com/en/graphql/overview/rate-limits-and-node-limits-for-the-graphql-api">Rate limits and node limits for the GraphQL API（GitHub docs）&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 GraphQL 對傳統限流模型的衝擊與平台的應對設計。</p>
<h2 id="觀察">觀察</h2>
<p>GitHub GraphQL API 依 connection 展開的請求數對每個 query 計 point（總請求數除以 100、最低 1 點）、user 每小時 5,000 點、Enterprise 10,000 點；另有 500,000 node 上限與 <code>first</code> / <code>last</code> 參數 1-100 的限制；client 可事後查 <code>rateLimit.cost</code>、也可事前預估。</p>
<h2 id="判讀">判讀</h2>
<p>一個 request 的成本在 GraphQL 下不再是常數 — per-request rate limiting 的假設被打破、平台被迫發明成本模型。node 上限與分頁參數上限是防執行爆炸的靜態防線、動靜兩層並存本身就是教學重點。</p>
<h2 id="對應大綱">對應大綱</h2>
<p><a href="/blog/backend/11-api-design/styles/graphql/graphql-execution-cost-security/" data-link-title="GraphQL 執行成本與攻擊面" data-link-desc="resolver 執行模型讓請求成本不再是常數 — N&#43;1 的基礎設施化、成本計點限流、introspection 偵察、persisted queries 的收斂路線">執行成本與攻擊面</a>（機制主寫、已引用）、<a href="/blog/backend/11-api-design/external-traffic-semantics/" data-link-title="11.9 對外流量語意" data-link-desc="rate limit 對消費者承諾什麼、429 與 Retry-After 怎麼設計、配額 header 該不該信 — 限流作為契約的語意設計">11.9 對外流量語意</a>（現象層、已引用）。GitHub cluster 之一。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://docs.github.com/en/graphql/overview/rate-limits-and-node-limits-for-the-graphql-api">Rate limits and node limits for the GraphQL API（GitHub docs）</a></li>
</ul>
]]></content:encoded></item><item><title>11.C20 GitHub：REST 與 GraphQL 雙軌並行的十年穩態</title><link>https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-github-rest-parallel/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-github-rest-parallel/</guid><description>&lt;p>這個案例的核心責任是記錄大平台採用 GraphQL 後的長期穩態。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>GitHub 官方文件明言「不需要獨佔使用其中一個 API」；GraphQL 建議用於減少請求數與精準取數（mobile、巢狀關聯）、REST 建議給熟悉傳統 HTTP 慣例者；並承認「某功能可能只在其中一個 API 支援」。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>2016 年的採用者（C18）在多年後的穩態是雙軌並行、功能覆蓋不對等 — 這是「大平台採用 GraphQL 的長期終點是共存」的最直接證據、支撐「進退」章的結論框架。跟 Shopify 的 all-in 策略（C21）形成兩個極端。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/graphql-public-api-tradeoffs/" data-link-title="公開 API 的 GraphQL 進退" data-link-desc="GitHub 雙軌、Shopify all-in、與撤退案例 — 同一技術不同結局的情境變數、GraphQL 的適用邊界">公開 API 的 GraphQL 進退&lt;/a>（anchor、已引用）、11.2 風格選型（共存段、已引用）。GitHub cluster 之一。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://docs.github.com/en/rest/about-the-rest-api/comparing-githubs-rest-api-and-graphql-api">Comparing GitHub&amp;rsquo;s REST API and GraphQL API（GitHub docs）&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是記錄大平台採用 GraphQL 後的長期穩態。</p>
<h2 id="觀察">觀察</h2>
<p>GitHub 官方文件明言「不需要獨佔使用其中一個 API」；GraphQL 建議用於減少請求數與精準取數（mobile、巢狀關聯）、REST 建議給熟悉傳統 HTTP 慣例者；並承認「某功能可能只在其中一個 API 支援」。</p>
<h2 id="判讀">判讀</h2>
<p>2016 年的採用者（C18）在多年後的穩態是雙軌並行、功能覆蓋不對等 — 這是「大平台採用 GraphQL 的長期終點是共存」的最直接證據、支撐「進退」章的結論框架。跟 Shopify 的 all-in 策略（C21）形成兩個極端。</p>
<h2 id="對應大綱">對應大綱</h2>
<p><a href="/blog/backend/11-api-design/styles/graphql/graphql-public-api-tradeoffs/" data-link-title="公開 API 的 GraphQL 進退" data-link-desc="GitHub 雙軌、Shopify all-in、與撤退案例 — 同一技術不同結局的情境變數、GraphQL 的適用邊界">公開 API 的 GraphQL 進退</a>（anchor、已引用）、11.2 風格選型（共存段、已引用）。GitHub cluster 之一。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://docs.github.com/en/rest/about-the-rest-api/comparing-githubs-rest-api-and-graphql-api">Comparing GitHub&rsquo;s REST API and GraphQL API（GitHub docs）</a></li>
</ul>
]]></content:encoded></item><item><title>11.C21 Shopify：宣告 GraphQL 為唯一 API、REST 轉 legacy</title><link>https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-shopify-all-in/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-shopify-all-in/</guid><description>&lt;p>這個案例的核心責任是記錄 GraphQL 採用光譜的另一個極端：平台強制遷移。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Shopify 2024-10 宣告 REST Admin API 標為 legacy、不再開發新功能；2025-04-01 起新上架 app 必須只用 GraphQL；配套措施包含 rate limit 加倍、connection query 成本降 75%；部分新能力（2,000 product variants、Metaobjects）只在 GraphQL 提供。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>跟 GitHub 的共存路線（C20）相反 — 用「新功能只上 GraphQL」製造遷移壓力。判準案例：「平台對生態系有強制力時才可能 all-in」。成本降 75% 的配套也反向印證 cost-based limiting 是 GraphQL 採用的隱含稅。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/graphql-public-api-tradeoffs/" data-link-title="公開 API 的 GraphQL 進退" data-link-desc="GitHub 雙軌、Shopify all-in、與撤退案例 — 同一技術不同結局的情境變數、GraphQL 的適用邊界">公開 API 的 GraphQL 進退&lt;/a>（anchor、已引用）、11.2 風格選型（共存段、已引用）。Shopify cluster（與 C24 graphql-batch 相關）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.shopify.com/partners/blog/all-in-on-graphql">All in on GraphQL（Shopify partners blog、2024）&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是記錄 GraphQL 採用光譜的另一個極端：平台強制遷移。</p>
<h2 id="觀察">觀察</h2>
<p>Shopify 2024-10 宣告 REST Admin API 標為 legacy、不再開發新功能；2025-04-01 起新上架 app 必須只用 GraphQL；配套措施包含 rate limit 加倍、connection query 成本降 75%；部分新能力（2,000 product variants、Metaobjects）只在 GraphQL 提供。</p>
<h2 id="判讀">判讀</h2>
<p>跟 GitHub 的共存路線（C20）相反 — 用「新功能只上 GraphQL」製造遷移壓力。判準案例：「平台對生態系有強制力時才可能 all-in」。成本降 75% 的配套也反向印證 cost-based limiting 是 GraphQL 採用的隱含稅。</p>
<h2 id="對應大綱">對應大綱</h2>
<p><a href="/blog/backend/11-api-design/styles/graphql/graphql-public-api-tradeoffs/" data-link-title="公開 API 的 GraphQL 進退" data-link-desc="GitHub 雙軌、Shopify all-in、與撤退案例 — 同一技術不同結局的情境變數、GraphQL 的適用邊界">公開 API 的 GraphQL 進退</a>（anchor、已引用）、11.2 風格選型（共存段、已引用）。Shopify cluster（與 C24 graphql-batch 相關）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.shopify.com/partners/blog/all-in-on-graphql">All in on GraphQL（Shopify partners blog、2024）</a></li>
</ul>
]]></content:encoded></item><item><title>11.C22 Matt Bessey：六年 GraphQL 老手的撤退清單（反例）</title><link>https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-bessey-retreat/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-bessey-retreat/</guid><description>&lt;p>這個案例的核心責任是提供 GraphQL 撤退論證中最完整的執行期代價清單。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>作者（六年 GraphQL 使用經驗）列舉：每個 field 都要各自做授權檢查；128 bytes 的惡意查詢可耗 10 秒 CPU；畸形 directives 造成 2,000 倍記憶體放大；N+1 迫使處處防禦性套 Dataloader、且授權檢查本身也會 N+1。建議控制得了 client 的團隊改用 OpenAPI 3 REST（FastAPI / tsoa / TypeSpec）。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>代價清單全部落在執行期與安全面 — 跟採用文宣的 DX 敘事正交、逐條對應「執行成本與安全」章的大綱（授權下推到 field、成本不可預測、解析層攻擊面）。撤退判準的核心句：「控制得了 client、就不需要 GraphQL 的彈性」。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/graphql-execution-cost-security/" data-link-title="GraphQL 執行成本與攻擊面" data-link-desc="resolver 執行模型讓請求成本不再是常數 — N&amp;#43;1 的基礎設施化、成本計點限流、introspection 偵察、persisted queries 的收斂路線">執行成本與攻擊面&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/graphql-public-api-tradeoffs/" data-link-title="公開 API 的 GraphQL 進退" data-link-desc="GitHub 雙軌、Shopify all-in、與撤退案例 — 同一技術不同結局的情境變數、GraphQL 的適用邊界">公開 API 的 GraphQL 進退&lt;/a>（皆已引用）、11.2 風格選型（消費者形狀軸、已引用）。反例。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://bessey.dev/blog/2024/05/24/why-im-over-graphql/">Why, after 6 years, I&amp;rsquo;m over GraphQL（Matt Bessey、2024）&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是提供 GraphQL 撤退論證中最完整的執行期代價清單。</p>
<h2 id="觀察">觀察</h2>
<p>作者（六年 GraphQL 使用經驗）列舉：每個 field 都要各自做授權檢查；128 bytes 的惡意查詢可耗 10 秒 CPU；畸形 directives 造成 2,000 倍記憶體放大；N+1 迫使處處防禦性套 Dataloader、且授權檢查本身也會 N+1。建議控制得了 client 的團隊改用 OpenAPI 3 REST（FastAPI / tsoa / TypeSpec）。</p>
<h2 id="判讀">判讀</h2>
<p>代價清單全部落在執行期與安全面 — 跟採用文宣的 DX 敘事正交、逐條對應「執行成本與安全」章的大綱（授權下推到 field、成本不可預測、解析層攻擊面）。撤退判準的核心句：「控制得了 client、就不需要 GraphQL 的彈性」。</p>
<h2 id="對應大綱">對應大綱</h2>
<p><a href="/blog/backend/11-api-design/styles/graphql/graphql-execution-cost-security/" data-link-title="GraphQL 執行成本與攻擊面" data-link-desc="resolver 執行模型讓請求成本不再是常數 — N&#43;1 的基礎設施化、成本計點限流、introspection 偵察、persisted queries 的收斂路線">執行成本與攻擊面</a> 與 <a href="/blog/backend/11-api-design/styles/graphql/graphql-public-api-tradeoffs/" data-link-title="公開 API 的 GraphQL 進退" data-link-desc="GitHub 雙軌、Shopify all-in、與撤退案例 — 同一技術不同結局的情境變數、GraphQL 的適用邊界">公開 API 的 GraphQL 進退</a>（皆已引用）、11.2 風格選型（消費者形狀軸、已引用）。反例。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://bessey.dev/blog/2024/05/24/why-im-over-graphql/">Why, after 6 years, I&rsquo;m over GraphQL（Matt Bessey、2024）</a></li>
</ul>
]]></content:encoded></item><item><title>11.C23 Echobind：從 GraphQL 撤到 tRPC 的量化帳（反例）</title><link>https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-echobind-trpc-retreat/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-echobind-trpc-retreat/</guid><description>&lt;p>這個案例的核心責任是劃出 GraphQL 與 tRPC 各自的適用邊界、單一 TypeScript 團隊場景的量化對照。跨主題案例：GraphQL 撤退面與 tRPC 採用面共用本檔。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>痛點是 double declaration：同一資料形狀要在 Prisma / Nexus / GraphQL operations / codegen types / client queries 五層宣告；三層 codegen 產出 8,200 行型別檔、常需重啟 VSCode language server；GraphQL 依賴 81.2kb、tRPC 23.7kb；Apollo normalized cache 在 mutation 後常態性要手動 &lt;code>refetchQueries&lt;/code>。遷移後淨減 1,608 行。文章同時明列 tRPC 前提：「server 用 TypeScript 且與 client 共置」、無法有效服務公開第三方 API。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>撤退動因是「單一團隊同時擁有前後端」時、GraphQL 的 schema 中介層變成純開銷 — schema 作為跨團隊 / 跨 client 契約才有價值、同構 TypeScript 單團隊是反指標。作者自列的 tRPC 邊界（公開 API 不適用）可直接引用、避免被讀成萬用推薦。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/graphql-public-api-tradeoffs/" data-link-title="公開 API 的 GraphQL 進退" data-link-desc="GitHub 雙軌、Shopify all-in、與撤退案例 — 同一技術不同結局的情境變數、GraphQL 的適用邊界">公開 API 的 GraphQL 進退&lt;/a>（適用邊界段、反例、已引用）、styles/rpc-revival/「tRPC 與型別共享」（anchor、backlog）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://echobind.com/post/why-we-ditched-graphql-for-trpc">Why we ditched GraphQL for tRPC（Echobind blog、2022）&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是劃出 GraphQL 與 tRPC 各自的適用邊界、單一 TypeScript 團隊場景的量化對照。跨主題案例：GraphQL 撤退面與 tRPC 採用面共用本檔。</p>
<h2 id="觀察">觀察</h2>
<p>痛點是 double declaration：同一資料形狀要在 Prisma / Nexus / GraphQL operations / codegen types / client queries 五層宣告；三層 codegen 產出 8,200 行型別檔、常需重啟 VSCode language server；GraphQL 依賴 81.2kb、tRPC 23.7kb；Apollo normalized cache 在 mutation 後常態性要手動 <code>refetchQueries</code>。遷移後淨減 1,608 行。文章同時明列 tRPC 前提：「server 用 TypeScript 且與 client 共置」、無法有效服務公開第三方 API。</p>
<h2 id="判讀">判讀</h2>
<p>撤退動因是「單一團隊同時擁有前後端」時、GraphQL 的 schema 中介層變成純開銷 — schema 作為跨團隊 / 跨 client 契約才有價值、同構 TypeScript 單團隊是反指標。作者自列的 tRPC 邊界（公開 API 不適用）可直接引用、避免被讀成萬用推薦。</p>
<h2 id="對應大綱">對應大綱</h2>
<p><a href="/blog/backend/11-api-design/styles/graphql/graphql-public-api-tradeoffs/" data-link-title="公開 API 的 GraphQL 進退" data-link-desc="GitHub 雙軌、Shopify all-in、與撤退案例 — 同一技術不同結局的情境變數、GraphQL 的適用邊界">公開 API 的 GraphQL 進退</a>（適用邊界段、反例、已引用）、styles/rpc-revival/「tRPC 與型別共享」（anchor、backlog）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://echobind.com/post/why-we-ditched-graphql-for-trpc">Why we ditched GraphQL for tRPC（Echobind blog、2022）</a></li>
</ul>
]]></content:encoded></item><item><title>11.C24 DataLoader 譜系：N+1 的官方解法變成基礎設施</title><link>https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-dataloader-n-plus-one/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-dataloader-n-plus-one/</guid><description>&lt;p>這個案例的核心責任是說明 N+1 在 GraphQL 執行模型下的地位轉變與解法譜系。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>DataLoader 把單一執行 frame 內的個別 load 合併成 batch、加 per-request 快取；概念源自 Facebook 2010 年的內部 Loader API、早於 GraphQL 開源、跨語言存在（如 Haskell 的 Haxl）。Shopify 維護的 Ruby 版 graphql-batch（loader pattern + promise）被 GitHub 2016 年採用時直接引入、至 2025-09 仍在發版。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>N+1 不是 GraphQL 發明的問題、但 resolver-per-field 執行模型讓它從偶發變成預設 — 所以官方生態把 batching 做成基礎設施、不是優化技巧。「連 GitHub day-one 都要帶 graphql-batch」是最直接的教學證據。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/graphql-execution-cost-security/" data-link-title="GraphQL 執行成本與攻擊面" data-link-desc="resolver 執行模型讓請求成本不再是常數 — N&amp;#43;1 的基礎設施化、成本計點限流、introspection 偵察、persisted queries 的收斂路線">執行成本與攻擊面&lt;/a>（N+1 段、anchor、已引用）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://github.com/graphql/dataloader">graphql/dataloader（GraphQL 基金會、GitHub repo）&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/Shopify/graphql-batch">Shopify/graphql-batch（GitHub repo）&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 N+1 在 GraphQL 執行模型下的地位轉變與解法譜系。</p>
<h2 id="觀察">觀察</h2>
<p>DataLoader 把單一執行 frame 內的個別 load 合併成 batch、加 per-request 快取；概念源自 Facebook 2010 年的內部 Loader API、早於 GraphQL 開源、跨語言存在（如 Haskell 的 Haxl）。Shopify 維護的 Ruby 版 graphql-batch（loader pattern + promise）被 GitHub 2016 年採用時直接引入、至 2025-09 仍在發版。</p>
<h2 id="判讀">判讀</h2>
<p>N+1 不是 GraphQL 發明的問題、但 resolver-per-field 執行模型讓它從偶發變成預設 — 所以官方生態把 batching 做成基礎設施、不是優化技巧。「連 GitHub day-one 都要帶 graphql-batch」是最直接的教學證據。</p>
<h2 id="對應大綱">對應大綱</h2>
<p><a href="/blog/backend/11-api-design/styles/graphql/graphql-execution-cost-security/" data-link-title="GraphQL 執行成本與攻擊面" data-link-desc="resolver 執行模型讓請求成本不再是常數 — N&#43;1 的基礎設施化、成本計點限流、introspection 偵察、persisted queries 的收斂路線">執行成本與攻擊面</a>（N+1 段、anchor、已引用）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://github.com/graphql/dataloader">graphql/dataloader（GraphQL 基金會、GitHub repo）</a></li>
<li><a href="https://github.com/Shopify/graphql-batch">Shopify/graphql-batch（GitHub repo）</a></li>
</ul>
]]></content:encoded></item><item><title>11.C25 HackerOne：introspection 列舉出未授權的 CreateAdminUser</title><link>https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-introspection-auth-bypass/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-introspection-auth-bypass/</guid><description>&lt;p>這個案例的核心責任是提供「introspection 等於攻擊面偵察工具」的具體實證。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>某電商平台的第三方 banner 服務暴露 GraphQL 端點、introspection 開啟；研究者（J. Francisco Bolivar、2023 HackerOne Ambassador World Cup 期間回報）用 introspection 列舉 schema、發現未加驗證的 &lt;code>CreateAdminUser&lt;/code> mutation、直接取得管理權限、數日內修復。報告結論建議：production 關 introspection、field-level authorization、移除不必要的 mutation。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>REST 世界要靠 fuzzing 才找得到的隱藏端點、GraphQL 用型別系統自己告訴你。教學上與 C22 的 CPU / 記憶體放大並列成兩類攻擊面：資訊暴露與資源耗盡。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/graphql-execution-cost-security/" data-link-title="GraphQL 執行成本與攻擊面" data-link-desc="resolver 執行模型讓請求成本不再是常數 — N&amp;#43;1 的基礎設施化、成本計點限流、introspection 偵察、persisted queries 的收斂路線">執行成本與攻擊面&lt;/a>（introspection 段、已引用）。邊緣（安全單點案例）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.hackerone.com/blog/how-graphql-bug-resulted-authentication-bypass">How a GraphQL Bug Resulted in Authentication Bypass（HackerOne blog）&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是提供「introspection 等於攻擊面偵察工具」的具體實證。</p>
<h2 id="觀察">觀察</h2>
<p>某電商平台的第三方 banner 服務暴露 GraphQL 端點、introspection 開啟；研究者（J. Francisco Bolivar、2023 HackerOne Ambassador World Cup 期間回報）用 introspection 列舉 schema、發現未加驗證的 <code>CreateAdminUser</code> mutation、直接取得管理權限、數日內修復。報告結論建議：production 關 introspection、field-level authorization、移除不必要的 mutation。</p>
<h2 id="判讀">判讀</h2>
<p>REST 世界要靠 fuzzing 才找得到的隱藏端點、GraphQL 用型別系統自己告訴你。教學上與 C22 的 CPU / 記憶體放大並列成兩類攻擊面：資訊暴露與資源耗盡。</p>
<h2 id="對應大綱">對應大綱</h2>
<p><a href="/blog/backend/11-api-design/styles/graphql/graphql-execution-cost-security/" data-link-title="GraphQL 執行成本與攻擊面" data-link-desc="resolver 執行模型讓請求成本不再是常數 — N&#43;1 的基礎設施化、成本計點限流、introspection 偵察、persisted queries 的收斂路線">執行成本與攻擊面</a>（introspection 段、已引用）。邊緣（安全單點案例）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.hackerone.com/blog/how-graphql-bug-resulted-authentication-bypass">How a GraphQL Bug Resulted in Authentication Bypass（HackerOne blog）</a></li>
</ul>
]]></content:encoded></item><item><title>11.C26 GraphQL 官方：versionless API 與 nullable-by-default</title><link>https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-versionless-evolution/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-versionless-evolution/</guid><description>&lt;p>這個案例的核心責任是記錄 GraphQL 官方的 versionless 設計哲學與其依賴的紀律。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>官方教學明文：GraphQL 只回傳明確請求的資料、所以新能力可以透過新 type 或既有 type 的新 field 加入而不造成 breaking change；「永遠避免 breaking change、提供 versionless API」被稱為 common practice。同時「type system 中每個 field 預設 nullable」、理由包含後端局部故障與細粒度授權。驗證備註：官方站主頁對 fetcher 回 403、內容以官方 GitHub repo 的 source 檔驗證、對外引用用官方教學頁 URL。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>no-versioning 的實質是把演進成本轉嫁到三個紀律：只加不改、deprecation 標注、nullable 預設 — 版本管理的工作換了位置、沒有消失。nullable-by-default 正是為了讓局部失敗與授權拒絕不炸掉整個 response — 這條因果鏈是 schema 演進篇的骨幹。可與 WunderGraph 批評（C27）對照：versionless 解 schema 相容、解不了組織層的舊 client 支援。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/graphql-schema-evolution/" data-link-title="GraphQL Schema 演進：versionless 的紀律代價" data-link-desc="只加不改、deprecation 標注、nullable 預設怎麼共同取代版本號 — 以及每個紀律各自的隱藏帳單">Schema 演進&lt;/a>（anchor、已引用）、11.6 格式層紀律（主引用）、11.2 / 11.5 交叉、版本策略爭論文章。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://graphql.org/learn/schema-design/">Schema design（GraphQL 官方教學）&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是記錄 GraphQL 官方的 versionless 設計哲學與其依賴的紀律。</p>
<h2 id="觀察">觀察</h2>
<p>官方教學明文：GraphQL 只回傳明確請求的資料、所以新能力可以透過新 type 或既有 type 的新 field 加入而不造成 breaking change；「永遠避免 breaking change、提供 versionless API」被稱為 common practice。同時「type system 中每個 field 預設 nullable」、理由包含後端局部故障與細粒度授權。驗證備註：官方站主頁對 fetcher 回 403、內容以官方 GitHub repo 的 source 檔驗證、對外引用用官方教學頁 URL。</p>
<h2 id="判讀">判讀</h2>
<p>no-versioning 的實質是把演進成本轉嫁到三個紀律：只加不改、deprecation 標注、nullable 預設 — 版本管理的工作換了位置、沒有消失。nullable-by-default 正是為了讓局部失敗與授權拒絕不炸掉整個 response — 這條因果鏈是 schema 演進篇的骨幹。可與 WunderGraph 批評（C27）對照：versionless 解 schema 相容、解不了組織層的舊 client 支援。</p>
<h2 id="對應大綱">對應大綱</h2>
<p><a href="/blog/backend/11-api-design/styles/graphql/graphql-schema-evolution/" data-link-title="GraphQL Schema 演進：versionless 的紀律代價" data-link-desc="只加不改、deprecation 標注、nullable 預設怎麼共同取代版本號 — 以及每個紀律各自的隱藏帳單">Schema 演進</a>（anchor、已引用）、11.6 格式層紀律（主引用）、11.2 / 11.5 交叉、版本策略爭論文章。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://graphql.org/learn/schema-design/">Schema design（GraphQL 官方教學）</a></li>
</ul>
]]></content:encoded></item><item><title>11.C27 WunderGraph：GraphQL 不該直接暴露在公網</title><link>https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-wundergraph-not-for-internet/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-wundergraph-not-for-internet/</guid><description>&lt;p>這個案例的核心責任是提供公開 API 進退光譜的中間選項：persisted queries 家族做法。vendor 創辦人立場、引用時標明利益相關。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>兩篇文章的論點：單一 query 可觸發數千次 resolver、middleware 式安全模型失效；introspection 洩漏內部架構；schema traversal 可越權。解法是把 named operations 存在後端、對外轉成 JSON-RPC 式端點、完全不暴露 GraphQL endpoint。後篇補充：versioning 的組織問題 GraphQL 沒解、self-documenting 是迷思、type safety OpenAPI 也有。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>「GraphQL 當 server-side 查詢語言、對外只開 persisted operations」是介於全開與撤退之間的第三條路、公開 API 進退章需要這個中間選項。來源是賣此方案的 vendor、立場要標明；但攻擊面描述與 Bessey（C22）、HackerOne（C25）獨立互證。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/graphql-execution-cost-security/" data-link-title="GraphQL 執行成本與攻擊面" data-link-desc="resolver 執行模型讓請求成本不再是常數 — N&amp;#43;1 的基礎設施化、成本計點限流、introspection 偵察、persisted queries 的收斂路線">執行成本與攻擊面&lt;/a>（persisted queries 段、已引用）與 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/graphql-public-api-tradeoffs/" data-link-title="公開 API 的 GraphQL 進退" data-link-desc="GitHub 雙軌、Shopify all-in、與撤退案例 — 同一技術不同結局的情境變數、GraphQL 的適用邊界">公開 API 的 GraphQL 進退&lt;/a>（中間路線段、已引用）。邊緣偏反例。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://wundergraph.com/blog/graphql_is_not_meant_to_be_exposed_over_the_internet">GraphQL is not meant to be exposed over the internet（WunderGraph blog、Jens Neuse）&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://wundergraph.com/blog/why_not_use_graphql">Why not use GraphQL?（WunderGraph blog）&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是提供公開 API 進退光譜的中間選項：persisted queries 家族做法。vendor 創辦人立場、引用時標明利益相關。</p>
<h2 id="觀察">觀察</h2>
<p>兩篇文章的論點：單一 query 可觸發數千次 resolver、middleware 式安全模型失效；introspection 洩漏內部架構；schema traversal 可越權。解法是把 named operations 存在後端、對外轉成 JSON-RPC 式端點、完全不暴露 GraphQL endpoint。後篇補充：versioning 的組織問題 GraphQL 沒解、self-documenting 是迷思、type safety OpenAPI 也有。</p>
<h2 id="判讀">判讀</h2>
<p>「GraphQL 當 server-side 查詢語言、對外只開 persisted operations」是介於全開與撤退之間的第三條路、公開 API 進退章需要這個中間選項。來源是賣此方案的 vendor、立場要標明；但攻擊面描述與 Bessey（C22）、HackerOne（C25）獨立互證。</p>
<h2 id="對應大綱">對應大綱</h2>
<p><a href="/blog/backend/11-api-design/styles/graphql/graphql-execution-cost-security/" data-link-title="GraphQL 執行成本與攻擊面" data-link-desc="resolver 執行模型讓請求成本不再是常數 — N&#43;1 的基礎設施化、成本計點限流、introspection 偵察、persisted queries 的收斂路線">執行成本與攻擊面</a>（persisted queries 段、已引用）與 <a href="/blog/backend/11-api-design/styles/graphql/graphql-public-api-tradeoffs/" data-link-title="公開 API 的 GraphQL 進退" data-link-desc="GitHub 雙軌、Shopify all-in、與撤退案例 — 同一技術不同結局的情境變數、GraphQL 的適用邊界">公開 API 的 GraphQL 進退</a>（中間路線段、已引用）。邊緣偏反例。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://wundergraph.com/blog/graphql_is_not_meant_to_be_exposed_over_the_internet">GraphQL is not meant to be exposed over the internet（WunderGraph blog、Jens Neuse）</a></li>
<li><a href="https://wundergraph.com/blog/why_not_use_graphql">Why not use GraphQL?（WunderGraph blog）</a></li>
</ul>
]]></content:encoded></item></channel></rss>