探索主題

Agent: 探索者 (explorer)
Confidence: 73%
Cost: $0.3868
Duration: 1m 27s
Task ID: 6742cf1d-9112-4765-b227-6bfc04426648


現在我已經收集到足夠的資料了。讓我彙整成探索報告。


探索主題

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

發現

  1. Composer 是模組化的關鍵:grammY 的 Composer<Context> 不只是分組工具——它本身就是中介軟體物件,支援 .filter() 鏈式 AND 邏輯和 .use() 的樹狀結構(深度優先遍歷)。我們專案目前的 setupMiddleware() 用的是平面的 bot.use() 序列,沒有利用 Composer 的模組化能力。

  2. 測試黃金模式:Transformer + handleUpdate:最實用的測試方法來自 MobinAskari 的 Gist——在 bot.api.config.use() 安裝 transformer 攔截所有 outgoing API 呼叫,搭配 bot.handleUpdate(fakeUpdate) 注入假的 Update 物件,然後 assert outgoingRequests 陣列。完全不需要 mock Telegram 伺服器

  3. Composer 可獨立測試:用 composer.middleware()(mockCtx, () => Promise.resolve()) 就能在隔離環境測試單一中介軟體,不經過整個 middleware stack。這對我們的 authMiddlewarerateLimitMiddleware 等特別有用。

  4. 我們的中介軟體現狀:4 個中介軟體都是裸函數(src/telegram/middleware/),未使用 Composer。順序寫死在 setupMiddleware() 裡。其中 rateLimitMiddleware 用模組層級 Map 存狀態,測試時需注意狀態清理。

  5. Filter 類型窄化:grammY 的 Filter<Context, ":text"> 可以讓 TypeScript 在 middleware 內精確推斷 ctx.message.text 存在,減少 null check。我們現有的 handler 大量使用 ctx.message?.text ?? '' 可以用這個簡化。

有趣的連結

  • grammY middleware = Koa middleware:洋蔥模型,await next() 前後分別是 request/response 階段——errorHandlerMiddleware 正確地利用了這個模式(try/catch 包住 next()),但 loggingMiddleware 只記了 request 階段,若加上 response time 度量會更有價值。
  • grammy-middlewares 套件:提供 ignoreOld()sequentialize() 等現成中介軟體,其中 sequentialize() 與我們的 per-chat queue 概念相近,值得比較。

延伸問題

  • 如何將現有的 4 個 middleware 重構為獨立 Composer + 配套 vitest 測試?具體步驟是什麼?(可派給 architect 評估 + programmer 執行)
  • sequentialize() 官方 plugin 能否取代我們自建的 per-chat queue 邏輯,降低維護成本?

重要性:4/5

能改善專案品質——目前的中介軟體層缺乏測試且結構扁平。採用 Composer 模組化 + transformer 測試模式可以顯著提升可測試性和可維護性,且是低風險重構。

★ Insight ─────────────────────────────────────
核心技術洞察:

  • grammY 的 bot.api.config.use(transformer) 是測試的金鑰——它攔截所有 outgoing API 呼叫,讓你在不碰 Telegram 伺服器的情況下 assert bot 的「行為」而非「實作」
  • Composer 的真正威力在於它是可獨立實例化、獨立測試、獨立組合的中介軟體單元——每個 Composer 就是一個微型 bot
  • 我們專案 authMiddlewarereturn Promise.resolve() 終止模式是正確的 grammY 慣例(不呼叫 next() = 終止 middleware chain)
    ─────────────────────────────────────────────────