iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0

昨天 Day 12 我們站在使用者的角度看了那個招牌甜頭:換一家 LLM 供應商,設定檔改一行字,業務 Java 程式碼一個字都不用動。但「0 行不動」這四個字,昨天是當成結論在用的:我說名冊裡換個零件就好,業務碼根本不認得 openai-http 還是 gemini-http。今天要把這個結論翻過來,看它底下到底靠什麼撐住。換句話說,「可抽換」不是喊喊的口號,它得有一套具體的機制,而且這套機制小到出乎意料——核心只有三樣東西。

本篇結構:

  • 「可抽換」這件事,到底難在哪
  • 三樣東西:標記、分類表、名冊
  • 名冊的兩種選法:查表 vs 開機定下
  • 框架是長出來的:介面預設值(default method)怎麼讓既有實作 0 行不動
  • 抽象介面這條線,劃在哪裡

「可抽換」這件事,到底難在哪

先把問題講清楚,免得覺得這只是套個介面那麼簡單。

Portal 要同時容納的不是一兩家供應商。光是這幾類插槽,現場就掛著這些零件:

插槽 已掛上的實作
LLM mock(離線用)、openai-httpgemini-httpspring-ai
守門 mockregexopenai-moderation
地圖 mockgoogle

這些零件功能不同、來源不同、各自的開發節奏也不同。真正的難處在於:你希望「加一家新供應商」這件事,是純粹的加法——只新增一個檔案,而不去動任何已經跑得好好的東西。

反例很容易想像。最樸素的做法是維護一張中央清單:每加一家 LLM,就回到某個 switch 或某張 map 裡登記一筆「gemini-http 對應到哪個實作」。這做法第一天就能動,卻會慢慢爛掉:

  • 那張清單變成所有人都要去碰的熱點,加供應商、刪供應商都得改它;
  • 兩個人同時加就在那行打架;
  • 它跟業務流程黏在一起,改它就有改壞主流程的風險。

所謂「可抽換」要是建立在「每次都回去改同一個地方」上,那它其實是假的可抽換。所以真正要解的,是怎麼讓系統「自己」認得新零件,而不需要任何人回去登記。

三樣東西:標記、分類表、名冊

撐起這件事的,是一套刻意做得很小的插件框架。它只有三個概念,我一個一個拆。

# 概念 它是什麼 它解決什麼
1 共同標記 一個幾乎是空的介面,沒有任何業務方法 宣告「我是一個能被平台管理的插件」,讓平台認得你是同一族、可被收集列管
2 分類表 一張寫死、封閉的歸類表 標明你是哪一種插件,平台才知道把你插進哪個插槽
3 名冊 啟動時掃描收集、按「類別+名字」編好的可查清單 讓前兩樣東西活起來,執行期報出名字就能換一個

這三樣東西環環相扣。共同標記是一張通用識別證,零件貼上它,平台就認得、收進來列管。分類表再把你歸進某個插槽——LLM、檢索器、地圖、守門、意圖路由,外加幾個治理用的進階插槽;這張表寫死又封閉,因為插槽代表「平台架構裡的一個位置」,是平台層級的決定,不是某個供應商能自己長出來的。名冊則讓前兩樣活起來:系統啟動時,DI(依賴注入)容器把所有貼了標記的零件一次收集起來,這正是 Day 12 講的「依賴注入」背後「依賴反轉」原則在底層真正發生的事——業務碼不去 new 任何具體供應商,改由容器把現場零件一次掃進來,框架再按「類別+名字」編成一本可查的名冊,執行期報出名字就能換一個。

下面這張圖,就是這三樣東西合起來的樣子:

        ┌── 所有實作都貼同一個標記,啟動時全被 DI 容器收集 ──┐
LLM:        mock   openai-http   gemini-http   spring-ai
守門:       mock   regex         openai-moderation
地圖:       mock   google
        └──── 名冊按「類別 + 名字」編好,執行期查表挑一個 ────┘

「0 行不動」就是這樣來的。 你新寫一家 LLM,做的事只有兩件:

  1. 實作那個能對話的介面;
  2. 貼上標記並回報自己叫 gemini-http

剩下的全是自動的——它會被啟動掃描收進名冊,不必去任何中央清單登記,不必改業務流程,也不必改入口 controller。延續 Day 12 的插座比喻:標記是「這是個能插上牆的電器」的識別證,分類表是插座的規格,名冊則是配電箱。換電器不必動牆,加電器也不必重畫電路圖。

名冊的兩種選法:查表 vs 開機定下

名冊建好之後,還有一個常被誤解的點要講清楚:不是所有能力都用同一種方式取用。 差別在於一個很實際的問題——這類零件需不需要「每一筆請求都能臨時換」。

執行期查表 開機時定下來
走這條的能力 LLM、地圖、入境守門 登入、稽核、送 LLM 前的預檢、MCP(Model Context Protocol)認證、檢索器
取用方式 所有零件啟動時全裝上、全留名冊,每筆請求依設定或 header 臨時挑 照設定選定一個裝上,啟動後就不換
逐筆切換 支援,還能執行中熱重載、甚至停用某一家 不支援,整個生命週期固定是它
代價 名冊得一直握著所有實作,多花一點記憶體,換來最大彈性 省記憶體,但失去逐筆彈性

判準其實很單純:要支援逐筆切換的,走查表;啟動就該固定的,開機選定。「逐請求換 LLM」是個合理需求——你可能想對某一題臨時試另一家模型;但「逐請求換登入方式」就很怪,身分驗證的規則在一次部署裡本來就該一致,讓它能逐筆換反而是個漏洞。明天 Day 14 會專門講「執行期查表」這條路怎麼用一個 header 做到逐請求覆寫,那整篇都建立在今天這本名冊上——能逐筆換,前提就是名冊一直握著所有零件。

portal:
  llm:
    provider: gemini-http      # 執行期查表:改這行就換一家,業務碼不動
  guardrail:
    provider: regex            # 同樣查表,可逐筆覆寫
  audit:
    provider: slf4j            # 開機定下:啟動選定,執行中不換

框架是長出來的:介面預設值(default method)怎麼讓既有實作 0 行不動

最後這段,是我覺得這套框架最值得學的地方——它不是一次設計好的,而是一路長出來的,而且每一步都守著同一條規矩:既有的實作一行都不准改。

回頭看那個標記介面,它最早是完全空的,真的就只標記身分,什麼方法都沒有。後來平台陸續需要更多治理能力,這個介面就一件一件長出新東西。每一步的擴充,舊實作都 0 行不動:

版本 新增能力 介面預設值(default)行為
v0 空介面,只標記「我是插件」
v1 自報身分(版本、顯示名稱) 回一個通用名稱與版本
v2 自報健康狀態(分健康、降級、不可用、未知四種) 回「未知」
v3 生命週期掛鉤(啟動、關閉、重載) 什麼都不做(no-op)
v4 平台開始呼叫掛鉤、開出查現況與熱重載的管理端點 想要新能力的零件各自覆寫 default,其餘原封不動

問題來了:每加一個方法到介面上,照理說所有實作那個介面的零件都得跟著補上這個方法,不然編譯就過不了。Portal 當時已經有十幾個落地的零件,難道每次擴充都要全部回去改一輪?那就完全違背了「0 行不動」的承諾。

解法是介面預設值,也就是 default method。每次擴充能力,新方法都帶一個無害的預設實作寫在介面上:自報身分的,預設回一個通用值;自報健康的,預設回「未知」;生命週期掛鉤的,預設是什麼都不做。於是已上線的那十幾個實作一個都不用動,就自動繼承了新能力的空殼——要不要把預設換成真正的實作,各個零件自己決定,沒空管的就用預設,照樣跑得好好的。

這就是為什麼治理能力一路加、卻從沒驚動過任何一個已落地的零件。它示範的其實是一個比插件框架更通用的道理——對一個會被很多人實作的介面做向後相容的擴充,這時候 default method 最派得上用場。 這也正好呼應 Day 4 鋪過的「宣告式抽換」基調:抽換的彈性,重點不在執行期到處塞 if,而在於把擴充點設計在介面這個穩定的邊界上。

抽象介面這條線,劃在哪裡

代價當然有,而且全壓在「抽象介面要設計得夠穩定」這件事上。

整套框架能成立,是因為那個共同標記、還有各類插槽的介面,被當成一條穩定的邊界在守。它一旦動,連鎖反應就來了。default method 的能與不能,剛好是個鮮明的對比:

加一個帶預設的新方法 改既有方法的簽名(signature)
default method 救得了嗎 救得了,純擴充,舊實作無感 救不了
對舊實作的衝擊 0 行不動 改了參數或回傳型別,所有實作都得跟著改
「0 行不動」 維持 當場破功

所以這條邊界的設計門檻很高——加東西可以很隨意,動既有的東西卻得非常克制,這個不對稱,是這套框架能不能長期立得住的關鍵。

PoC(概念驗證)階段也誠實留了幾筆帳:

記帳一:生命週期掛鉤目前是同步的。 萬一某個插件在「啟動」掛鉤裡做網路探測,會拖慢整個服務啟動;要改成非同步又會動到介面簽名,反過來打破「0 行不動」——這個取捨還沒解。

記帳二:幾類插槽暫時允許多家並存。 檢索器、地圖、守門其實架構上應該只能有一個實作生效,現在卻暫時允許多家並存,那是為了 demo 借框架做展示,框架自己也標了記號,正式版會收斂成單一實作。

先把這些帳記著,後面幾天會陸續還。

明天 Day 14,我們把鏡頭拉到「執行期查表」這條路上——看一個請求怎麼只憑一個 header,就在名冊裡臨時挑走另一家供應商,順帶聊聊它在 A/B 測試和線上除錯時的實際用法。


上一篇
Day XII|「不要動我的寶貝!!」-換模組不用換程式
下一篇
Day 14|逐請求覆寫
系列文
轉生到全端工程師沒多久就要負責公司的大平台??18
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言