探索主題

Agent: 探索者 (explorer)
Confidence: 73%
Cost: $0.6204
Duration: 2m 1s
Task ID: f7a580ae-2001-4797-9668-002a9a7583be


好的,我現在已經收集了足夠的資訊。讓我整理成探索報告。


探索主題

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

發現

  1. 我們目前的 middleware 是扁平函式、直接掛在 bot 上src/telegram/middleware/),4 個 middleware 各自是獨立函式,透過 setupMiddleware(bot) 依序 bot.use()。這是最基本的 stack 模式,但完全沒用到 Composer 的樹狀分支能力。

  2. grammY 的 Composer 類別是中介軟體的核心Bot 本身就是 Composer 的子類別。每個 Composer 實例可以獨立組合 middleware,再掛進 bot——這是模組化的關鍵。官方推薦每個功能模組 export 一個 Composer,然後在 bot.ts 集中 bot.use(module)

  3. 我們的 CommandRegistry 自建了路由邏輯(intent patterns、alias matching、callback routing),但沒有利用 grammY 原生的 bot.route() 或 Router plugin。這導致路由分散在 command-registry.tsplugin-router.tsmessage-handler.ts 三處,增加了理解和測試難度。

  4. 可測試性瓶頸:目前的 middleware 函式直接 import configlogger 等模組級 singleton。grammY 官方測試方式是構造 mock Context 物件 + 呼叫 middleware 函式——但我們的 middleware 與 config singleton 耦合,mock 需要 vi.mock() 整個模組。改用 Composer + 依賴注入工廠模式(如 createAuthMiddleware(config))可以大幅提升可測試性。

  5. grammY 的 Composer.filter() + chain 模式能取代我們的手動 if-return 邏輯。例如 authMiddleware 的白名單檢查可以用 composer.filter(ctx => isAllowed(ctx)).use(next) 表達,讓 grammY 的樹狀調度自動處理分支,而不是手動呼叫 next()return Promise.resolve()

有趣的連結

  • grammY 的 middleware 是樹狀結構(不是簡單 stack),每次 .use() 回傳的 Composer 可以繼續 chain——這比 Express 的線性 stack 更有表達力。我們的 CommandRegistry.bindToBot() 本質上在重新實作 grammY 已經提供的功能(bot.command()bot.on('callback_query:data')),但失去了 filter narrowing 帶來的型別安全。

延伸問題

  • 重構路線:能否將 CommandRegistry 重構為多個 Composer 模組(auth-composer、rate-limit-composer、command-composer、plugin-composer),每個可獨立 unit test?具體步驟是什麼?
  • middleware 測試模式:如何在 vitest 中構造最小的 grammY mock Context,不需要真正的 Bot 實例就能測試單一 middleware?

重要性:4/5

(能改善專案品質——目前 middleware 層缺少測試、路由邏輯分散、Composer 能力未被利用。重構後可以提升可維護性和測試覆蓋率。)

★ Insight ─────────────────────────────────────
關鍵技術洞察

  1. Composer 是一等公民:grammY 的設計哲學是「每個功能模組就是一個 Composer」。我們目前把所有東西掛在一個 Bot 實例上,等於把所有邏輯塞進一棵樹的根節點。正確做法是 const authModule = new Composer<BotContext>() → 獨立開發 → bot.use(authModule)
  2. Filter chain 取代 if-returncomposer.filter(predicate).use(handler) 不只更簡潔,還讓 TypeScript 自動 narrow context type(例如 ctx.message 保證存在)。我們的 authMiddleware 手動 if (!userId) return next() 失去了這個型別安全優勢。
  3. 測試策略:最小 mock 是 { from: { id: 123 }, chat: { id: 456 }, update: { update_id: 1 }, reply: vi.fn() } as unknown as BotContext,傳入 middleware 函式 + vi.fn() 作為 next。Composer 模式讓這變得更乾淨——直接測試 Composer 的 middleware 函式,不需要整個 Bot。
    ─────────────────────────────────────────────────