探索主題

Agent: 探索者 (explorer)
Confidence: 86%
Cost: $0.4670
Duration: 1m 56s
Task ID: 1a5f3770-bec4-422a-af82-d6763f5f5066


現在我有足夠的資訊來撰寫完整的探索報告了。讓我彙整所有發現。


探索主題

grammY Bot 框架的中介軟體設計模式:如何寫出高效、可測試的 Telegram Bot 中介軟體?

發現

  1. 我們的中介軟體層極為單薄,核心邏輯全塞在 message-handler.ts(440 行)。grammY 的 Composer class 設計的核心價值——模組化樹狀中介軟體——我們幾乎沒用到。目前 setupMiddleware() 只有 4 個扁平的 bot.use() 呼叫。

  2. grammY 的 Composer 不是扁平 stack,是一棵樹。與 Express 不同,grammY 保留你 .use() 建立的樹結構,用深度優先遍歷。這意味著你可以用 Composer 建立「模組」,每個模組獨立組合自己的 filter → handler 鏈,後期還能動態追加中介軟體(composer.use()bot.use(composer) 之後仍有效)。

  3. 三個我們沒用但應該用的 grammY 原生模式

    • errorBoundary:可為不同模組建立獨立錯誤邊界,取代 message-handler.ts 中散落的 try/catch。例如把 Claude Code 呼叫用 errorBoundary 包起來,Admin 專區和一般使用者區各自隔離。
    • filter chainingbot.filter(ctx => ctx.from?.id === ADMIN_ID).use(adminComposer) 可以取代 message-handler.ts:190if (isAdmin) 分支,讓 admin/user 路由成為宣告式的中介軟體樹。
    • Router plugin:我們的 model routing(haiku/sonnet/opus 三層路由)完全可以用 Router 來做,取代 processMessage() 裡的 if (route.tier !== 'haiku') 多層巢狀。
  4. Transformer middleware(bot.api.config.use())我們已經正確使用apiThrottler()autoRetry() 都是 API 呼叫層的 transformer。這一層設計正確。

  5. 可測試性的關鍵是把依賴從閉包提出來。grammY 官方推薦用 Composer 子類 + 工廠函數注入依賴(如 logger、config),這樣測試時可以傳入 mock。我們目前的中介軟體直接 import configlogger,雖然可用 vi.mock() 但不夠乾淨。工廠模式範例:

1
2
3
4
5
6
7
8
9
10
// 可測試的中介軟體工廠
function createAuthMiddleware(deps: { allowedUsers: number[]; adminId: number }) {
return new Composer<BotContext>()
.filter(ctx => {
const uid = ctx.from?.id;
if (!uid) return true;
if (deps.allowedUsers.length === 0) return true;
return deps.allowedUsers.includes(uid) || uid === deps.adminId;
});
}

有趣的連結

  • 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 的子樹。

延伸問題

  1. 能否用 Composer 子樹替換重構 message-handler.ts 的 440 行巨石? 具體拆法:adminComposer(context weaving + Claude Code)、userComposer(default reply)、intentRouter(NL intent matching),各自獨立、可單元測試。
  2. 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)中特別有價值。
  • 可測試性的真正瓶頸不在框架,而在依賴注入。把 configloggeraskClaudeCode 從 module-level import 變成工廠函數參數,就能無痛 mock,無論用什麼測試框架。
    ─────────────────────────────────────────────────