<?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>Selection on Tarragon</title><link>https://tarrragon.github.io/blog/tags/selection/</link><description>Recent content in Selection 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/selection/index.xml" rel="self" type="application/rss+xml"/><item><title>11.2 風格選型總覽</title><link>https://tarrragon.github.io/blog/backend/11-api-design/api-style-selection/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/11-api-design/api-style-selection/</guid><description>&lt;p>風格選型的判準是介面的使用情境、而非風格本身的技術優劣。同一個團隊裡可能同時存在三個正確答案：對外公開 API 用 HTTP+JSON、內部服務間用 gRPC、前後端同倉的產品用 tRPC — 三個介面的消費者形狀不同、答案就不同。本章建立三條判準軸；各風格內部的深度論證（流派自己怎麼說、失敗案例、適用邊界）收在 &lt;code>styles/&lt;/code> 流派層、本章結尾的爭論地圖列出路由。本章的判準軸是從 &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;p>消費者形狀指誰在呼叫、服務端對呼叫方有多少控制力、呼叫方跟服務端的部署與語言關係。這條軸是三條裡權重最高的、因為它決定其他軸的成本怎麼放大。&lt;/p>
&lt;p>流派自己劃出的邊界最有說服力。tRPC 官方 FAQ 明言前提：脫離 monorepo 就失去 client 與 server 一起運作的保證、替代方案是把 backend 型別發成 private npm package — 語言鎖定 TypeScript、部署形態鎖定同倉或私包（見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/rpc-trpc-design-philosophy/" data-link-title="11.C33 tRPC 設計哲學：無 schema 無 codegen 的型別共享" data-link-desc="把 API 契約從 IDL 檔搬進型別系統的極端點、官方自述的前提與代價（TS-only、同倉共置）">11.C33&lt;/a>）。公開撤退立場的六年 GraphQL 實踐者也給出同構的判準句、並建議控制得了 client 的團隊改用 OpenAPI REST（見 &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>、反例、作者判讀層）。兩個相反方向的來源指向同一條判讀：&lt;strong>選型的第一步是確認消費者是誰、再比對風格能力&lt;/strong>。&lt;/p>
&lt;p>消費者形狀的常見分型與傾向：&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;/td>
 &lt;td>HTTP+JSON（REST 式）&lt;/td>
 &lt;td>工具鏈普及、curl 可及、文件生態成熟&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>內部服務、跨語言、多團隊&lt;/td>
 &lt;td>gRPC / protobuf&lt;/td>
 &lt;td>schema-first 跨語言、契約可 CI 檢查&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>前後端同倉、全 TypeScript&lt;/td>
 &lt;td>tRPC&lt;/td>
 &lt;td>型別即契約、零 codegen；前提與代價見上&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>多形狀 client 拼裝巢狀資料&lt;/td>
 &lt;td>GraphQL&lt;/td>
 &lt;td>client 聲明取數；執行成本與安全代價見爭論地圖&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>本地 process 間、雙向、低頻&lt;/td>
 &lt;td>JSON-RPC&lt;/td>
 &lt;td>最小夠用訊息層；LSP 與 MCP 的採用是實證（見下）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>下游要事件不是查詢&lt;/td>
 &lt;td>event / queue&lt;/td>
 &lt;td>交接語意不同、路由到 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>表格是索引、每列的成立條件在真實情境裡要重新判讀。以 JSON-RPC 列為例：它在 web API 世代被 REST 式做法取代、卻在 LSP 與 MCP 兩份現代 spec 裡被選為訊息層 — 兩份 spec 都在 JSON-RPC 2.0 上加約束、而非發明新協議（見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/rpc-jsonrpc-lsp-mcp-revival/" data-link-title="11.C34 JSON-RPC 重生：LSP 與 MCP 都選它當訊息層" data-link-desc="死在 web API、活在編輯器與 agent 協議：最小夠用訊息層的選型條件組合">11.C34&lt;/a>；spec 只陳述採用、選型理由是本模組的判讀）。共同的條件組合是本地、雙向、需要 notification 語意、生態工具要求零 codegen 可自省 — 這組條件下 gRPC 的 HTTP/2 與 codegen 成本全是負資產。判讀重點是條件組合、而非「JSON-RPC 回來了」這種風格敘事。其餘各列的成立條件同樣散在後文 — gRPC 與 tRPC 的前提在判準軸二、三展開、GraphQL 的代價在爭論地圖路由的流派層、表格只負責索引。&lt;/p>
&lt;h2 id="判準軸二演進成本">判準軸二：演進成本&lt;/h2>
&lt;p>每種風格都要回答「介面上線後怎麼改」、機制差異很大。兩個代表性答案：GraphQL 的 versionless 路線把演進成本轉嫁到 schema 層的紀律、protobuf 把相容性直接做成編碼格式的性質 — 兩者的具體紀律與條款、主寫在 &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> 的格式層段（案例 &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>、&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/grpc-protobuf-field-number-discipline/" data-link-title="11.C28 protobuf 官方規範：field number 紀律" data-link-desc="編號不可改、刪除必 reserve、重用導致資料損毀；契約相容性是編碼格式的性質、不是 review 慣例">11.C28&lt;/a>）。HTTP+JSON 的演進紀律則多半靠約定與流程、缺格式層的強制 — 這是它自由度最高也最容易踩線的原因。&lt;/p></description><content:encoded><![CDATA[<p>風格選型的判準是介面的使用情境、而非風格本身的技術優劣。同一個團隊裡可能同時存在三個正確答案：對外公開 API 用 HTTP+JSON、內部服務間用 gRPC、前後端同倉的產品用 tRPC — 三個介面的消費者形狀不同、答案就不同。本章建立三條判準軸；各風格內部的深度論證（流派自己怎麼說、失敗案例、適用邊界）收在 <code>styles/</code> 流派層、本章結尾的爭論地圖列出路由。本章的判準軸是從 <a href="/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">案例庫</a> 跨案例合成的、標明為推導。</p>
<h2 id="判準軸一消費者形狀">判準軸一：消費者形狀</h2>
<p>消費者形狀指誰在呼叫、服務端對呼叫方有多少控制力、呼叫方跟服務端的部署與語言關係。這條軸是三條裡權重最高的、因為它決定其他軸的成本怎麼放大。</p>
<p>流派自己劃出的邊界最有說服力。tRPC 官方 FAQ 明言前提：脫離 monorepo 就失去 client 與 server 一起運作的保證、替代方案是把 backend 型別發成 private npm package — 語言鎖定 TypeScript、部署形態鎖定同倉或私包（見 <a href="/blog/backend/11-api-design/cases/rpc-trpc-design-philosophy/" data-link-title="11.C33 tRPC 設計哲學：無 schema 無 codegen 的型別共享" data-link-desc="把 API 契約從 IDL 檔搬進型別系統的極端點、官方自述的前提與代價（TS-only、同倉共置）">11.C33</a>）。公開撤退立場的六年 GraphQL 實踐者也給出同構的判準句、並建議控制得了 client 的團隊改用 OpenAPI REST（見 <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>、反例、作者判讀層）。兩個相反方向的來源指向同一條判讀：<strong>選型的第一步是確認消費者是誰、再比對風格能力</strong>。</p>
<p>消費者形狀的常見分型與傾向：</p>
<table>
  <thead>
      <tr>
          <th>消費者形狀</th>
          <th>傾向風格</th>
          <th>原因</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>匿名第三方開發者（公開平台）</td>
          <td>HTTP+JSON（REST 式）</td>
          <td>工具鏈普及、curl 可及、文件生態成熟</td>
      </tr>
      <tr>
          <td>內部服務、跨語言、多團隊</td>
          <td>gRPC / protobuf</td>
          <td>schema-first 跨語言、契約可 CI 檢查</td>
      </tr>
      <tr>
          <td>前後端同倉、全 TypeScript</td>
          <td>tRPC</td>
          <td>型別即契約、零 codegen；前提與代價見上</td>
      </tr>
      <tr>
          <td>多形狀 client 拼裝巢狀資料</td>
          <td>GraphQL</td>
          <td>client 聲明取數；執行成本與安全代價見爭論地圖</td>
      </tr>
      <tr>
          <td>本地 process 間、雙向、低頻</td>
          <td>JSON-RPC</td>
          <td>最小夠用訊息層；LSP 與 MCP 的採用是實證（見下）</td>
      </tr>
      <tr>
          <td>下游要事件不是查詢</td>
          <td>event / queue</td>
          <td>交接語意不同、路由到 <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列</a></td>
      </tr>
  </tbody>
</table>
<p>表格是索引、每列的成立條件在真實情境裡要重新判讀。以 JSON-RPC 列為例：它在 web API 世代被 REST 式做法取代、卻在 LSP 與 MCP 兩份現代 spec 裡被選為訊息層 — 兩份 spec 都在 JSON-RPC 2.0 上加約束、而非發明新協議（見 <a href="/blog/backend/11-api-design/cases/rpc-jsonrpc-lsp-mcp-revival/" data-link-title="11.C34 JSON-RPC 重生：LSP 與 MCP 都選它當訊息層" data-link-desc="死在 web API、活在編輯器與 agent 協議：最小夠用訊息層的選型條件組合">11.C34</a>；spec 只陳述採用、選型理由是本模組的判讀）。共同的條件組合是本地、雙向、需要 notification 語意、生態工具要求零 codegen 可自省 — 這組條件下 gRPC 的 HTTP/2 與 codegen 成本全是負資產。判讀重點是條件組合、而非「JSON-RPC 回來了」這種風格敘事。其餘各列的成立條件同樣散在後文 — gRPC 與 tRPC 的前提在判準軸二、三展開、GraphQL 的代價在爭論地圖路由的流派層、表格只負責索引。</p>
<h2 id="判準軸二演進成本">判準軸二：演進成本</h2>
<p>每種風格都要回答「介面上線後怎麼改」、機制差異很大。兩個代表性答案：GraphQL 的 versionless 路線把演進成本轉嫁到 schema 層的紀律、protobuf 把相容性直接做成編碼格式的性質 — 兩者的具體紀律與條款、主寫在 <a href="/blog/backend/11-api-design/backward-compatibility-discipline/" data-link-title="11.6 向後相容的變更紀律" data-link-desc="哪些變更算 breaking、相容性檢查放人工還是 CI、檢查粒度怎麼選 — 讓介面變更可審可擋的日常紀律">11.6 變更紀律</a> 的格式層段（案例 <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>、<a href="/blog/backend/11-api-design/cases/grpc-protobuf-field-number-discipline/" data-link-title="11.C28 protobuf 官方規範：field number 紀律" data-link-desc="編號不可改、刪除必 reserve、重用導致資料損毀；契約相容性是編碼格式的性質、不是 review 慣例">11.C28</a>）。HTTP+JSON 的演進紀律則多半靠約定與流程、缺格式層的強制 — 這是它自由度最高也最容易踩線的原因。</p>
<p>選型時的判讀問題是團隊承擔得起哪種紀律：格式層強制（protobuf）適合跨團隊多語言、因為紀律不依賴人的自覺；約定層紀律（JSON、GraphQL SDL）需要配套的變更審查與工具、組織面見 <a href="/blog/backend/11-api-design/api-governance/" data-link-title="11.10 API 規範治理" data-link-desc="設計規範怎麼讓幾十個團隊持續遵守 — 提案制、Guild 制、分軌制的治理模式比較、linting 進 CI、規範失敗的成因">11.10 規範治理</a>。</p>
<h2 id="判準軸三操作與-debug-可及性">判準軸三：操作與 debug 可及性</h2>
<p>介面會被 on-call 的人徒手戳、會過 LB 與 proxy、會被防火牆規則篩 — 這些操作面的成本在能力比較表上通常缺席。gRPC 在這條軸上的代價有完整的一手批評：協議要求端到端 HTTP/2 加 trailers、瀏覽器支援需要翻譯 proxy、debug 時 <code>curl | jq</code> 不可行（見 <a href="/blog/backend/11-api-design/cases/grpc-buf-connect-critique/" data-link-title="11.C30 Buf Connect 發布文：對 grpc-go 的系統性批評" data-link-desc="gRPC 部署邊界（trailers、瀏覽器、proxy）最完整的一手批評；發布方是競品、立場需標明">11.C30</a>、發布方是競品 vendor、批評點與 <a href="/blog/backend/11-api-design/cases/grpc-kmcd-bad-parts/" data-link-title="11.C32 gRPC: The Bad Parts：cURL 測試不過的 API（反例）" data-link-desc="反例：獨立實踐者的 gRPC 批評清單、debug 可及性判準、含生態已修補的平衡敘述">11.C32</a> 的獨立實踐者批評互證後採用）。C32 提出的「傳一個 cURL 範例給朋友」測試是這條軸的可操作判準。</p>
<p>這條軸的權重跟組織的 infra 成熟度成反比：有能力在框架層集中處理 proxy、觀測、部署的組織（如 Dropbox 的 Courier、見 <a href="/blog/backend/11-api-design/cases/grpc-dropbox-courier/" data-link-title="11.C31 Dropbox Courier：百萬 RPS 規模的 gRPC 遷移" data-link-desc="gRPC 當框架層集中可靠性的載體、遷移成本與 TLS 握手踩雷的規模判讀訊號">11.C31</a>）、操作成本被平台吸收；小團隊每個介面都要自己扛操作面、可及性差的風格會在 on-call 時收利息。</p>
<h2 id="共存是常態取代是例外">共存是常態、取代是例外</h2>
<p>大平台的長期實證支持「多風格共存」而非「新風格取代舊風格」。GitHub 2016 年採用 GraphQL、多年後的官方立場是 REST 與 GraphQL 並行、依情境選用、且明文說明功能覆蓋不對等（見 <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>）。反方向的 Shopify 宣告 GraphQL 為唯一 API — 但它動用了「新功能只上 GraphQL、新 app 強制」的平台強制力、還配套 rate limit 加倍與查詢成本降 75%（見 <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>）。判讀：沒有平台強制力的組織、務實的預期是多風格長期共存、選型的真正產出是「每個介面用對風格」、而非全公司統一答案。</p>
<h2 id="爭論地圖">爭論地圖</h2>
<p>本章只給判準軸；各風格的深度交鋒在 <code>styles/</code> 流派層：REST 語意學之爭與 hypermedia 復興（<a href="/blog/backend/11-api-design/styles/rest/" data-link-title="REST 流派：語意學之爭與 hypermedia 復興" data-link-desc="REST 這個詞的定義權爭奪、hypermedia 路線的復興論證與業界拒絕的理由、成熟度模型的實用讀法">已完成</a>）、GraphQL 的執行成本與進退（<a href="/blog/backend/11-api-design/styles/graphql/" data-link-title="GraphQL 流派：schema 演進、執行成本與公開 API 進退" data-link-desc="versionless 演進的紀律代價、resolver 執行模型的成本與攻擊面、大平台採用與撤退的情境差異">已完成</a>）、proto 演進紀律與部署邊界（styles/grpc、backlog）、tRPC 與 JSON-RPC 的復興條件（styles/rpc-revival、backlog）、格式標準化的反覆嘗試（styles/standards、backlog）、server 推 client 的承諾差異（styles/realtime、案例待採集）。完整 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/api-boundary-responsibility/" data-link-title="11.1 API 作為服務邊界的責任" data-link-desc="介面變更該由誰付成本、哪些介面性質算對外承諾、承諾違約有哪些模式 — 動手設計 endpoint 前的責任框架">11.1 API 作為服務邊界的責任</a></li>
<li>選了風格之後的資源建模：<a href="/blog/backend/11-api-design/resource-modeling-operation-semantics/" data-link-title="11.3 資源建模與操作語意" data-link-desc="endpoint 該建模成資源還是動作、HTTP method 與 status 承諾了什麼、available actions 由誰計算 — 建模決策的判準">11.3 資源建模與操作語意</a></li>
<li>事件式交接的能力邊界：<a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列</a>、<a href="/blog/backend/knowledge-cards/webhook/" data-link-title="Webhook" data-link-desc="說明外部系統回呼事件的接收、驗證與處理邊界">Webhook 知識卡</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></channel></rss>