<?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>gRPC 流派：proto 演進、部署邊界、內部 RPC 選型 on Tarragon</title><link>https://tarrragon.github.io/blog/backend/11-api-design/styles/grpc/</link><description>Recent content in gRPC 流派：proto 演進、部署邊界、內部 RPC 選型 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 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/backend/11-api-design/styles/grpc/index.xml" rel="self" type="application/rss+xml"/><item><title>gRPC proto 演進紀律：編碼層相容與 CI gate</title><link>https://tarrragon.github.io/blog/backend/11-api-design/styles/grpc/grpc-proto-evolution-discipline/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/backend/11-api-design/styles/grpc/grpc-proto-evolution-discipline/</guid><description>&lt;p>proto 的演進紀律建立在一個跟其他風格不同的前提上：相容性是編碼格式的性質、不是 code review 的慣例。protobuf 用 field number 當 wire format 的欄位識別、所以「哪些變更安全」由編碼規則決定、而非團隊約定。這讓 gRPC 的契約演進可以做成機器可檢的 CI gate、也讓它的紀律比 JSON 硬。本文回答兩件使用層的事：proto 該怎麼改才安全、以及怎麼把這條紀律放進 CI。跨風格的變更紀律框架（格式層 / 工具層 / 流程層）主寫在 &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>、本文是 gRPC 內部機制的深化。&lt;/p>
&lt;h2 id="安全的變更由編碼規則界定">安全的變更由編碼規則界定&lt;/h2>
&lt;p>protobuf 官方語言規範把 schema 變更分三類：wire-unsafe、wire-safe（加欄位、加 enum 值、刪欄位皆安全）、conditionally wire-compatible（見 &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>）。判準是 wire format 認不認得出差異、不是欄位語意變沒變。加欄位安全、因為舊 client 不認得新編號就跳過；刪欄位安全、因為對應編號的資料被當未知欄位忽略。&lt;/p>
&lt;p>這條規則的硬核是 field number 不可重用。編號一旦投入使用即固定、因為它就是 wire 上的欄位身分；刪欄位後必須 &lt;code>reserved&lt;/code> 該編號、擋住未來誤用。重用一個舊編號、wire 解碼會把新欄位的 bytes 當成舊欄位解、後果落在資料層 — 官方列舉的後果包含 parse / merge error、PII 洩漏、資料損毀；解碼報錯只是其中一種結局、多數情況是靜默的損毀。使用層的操作紀律因此只有兩條：只加不改、刪了就永久 reserve 編號。把相容性做進編碼層不是 protobuf 獨有 —— Thrift 的 field id、Avro 的 schema resolution 是同一類機制、演進判準類似；本文聚焦 protobuf 是因為它的生態成熟度（buf）與本模組的案例密度。&lt;/p>
&lt;h2 id="從自律升級成-ci-gate">從自律升級成 CI gate&lt;/h2>
&lt;p>編碼規則界定了安全範圍、但「只加不改」靠人腦守不住 — 破壞發生在多層、改欄位名破壞 generated code、改型別破壞 wire format、人工 review 抓不全。&lt;code>buf breaking&lt;/code> 把這條紀律做成 merge 前的自動檢查：對比歷史版本 schema、擋下如「Field 1 type int32 改 string」這類變更。官方文件對這個定位的措辭直接 —「Catching this before merge is the point」（見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/grpc-buf-breaking-detection/" data-link-title="11.C29 Buf breaking detection：四級規則對應消費者依賴" data-link-desc="把 proto 相容紀律從人的自律升級成 CI gate、檢查粒度是產品決策不是工具預設">11.C29&lt;/a>、Buf 官方文件）。&lt;/p>
&lt;p>使用層要做的判斷不是「要不要開這個檢查」、而是「檢查到哪一級」。buf 的規則分四級、嚴格程度遞增：WIRE（只保 wire 相容）、WIRE_JSON、PACKAGE、FILE。等級的選擇是產品決策、對應消費者實際依賴的形狀：消費者只透過 wire 通訊、選 WIRE 就夠；有外部程式碼直接 import 你的 generated package、就要 PACKAGE 擋住改套件路徑這種對 wire 無害、卻會炸掉別人 build 的變更。等級選太寬會放行破壞、選太嚴會擋住原本安全的重構 — 判準永遠是回到「誰在依賴這個 schema、依賴到哪一層」。&lt;/p>
&lt;h2 id="契約放哪裡與-trpc-的對照">契約放哪裡：與 tRPC 的對照&lt;/h2>
&lt;p>proto 演進紀律的本質是把契約外置成一份 IDL（介面定義語言）檔、相容性檢查對這份檔做。這跟 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/rpc-revival/rpc-revival-trpc-type-sharing/" data-link-title="tRPC 型別共享：型別即契約的前提與代價" data-link-desc="tRPC 靠 TypeScript 型別推導同步契約、零 codegen；適用同倉 TS-only、公開第三方 API 不適用">rpc-revival 的 tRPC 路線&lt;/a> 形成選型上的對照：tRPC 把契約放進 TypeScript 型別系統、靠推導同步、不產 IDL 檔。兩者都在解「契約怎麼跨 client/server 同步」、差別在契約放在哪 —— proto 外置換到跨語言與 CI 可檢、代價是要維護 IDL 與 codegen；型別內嵌換到零 codegen 的開發體驗、代價是鎖定單一語言。演進成本這條選型軸就是在問團隊承擔得起哪種紀律、判準見 &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;ul>
&lt;li>跨風格的變更紀律框架：&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;/li>
&lt;li>選了 gRPC 之後的部署約束：&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/grpc/grpc-streaming-deployment-boundary/" data-link-title="gRPC streaming 與部署邊界：trailers、proxy 與 debug 可及性" data-link-desc="選 gRPC 前要先確認的部署約束：瀏覽器過不了 trailers 需翻譯 proxy、on-call 徒手 debug 的可及性代價">streaming 與部署邊界&lt;/a>&lt;/li>
&lt;li>gRPC 值得選的組織前提：&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/grpc/grpc-internal-rpc-selection/" data-link-title="gRPC 內部 RPC 的選型位置：框架層集中的組織前提" data-link-desc="gRPC 值得選的判準是要不要一個框架層集中點、不是序列化效能；用規模兩端判讀何時集中價值蓋過 debug 代價">內部 RPC 的選型位置&lt;/a>&lt;/li>
&lt;li>契約放型別系統的對照路線：&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/rpc-revival/rpc-revival-trpc-type-sharing/" data-link-title="tRPC 型別共享：型別即契約的前提與代價" data-link-desc="tRPC 靠 TypeScript 型別推導同步契約、零 codegen；適用同倉 TS-only、公開第三方 API 不適用">tRPC 型別共享&lt;/a>&lt;/li>
&lt;li>案例原文：&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>proto 的演進紀律建立在一個跟其他風格不同的前提上：相容性是編碼格式的性質、不是 code review 的慣例。protobuf 用 field number 當 wire format 的欄位識別、所以「哪些變更安全」由編碼規則決定、而非團隊約定。這讓 gRPC 的契約演進可以做成機器可檢的 CI gate、也讓它的紀律比 JSON 硬。本文回答兩件使用層的事：proto 該怎麼改才安全、以及怎麼把這條紀律放進 CI。跨風格的變更紀律框架（格式層 / 工具層 / 流程層）主寫在 <a href="/blog/backend/11-api-design/backward-compatibility-discipline/" data-link-title="11.6 向後相容的變更紀律" data-link-desc="哪些變更算 breaking、相容性檢查放人工還是 CI、檢查粒度怎麼選 — 讓介面變更可審可擋的日常紀律">11.6 向後相容的變更紀律</a>、本文是 gRPC 內部機制的深化。</p>
<h2 id="安全的變更由編碼規則界定">安全的變更由編碼規則界定</h2>
<p>protobuf 官方語言規範把 schema 變更分三類：wire-unsafe、wire-safe（加欄位、加 enum 值、刪欄位皆安全）、conditionally wire-compatible（見 <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>）。判準是 wire format 認不認得出差異、不是欄位語意變沒變。加欄位安全、因為舊 client 不認得新編號就跳過；刪欄位安全、因為對應編號的資料被當未知欄位忽略。</p>
<p>這條規則的硬核是 field number 不可重用。編號一旦投入使用即固定、因為它就是 wire 上的欄位身分；刪欄位後必須 <code>reserved</code> 該編號、擋住未來誤用。重用一個舊編號、wire 解碼會把新欄位的 bytes 當成舊欄位解、後果落在資料層 — 官方列舉的後果包含 parse / merge error、PII 洩漏、資料損毀；解碼報錯只是其中一種結局、多數情況是靜默的損毀。使用層的操作紀律因此只有兩條：只加不改、刪了就永久 reserve 編號。把相容性做進編碼層不是 protobuf 獨有 —— Thrift 的 field id、Avro 的 schema resolution 是同一類機制、演進判準類似；本文聚焦 protobuf 是因為它的生態成熟度（buf）與本模組的案例密度。</p>
<h2 id="從自律升級成-ci-gate">從自律升級成 CI gate</h2>
<p>編碼規則界定了安全範圍、但「只加不改」靠人腦守不住 — 破壞發生在多層、改欄位名破壞 generated code、改型別破壞 wire format、人工 review 抓不全。<code>buf breaking</code> 把這條紀律做成 merge 前的自動檢查：對比歷史版本 schema、擋下如「Field 1 type int32 改 string」這類變更。官方文件對這個定位的措辭直接 —「Catching this before merge is the point」（見 <a href="/blog/backend/11-api-design/cases/grpc-buf-breaking-detection/" data-link-title="11.C29 Buf breaking detection：四級規則對應消費者依賴" data-link-desc="把 proto 相容紀律從人的自律升級成 CI gate、檢查粒度是產品決策不是工具預設">11.C29</a>、Buf 官方文件）。</p>
<p>使用層要做的判斷不是「要不要開這個檢查」、而是「檢查到哪一級」。buf 的規則分四級、嚴格程度遞增：WIRE（只保 wire 相容）、WIRE_JSON、PACKAGE、FILE。等級的選擇是產品決策、對應消費者實際依賴的形狀：消費者只透過 wire 通訊、選 WIRE 就夠；有外部程式碼直接 import 你的 generated package、就要 PACKAGE 擋住改套件路徑這種對 wire 無害、卻會炸掉別人 build 的變更。等級選太寬會放行破壞、選太嚴會擋住原本安全的重構 — 判準永遠是回到「誰在依賴這個 schema、依賴到哪一層」。</p>
<h2 id="契約放哪裡與-trpc-的對照">契約放哪裡：與 tRPC 的對照</h2>
<p>proto 演進紀律的本質是把契約外置成一份 IDL（介面定義語言）檔、相容性檢查對這份檔做。這跟 <a href="/blog/backend/11-api-design/styles/rpc-revival/rpc-revival-trpc-type-sharing/" data-link-title="tRPC 型別共享：型別即契約的前提與代價" data-link-desc="tRPC 靠 TypeScript 型別推導同步契約、零 codegen；適用同倉 TS-only、公開第三方 API 不適用">rpc-revival 的 tRPC 路線</a> 形成選型上的對照：tRPC 把契約放進 TypeScript 型別系統、靠推導同步、不產 IDL 檔。兩者都在解「契約怎麼跨 client/server 同步」、差別在契約放在哪 —— proto 外置換到跨語言與 CI 可檢、代價是要維護 IDL 與 codegen；型別內嵌換到零 codegen 的開發體驗、代價是鎖定單一語言。演進成本這條選型軸就是在問團隊承擔得起哪種紀律、判準見 <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>
<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>選了 gRPC 之後的部署約束：<a href="/blog/backend/11-api-design/styles/grpc/grpc-streaming-deployment-boundary/" data-link-title="gRPC streaming 與部署邊界：trailers、proxy 與 debug 可及性" data-link-desc="選 gRPC 前要先確認的部署約束：瀏覽器過不了 trailers 需翻譯 proxy、on-call 徒手 debug 的可及性代價">streaming 與部署邊界</a></li>
<li>gRPC 值得選的組織前提：<a href="/blog/backend/11-api-design/styles/grpc/grpc-internal-rpc-selection/" data-link-title="gRPC 內部 RPC 的選型位置：框架層集中的組織前提" data-link-desc="gRPC 值得選的判準是要不要一個框架層集中點、不是序列化效能；用規模兩端判讀何時集中價值蓋過 debug 代價">內部 RPC 的選型位置</a></li>
<li>契約放型別系統的對照路線：<a href="/blog/backend/11-api-design/styles/rpc-revival/rpc-revival-trpc-type-sharing/" data-link-title="tRPC 型別共享：型別即契約的前提與代價" data-link-desc="tRPC 靠 TypeScript 型別推導同步契約、零 codegen；適用同倉 TS-only、公開第三方 API 不適用">tRPC 型別共享</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>gRPC streaming 與部署邊界：trailers、proxy 與 debug 可及性</title><link>https://tarrragon.github.io/blog/backend/11-api-design/styles/grpc/grpc-streaming-deployment-boundary/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/backend/11-api-design/styles/grpc/grpc-streaming-deployment-boundary/</guid><description>&lt;p>選 gRPC 之前要先確認一組部署約束、這組約束在協議能力表上通常缺席：協議要求端到端 HTTP/2 加 trailers、瀏覽器不能直接連、on-call 的人不能用 &lt;code>curl&lt;/code> 徒手戳。這些是選型時要先算進去的成本，跟 gRPC 該不該存在無關。本文把這條「操作可及性」判準軸拆開、對應 &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="trailers-與瀏覽器協議事實層面的約束">trailers 與瀏覽器：協議事實層面的約束&lt;/h2>
&lt;p>gRPC 用 HTTP/2 的 trailers（在 response body 之後才送的 metadata）傳狀態碼、這是協議規格、不是實作選擇。約束由此而來：瀏覽器的 fetch API 讀不到 trailers、所以瀏覽器不能直接當 gRPC client、要靠 gRPC-Web 加一個翻譯 proxy 把 trailers 搬進 body（見 &lt;a href="https://tarrragon.github.io/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&lt;/a>、獨立實踐者批評）。同樣的 trailers 依賴也讓中間層變挑剔 —— 不是每個 LB、proxy、防火牆規則都原樣放行 HTTP/2 trailers。&lt;/p>
&lt;p>這條約束值得跟立場切開看。指出它最完整的一手清單來自 Buf 的 Connect 發布文（見 &lt;a href="https://tarrragon.github.io/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&lt;/a>）、而 Buf 是提供競品的利益相關方、它對 grpc-go 實作的批評（自帶 HTTP/2、不與其他 HTTP 流量共存）帶立場。但「瀏覽器讀不到 trailers、需要翻譯 proxy」是可獨立驗證的協議事實、跟誰在批評無關 —— C32 的獨立批評指向同一點、兩個來源互證後這條約束脫離 vendor 立場成立。使用層的判讀：如果介面要被瀏覽器直接消費、gRPC-Web 加 proxy 是必經的一層、選型時就要把這層 infra 算進成本。&lt;/p>
&lt;p>這組約束有一條中間路線。同一份 C30 除了批評、也給了解方 Connect protocol：建在 &lt;code>net/http&lt;/code> 上、以 HTTP/1.1 承載、同時支援 gRPC、gRPC-Web、Connect 三種協議、瀏覽器原生可連、JSON 版也能 &lt;code>curl&lt;/code>。它放寬了「端到端 HTTP/2 加 trailers」這組約束、代價是離開純 gRPC 生態(Connect 是 Buf 自家協議)。所以部署邊界不是「要 proto 就得吞下整組 gRPC 約束」的二選一 —— 要 proto 契約與 codegen、但消費端有瀏覽器或過不了 HTTP/2 的中間層時、Connect 這格比硬架 gRPC-Web proxy 省事。&lt;/p>
&lt;h2 id="debug-可及性curl-測試">debug 可及性：cURL 測試&lt;/h2>
&lt;p>介面上線後會被 on-call 的人徒手戳、這個場景在效率比較裡沒有位置、卻是 gRPC 收利息的地方。這位獨立實踐者提出一個可操作的判準 ——「傳一個 cURL 範例給朋友」測試：一個 HTTP+JSON endpoint 可以貼一行 &lt;code>curl&lt;/code> 讓對方立刻重現、gRPC 的 binary protobuf over HTTP/2 做不到、要對方裝 client、載 proto、才能發一個請求。這個差距在正常運作時看不到、在半夜排查一個異常請求時變成實質成本。&lt;/p>
&lt;p>判準要平衡地用。C32 同時承認生態已修補部分問題：Buf CLI、ConnectRPC、Postman 對 gRPC 的支援讓「徒手戳」不再完全不可行。所以這條軸的現況不是「gRPC 不能 debug」、而是「gRPC 的 debug 需要預先備好工具鏈、不像 HTTP+JSON 零準備可及」。選型判讀：團隊有沒有把這套工具鏈鋪到每個會碰介面的人手上、決定這條軸的實際權重。&lt;/p>
&lt;h2 id="這條軸的權重隨組織而變">這條軸的權重隨組織而變&lt;/h2>
&lt;p>部署邊界與 debug 可及性的成本不是固定值、跟組織能不能在框架層集中吸收它成反比。有平台團隊統一處理 proxy、觀測、client 工具鏈的組織、這些成本被平台吸收、個別服務作者感受不到；每個介面都要作者自己扛 proxy 與 debug 的小團隊、可及性差的代價會在 on-call 時逐次付還。這條「集中吸收 vs 逐次付還」的判讀、在 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/grpc/grpc-internal-rpc-selection/" data-link-title="gRPC 內部 RPC 的選型位置：框架層集中的組織前提" data-link-desc="gRPC 值得選的判準是要不要一個框架層集中點、不是序列化效能；用規模兩端判讀何時集中價值蓋過 debug 代價">內部 RPC 的選型位置&lt;/a> 用規模兩端展開。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>gRPC 值得選的組織前提：&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/grpc/grpc-internal-rpc-selection/" data-link-title="gRPC 內部 RPC 的選型位置：框架層集中的組織前提" data-link-desc="gRPC 值得選的判準是要不要一個框架層集中點、不是序列化效能；用規模兩端判讀何時集中價值蓋過 debug 代價">內部 RPC 的選型位置&lt;/a>&lt;/li>
&lt;li>三軸選型判準：&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;/li>
&lt;li>proto 契約怎麼演進：&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/grpc/grpc-proto-evolution-discipline/" data-link-title="gRPC proto 演進紀律：編碼層相容與 CI gate" data-link-desc="要改 proto 又得保證 wire 相容、並想把相容檢查落成 merge 前 CI gate、選檢查等級時的判準">proto 演進紀律&lt;/a>&lt;/li>
&lt;li>案例原文：&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>選 gRPC 之前要先確認一組部署約束、這組約束在協議能力表上通常缺席：協議要求端到端 HTTP/2 加 trailers、瀏覽器不能直接連、on-call 的人不能用 <code>curl</code> 徒手戳。這些是選型時要先算進去的成本，跟 gRPC 該不該存在無關。本文把這條「操作可及性」判準軸拆開、對應 <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="trailers-與瀏覽器協議事實層面的約束">trailers 與瀏覽器：協議事實層面的約束</h2>
<p>gRPC 用 HTTP/2 的 trailers（在 response body 之後才送的 metadata）傳狀態碼、這是協議規格、不是實作選擇。約束由此而來：瀏覽器的 fetch API 讀不到 trailers、所以瀏覽器不能直接當 gRPC client、要靠 gRPC-Web 加一個翻譯 proxy 把 trailers 搬進 body（見 <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>、獨立實踐者批評）。同樣的 trailers 依賴也讓中間層變挑剔 —— 不是每個 LB、proxy、防火牆規則都原樣放行 HTTP/2 trailers。</p>
<p>這條約束值得跟立場切開看。指出它最完整的一手清單來自 Buf 的 Connect 發布文（見 <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>）、而 Buf 是提供競品的利益相關方、它對 grpc-go 實作的批評（自帶 HTTP/2、不與其他 HTTP 流量共存）帶立場。但「瀏覽器讀不到 trailers、需要翻譯 proxy」是可獨立驗證的協議事實、跟誰在批評無關 —— C32 的獨立批評指向同一點、兩個來源互證後這條約束脫離 vendor 立場成立。使用層的判讀：如果介面要被瀏覽器直接消費、gRPC-Web 加 proxy 是必經的一層、選型時就要把這層 infra 算進成本。</p>
<p>這組約束有一條中間路線。同一份 C30 除了批評、也給了解方 Connect protocol：建在 <code>net/http</code> 上、以 HTTP/1.1 承載、同時支援 gRPC、gRPC-Web、Connect 三種協議、瀏覽器原生可連、JSON 版也能 <code>curl</code>。它放寬了「端到端 HTTP/2 加 trailers」這組約束、代價是離開純 gRPC 生態(Connect 是 Buf 自家協議)。所以部署邊界不是「要 proto 就得吞下整組 gRPC 約束」的二選一 —— 要 proto 契約與 codegen、但消費端有瀏覽器或過不了 HTTP/2 的中間層時、Connect 這格比硬架 gRPC-Web proxy 省事。</p>
<h2 id="debug-可及性curl-測試">debug 可及性：cURL 測試</h2>
<p>介面上線後會被 on-call 的人徒手戳、這個場景在效率比較裡沒有位置、卻是 gRPC 收利息的地方。這位獨立實踐者提出一個可操作的判準 ——「傳一個 cURL 範例給朋友」測試：一個 HTTP+JSON endpoint 可以貼一行 <code>curl</code> 讓對方立刻重現、gRPC 的 binary protobuf over HTTP/2 做不到、要對方裝 client、載 proto、才能發一個請求。這個差距在正常運作時看不到、在半夜排查一個異常請求時變成實質成本。</p>
<p>判準要平衡地用。C32 同時承認生態已修補部分問題：Buf CLI、ConnectRPC、Postman 對 gRPC 的支援讓「徒手戳」不再完全不可行。所以這條軸的現況不是「gRPC 不能 debug」、而是「gRPC 的 debug 需要預先備好工具鏈、不像 HTTP+JSON 零準備可及」。選型判讀：團隊有沒有把這套工具鏈鋪到每個會碰介面的人手上、決定這條軸的實際權重。</p>
<h2 id="這條軸的權重隨組織而變">這條軸的權重隨組織而變</h2>
<p>部署邊界與 debug 可及性的成本不是固定值、跟組織能不能在框架層集中吸收它成反比。有平台團隊統一處理 proxy、觀測、client 工具鏈的組織、這些成本被平台吸收、個別服務作者感受不到；每個介面都要作者自己扛 proxy 與 debug 的小團隊、可及性差的代價會在 on-call 時逐次付還。這條「集中吸收 vs 逐次付還」的判讀、在 <a href="/blog/backend/11-api-design/styles/grpc/grpc-internal-rpc-selection/" data-link-title="gRPC 內部 RPC 的選型位置：框架層集中的組織前提" data-link-desc="gRPC 值得選的判準是要不要一個框架層集中點、不是序列化效能；用規模兩端判讀何時集中價值蓋過 debug 代價">內部 RPC 的選型位置</a> 用規模兩端展開。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>gRPC 值得選的組織前提：<a href="/blog/backend/11-api-design/styles/grpc/grpc-internal-rpc-selection/" data-link-title="gRPC 內部 RPC 的選型位置：框架層集中的組織前提" data-link-desc="gRPC 值得選的判準是要不要一個框架層集中點、不是序列化效能；用規模兩端判讀何時集中價值蓋過 debug 代價">內部 RPC 的選型位置</a></li>
<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>proto 契約怎麼演進：<a href="/blog/backend/11-api-design/styles/grpc/grpc-proto-evolution-discipline/" data-link-title="gRPC proto 演進紀律：編碼層相容與 CI gate" data-link-desc="要改 proto 又得保證 wire 相容、並想把相容檢查落成 merge 前 CI gate、選檢查等級時的判準">proto 演進紀律</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>gRPC 內部 RPC 的選型位置：框架層集中的組織前提</title><link>https://tarrragon.github.io/blog/backend/11-api-design/styles/grpc/grpc-internal-rpc-selection/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/backend/11-api-design/styles/grpc/grpc-internal-rpc-selection/</guid><description>&lt;p>gRPC 在內部服務間的定位是一個統一的呼叫層：身分驗證、觀測、可靠性策略在這層做一次、就套用到所有服務。選它的判準是組織要不要、也養不養得起這一層「框架層集中點」—— 序列化的位元組效率不是重點（protobuf 的序列化 REST 也拿得到）、真正換到的是這層集中能力、加上 HTTP/2 的連線層併發（multiplexing、雙向 streaming）。這一層值不值得、取決於組織有沒有平台層能集中吸收它的操作成本。本文用一個規模上限案例與一組反向成本、劃出 gRPC 落在哪個消費者形狀（誰在呼叫你的介面、部署在哪、用什麼語言）。&lt;/p>
&lt;h2 id="集中點的價值一個規模上限案例">集中點的價值：一個規模上限案例&lt;/h2>
&lt;p>Dropbox 的 Courier 把自製 RPC（HTTP/1.1 加 protobuf）遷移到 gRPC、動機是 multiplexing（單連線多路併發）與雙向 streaming —— 序列化層沒有變動、protobuf 本來就在用（見 &lt;a href="https://tarrragon.github.io/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&lt;/a>、Dropbox 一手工程紀錄）。遷移之後、他們在這條統一呼叫層上集中疊了四件可靠性能力：&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/tls-mtls/" data-link-title="TLS / mTLS" data-link-desc="說明傳輸加密與雙向憑證驗證如何保護跨邊界資料流">mTLS&lt;/a> 服務身分、per-method 統計、強制 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/deadline/" data-link-title="Deadline" data-link-desc="說明整體操作的截止時間如何沿著服務邊界傳遞">deadline&lt;/a> 傳播、LIFO queue &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/circuit-breaker/" data-link-title="Circuit Breaker" data-link-desc="說明下游持續失敗時如何暫停呼叫並保護系統">熔斷&lt;/a>。這四件都是「做一次、套全部服務」的 infra-wide 能力 —— 這正是框架層集中點的價值：可靠性策略不必每個服務各寫一遍。&lt;/p>
&lt;p>這個案例是規模上限、要當上限讀而非通例。它成立的前提是百萬 RPS（每秒請求數）級的流量與一支能扛遷移的平台團隊。案例本身也留了兩個規模訊號：遷移比初始開發久得多、以及大規模重啟時 TLS 握手成本迫使把 RSA 2048 換成 ECDSA P-256、HTTP/1.1 與 gRPC 還得拆成不同 server 處理。這些踩雷是「有能力集中」的組織才會遇到、也才承擔得起的問題 —— 對沒有平台層集中能力的組織、它們是警訊不是路標。&lt;/p>
&lt;h2 id="反向成本debug-可及性">反向成本：debug 可及性&lt;/h2>
&lt;p>集中點的價值要跟一項反向成本一起算：gRPC 的操作與 debug 可及性比 HTTP+JSON 差。這條成本在 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/grpc/grpc-streaming-deployment-boundary/" data-link-title="gRPC streaming 與部署邊界：trailers、proxy 與 debug 可及性" data-link-desc="選 gRPC 前要先確認的部署約束：瀏覽器過不了 trailers 需翻譯 proxy、on-call 徒手 debug 的可及性代價">streaming 與部署邊界&lt;/a> 完整展開 —— on-call 不能徒手 &lt;code>curl&lt;/code>、瀏覽器要 proxy、工具鏈要預先鋪好。兩相對照就成了選型位置的兩端：一端是 Dropbox 這種有平台團隊統一攤提可及性成本的組織、gRPC 的集中價值遠蓋過 debug 代價；另一端是沒有平台層、每個介面都要作者自己扛操作面的組織、集中點的價值兌現不出來、debug 代價在 on-call 現場一次次付出。&lt;/p>
&lt;h2 id="選型位置落在哪個消費者形狀">選型位置：落在哪個消費者形狀&lt;/h2>
&lt;p>把兩端收斂成判準：gRPC 的核心落點是「內部服務、多團隊、且有平台層能集中吸收操作成本」—— 這三者對齊、集中價值才兌現。跨語言是強放大器、不是門檻：schema-first 的跨語言契約確實是 gRPC 的長處、但全 Go 或全 Java 的內部多團隊照樣靠框架層集中拿到 mTLS、deadline 傳播、熔斷這些語言無關的能力、單語言不把 gRPC 排除掉。這一格對應 &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;p>往核心條件外走、判準就翻向別的風格：對外公開給匿名第三方、可及性與工具生態要求把答案推回 HTTP+JSON；前後端同倉全 TypeScript、契約同步的更省解是 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/rpc-revival/rpc-revival-trpc-type-sharing/" data-link-title="tRPC 型別共享：型別即契約的前提與代價" data-link-desc="tRPC 靠 TypeScript 型別推導同步契約、零 codegen；適用同倉 TS-only、公開第三方 API 不適用">tRPC&lt;/a>；本地 process 間雙向低頻、&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/rpc-revival/rpc-revival-jsonrpc-conditions/" data-link-title="JSON-RPC 的適用條件：最小夠用的訊息層" data-link-desc="本地雙向低頻、需 notification 語意、生態要求零 codegen 可自省 —— 這組條件下 JSON-RPC 比重型 RPC 更貼">JSON-RPC&lt;/a> 的最小訊息層比 gRPC 的 HTTP/2 加 codegen 更貼；要 proto 契約與 codegen、但消費端有瀏覽器或過不了 HTTP/2 的中間層、走 Connect 這類中間協議(見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/grpc/grpc-streaming-deployment-boundary/" data-link-title="gRPC streaming 與部署邊界：trailers、proxy 與 debug 可及性" data-link-desc="選 gRPC 前要先確認的部署約束：瀏覽器過不了 trailers 需翻譯 proxy、on-call 徒手 debug 的可及性代價">部署邊界&lt;/a>)比純 gRPC 省事。選型的產出是把 gRPC 放對位置、不是全公司統一一種風格。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>部署約束與 debug 可及性：&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/grpc/grpc-streaming-deployment-boundary/" data-link-title="gRPC streaming 與部署邊界：trailers、proxy 與 debug 可及性" data-link-desc="選 gRPC 前要先確認的部署約束：瀏覽器過不了 trailers 需翻譯 proxy、on-call 徒手 debug 的可及性代價">streaming 與部署邊界&lt;/a>&lt;/li>
&lt;li>三軸選型判準：&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;/li>
&lt;li>同倉型別共享的對照路線：&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/rpc-revival/rpc-revival-trpc-type-sharing/" data-link-title="tRPC 型別共享：型別即契約的前提與代價" data-link-desc="tRPC 靠 TypeScript 型別推導同步契約、零 codegen；適用同倉 TS-only、公開第三方 API 不適用">tRPC 型別共享&lt;/a>&lt;/li>
&lt;li>案例原文：&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>gRPC 在內部服務間的定位是一個統一的呼叫層：身分驗證、觀測、可靠性策略在這層做一次、就套用到所有服務。選它的判準是組織要不要、也養不養得起這一層「框架層集中點」—— 序列化的位元組效率不是重點（protobuf 的序列化 REST 也拿得到）、真正換到的是這層集中能力、加上 HTTP/2 的連線層併發（multiplexing、雙向 streaming）。這一層值不值得、取決於組織有沒有平台層能集中吸收它的操作成本。本文用一個規模上限案例與一組反向成本、劃出 gRPC 落在哪個消費者形狀（誰在呼叫你的介面、部署在哪、用什麼語言）。</p>
<h2 id="集中點的價值一個規模上限案例">集中點的價值：一個規模上限案例</h2>
<p>Dropbox 的 Courier 把自製 RPC（HTTP/1.1 加 protobuf）遷移到 gRPC、動機是 multiplexing（單連線多路併發）與雙向 streaming —— 序列化層沒有變動、protobuf 本來就在用（見 <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>、Dropbox 一手工程紀錄）。遷移之後、他們在這條統一呼叫層上集中疊了四件可靠性能力：<a href="/blog/backend/knowledge-cards/tls-mtls/" data-link-title="TLS / mTLS" data-link-desc="說明傳輸加密與雙向憑證驗證如何保護跨邊界資料流">mTLS</a> 服務身分、per-method 統計、強制 <a href="/blog/backend/knowledge-cards/deadline/" data-link-title="Deadline" data-link-desc="說明整體操作的截止時間如何沿著服務邊界傳遞">deadline</a> 傳播、LIFO queue <a href="/blog/backend/knowledge-cards/circuit-breaker/" data-link-title="Circuit Breaker" data-link-desc="說明下游持續失敗時如何暫停呼叫並保護系統">熔斷</a>。這四件都是「做一次、套全部服務」的 infra-wide 能力 —— 這正是框架層集中點的價值：可靠性策略不必每個服務各寫一遍。</p>
<p>這個案例是規模上限、要當上限讀而非通例。它成立的前提是百萬 RPS（每秒請求數）級的流量與一支能扛遷移的平台團隊。案例本身也留了兩個規模訊號：遷移比初始開發久得多、以及大規模重啟時 TLS 握手成本迫使把 RSA 2048 換成 ECDSA P-256、HTTP/1.1 與 gRPC 還得拆成不同 server 處理。這些踩雷是「有能力集中」的組織才會遇到、也才承擔得起的問題 —— 對沒有平台層集中能力的組織、它們是警訊不是路標。</p>
<h2 id="反向成本debug-可及性">反向成本：debug 可及性</h2>
<p>集中點的價值要跟一項反向成本一起算：gRPC 的操作與 debug 可及性比 HTTP+JSON 差。這條成本在 <a href="/blog/backend/11-api-design/styles/grpc/grpc-streaming-deployment-boundary/" data-link-title="gRPC streaming 與部署邊界：trailers、proxy 與 debug 可及性" data-link-desc="選 gRPC 前要先確認的部署約束：瀏覽器過不了 trailers 需翻譯 proxy、on-call 徒手 debug 的可及性代價">streaming 與部署邊界</a> 完整展開 —— on-call 不能徒手 <code>curl</code>、瀏覽器要 proxy、工具鏈要預先鋪好。兩相對照就成了選型位置的兩端：一端是 Dropbox 這種有平台團隊統一攤提可及性成本的組織、gRPC 的集中價值遠蓋過 debug 代價；另一端是沒有平台層、每個介面都要作者自己扛操作面的組織、集中點的價值兌現不出來、debug 代價在 on-call 現場一次次付出。</p>
<h2 id="選型位置落在哪個消費者形狀">選型位置：落在哪個消費者形狀</h2>
<p>把兩端收斂成判準：gRPC 的核心落點是「內部服務、多團隊、且有平台層能集中吸收操作成本」—— 這三者對齊、集中價值才兌現。跨語言是強放大器、不是門檻：schema-first 的跨語言契約確實是 gRPC 的長處、但全 Go 或全 Java 的內部多團隊照樣靠框架層集中拿到 mTLS、deadline 傳播、熔斷這些語言無關的能力、單語言不把 gRPC 排除掉。這一格對應 <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>
<p>往核心條件外走、判準就翻向別的風格：對外公開給匿名第三方、可及性與工具生態要求把答案推回 HTTP+JSON；前後端同倉全 TypeScript、契約同步的更省解是 <a href="/blog/backend/11-api-design/styles/rpc-revival/rpc-revival-trpc-type-sharing/" data-link-title="tRPC 型別共享：型別即契約的前提與代價" data-link-desc="tRPC 靠 TypeScript 型別推導同步契約、零 codegen；適用同倉 TS-only、公開第三方 API 不適用">tRPC</a>；本地 process 間雙向低頻、<a href="/blog/backend/11-api-design/styles/rpc-revival/rpc-revival-jsonrpc-conditions/" data-link-title="JSON-RPC 的適用條件：最小夠用的訊息層" data-link-desc="本地雙向低頻、需 notification 語意、生態要求零 codegen 可自省 —— 這組條件下 JSON-RPC 比重型 RPC 更貼">JSON-RPC</a> 的最小訊息層比 gRPC 的 HTTP/2 加 codegen 更貼；要 proto 契約與 codegen、但消費端有瀏覽器或過不了 HTTP/2 的中間層、走 Connect 這類中間協議(見 <a href="/blog/backend/11-api-design/styles/grpc/grpc-streaming-deployment-boundary/" data-link-title="gRPC streaming 與部署邊界：trailers、proxy 與 debug 可及性" data-link-desc="選 gRPC 前要先確認的部署約束：瀏覽器過不了 trailers 需翻譯 proxy、on-call 徒手 debug 的可及性代價">部署邊界</a>)比純 gRPC 省事。選型的產出是把 gRPC 放對位置、不是全公司統一一種風格。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>部署約束與 debug 可及性：<a href="/blog/backend/11-api-design/styles/grpc/grpc-streaming-deployment-boundary/" data-link-title="gRPC streaming 與部署邊界：trailers、proxy 與 debug 可及性" data-link-desc="選 gRPC 前要先確認的部署約束：瀏覽器過不了 trailers 需翻譯 proxy、on-call 徒手 debug 的可及性代價">streaming 與部署邊界</a></li>
<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/rpc-revival/rpc-revival-trpc-type-sharing/" data-link-title="tRPC 型別共享：型別即契約的前提與代價" data-link-desc="tRPC 靠 TypeScript 型別推導同步契約、零 codegen；適用同倉 TS-only、公開第三方 API 不適用">tRPC 型別共享</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>