Agent: 探索者 (
explorer)
Confidence: 86%
Cost: $0.4670
Duration: 1m 56s
Task ID:1a5f3770-bec4-422a-af82-d6763f5f5066
現在我有足夠的資訊來撰寫完整的探索報告了。讓我彙整所有發現。
探索主題
grammY Bot 框架的中介軟體設計模式:如何寫出高效、可測試的 Telegram Bot 中介軟體?
發現
我們的中介軟體層極為單薄,核心邏輯全塞在
message-handler.ts(440 行)。grammY 的Composerclass 設計的核心價值——模組化樹狀中介軟體——我們幾乎沒用到。目前setupMiddleware()只有 4 個扁平的bot.use()呼叫。grammY 的
Composer不是扁平 stack,是一棵樹。與 Express 不同,grammY 保留你.use()建立的樹結構,用深度優先遍歷。這意味著你可以用Composer建立「模組」,每個模組獨立組合自己的 filter → handler 鏈,後期還能動態追加中介軟體(composer.use()在bot.use(composer)之後仍有效)。三個我們沒用但應該用的 grammY 原生模式:
errorBoundary:可為不同模組建立獨立錯誤邊界,取代message-handler.ts中散落的 try/catch。例如把 Claude Code 呼叫用errorBoundary包起來,Admin 專區和一般使用者區各自隔離。filterchaining:bot.filter(ctx => ctx.from?.id === ADMIN_ID).use(adminComposer)可以取代message-handler.ts:190的if (isAdmin)分支,讓 admin/user 路由成為宣告式的中介軟體樹。Routerplugin:我們的 model routing(haiku/sonnet/opus 三層路由)完全可以用Router來做,取代processMessage()裡的if (route.tier !== 'haiku')多層巢狀。
Transformer middleware(
bot.api.config.use())我們已經正確使用:apiThrottler()和autoRetry()都是 API 呼叫層的 transformer。這一層設計正確。可測試性的關鍵是把依賴從閉包提出來。grammY 官方推薦用
Composer子類 + 工廠函數注入依賴(如 logger、config),這樣測試時可以傳入 mock。我們目前的中介軟體直接 importconfig和logger,雖然可用vi.mock()但不夠乾淨。工廠模式範例:
1 | // 可測試的中介軟體工廠 |
有趣的連結
message-handler.ts的 message merge buffer 邏輯(per-chat queue + drain loop)其實是一個自製的背壓機制,與 grammY runner 的fork+ backpressure 有功能重疊。如果用bot.fork()搭配 Composer 來做 per-chat 隔離,可能可以簡化 buffer 邏輯。- grammY 的
Composer動態追加特性(安裝後仍可.use())與我們的 plugin hot-reload 需求天然契合——可以把每個 plugin 變成一個 Composer,熱載入時替換 Composer 的子樹。
延伸問題
- 能否用
Composer子樹替換重構message-handler.ts的 440 行巨石? 具體拆法:adminComposer(context weaving + Claude Code)、userComposer(default reply)、intentRouter(NL intent matching),各自獨立、可單元測試。 - grammY 的
lazyMiddleware能否用於按需載入重型模組? 目前message-handler.ts有大量 dynamic import(await import(...)),改用lazy()可能更符合框架慣例。
重要性:4/5
能直接改善專案品質——message-handler.ts 是目前最大的單體檔案,用 Composer 拆分後可提升可測試性、錯誤隔離、和 plugin 擴展性。
★ Insight ─────────────────────────────────────
- grammY 的
Composer樹狀結構 vs Express 的扁平 stack 是關鍵差異:Express 的use()回傳的是app本身(鏈式 API),grammY 的use()回傳新的Composer實例(形成子樹)。這讓「模組化」不只是概念上的分檔,而是框架層面的隔離。 errorBoundary是 grammY 版的 React Error Boundary——你可以為不同功能區塊建立獨立的錯誤處理策略,而不是一個全局 try/catch 搞定一切。這在我們的多層路由(haiku→sonnet→opus fallback)中特別有價值。- 可測試性的真正瓶頸不在框架,而在依賴注入。把
config、logger、askClaudeCode從 module-level import 變成工廠函數參數,就能無痛 mock,無論用什麼測試框架。─────────────────────────────────────────────────